/* ============================================
   Components - Buttons, Cards, Badges, etc.
   ============================================ */

/* ===========================================
   BUTTONS — one geometry, Bootstrap's variable contract (2026-09-12)

   Every button in the app is a Bootstrap .btn, and Bootstrap draws a .btn from
   --bs-btn-* custom properties: .btn reads them, .btn-sm / .btn-lg change the
   SIZE ones, each colour variant changes the COLOUR ones. This block therefore
   sets VARIABLES, never the properties themselves.

   Until 2026-09-12 .btn-primary and .btn-secondary wrote `padding`, `border`
   and `border-radius` directly. At the same specificity as .btn-sm and loaded
   after it, a property beats a variable every time, so `btn-sm btn-primary`
   drew full size at 46 call sites while the outline variants beside it kept
   Bootstrap's smaller default: five heights for one word (measured 46.4 /
   44.5 / 37.3 / 43.4 / 30.3px) and every toolbar in the app mixing two of
   them. See plans/button-scale-plan.md.

   The rules that keep it fixed:
     - geometry lives on .btn, and on the two size modifiers RE-DECLARED right
       after it — source order is what lets them win again;
     - a variant sets COLOUR variables only: never padding, border or
       border-radius on a .btn-* — which also means a variant is NOTHING
       without the base: `class="btn-primary"` alone is a raw <button> with a
       gradient (twelve modal submit buttons were written that way and shrank
       to 30px chips the day this landed; all carry `btn btn-primary` now);
     - no inline `style="padding…"` on a button, ever. A button beside a -sm
       field is btn-sm, beside a plain field it is plain — they land on the
       same pixel by construction, because the default block padding below is
       the FIELD's own (0.65rem, _forms.css): a plain button is exactly as tall
       as the .form-control beside it (46.8px) and btn-sm exactly as tall as
       .form-control-sm (30.3px);
     - btn-sm lives only INSIDE a <tr>, a card in a list or a dropdown widget
       (row actions, the ⋮ menu, per-item card buttons); every control on a
       page, tab, form or modal surface is the plain size — a JS-built row
       EDITOR inside a form (invoice lines, instalments, journal lines, the
       settings rows) included, because a .repeat-row is a form, not a table
       row (owner, 2026-09-12 — plans/button-scale-plan.md §3.5).
   =========================================== */

.btn {
    --bs-btn-padding-y: 0.65rem;
    --bs-btn-padding-x: 1.25rem;
    --bs-btn-font-weight: 500;
    /* The plain button's SHARED shape, decided once in _variables.css and
       never per design; name the role token here, never a pixel and never a
       scale step. It is 8px, NOT the sheets' pill — the pill was applied and
       rejected by the owner on 2026-09-16; _variables.css carries the note.
       The size modifiers below keep their own steps: how round a SMALL button
       is, and how round a LARGE one is, are separate decisions from this. */
    --bs-btn-border-radius: var(--radius-btn);
    /* The FIELD's ring (.form-control:focus), never Bootstrap's blue. Each
       status variant swaps the rgb for its own below. */
    --bs-btn-focus-shadow-rgb: var(--primary-rgb);
    --bs-btn-focus-box-shadow: 0 0 0 3px rgba(var(--bs-btn-focus-shadow-rgb), 0.25);
    transition: all var(--transition-normal);
}

/* Bootstrap's own sizes, restated AFTER .btn so they outrank it by source
   order — they set the same variables at the same specificity. Radii are the
   project's tokens rather than --bs-border-radius-*. */
.btn-group-sm > .btn,
.btn-sm {
    --bs-btn-padding-y: 0.25rem;
    --bs-btn-padding-x: 0.5rem;
    --bs-btn-font-size: 0.875rem;
    --bs-btn-border-radius: var(--radius-sm);
}

.btn-group-lg > .btn,
.btn-lg {
    --bs-btn-padding-y: 0.75rem;
    --bs-btn-padding-x: 1.5rem;
    --bs-btn-font-size: 1.125rem;
    --bs-btn-border-radius: var(--radius-lg);
}

/* ===========================================
   Primary Button — the brand fill (--primary-fill) is a background-IMAGE
   painted over --bs-btn-bg, which stays as the fallback and as the colour
   every state variable names (disabled keeps the image and Bootstrap's
   opacity). The label is --on-primary-fill in every state.
   =========================================== */

.btn-primary {
    --bs-btn-color: var(--on-primary-fill);
    --bs-btn-bg: var(--primary);
    --bs-btn-border-color: transparent;
    --bs-btn-hover-color: var(--on-primary-fill);
    --bs-btn-hover-bg: var(--primary);
    --bs-btn-hover-border-color: transparent;
    --bs-btn-active-color: var(--on-primary-fill);
    --bs-btn-active-bg: var(--primary-dark);
    --bs-btn-active-border-color: transparent;
    --bs-btn-disabled-color: var(--on-primary-fill);
    --bs-btn-disabled-bg: var(--primary);
    --bs-btn-disabled-border-color: transparent;
    --bs-btn-font-weight: 600;
    background-image: var(--primary-fill);
}

.btn-primary:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-primary-hover);
}

.btn-primary:active,
.btn-primary:disabled,
.btn-primary.disabled {
    transform: none;
    box-shadow: none;
}

/* ===========================================
   Secondary Button — the filled neutral: a modal's cancel, the quiet half of a
   page-header pair. Hover stays NEUTRAL (the old primary-coloured hover lit a
   cancel button up like a call to action).
   =========================================== */

.btn-secondary {
    --bs-btn-color: var(--text);
    --bs-btn-bg: var(--card);
    --bs-btn-border-color: var(--border);
    --bs-btn-hover-color: var(--text);
    --bs-btn-hover-bg: var(--bg-secondary);
    --bs-btn-hover-border-color: var(--border-strong);
    --bs-btn-active-color: var(--text);
    --bs-btn-active-bg: var(--bg-secondary);
    --bs-btn-active-border-color: var(--primary);
    --bs-btn-disabled-color: var(--text-secondary);
    --bs-btn-disabled-bg: var(--card);
    --bs-btn-disabled-border-color: var(--border);
}

/* ===========================================
   Outline Primary Button — neutral at rest, primary on hover; checked (the
   segmented view toggles) wears the gradient.
   =========================================== */

.btn-outline-primary {
    --bs-btn-color: var(--text);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: var(--border);
    --bs-btn-hover-color: var(--on-primary);
    --bs-btn-hover-bg: var(--primary);
    --bs-btn-hover-border-color: var(--primary);
    --bs-btn-active-color: var(--on-primary);
    --bs-btn-active-bg: var(--primary-dark);
    --bs-btn-active-border-color: var(--primary);
    --bs-btn-disabled-color: var(--text-secondary);
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: var(--border);
}

/* Bootstrap names ONE active colour for a press and for a selected segment,
   but they sit on different fills: a press on the flat --primary-dark (dark
   ink in the dark theme), a selected segment on the brand fill (white). This
   rule is the more specific of the two, so only the segment takes
   --on-primary-fill. */
.btn-outline-primary.active,
.btn-check:checked + .btn-outline-primary {
    --bs-btn-active-color: var(--on-primary-fill);
    background-image: var(--primary-fill);
}

/*
 * .btn-outline-secondary is EVERY row-action button in the app (view, edit,
 * archive, restore, delete, the detail chevron — see the row-actions rule in
 * CLAUDE.md), so this is one of the most repeated colours on screen. Bootstrap
 * draws it from --bs-secondary (#6c757d), one of the --bs-* values this app
 * never themes: it was frozen at 4.47:1 on white and IDENTICAL in dark mode,
 * where a mid-grey on a #1a1a2e card is the wrong end of the ramp entirely.
 *
 * Border on --border-strong rather than --border because these are interactive
 * controls whose outline is their only boundary — the same 1.4.11 argument as
 * .form-control. Active / .show would otherwise flash Bootstrap's untokenised
 * grey fill with white text for the length of a click.
 */
.btn-outline-secondary {
    --bs-btn-color: var(--text-secondary);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: var(--border-strong);
    --bs-btn-hover-color: var(--text);
    --bs-btn-hover-bg: var(--bg-secondary);
    --bs-btn-hover-border-color: var(--border-strong);
    --bs-btn-active-color: var(--text);
    --bs-btn-active-bg: var(--bg-secondary);
    --bs-btn-active-border-color: var(--primary);
    --bs-btn-disabled-color: var(--text-muted);
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: var(--border);
}

/* ===========================================
   Status buttons — project tokens, AA in both themes.

   The base --danger / --success / --info tokens are tuned for badges, rings
   and icons; as a FILL under white text three of them fail AA (3.76 / 2.04 /
   3.68:1), so filled buttons take the --*-fill shades and outlines take the
   --*-text shades (_variables.css, per theme). Amber is the exception: no
   amber passes with white, so .btn-warning carries DARK text (--on-warning,
   6.83:1) like Bootstrap's own does.
   =========================================== */

.btn-danger {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--danger-fill);
    --bs-btn-border-color: var(--danger-fill);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--danger-fill-hover);
    --bs-btn-hover-border-color: var(--danger-fill-hover);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--danger-fill-hover);
    --bs-btn-active-border-color: var(--danger-fill-hover);
    --bs-btn-disabled-color: #fff;
    --bs-btn-disabled-bg: var(--danger-fill);
    --bs-btn-disabled-border-color: var(--danger-fill);
    --bs-btn-focus-shadow-rgb: var(--danger-rgb);
    --bs-btn-font-weight: 600;
}

.btn-warning {
    --bs-btn-color: var(--on-warning);
    --bs-btn-bg: var(--warning-fill);
    --bs-btn-border-color: var(--warning-fill);
    --bs-btn-hover-color: var(--on-warning);
    --bs-btn-hover-bg: var(--warning-fill-hover);
    --bs-btn-hover-border-color: var(--warning-fill-hover);
    --bs-btn-active-color: var(--on-warning);
    --bs-btn-active-bg: var(--warning-fill-hover);
    --bs-btn-active-border-color: var(--warning-fill-hover);
    --bs-btn-disabled-color: var(--on-warning);
    --bs-btn-disabled-bg: var(--warning-fill);
    --bs-btn-disabled-border-color: var(--warning-fill);
    --bs-btn-focus-shadow-rgb: var(--warning-rgb);
    --bs-btn-font-weight: 600;
}

.btn-success {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--success-fill);
    --bs-btn-border-color: var(--success-fill);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--success-fill-hover);
    --bs-btn-hover-border-color: var(--success-fill-hover);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--success-fill-hover);
    --bs-btn-active-border-color: var(--success-fill-hover);
    --bs-btn-disabled-color: #fff;
    --bs-btn-disabled-bg: var(--success-fill);
    --bs-btn-disabled-border-color: var(--success-fill);
    --bs-btn-focus-shadow-rgb: var(--success-rgb);
    --bs-btn-font-weight: 600;
}

.btn-info {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--info-fill);
    --bs-btn-border-color: var(--info-fill);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--info-fill-hover);
    --bs-btn-hover-border-color: var(--info-fill-hover);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--info-fill-hover);
    --bs-btn-active-border-color: var(--info-fill-hover);
    --bs-btn-disabled-color: #fff;
    --bs-btn-disabled-bg: var(--info-fill);
    --bs-btn-disabled-border-color: var(--info-fill);
    --bs-btn-focus-shadow-rgb: var(--info-rgb);
    --bs-btn-font-weight: 600;
}

.btn-outline-danger {
    --bs-btn-color: var(--danger-text);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: var(--danger-text);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--danger-fill);
    --bs-btn-hover-border-color: var(--danger-fill);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--danger-fill-hover);
    --bs-btn-active-border-color: var(--danger-fill-hover);
    --bs-btn-disabled-color: var(--danger-text);
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: var(--danger-text);
    --bs-btn-focus-shadow-rgb: var(--danger-rgb);
}

.btn-outline-warning {
    --bs-btn-color: var(--warning-text);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: var(--warning-text);
    --bs-btn-hover-color: var(--on-warning);
    --bs-btn-hover-bg: var(--warning-fill);
    --bs-btn-hover-border-color: var(--warning-fill);
    --bs-btn-active-color: var(--on-warning);
    --bs-btn-active-bg: var(--warning-fill-hover);
    --bs-btn-active-border-color: var(--warning-fill-hover);
    --bs-btn-disabled-color: var(--warning-text);
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: var(--warning-text);
    --bs-btn-focus-shadow-rgb: var(--warning-rgb);
}

.btn-outline-success {
    --bs-btn-color: var(--success-text);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: var(--success-text);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--success-fill);
    --bs-btn-hover-border-color: var(--success-fill);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--success-fill-hover);
    --bs-btn-active-border-color: var(--success-fill-hover);
    --bs-btn-disabled-color: var(--success-text);
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: var(--success-text);
    --bs-btn-focus-shadow-rgb: var(--success-rgb);
}

/* Bootstrap colours these two through --bs-link-color / --bs-light, neither
   of which follows data-theme. */
.btn-link {
    --bs-btn-color: var(--primary);
    --bs-btn-hover-color: var(--primary-dark);
    --bs-btn-active-color: var(--primary-dark);
    --bs-btn-disabled-color: var(--text-muted);
}

.btn-light {
    --bs-btn-color: var(--text);
    --bs-btn-bg: var(--card);
    --bs-btn-border-color: var(--border);
    --bs-btn-hover-color: var(--text);
    --bs-btn-hover-bg: var(--bg-secondary);
    --bs-btn-hover-border-color: var(--border-strong);
    --bs-btn-active-color: var(--text);
    --bs-btn-active-bg: var(--bg-secondary);
    --bs-btn-active-border-color: var(--border-strong);
    --bs-btn-disabled-color: var(--text-secondary);
    --bs-btn-disabled-bg: var(--card);
    --bs-btn-disabled-border-color: var(--border);
}

/* ===========================================
   Alternate Primary Action Button
   =========================================== */

/*
 * For a REAL action in a panel that already has a primary one — تسجيل النتيجة
 * beside حفظ in the task edit modal, where the two open different flows.
 *
 * Neither generic outline class works here. .btn-outline-secondary is tuned for
 * the ~40 row-action buttons it normally paints, so it rests at
 * --text-secondary on --border-strong and reads as DISABLED next to a solid
 * .btn-primary; .btn-outline-primary is remapped to a neutral rest (--text on
 * --border) and only becomes primary on hover, so it is just as quiet. This one
 * carries the accent at rest: coloured border, coloured label, no fill — present
 * enough to be seen, quiet enough not to compete with the solid button beside it.
 *
 * NO resting background tint, deliberately. A color-mix() tint reads well in
 * light mode but lifts the very dark --card (#1a1a2e) in dark mode, and
 * --primary-on-card drops from 4.87:1 to roughly 3.9:1 — under the 4.5:1 AA
 * floor. Left unfilled, the label holds ~4.9:1 in BOTH themes (#5a5fd0 on
 * #f7f9fc = 4.89:1, #8b7dd8 on #1a1a2e = 4.87:1). Check that again before
 * adding a fill.
 *
 * The hover fill matches .btn-outline-primary:hover exactly, so the app keeps
 * one hover behaviour for a primary-coloured outline rather than two.
 */
.btn-alt-primary {
    --bs-btn-color: var(--primary);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: var(--primary);
    --bs-btn-hover-color: var(--on-primary);
    --bs-btn-hover-bg: var(--primary);
    --bs-btn-hover-border-color: var(--primary);
    --bs-btn-active-color: var(--on-primary);
    --bs-btn-active-bg: var(--primary-dark);
    --bs-btn-active-border-color: var(--primary-dark);
    --bs-btn-disabled-color: var(--primary);
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: var(--primary);
}

/* ===========================================
   Primary Action Button (Gradient)
   Consolidates: .btn-add-sources, .btn-add-note, 
   .btn-upload-source, .modal-source-btn.primary
   =========================================== */

.btn-primary-action {
    background: var(--primary);
    color: #ffffff;
    border: none;
    padding: 0.75rem 1.25rem;
    border-radius: var(--radius-xl);
    font-size: 0.875rem;
    font-weight: 600;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    width: 100%;
    transition: all var(--transition-normal);
    box-shadow: var(--shadow-primary);
}

.btn-primary-action:hover {
    background: var(--primary-dark);
    transform: translateY(-2px);
    box-shadow: var(--shadow-primary-hover);
}

.btn-primary-action:active {
    transform: translateY(0);
}

/* Pill variant */
.btn-primary-action.btn-pill {
    border-radius: var(--radius-pill);
}

/* Specific implementations using base class */
.btn-add-sources {
    background: var(--primary);
    color: #fff;
    border: none;
    padding: 0.75rem 1.25rem;
    border-radius: var(--radius-xl);
    font-size: 0.875rem;
    font-weight: 600;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    width: 100%;
    margin-bottom: 1rem;
    transition: all var(--transition-normal);
    box-shadow: var(--shadow-primary);
}

.btn-add-sources:hover {
    background: var(--primary-dark);
    transform: translateY(-2px);
    box-shadow: var(--shadow-primary-hover);
}

.btn-add-sources:active {
    transform: translateY(0);
}

.btn-add-note {
    background: var(--primary);
    color: #ffffff;
    border: none;
    padding: 0.75rem 1.25rem;
    border-radius: var(--radius-pill);
    font-size: 0.875rem;
    font-weight: 600;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    width: 100%;
    transition: all var(--transition-normal);
    box-shadow: var(--shadow-primary);
}

.btn-add-note:hover {
    background: var(--primary-dark);
    transform: translateY(-2px);
    box-shadow: var(--shadow-primary-hover);
}

.btn-add-note:active {
    transform: translateY(0);
}

.btn-upload-source {
    background: var(--primary);
    border: none;
    color: #ffffff;
    padding: 0.75rem 1.5rem;
    border-radius: var(--radius-pill);
    font-size: 0.875rem;
    font-weight: 600;
    cursor: pointer;
    transition: all var(--transition-normal);
    box-shadow: var(--shadow-primary);
}

.btn-upload-source:hover {
    background: var(--primary-dark);
    transform: translateY(-2px);
    box-shadow: var(--shadow-primary-hover);
}

.btn-upload-source:active {
    transform: translateY(0);
}

/* ===========================================
   Icon Circle Button (Reusable Component)
   Consolidates: .doc-action-btn, .note-action-btn,
   .btn-like, .btn-dislike, .btn-copy, .btn-note,
   .btn-chat-info, .folder-action-btn
   =========================================== */

.btn-icon-circle {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-muted);
    width: 32px;
    height: 32px;
    min-width: 32px;
    border-radius: var(--radius-circle);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: all var(--transition-normal);
    padding: 0;
    font-size: 0.85rem;
}

.btn-icon-circle:hover {
    background: var(--bg-secondary);
    border-color: var(--primary);
    color: var(--primary);
    transform: scale(1.1);
}

.btn-icon-circle:active {
    transform: scale(0.95);
}

/* Size variants */
.btn-icon-circle.btn-icon-sm {
    width: 24px;
    height: 24px;
    min-width: 24px;
    font-size: 0.75rem;
}

.btn-icon-circle.btn-icon-lg {
    width: 40px;
    height: 40px;
    min-width: 40px;
    font-size: 1rem;
}

/* Color variants */
.btn-icon-circle.btn-icon-primary:hover {
    background: var(--primary);
    color: #ffffff;
    border-color: var(--primary);
}

.btn-icon-circle.btn-icon-danger:hover {
    background: rgba(var(--danger-rgb), 0.1);
    border-color: var(--danger);
    color: var(--danger);
}

.btn-icon-circle.btn-icon-success.active {
    background: var(--success);
    color: #ffffff;
    border-color: var(--success);
}

.btn-icon-circle.btn-icon-danger.active {
    background: var(--danger);
    color: #ffffff;
    border-color: var(--danger);
}

.btn-icon-circle.btn-icon-info.active {
    background: var(--info);
    color: #ffffff;
    border-color: var(--info);
}

/* Specific button implementations */
.doc-action-btn,
.note-action-btn {
    background: transparent;
    border: none;
    color: var(--text-muted);
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-xs);
    cursor: pointer;
    transition: all var(--transition-normal);
    padding: 0;
    font-size: 0.8rem;
}

.doc-action-btn:hover,
.note-action-btn:hover {
    background: var(--bg);
    color: var(--primary);
}

.doc-action-btn .fa-eye,
.note-action-btn .fa-eye {
    color: var(--primary);
}

.doc-action-btn .fa-info-circle {
    color: #17a2b8;
}

.doc-action-btn .fa-trash-alt {
    color: var(--danger);
}

.doc-action-btn.btn-delete-doc:hover {
    background: rgba(var(--danger-rgb), 0.1);
}

.btn-chat-info {
    background: transparent;
    border: 1px solid var(--border);
    width: 36px;
    height: 36px;
    border-radius: var(--radius-circle);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: all var(--transition-normal);
    color: var(--text-muted);
}

.btn-chat-info:hover {
    background: var(--bg-secondary);
    border-color: var(--primary);
    color: var(--primary);
}

.btn-chat-info i {
    font-size: 0.875rem;
}

.btn-send {
    background: var(--primary);
    border: 1px solid var(--primary);
    width: 36px;
    height: 36px;
    border-radius: var(--radius-circle);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: all var(--transition-normal);
}

.btn-send:hover {
    background: var(--primary-hover);
    border-color: var(--primary-hover);
}

.btn-send i {
    color: #ffffff;
    font-size: 0.875rem;
}

.btn-send:disabled,
.btn-send.disabled {
    background: var(--bg-secondary);
    border-color: var(--border);
    cursor: not-allowed;
    opacity: 0.5;
}

.btn-send:disabled:hover,
.btn-send.disabled:hover {
    background: var(--bg-secondary);
    border-color: var(--border);
}

.btn-send:disabled i,
.btn-send.disabled i {
    color: var(--text-muted);
}

/* ===========================================
   CARDS
   =========================================== */

.card {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-sm);
    transition: all var(--transition-normal);
    position: relative;
    z-index: 1;
}

.card .card-body {
    padding: 1rem;
}

.card:hover {
    box-shadow: var(--shadow-md);
}

/* ===========================================
   Conversation Cards
   =========================================== */

.conversation-card {
    transition: all var(--transition-normal);
    cursor: pointer;
    position: relative;
    z-index: 1;
}

.conversation-card .card-body {
    padding: calc(var(--base-spacing) * 0.875);
}

.conversation-card:hover {
    transform: translateY(-2px);
    z-index: 2;
}

.conversation-card.dropdown-active {
    z-index: var(--z-modal-backdrop) !important;
}

/*
 * Fix: open dropdown was being painted *behind* sibling cards (later rows)
 * because every .card creates its own stacking context (position:relative; z-index:1)
 * and sibling cards at the same z-index render on top in DOM order.
 *
 * When a card contains an open dropdown menu, lift the entire card above its
 * siblings so the menu (and its overflowing content) is visible on top.
 *
 * Uses :has() — supported in all modern evergreen browsers.
 */
.card:has(.dropdown-menu.show),
.card:has(.dropdown.show) {
    z-index: var(--z-modal-backdrop) !important;
}

/* Also lift the column wrapper, in case it ever gains its own stacking context */
[class*="col-"]:has(.dropdown-menu.show) {
    z-index: var(--z-modal-backdrop);
    position: relative;
}

.conversation-card .badge {
    font-size: 0.7rem;
    padding: 0.25rem 0.5rem;
    font-weight: 500;
}

.conversation-card .card-title {
    color: var(--text);
    font-size: 1rem;
    font-weight: 600;
    margin-bottom: 0.5rem;
}

/* Global card-title color — JS-rendered card titles (e.g. conversation list items
   inside chat sidebar / dark panels) inherit white from their dark parent and
   become invisible on the white card surface. Pin them to the theme text color. */
.card-title {
    color: var(--text);
}

/* ===========================================
   BADGES
   =========================================== */

.badge {
    font-weight: 500;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.75rem;
    padding: 0.25rem 0.5rem;
}

.badge .fa-circle {
    font-size: 0.5rem;
}


/* ===========================================
   SOLID STATUS BADGES — the fill tokens, not literals
   ===========================================
   Twelve rules of hardcoded colour until phase 4, and they had two faults at
   once.

   THE DESIGN LEAK: .bg-primary was #667eea light and #7c3aed dark — the violet
   the app wore before there were four designs. It is the brand class, so on
   نيلي, أخضر and ليموني it painted the OLD brand, in every register that
   badges a count or a state. Nothing reports that; the badge looks like a
   badge. It is --primary-fill-solid now, which is stop 1 of the design's own
   brand fill, under --on-primary-fill, the label that fill was measured with —
   so the figure is already a row of the plan's §9.3 contrast table rather than
   a number someone has to take again here.

   THE CONTRAST: every one of the other five was a base status token under
   WHITE, which is the pairing _variables.css exists to prevent — measured,
   light: success 2.54:1, warning 2.15:1, info 3.68:1, danger 3.76:1, primary
   3.66:1, against a 4.5:1 floor. The dark set was no better (2.86:1 to
   4.24:1) despite looking deliberate, because a pale tinted label on a
   mid-toned fill reads softer than it measures. The --*-fill shades are the
   same hex in both themes for the reason the buttons are: a badge's contrast
   is its label against its own fill, and the card behind it changes nothing.

   So the two blocks collapse into one, and the dark half keeps only what is
   genuinely about the dark card — a 1px ring, so a badge still has an edge on
   a surface close to its own tone. The ring is derived from the token now, so
   .bg-primary's follows the design instead of ringing every design in violet.

   NOT scoped to .badge, deliberately: these are Bootstrap utility classes and
   a handful of call sites put them on a pill that is not a .badge. Scoping
   would silently drop those back to Bootstrap's own --bs-* values, which this
   app never themes. */
.bg-primary {
    background-color: var(--primary-fill-solid) !important;
    color: var(--on-primary-fill) !important;
}

.bg-success {
    background-color: var(--success-fill) !important;
    color: #ffffff !important;
}

.bg-warning {
    /* Amber is the one that only passes under DARK text — .btn-warning carries
       --on-warning for the same reason. White here measured 2.15:1. */
    background-color: var(--warning-fill) !important;
    color: var(--on-warning) !important;
}

.bg-danger {
    background-color: var(--danger-fill) !important;
    color: #ffffff !important;
}

.bg-info {
    background-color: var(--info-fill) !important;
    color: #ffffff !important;
}

.bg-secondary {
    background-color: var(--neutral-fill) !important;
    color: #ffffff !important;
}

/* Dark: the FILL and the LABEL are the same as above — only the ring is about
   the dark card, where a badge and the surface under it can sit a few steps
   apart. rgba(var(--*-rgb)) rather than a literal, so .bg-primary's ring is
   the design's. */
[data-theme="dark"] .bg-primary   { box-shadow: 0 0 0 1px rgba(var(--primary-rgb), 0.3); }
[data-theme="dark"] .bg-success   { box-shadow: 0 0 0 1px rgba(var(--success-rgb), 0.3); }
[data-theme="dark"] .bg-warning   { box-shadow: 0 0 0 1px rgba(var(--warning-rgb), 0.3); }
[data-theme="dark"] .bg-danger    { box-shadow: 0 0 0 1px rgba(var(--danger-rgb), 0.3); }
[data-theme="dark"] .bg-info      { box-shadow: 0 0 0 1px rgba(var(--info-rgb), 0.3); }
[data-theme="dark"] .bg-secondary { box-shadow: 0 0 0 1px rgba(107, 114, 128, 0.3); }

/* ===========================================
   SUBTLE (chip) STATUS BADGES
   ===========================================
   The `bg-X-subtle text-X` pair is the app's standard status chip — it is what
   Lookups.BADGE_PALETTE emits, and it is hand-written in the status maps of
   offices, employees, tasks, admin/users and the two notifications modules.
   Left to Bootstrap it came out of --bs-* variables, which this app never
   themes (we switch on data-theme, Bootstrap on data-bs-theme). Two
   consequences, both measured:

     - it FAILED contrast everywhere: "New" (bg-info-subtle) was 1.68:1,
       bg-primary-subtle 3.42:1, bg-success-subtle 3.49:1
     - it was byte-IDENTICAL in dark mode — the same pale lavender/cyan/mint
       chips on a #1a1a2e card, because no --bs-* value ever changed

   Overriding the Bootstrap classes rather than minting new ones is deliberate:
   it fixes all ~12 JS call sites at once with no JS change and no risk of
   missing one, and it repairs the dark theme in the same stroke.

   Scoped to .badge on purpose — .text-success / .text-primary are ordinary
   text utilities elsewhere in the app and must keep their meaning there.

   WHY THE MIX PERCENTAGES DIFFER PER HUE, and why they are not one shared
   number: luminance is not evenly distributed around the colour wheel. Amber
   and green sit near the top of the ramp, indigo near the bottom, so pushing
   every token the same distance toward black leaves green at ~3.5:1 while
   indigo overshoots. Each percentage below is tuned to land its own hue in the
   5-7:1 band; retune the ONE that moves if a status token is ever changed. */
.badge.bg-primary-subtle   { background-color: color-mix(in srgb, var(--primary) 15%, var(--card)) !important; }
.badge.bg-success-subtle   { background-color: color-mix(in srgb, var(--success) 18%, var(--card)) !important; }
.badge.bg-info-subtle      { background-color: color-mix(in srgb, var(--info) 15%, var(--card)) !important; }
.badge.bg-warning-subtle   { background-color: color-mix(in srgb, var(--warning) 20%, var(--card)) !important; }
.badge.bg-danger-subtle    { background-color: color-mix(in srgb, var(--danger) 15%, var(--card)) !important; }
.badge.bg-secondary-subtle { background-color: color-mix(in srgb, var(--text-secondary) 14%, var(--card)) !important; }

.badge.text-primary   { color: color-mix(in srgb, var(--primary) 82%, #000) !important; }
.badge.text-success   { color: color-mix(in srgb, var(--success) 52%, #000) !important; }
.badge.text-info      { color: color-mix(in srgb, var(--info) 62%, #000) !important; }
.badge.text-warning   { color: color-mix(in srgb, var(--warning) 44%, #000) !important; }
.badge.text-danger    { color: color-mix(in srgb, var(--danger) 68%, #000) !important; }
.badge.text-secondary,
.badge.text-secondary-emphasis { color: var(--text-secondary) !important; }

/* Dark: the chip is a tint of the status colour over the DARK card, so the
   label has to travel the other way — toward white, not black. Same tuning
   logic, mirrored.
   ─────────────────────────────────────────────────────────────────────────
   RETUNED IN PHASE 4, and by the rule above ("retune the ONE that moves") —
   except that what moved was not a status token but the CARD. Both halves of
   this chip are mixed with --card, so a design whose dark card is lighter
   yields a lighter chip under the same label: on ليموني's #143a44 the danger
   chip measured 4.32:1 and the info chip 4.31:1, against 5.45–5.67:1 on the
   other three, with success at 4.59:1 a step behind it. The percentages below
   are the largest status share — i.e. the smallest change from what was here —
   that clears 4.8:1 on ALL FOUR dark cards, so the band is 4.8–6.9 rather than
   the light half's 5–7. They cannot live in a design file: one percentage
   serves every design, which is what makes the tightest card the one to tune
   against. warning was already clear at 5.13:1 and did not move. */
[data-theme="dark"] .badge.bg-primary-subtle   { background-color: color-mix(in srgb, var(--primary) 22%, var(--card)) !important; }
[data-theme="dark"] .badge.bg-success-subtle   { background-color: color-mix(in srgb, var(--success) 20%, var(--card)) !important; }
[data-theme="dark"] .badge.bg-info-subtle      { background-color: color-mix(in srgb, var(--info) 20%, var(--card)) !important; }
[data-theme="dark"] .badge.bg-warning-subtle   { background-color: color-mix(in srgb, var(--warning) 20%, var(--card)) !important; }
[data-theme="dark"] .badge.bg-danger-subtle    { background-color: color-mix(in srgb, var(--danger) 20%, var(--card)) !important; }
[data-theme="dark"] .badge.bg-secondary-subtle { background-color: color-mix(in srgb, var(--text-secondary) 18%, var(--card)) !important; }

[data-theme="dark"] .badge.text-primary   { color: color-mix(in srgb, var(--primary) 70%, #fff) !important; }
[data-theme="dark"] .badge.text-success   { color: color-mix(in srgb, var(--success) 77%, #fff) !important; }
[data-theme="dark"] .badge.text-info      { color: color-mix(in srgb, var(--info) 67%, #fff) !important; }
[data-theme="dark"] .badge.text-warning   { color: color-mix(in srgb, var(--warning) 88%, #fff) !important; }
[data-theme="dark"] .badge.text-danger    { color: color-mix(in srgb, var(--danger) 67%, #fff) !important; }
[data-theme="dark"] .badge.text-secondary,
[data-theme="dark"] .badge.text-secondary-emphasis { color: var(--text-secondary) !important; }

/* ===========================================
   ALERTS
   =========================================== */

.alert {
    border-radius: var(--radius-lg);
    border: 1px solid;
    padding: 0.875rem 1rem;
}

/*
 * AA note — an alert's text is --*-text, the readable-as-text shade, and NOT
 * the matching --danger / --success / --warning / --info token beside it.
 *
 * An alert paints its text over a 10%-tint-of-itself on the surface it sits
 * on, so the background is near the surface and the readable range is much
 * narrower than it looks. The 600-level tokens fail WCAG AA against that
 * flattened background — danger 4.24:1, success 3.00:1, warning 2.95:1 — which
 * is exactly why --*-text exists in _variables.css, tuned per theme against
 * the page background.
 *
 * PHASE 4 REPLACED FOUR LIGHT AND FOUR DARK LITERALS WITH THOSE TOKENS, and
 * doing it surfaced a second failure the literals had been hiding. An alert
 * appears on a CARD and on the PAGE BACKGROUND, and only the card was ever
 * measured: on --bg the light literals read 3.76:1 (success), 3.85:1
 * (warning) and 3.80:1 (info) on بنفسجي, all under the floor, and no better
 * on the other three designs. The tokens clear both surfaces in all four:
 * 4.67:1 at the tightest, 6.99:1 at the widest.
 *
 * The two blocks still do not merge — the tint and the border are 0.1/0.3 in
 * light and 0.15/0.4 in dark — but the COLOUR is one declaration now, because
 * --*-text already flips with the theme. That is the part that had drifted:
 * eight hand-kept values for four concepts.
 *
 * Do not put a raw hex back here. If an alert reads badly, the shade is
 * --*-text in _variables.css and it is measured on four cards at once.
 */

.alert-danger,
.alert-error {
    background: rgba(var(--danger-rgb), 0.1);
    border-color: rgba(var(--danger-rgb), 0.3);
    color: var(--danger-text);
}

[data-theme="dark"] .alert-danger,
[data-theme="dark"] .alert-error {
    background: rgba(var(--danger-rgb), 0.15);
    border-color: rgba(var(--danger-rgb), 0.4);
}

.alert-success {
    background: rgba(var(--success-rgb), 0.1);
    border-color: rgba(var(--success-rgb), 0.3);
    color: var(--success-text);
}

[data-theme="dark"] .alert-success {
    background: rgba(var(--success-rgb), 0.15);
    border-color: rgba(var(--success-rgb), 0.4);
}

.alert-warning {
    background: rgba(var(--warning-rgb), 0.1);
    border-color: rgba(var(--warning-rgb), 0.3);
    color: var(--warning-text);
}

[data-theme="dark"] .alert-warning {
    background: rgba(var(--warning-rgb), 0.15);
    border-color: rgba(var(--warning-rgb), 0.4);
}

.alert-info {
    background: rgba(var(--info-rgb), 0.1);
    border-color: rgba(var(--info-rgb), 0.3);
    color: var(--info-text);
}

[data-theme="dark"] .alert-info {
    background: rgba(var(--info-rgb), 0.15);
    border-color: rgba(var(--info-rgb), 0.4);
}

/* ===========================================
   DROPDOWNS
   =========================================== */

.dropdown {
    position: relative;
}

.dropdown.show {
    z-index: var(--z-modal-backdrop);
}

.dropdown-menu {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-md);
    padding: 0.5rem;
    z-index: var(--z-modal) !important;
}

.dropdown-menu.show {
    z-index: var(--z-modal) !important;
}

.dropdown-item {
    color: var(--text);
    border-radius: var(--radius-sm);
    padding: 0.5rem 0.75rem;
}

.dropdown-item:hover {
    background: var(--bg-secondary);
    color: var(--primary);
}

.dropdown-item.active,
.dropdown-item:active {
    background-color: var(--primary);
    color: var(--on-primary) !important;
}

.dropdown-item:disabled,
.dropdown-item.disabled {
    background-color: var(--bg-secondary);
    color: var(--text-muted) !important;
    opacity: 0.7;
    cursor: not-allowed;
}

.dropdown-item.active .text-success,
.dropdown-item:disabled .text-success,
.dropdown-item.disabled .text-success,
.dropdown-item.active .text-secondary,
.dropdown-item:disabled .text-secondary,
.dropdown-item.disabled .text-secondary,
.dropdown-item.active .text-dark,
.dropdown-item:disabled .text-dark,
.dropdown-item.disabled .text-dark {
    color: currentColor !important;
}

/* NO text-transform and NO letter-spacing. This heading is «قائمة المستخدم»:
   uppercase is a no-op in Arabic — the script has no letter case — and
   letter-spacing pulls its cursive joins apart, so the pair only ever cost
   legibility here. Same audit as .admin-table thead th and .perm-group-row. */
.dropdown-header {
    color: var(--text-muted);
    font-size: 0.75rem;
}

.dropdown-divider {
    border-color: var(--border);
}

/* Dark mode dropdown colors.
   Three hardcoded greys until phase 4, and they were patching a problem that
   has since been fixed one level up: .text-success, .text-secondary and
   .text-dark are Bootstrap utilities on frozen --bs-* values, so they follow
   neither the theme nor the design, and this rule re-coloured them for the
   dark dropdown alone — every OTHER use of them on the page stayed frozen.
   _base.css now remaps the utilities themselves, so these only have to say
   what is different INSIDE a dropdown, which is nothing. .text-dark is the
   exception worth keeping: it names a colour rather than a role, so on the
   dark theme it has to mean --text or it paints near-black on near-black. */
[data-theme="dark"] .dropdown-item .text-dark {
    color: var(--text) !important;
}

.dropdown-item:hover .text-success,
.dropdown-item:hover .text-secondary,
.dropdown-item:hover .text-dark {
    opacity: 0.9;
}

/* ===========================================
   MODALS
   =========================================== */

.modal-content {
    background-color: var(--card);
    border-color: var(--border);
}

.modal-content.modal-sm {
    max-width: 360px;
}

.modal-header {
    border-bottom-color: var(--border);
}

.modal-footer {
    border-top-color: var(--border);
}

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

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

[data-theme="dark"] .btn-close {
    filter: invert(1) grayscale(100%) brightness(200%);
}

/* iOS: Bootstrap's .modal covers the whole screen and puts the dialog 0.5rem
   from its top on a phone — under the status bar wherever the page is drawn
   beneath it (see "iOS PWA SAFE-AREA" in _layout.css). Padding the .modal box
   moves every dialog, centred, scrollable and fullscreen alike, since their
   heights are percentages of it. Every inset is 0 on a desktop and in a
   Safari tab. Bootstrap's JS pads only the left and right, for the scrollbar. */
.modal {
    padding-top: env(safe-area-inset-top, 0px);
    padding-bottom: env(safe-area-inset-bottom, 0px);
}

/* Modal Overlay */
.modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.7);
    display: none;
    align-items: center;
    justify-content: center;
    z-index: var(--z-modal);
    /* iOS: keep the centred dialog clear of the status bar and home bar. */
    padding-block: env(safe-area-inset-top, 0px) env(safe-area-inset-bottom, 0px);
}

.modal-overlay.show {
    display: flex;
}

.modal-overlay .modal-content {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius-modal);
    width: 90%;
    max-width: 700px;
    /* dvh, not vh — on iOS Safari `vh` is the TOOLBAR-HIDDEN height, so 90vh is
       taller than what the user can actually see whenever the toolbar is up,
       and the modal's action row sat just below the fold. The overlay scrolls,
       so Save was reachable, but only by scrolling a dialog that looked like it
       already ended. `dvh` tracks the live viewport.

       The vh line stays as the fallback for anything without dvh support; a
       browser that understands both takes the second. Less the iOS insets
       the overlay pads, so the dialog still fits between them. */
    max-height: calc(90vh - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px));
    max-height: calc(90dvh - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px));
    overflow-y: auto;
    position: relative;
    box-shadow: var(--shadow-lg);
}

.modal-overlay .modal-header {
    text-align: center;
    padding: 1rem 2rem 1.5rem;
    background: linear-gradient(180deg, rgba(var(--primary-rgb), 0.1) 0%, transparent 100%);
    border-bottom: 1px solid var(--border);
}

.modal-overlay .modal-header h2 {
    font-size: 1.5rem;
    color: var(--text);
    margin-bottom: 0.5rem;
    font-weight: 600;
}

.modal-overlay .modal-header p {
    color: var(--primary);
    font-size: 1rem;
}

.modal-close {
    position: absolute;
    top: 1rem;
    right: 1rem;
    background: none;
    border: none;
    color: var(--text-secondary);
    font-size: 1.5rem;
    cursor: pointer;
    padding: 0.5rem;
    line-height: 1;
    transition: color var(--transition-normal);
    z-index: 10;
}

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

.modal-overlay .modal-body {
    padding: 1rem 1.5rem 1rem;
    color: var(--text);
}

/*
 * Long form inside a modal: the FORM scrolls, not the whole modal.
 * .modal-overlay .modal-content already caps at 90vh and scrolls, but that
 * scrolls the header and the .modal-actions row away with the fields — on a
 * short viewport the submit button ends up below the fold of a dialog that
 * looks complete. Bounding the form instead keeps the title and Cancel/Save
 * fixed while only the fields move.
 *
 * 62vh matches .lookup-source-panel (_admin.css) and leaves room for the
 * header + actions inside the 90vh cap. Same thin-scrollbar treatment as that
 * panel, so both scrollers read as one control.
 */
.modal-form-scroll {
    max-height: 62vh;
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-gutter: stable;
    /*
     * Absorbs the .row negative inline margins, cancelled by an equal negative
     * margin so the fields stay exactly where they were.
     *
     * Required, not cosmetic: a Bootstrap .row pulls itself half a gutter wider
     * than its parent on each side (g-3 -> -0.5rem), which used to spill
     * harmlessly into .modal-body's padding while this element was
     * overflow: visible. It is not any more — per the overflow spec, an
     * overflow-y of auto makes the other axis compute from visible to auto —
     * so without this the overhang becomes a HORIZONTAL scrollbar across the
     * form. 0.75rem rather than 0.5rem leaves the extra quarter for Bootstrap's
     * focus ring, which overflow-x: hidden would have clipped instead.
     */
    padding-inline: 0.75rem;
    margin-inline: -0.75rem;
    /* Scrollbar skin comes from the global rules in _base.css. */
}

/*
 * Section heading inside a long .modal-form-scroll form.
 *
 * Shared, not scoped to one form: offices' 12-field modal reads fine as a
 * flat .row, but past ~15 fields the form becomes a wall and the scroll
 * container means the user never sees it whole. Headings are what make a
 * scroll position mean something. First use: the clients modal (20 fields).
 *
 * Colours are project tokens, so this follows data-theme with no extra rule.
 * --text (not --text-muted) because a section heading is structure, not
 * decoration. That reasoning is now the WHOLE reason: this used to add "and
 * --text-muted is only 2.5:1 on the light card", which stopped being true when
 * the light --text-muted was fixed (2.54 -> 5.29:1). Do not read this rule as
 * evidence that --text-muted is unusable — it is a hierarchy choice.
 *
 * NO horizontal margin: this sits between .row siblings inside a container
 * that already carries .modal-form-scroll's padding/margin-inline pair, and
 * adding any here would break that cancellation.
 */
/*
 * Asymmetric margin on purpose: a large gap ABOVE separates this section from
 * the previous one's last field, a small gap BELOW keeps the heading visually
 * attached to the fields it labels. Equal margins would make the heading float
 * between two groups and read as belonging to neither.
 */
.modal-form-section {
    margin-block: 2.25rem 0.875rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid var(--border);
    color: var(--text);
    font-size: 0.9375rem;
    font-weight: 600;
}

.modal-form-section:first-child {
    margin-block-start: 0;
}

/*
 * Modal form tabs — a FIXED strip above .modal-form-scroll.
 *
 * First use: the employees modal, which splits ~23 fields into "employee
 * details" and "system login details". The strip sits OUTSIDE the scroll
 * container so it stays put while only the active pane's fields scroll — the
 * same reasoning that keeps the title and the Cancel/Save row fixed.
 *
 * Not Bootstrap's .nav-tabs: that is coloured by --bs-* variables, which this
 * app never sets (we switch on data-theme), so it would keep its light values
 * forever on a dark card. Project tokens only.
 */
.modal-form-tabs {
    display: flex;
    /* Wrap rather than scroll: three long Arabic tab labels overflow a phone
       viewport, and a horizontal scroller hides tabs behind an affordance the
       user has no reason to look for. Wrapping keeps every tab visible. */
    flex-wrap: wrap;
    gap: 0.25rem;
    margin-bottom: 1rem;
    border-bottom: 1px solid var(--border);
}

.modal-form-tab {
    padding: 0.5rem 1rem;
    border: none;
    border-bottom: 2px solid transparent;
    background: transparent;
    color: var(--text-secondary);
    font-size: 0.9375rem;
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
}

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

/* --primary-dark for the LABEL, --primary for the underline — the same split
   .sidebar-link.active uses, for the same reason: this text sits on --bg (the
   tab strip is on the bare page), and --primary is tuned to pass on --card;
   on the dimmed page it measures 4.28:1. --primary-dark is the
   higher-contrast member of the pair in both themes. The underline is a
   3:1-class boundary, not text, so it keeps the brand token. */
.modal-form-tab.active {
    border-bottom-color: var(--primary);
    color: var(--primary-dark);
}

/*
 * A form switch that owns its own bordered box — the employees modal's "enable
 * a system login" master switch, which gates the whole pane below it.
 *
 * Pair with Bootstrap's p-3 for the padding. The reason this class has to exist
 * at all: .form-switch sets padding-inline-start: 2.5em on the WRAPPER and
 * margin-inline-start: -2.5em on the INPUT, and the two cancel out. Inside a
 * bordered box that puts the toggle flush against the border while the other
 * three sides keep their padding. Zeroing the negative margin lets the box's
 * own padding apply on all four sides.
 *
 * margin-inline-start, not margin-left: the RTL Bootstrap build sets the
 * negative margin on the right, so a physical property would fix one direction
 * and leave the other broken.
 */
.form-switch-box {
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}

.form-switch-box .form-check-input {
    margin-inline-start: 0;
}

/*
 * A bordered box holding a group of checkboxes — the employees modal's office
 * access and roles groups. Pair with Bootstrap's p-3 for the padding.
 *
 * The max-height is the reason this is a class and not two inline styles. Both
 * groups list TENANT data (offices, roles), so their length is unbounded: an
 * office list that grows past a dozen entries pushes the Save button off the
 * bottom of the modal, and the control that replaced the <select multiple> ends
 * up worse than the one it replaced. The select had size="5" doing this job;
 * scrolling the box is the checkbox equivalent.
 *
 * No fixed height — a two-role tenant gets a two-row box, not four rows of
 * blank bordered space.
 */
.checkbox-group-box {
    max-height: 12rem;
    overflow-y: auto;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}

/*
 * Table/card view toggle. Both views render from the SAME page data, so
 * switching never refetches and the two can never disagree.
 */
.view-toggle {
    display: inline-flex;
    gap: 2px;
    padding: 2px;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--bg-secondary);
}

.view-toggle-btn {
    padding: 0.35rem 0.65rem;
    border: none;
    border-radius: calc(var(--radius-md) - 3px);
    background: transparent;
    color: var(--text-secondary);
    cursor: pointer;
}

.view-toggle-btn:hover {
    color: var(--text);
}

.view-toggle-btn[aria-pressed="true"] {
    background: var(--card);
    color: var(--primary);
}

/*
 * Employee card — the card half of that toggle. Flex column with the actions
 * pushed to the bottom by margin-top:auto, so cards in a row keep their action
 * bars aligned however much the meta lines wrap.
 */
.employee-card {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    height: 100%;
    padding: 1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-xl);
    background: var(--card);
}

.employee-card:hover {
    border-color: var(--primary);
}

.employee-card-name {
    color: var(--text);
    font-size: 1rem;
    font-weight: 600;
}

.employee-card-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.75rem;
    color: var(--text-secondary);
    font-size: 0.8125rem;
}

.employee-card-contact {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.75rem;
    color: var(--text-muted);
    font-size: 0.8125rem;
}

.employee-card-actions {
    display: flex;
    gap: 0.375rem;
    margin-top: auto;
    padding-top: 0.5rem;
    border-top: 1px solid var(--border);
}

/* Input Group Text (global, applies to all pages). One rule serves both
   themes — every value is a themed token, so the old [data-theme="dark"]
   duplicate said nothing the base rule didn't.
   border-color is --border-strong, NOT --border: the add-on is part of a
   CONTROL's boundary (it continues the input's outline around the icon), and
   .form-control draws --border-strong (_forms.css, WCAG 1.4.11). An add-on
   left on --border (1.47:1 on the light card) renders as a borderless grey
   slab welded to a visibly framed input — the /login icon-box bug. */
.input-group-text {
    background-color: var(--bg-secondary);
    border-color: var(--border-strong);
    color: var(--text-muted);
    /* The FIELD's radius, for the same reason as the border colour above: the
       add-on continues the input's outline around the icon, so the two halves
       of one control have to round by the same amount. Bootstrap leaves it on
       --bs-border-radius, which follows neither the theme nor the design and
       is 6px against --radius-field's 8px — a 2px step where the add-on meets
       the field's outer corner, on every input-group in the app.
       The SEAM corners are untouched: Bootstrap zeroes them through
       `.input-group > :not(:last-child)…`, which is (0,6,0) and outranks this
       flat property, so only the outer corners take the token. */
    border-radius: var(--radius-field);
}

/* Verification Code Input - global */
.verification-code-input {
    text-align: center;
    font-size: 1.75rem;
    font-weight: 600;
    letter-spacing: 0.5rem;
    font-family: var(--font-mono);
    padding: 1rem;
}

/* Keyboard Shortcut List Styles */
.shortcut-list {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.shortcut-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0.75rem 1rem;
    background: var(--bg-secondary);
    border-radius: var(--radius-md);
    border: 1px solid var(--border);
}

.shortcut-keys {
    display: flex;
    align-items: center;
    gap: 0.25rem;
}

.shortcut-keys kbd {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius-xs);
    padding: 0.25rem 0.5rem;
    font-size: 0.8rem;
    font-family: var(--font-mono);
    color: var(--text);
    box-shadow: 0 2px 0 var(--border);
}

.shortcut-desc {
    color: var(--text-secondary);
    font-size: 0.9rem;
}

/* ===========================================
   Modal Drag & Drop
   =========================================== */

.modal-dropzone {
    border: 2px dashed var(--border);
    border-radius: var(--radius-xl);
    padding: 2rem;
    text-align: center;
    background: var(--bg-secondary);
    transition: border-color var(--transition-normal), background var(--transition-normal);
}

.modal-dropzone.dragover {
    border-color: var(--primary);
    background: rgba(var(--primary-rgb), 0.1);
}

.modal-dropzone.dropzone-error {
    border-color: var(--danger) !important;
    background: rgba(var(--danger-rgb), 0.1) !important;
    animation: shake 0.5s ease-in-out;
}

.modal-dropzone-text {
    color: var(--text-muted);
    font-size: 1rem;
    margin-bottom: 1.5rem;
}

.modal-source-buttons {
    display: flex;
    justify-content: center;
    gap: 0.75rem;
    flex-wrap: wrap;
}

.modal-source-btn {
    background: var(--bg);
    border: 1px solid var(--border);
    color: var(--text);
    padding: 0.75rem 1.5rem;
    border-radius: var(--radius-pill);
    font-size: 0.875rem;
    font-weight: 500;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    transition: all var(--transition-normal);
}

.modal-source-btn:hover {
    background: var(--bg-secondary);
    transform: translateY(-1px);
}

.modal-source-btn:active {
    transform: translateY(0);
}

.modal-source-btn.primary {
    background: var(--primary);
    color: #ffffff;
    border-color: var(--primary);
    font-weight: 600;
    box-shadow: var(--shadow-primary);
}

.modal-source-btn.primary:hover {
    background: var(--primary-dark);
    border-color: var(--primary-dark);
    box-shadow: var(--shadow-primary-hover);
}

/* Hidden file input */
.hidden-file-input {
    display: none;
}

/* ===========================================
   Delete Confirmation Modal
   =========================================== */

.delete-confirm-modal {
    z-index: 2100; /* Higher than note-view-modal (2000) */
}

.delete-confirm-content {
    max-width: 420px;
    width: 90%;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius-2xl);
    box-shadow: var(--shadow-xl);
    animation: modalSlideIn 0.3s ease-out;
}

.delete-confirm-content .modal-header {
    background: var(--bg-secondary);
    border-bottom: 1px solid var(--border);
    padding: 1.25rem 1.5rem;
    border-radius: var(--radius-2xl) var(--radius-2xl) 0 0;
}

.delete-confirm-content .modal-header h2 {
    color: var(--text);
    font-size: 1.15rem;
    font-weight: 600;
    margin: 0;
    display: flex;
    align-items: center;
}

.delete-confirm-content .modal-header .text-danger {
    color: var(--danger) !important;
}

.delete-confirm-content .modal-body {
    padding: 1.5rem;
}

.delete-confirm-message {
    color: var(--text);
    font-size: 0.95rem;
    line-height: 1.6;
    margin: 0 0 0.5rem 0;
}

.delete-confirm-message strong {
    color: var(--primary);
    font-weight: 600;
}

.delete-confirm-warning {
    color: var(--text-secondary);
    font-size: 0.85rem;
    margin: 0;
}

.delete-confirm-content .modal-close {
    position: absolute;
    top: 1rem;
    right: 1rem;
    background: none;
    border: none;
    color: var(--text-secondary);
    font-size: 1.25rem;
    cursor: pointer;
    padding: 0.25rem;
    line-height: 1;
    transition: color var(--transition-normal);
    z-index: 10;
}

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

.delete-confirm-content .btn-secondary {
    background: transparent;
    color: var(--text-secondary);
    border: 2px solid var(--border);
    font-weight: 600;
    padding: 0.6rem 1.25rem;
    border-radius: var(--radius-md);
    transition: all var(--transition-normal);
}

.delete-confirm-content .btn-secondary:hover {
    background: var(--bg-secondary);
    color: var(--text);
    border-color: var(--border);
}

/* The fill shades, like every other .btn-danger in the app. It was --danger
   under white (3.76:1 light, 2.77:1 dark) with #dc2626 and #f87171 written
   into the hover and the dark rule — four literals for what --danger-fill and
   --danger-fill-hover already say, on the button that carries out a delete.
   The dark pair goes with them: the fill is the same hex in both themes. */
.delete-confirm-content .btn-danger {
    background: var(--danger-fill);
    border-color: var(--danger-fill);
    color: #ffffff;
    font-weight: 600;
    padding: 0.6rem 1.25rem;
    border-radius: var(--radius-md);
    box-shadow: 0 2px 8px rgba(var(--danger-rgb), 0.3);
    transition: all var(--transition-normal);
}

.delete-confirm-content .btn-danger:hover {
    background: var(--danger-fill-hover);
    border-color: var(--danger-fill-hover);
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(var(--danger-rgb), 0.4);
}

.delete-confirm-content .btn-danger:disabled {
    background: var(--danger-fill);
    border-color: var(--danger-fill);
    opacity: 0.7;
    transform: none;
    cursor: not-allowed;
}

.delete-confirm-content .d-flex {
    display: flex !important;
}

.delete-confirm-content .gap-2 {
    gap: 0.75rem !important;
}

.delete-confirm-content .justify-content-end {
    justify-content: flex-end !important;
}

.delete-confirm-content .mt-4 {
    margin-top: 1.5rem !important;
}

/* ===========================================
   Delete Confirm Modal — conversation title display + input field
   (Moved from list.html inline <style> block)
   ============================================ */

/* Eighteen literals in ten rules until phase 4, and every pair said the same
   thing twice: a [data-theme="dark"] rule and a [data-theme="light"] rule
   naming two greys for one role. The ladder tokens already flip with the
   theme, so one declaration replaces each pair — and, unlike a grey, it
   follows the DESIGN too: these rules sat one step from painting a violet-era
   #f3f4f6 inside a ليموني modal.
   #deleteConfirmModal is a ROUTED id — /cases/{id} carries one — while
   #conversationTitleToDelete and #deleteTitleInput belong to the routeless
   conversations module. They are converted together because they are one
   modal's worth of rules and the routed half is the reason this cannot be
   left alone: the .form-label and .text-muted rules match nothing in today's
   routed markup, so they would come alive wearing hardcoded greys the first
   time someone adds a label to that modal. */
#conversationTitleToDelete {
    color: var(--text);
    background-color: var(--bg-secondary);
    padding: 1rem;
    border-radius: var(--radius-md);
    border: 2px solid var(--border);
    font-weight: 600;
    font-size: 1.1rem;
}

#deleteTitleInput {
    font-size: 1rem;
    color: var(--text);
    background-color: var(--card);
    border-color: var(--border);
}

#deleteTitleInput::placeholder {
    color: var(--text-muted);
    opacity: 1;
}

#deleteConfirmModal .form-label {
    color: var(--text);
    font-weight: 600;
}

#deleteConfirmModal .text-muted {
    color: var(--text-secondary);
    opacity: 1;
}

/* ===========================================
   TOAST NOTIFICATIONS
   =========================================== */

.toast-container {
    z-index: var(--z-toast) !important;
    max-width: 90vw;
}

/* Desktop default: top-right, below the navbar.
   Safe-area-inset-top ensures it clears the iOS notch
   when running as a standalone PWA. */
.toast-container-main {
    top: calc(1rem + env(safe-area-inset-top, 0px));
}

/* ONE toast shape for every design; only the colours differ (lime paints it
   ink, the others card-coloured). It took Bootstrap's .text-bg-success /
   -danger / -warning until 2026-09-16 — utilities that are !important and
   follow --bs-* rather than data-theme, so they could not be themed at all.
   toasts.js is untouched by the swap: it only toggles show / hide / showing. */
.toast {
    backdrop-filter: blur(10px);
    min-width: 300px;
    max-width: 500px;
    width: auto;
    background: var(--toast-bg);
    color: var(--toast-text);
    border: 1px solid var(--toast-border);
    box-shadow: var(--shadow-lg);
    border-radius: var(--radius-card);
    margin-bottom: 0.75rem;
    animation: slideInRight 0.3s ease-out;
}

/* The icon sits in a filled circle — that is what carries the meaning now
   that the toast itself is a neutral surface. The glyph inside INHERITS this
   font-size and is centred by the circle, so it needs no rule of its own:
   the old `.toast-body i` sized it 1.1rem and pushed it down 0.15rem, which
   was right when it was a bare icon beside flex-start text and is off-centre
   in a circle. `flex: 0 0 auto` keeps the circle a circle — see the message
   rule below for what was overriding it. */
.toast-icon {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 1.75rem;
    block-size: 1.75rem;
    margin-inline-end: 0.6rem;
    border-radius: var(--radius-circle);
    font-size: 0.9rem;
}

.toast-success .toast-icon {
    background: var(--toast-icon-success-bg);
    color: var(--toast-icon-success-fg);
}

.toast-error .toast-icon {
    background: var(--toast-icon-danger-bg);
    color: var(--toast-icon-danger-fg);
}

.toast-warning .toast-icon {
    background: var(--toast-icon-warning-bg);
    color: var(--toast-icon-warning-fg);
}

/* The close is the app's OWN icon — `fa-times` beside the circle's `fa-check`,
   so a toast carries one icon family and the × takes a plain `color` like
   everything else in the app. It was Bootstrap's `.btn-close`, a black SVG
   background-image that nothing but a `filter` could recolour; retiring it
   also retired --toast-close-filter from all four designs, where a filter had
   no business being (a design file is colours — rule 40).

   That swap is what fixes the WEIGHT. The two glyphs measured the same all
   along — Bootstrap's × 14px against the tick's 14.4px — but Bootstrap's is a
   solid mark filling its box corner to corner, so beside a thin-stroked tick
   and 14px Arabic text it was the heaviest thing in the toast. Font Awesome's
   × is the same stroke family as the tick, so equal sizes now read equal.

   And it is ANCHORED. The button wore `m-auto`, whose auto margins split the
   leftover space in two, so the × sat in the MIDDLE of the gap — measured
   104.9px from the end of a 300px toast — and moved with every message
   length. It is at the end edge now, inset by .toast-body's own padding, so
   the × mirrors the circle instead of drifting. */
.toast-close-btn {
    flex: 0 0 auto;
    align-self: flex-start;
    /* .toast-body's own padding, so the circle and this box are level and
       equally inset from their edges. The phone block below restates both,
       because that padding shrinks there. */
    margin-block-start: 0.75rem;
    margin-inline-start: auto;      /* to the end edge ... */
    margin-inline-end: 1rem;        /* ... and held inside it, mirroring the circle */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 1.75rem;
    block-size: 1.75rem;
    padding: 0;
    border: 0;
    border-radius: var(--radius-circle);
    background: transparent;
    color: var(--toast-text);
    font-size: 0.85rem;
    line-height: 1;
    opacity: 0.6;
    cursor: pointer;
    transition: opacity var(--transition-fast), background-color var(--transition-fast);
}

/* A tint of the toast's OWN ink, so the hover follows every design and both
   themes without a token of its own. */
.toast-close-btn:hover {
    opacity: 1;
    background-color: color-mix(in srgb, var(--toast-text) 12%, transparent);
}

.toast-close-btn:focus-visible {
    opacity: 1;
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

/* The picker's swatch (phase 3). It carries its OWN data-design, so these
   three tokens resolve to THAT design rather than the active one, and the
   descendant half of each design file's dark selector makes it follow the
   page's theme with no script. No hex and no design id in the markup. */
.design-swatch {
    display: inline-block;
    inline-size: 1.15rem;
    block-size: 1.15rem;
    margin-inline-end: 0.6rem;
    border-radius: var(--radius-xs);
    border: 1px solid var(--border);
    background: linear-gradient(135deg,
                var(--sidebar-bg) 0 40%,
                var(--primary-fill-solid) 40% 70%,
                var(--bg) 70% 100%);
    vertical-align: -0.2em;
}

/* The row of the design in use. design.js already marks it — aria-checked on
   a menuitemradio — and this is what PAINTS it: a brand tint behind the row
   and a brand check at its end, so the choice is not told by colour alone.
   The TEXT keeps --text, unlike the searchable select's selected option:
   --primary on its own tint measured 4.17:1 in بنفسجي's dark theme, under the
   4.5 floor, while the check is a graphic and needs only 3:1. The check keeps
   its place while hidden, so the menu does not change width when the choice
   moves. Declared after .dropdown-item:hover at equal weight, so the tint and
   the text colour both survive a hover. */
.design-pick {
    display: flex;
    align-items: center;
}

.design-pick-check {
    margin-inline-start: auto;
    padding-inline-start: 1rem;
    font-size: 0.8rem;
    visibility: hidden;
}

.design-pick[aria-checked="true"] {
    background-color: rgba(var(--primary-rgb), 0.12);
    color: var(--text);
    font-weight: 600;
}

.design-pick[aria-checked="true"] .design-pick-check {
    visibility: visible;
    color: var(--primary);
}

.toast.hide {
    animation: slideOutRight 0.3s ease-in;
}

.toast-body {
    font-weight: 500;
    display: flex;
    align-items: flex-start;
    padding: 0.75rem 1rem;
    word-wrap: break-word;
    word-break: break-word;
    white-space: normal;
    line-height: 1.5;
}

/* The MESSAGE span alone flexes — `.toast-body span` reached the icon too.
   That bare selector is (0,1,1) against `.toast-icon`'s (0,1,0), so it won:
   the circle took `flex: 1 1 0%`, its `inline-size` stopped applying (a
   flex-basis replaces the main size), and it grew to half the toast — a
   28px-tall box several hundred wide, which `border-radius: 50%` then drew
   as the giant ellipse. It was on every toast in the app. */
.toast-body > span:not(.toast-icon) {
    flex: 1;
    min-width: 0;
    overflow-wrap: break-word;
}

/* .btn-close-white is gone with the coloured toast bodies it was written for:
   a white close glyph needs a saturated background behind it, and the toast is
   a neutral surface now. Bootstrap's .btn-close followed it out; the close is
   .toast-close-btn above, coloured by --toast-text like the message it sits
   beside. */

/* Narrow screens: SIZE only — the toast stays at the top on every width.

   It used to be repositioned to the bottom here, and that was wrong twice
   over. The stated reason was the iOS notch, which `safe-area-inset-top` on
   .toast-container-main already clears. And the rule was scoped to a WIDTH
   breakpoint, so a merely narrow desktop window got the phone treatment —
   a 502px-wide browser showed its toasts at the bottom of the screen, far
   from the control that raised them. Width is the wrong axis for a device
   question (see the mobile/touch section in CLAUDE.md: `(pointer: coarse)`
   is how this project asks "is this a finger?").

   Everything left in here is genuinely about the width available. */
@media (max-width: 576px) {
    .toast-container {
        max-width: 95vw;
        padding: 0.5rem !important;
    }

    .toast {
        min-width: 280px;
        max-width: 400px;
    }

    .toast-body {
        font-size: 0.9rem;
        padding: 0.65rem 0.85rem;
    }

    /* Follows the padding above, or the × sits lower than the circle and
       closer to its edge than the circle is to the other one. */
    .toast-close-btn {
        margin-block-start: 0.65rem;
        margin-inline-end: 0.85rem;
    }
}

/* ===========================================
   SPINNERS
   =========================================== */

.spinner-border {
    border-color: var(--primary);
    border-right-color: transparent;
}

/* ===========================================
   EMPTY STATES
   =========================================== */

.empty-state {
    text-align: center;
    padding: 2rem 1rem;
    color: var(--text-muted);
}

.empty-state-icon {
    font-size: 2.5rem;
    margin-bottom: 1rem;
    opacity: 0.5;
    color: var(--text-muted);
}

.empty-state-title {
    font-size: 0.95rem;
    margin-bottom: 0.5rem;
    color: var(--text);
}

.empty-state-text {
    font-size: 0.8rem;
    line-height: 1.5;
}

/* ===========================================
   PAGINATION
   =========================================== */

#paginationContainer {
    clear: both;
    width: 100%;
    padding: 1.5rem 0;
    margin-top: 2rem;
    margin-bottom: 1rem;
    border-top: 1px solid var(--border);
    scroll-margin-top: 20px;
}

.pagination-info {
    text-align: center;
    color: var(--text);
    font-size: 0.95rem;
    font-weight: 500;
    margin-bottom: 1rem;
}

.pagination-info i {
    color: var(--primary);
}

.pagination {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin: 0;
    padding: 0;
    list-style: none;
}

.page-item {
    display: inline-block;
}

.page-link {
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 40px;
    min-height: 40px;
    padding: 0.5rem 0.75rem;
    font-size: 0.95rem;
    font-weight: 500;
    color: var(--text);
    background-color: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius-xl);
    text-decoration: none;
    transition: all var(--transition-normal);
    cursor: pointer;
}

.page-link:hover {
    color: var(--primary);
    background-color: var(--bg-secondary);
    border-color: var(--primary);
    transform: translateY(-2px);
    box-shadow: 0 2px 8px rgba(var(--primary-rgb), 0.15);
}

.page-item.active .page-link {
    color: var(--on-primary);
    background-color: var(--primary);
    border-color: var(--primary);
    font-weight: 600;
    cursor: default;
    box-shadow: var(--shadow-primary);
}

/* Dark's --primary (#8b7dd8) is a LIGHT purple — that is what makes it work
   as text on a dark card — so white ON it only reaches 3.49:1. Filling the
   active page with the brand fill instead puts its white label on the deep
   stops (7.49:1 / 5.91:1), and reuses the exact fill .btn-outline-primary.active
   above already uses for its selected state, so "selected = the brand fill"
   stays one rule. Dark-scoped: light's flat active fill passes as-is (5.27:1). */
[data-theme="dark"] .page-item.active .page-link {
    background: var(--primary-fill);
    border-color: var(--primary);
}

.page-item.active .page-link:hover {
    transform: none;
}

.page-item.disabled .page-link {
    color: var(--text-muted);
    background-color: var(--bg-secondary);
    border-color: var(--border);
    cursor: not-allowed;
    opacity: 0.5;
}

.page-item.disabled .page-link:hover {
    transform: none;
    box-shadow: none;
    border-color: var(--border);
}

.page-item.disabled .page-link span {
    color: var(--text-muted);
}

.page-link[aria-label="Previous"],
.page-link[aria-label="Next"] {
    font-weight: bold;
}

/* Dark Theme Overrides */
[data-theme="dark"] .page-link {
    background-color: var(--card);
    border-color: var(--border);
    color: var(--text);
}

[data-theme="dark"] .page-link:hover {
    background-color: rgba(var(--primary-rgb), 0.1);
    border-color: var(--primary);
}

/* The label sits on the brand fill the dark rule further up paints. */
[data-theme="dark"] .page-item.active .page-link {
    background-color: var(--primary);
    border-color: var(--primary);
    color: var(--on-primary-fill);
}

[data-theme="dark"] .page-item.disabled .page-link {
    background-color: rgba(255, 255, 255, 0.05);
    border-color: var(--border);
    opacity: 0.4;
}

/* Responsive Adjustments */
@media (max-width: 768px) {
    #paginationContainer {
        padding: 1rem 0;
    }
    
    .pagination {
        gap: 0.25rem;
    }
    
    .page-link {
        min-width: 36px;
        min-height: 36px;
        padding: 0.4rem 0.6rem;
        font-size: 0.875rem;
    }
    
    .pagination-info {
        font-size: 0.875rem;
    }
}

/* ===========================================
   STUDIO DOCUMENT LIST (summary, depth analysis, etc.)
   =========================================== */

.summary-document-list {
    max-height: 200px;
    overflow-y: auto;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: 0.5rem;
    margin-top: 0.25rem;
    background: var(--bg);
}

.summary-doc-item {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    padding: 0.375rem 0.5rem;
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background 0.15s;
    width: 100%;
    box-sizing: border-box;
}

.summary-doc-item:hover {
    background: var(--hover);
}

.summary-doc-item input[type="checkbox"] {
    flex-shrink: 0;
    margin-top: 0.2rem;
}

.summary-doc-item span {
    flex: 1;
    min-width: 0;          /* allow flex child to shrink below intrinsic width */
    overflow-wrap: break-word;
    word-break: break-all; /* break long filenames with underscores */
    white-space: normal;
    color: var(--text);
    font-size: 0.875rem;
    line-height: 1.4;
}

@media (max-width: 575.98px) {
    .summary-doc-item span {
        font-size: 0.8125rem;
    }
}

[data-theme="dark"] .summary-document-list {
    background: var(--card);
}

/* ===========================================
   UPLOAD PROGRESS
   =========================================== */

.upload-progress-container {
    padding: 1rem;
    text-align: center;
}

.upload-progress-title {
    color: var(--text);
    margin-bottom: 1.5rem;
}

.upload-progress-title i {
    color: var(--primary);
}

.upload-files-list {
    max-height: 200px;
    overflow-y: auto;
    margin-bottom: 1rem;
    text-align: left;
}

.upload-file-item {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    margin-bottom: 0.5rem;
    font-size: 0.875rem;
}

.upload-file-item .file-name {
    flex: 1;
    min-width: 0; /* allow flex child to shrink below content size */
    overflow-wrap: break-word;
    word-break: break-all;
    white-space: normal;
    color: var(--text);
}

@media (max-width: 575.98px) {
    .upload-file-item .file-name {
        font-size: 0.8125rem;
    }
}

.upload-file-item .file-status {
    flex-shrink: 0;
    min-width: 20px;
    text-align: center;
    padding-top: 0.1rem;
}

.upload-status {
    color: var(--text-muted);
    font-size: 0.875rem;
    padding: 0.5rem;
    background: var(--bg);
    border-radius: var(--radius-md);
    overflow-wrap: break-word;
    word-break: break-all;
    white-space: normal;
}

[data-theme="dark"] .upload-file-item {
    background: var(--card);
    border-color: var(--border);
}

[data-theme="dark"] .upload-status {
    background: var(--card);
}

/* ===========================================
   RESPONSIVE UTILITIES
   =========================================== */

@media (max-width: 767.98px) {
    .btn-group {
        display: flex;
        flex-wrap: wrap;
    }
    
    .btn-group label {
        flex: 1 1 auto;
        min-width: 100px;
    }
}

/* ===========================================
   SEARCH SUGGESTIONS DROPDOWN
   =========================================== */

.search-suggestions-dropdown {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    margin-top: 0.25rem;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    max-height: 400px;
    overflow-y: auto;
    z-index: 1050;
    animation: slideDown 0.2s ease-out;
    min-width: 350px; /* Ensure minimum width for better title display */
}

@keyframes slideDown {
    from {
        opacity: 0;
        transform: translateY(-10px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.suggestion-item {
    padding: 0.75rem 1rem;
    cursor: pointer;
    border-bottom: 1px solid var(--border);
    color: var(--text);
    transition: all var(--transition-fast);
    display: flex;
    align-items: flex-start; /* Align to top for multi-line titles */
    gap: 0.75rem;
}

.suggestion-item:last-child {
    border-bottom: none;
}

.suggestion-item:hover,
.suggestion-item.active {
    background: var(--bg-secondary);
    color: var(--primary);
}

.suggestion-item i {
    color: var(--text-muted);
    font-size: 0.875rem;
    margin-top: 0.2rem; /* Align icon with first line of text */
    flex-shrink: 0; /* Prevent icon from shrinking */
}

.suggestion-item:hover i,
.suggestion-item.active i {
    color: var(--primary);
}

.suggestion-title {
    flex: 1;
    font-weight: 500;
    line-height: 1.4;
    word-wrap: break-word; /* Allow long words to break */
    overflow-wrap: break-word; /* Modern browsers */
    /* Allow title to wrap to multiple lines */
    display: -webkit-box;
    -webkit-line-clamp: 2; /* Limit to 2 lines max */
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.suggestion-badge {
    font-size: 0.75rem;
    padding: 0.2rem 0.5rem;
    border-radius: var(--radius-sm);
    background: var(--bg);
    color: var(--text-muted);
    white-space: nowrap; /* Keep badge on one line */
    flex-shrink: 0; /* Prevent badge from shrinking */
    align-self: flex-start; /* Align badge to top */
}

.suggestion-empty {
    padding: 1.5rem 1rem;
    text-align: center;
    color: var(--text-muted);
    font-size: 0.875rem;
}

.suggestion-loading {
    padding: 1rem;
    text-align: center;
    color: var(--text-muted);
}

.suggestion-loading i {
    animation: spin 1s linear infinite;
}

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

/* Dark theme overrides */
[data-theme="dark"] .search-suggestions-dropdown {
    background: var(--card);
    border-color: var(--border);
}

[data-theme="dark"] .suggestion-item {
    border-color: var(--border);
}

[data-theme="dark"] .suggestion-item:hover,
[data-theme="dark"] .suggestion-item.active {
    background: rgba(255, 255, 255, 0.05);
}

[data-theme="dark"] .suggestion-badge {
    background: rgba(255, 255, 255, 0.1);
}

/* Responsive adjustments for mobile */
@media (max-width: 767.98px) {
    .search-suggestions-dropdown {
        min-width: 100%; /* Full width on mobile */
        left: 0;
        right: 0;
    }
    
    .suggestion-item {
        padding: 0.6rem 0.75rem;
    }
    
    .suggestion-title {
        font-size: 0.875rem; /* Slightly smaller on mobile */
    }
    
    .suggestion-badge {
        font-size: 0.7rem;
    }
}

/* Tooltip for truncated titles (optional, shows full title on hover) */
.suggestion-title[title] {
    cursor: help;
}
[data-theme="dark"] .suggestion-item:hover,
[data-theme="dark"] .suggestion-item.active {
    background: rgba(255, 255, 255, 0.05);
}

[data-theme="dark"] .suggestion-badge {
    background: rgba(255, 255, 255, 0.1);
}
  
/* Shared Conversations Tab Bar */
.shared-tab-bar { padding: 0 0.5rem; gap: 0.5rem; }
.shared-tab-btn { font-size: 0.875rem; font-weight: 500; } 

/* ===========================================
   Conversation Info Icon & Popover
   =========================================== */

.conversation-info-btn {
    cursor: pointer;
    color: var(--text-muted);
    font-size: 0.85rem;
    transition: color 0.2s;
    padding: 0 2px;
}

.conversation-info-btn:hover {
    color: var(--primary);
}

.conversation-info-popover {
    position: fixed;
    z-index: 1060;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius-md, 6px);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
    padding: 0.875rem 1rem;
    min-width: 280px;
    max-width: 340px;
    animation: infoPopoverIn 0.15s ease-out;
}

@keyframes infoPopoverIn {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}

[data-theme="dark"] .conversation-info-popover {
    background: var(--card);
    border-color: var(--border);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

.conversation-info-popover-content .info-section {
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
}

.conversation-info-popover-content .info-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 0.825rem;
    line-height: 1.4;
}

.conversation-info-popover-content .info-label {
    color: var(--text-muted);
    display: flex;
    align-items: center;
    gap: 0.25rem;
}

.conversation-info-popover-content .info-value {
    color: var(--text);
    text-align: right;
    max-width: 160px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.conversation-info-popover-content .info-value-wrap {
    color: var(--text);
    text-align: right;
    max-width: 160px;
    white-space: normal;
    word-break: break-word;
    line-height: 1.4;
}

.conversation-info-popover-content .info-status-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex-shrink: 0;
}

.conversation-info-popover-content .info-divider {
    margin: 0.5rem 0;
    border-color: var(--border);
}

.conversation-info-popover-content .info-flags {
    display: flex;
    gap: 0.75rem;
    flex-wrap: wrap;
}

.conversation-info-popover-content .info-flag {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.8rem;
    color: var(--text-muted);
    background: var(--bg-secondary);
    padding: 0.2rem 0.5rem;
    border-radius: var(--radius-sm, 4px);
}

.conversation-info-popover-content .info-loading {
    text-align: center;
    color: var(--text-muted);
    font-size: 0.85rem;
    padding: 0.5rem;
}

.conversation-info-popover-content .info-error {
    color: var(--danger);
    font-size: 0.85rem;
    text-align: center;
    padding: 0.5rem;
}
/*
 * Sticky action bar for a full-page form — حفظ / إلغاء on /cases/new.
 *
 * STICKY, not `position: fixed` as the demo uses. Fixed positions against the
 * viewport, so under RTL the bar would stretch across the sidebar as well as
 * the content column, and it would float above the footer fragment instead of
 * ending with the page. Sticky keeps it pinned to the bottom of the viewport
 * while the form scrolls and then settles naturally at the end of the content.
 *
 * `inset-block-end`, not `bottom`: logical properties throughout, per the RTL
 * rule — this one is direction-neutral today but the file's convention is not
 * worth breaking for one property.
 */
.form-page-actions {
    position: sticky;
    /* Pinned above the iPhone home bar rather than behind it; 0 on a desktop
       and in a Safari tab. */
    inset-block-end: env(safe-area-inset-bottom, 0px);
    z-index: 10;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding: 0.875rem 1rem;
    margin-block-start: 1.5rem;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-md);
}

/*
 * Everything after this goes to the END edge. `margin-inline-start: auto` on a
 * flex item, not `justify-content: space-between` on the parent: the parent has
 * to keep gap-based spacing for the buttons that end up wrapping onto a second
 * line at 430px, and space-between would fling them apart there.
 */
.form-page-actions-end {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-inline-start: auto;
}

/*
 * Direction-flippable icon. The chevrons are authored for the DEFAULT locale,
 * Arabic — "next" points to the page's start edge, which is the left in RTL.
 * Under LTR that is backwards, so mirror it. One rule beats maintaining two
 * icon names in the template and swapping them from JS.
 */
html[dir="ltr"] .step-nav-icon {
    transform: scaleX(-1);
}

/* =========================================================
   Form stepper — the progress rail for a multi-step page.
   ========================================================= */
/*
 * Replaces the tab strip + separate Prev/Next row on /cases/new. A tab strip
 * says "these are four independent views"; a stepper says "this is one task in
 * four parts, and here is where you are" — which is what the form actually is.
 *
 * Still an <ol> of buttons with role="tab": every step stays directly clickable
 * because this page ALSO serves /cases/{id}/edit, where jumping straight to the
 * one field you came to change is the whole point. A stepper that forces a
 * linear walk would make editing worse than the modal it replaced.
 */
.form-stepper {
    display: flex;
    gap: 0.25rem;
    list-style: none;
    margin: 0 0 1.75rem;
    padding: 0;
}

.form-step {
    position: relative;
    flex: 1 1 0;
    min-inline-size: 0;
}

/*
 * The connector, drawn from the PREVIOUS marker's centre to this one's.
 * inset-inline-start: -50% + inline-size: 100% spans exactly that gap, and
 * because both the property and the flex order flip under RTL, the rail reads
 * correctly in both directions with no second rule.
 */
.form-step:not(:first-child)::before {
    content: '';
    position: absolute;
    inset-block-start: 1.125rem;      /* half the 2.25rem marker */
    inset-inline-start: -50%;
    inline-size: 100%;
    block-size: 2px;
    background: var(--border);
    transition: background-color var(--transition-normal);
}

/* A visited step colours the rail BEHIND it, so the filled length is literally
   how far through the form you are. */
.form-step.is-visited::before,
.form-step.is-current::before {
    background: var(--primary);
}

.form-step-btn {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    inline-size: 100%;
    padding: 0.25rem;
    border: none;
    background: transparent;
    cursor: pointer;
}

.form-step-marker {
    display: flex;
    align-items: center;
    justify-content: center;
    inline-size: 2.25rem;
    block-size: 2.25rem;
    border: 2px solid var(--border);
    border-radius: 50%;
    background: var(--card);
    color: var(--text-muted);
    font-size: 0.875rem;
    font-weight: 600;
    transition: border-color var(--transition-normal),
                background-color var(--transition-normal),
                color var(--transition-normal);
}

.form-step-label {
    color: var(--text-muted);
    font-size: 0.8125rem;
    font-weight: 500;
    text-align: center;
    line-height: 1.3;
    transition: color var(--transition-normal);
}

/* ── states ─────────────────────────────────────────────────────────────── */

.form-step.is-visited .form-step-marker {
    border-color: var(--primary);
    color: var(--primary);
}

.form-step.is-visited .form-step-label {
    color: var(--text-secondary);
}

.form-step.is-current .form-step-marker {
    border-color: var(--primary);
    background: var(--primary);
    color: var(--on-primary);
    /* The one thing that must be unmistakable at a glance: which step you are
       on. A ring rather than a size change, so the rail does not reflow. */
    box-shadow: 0 0 0 4px rgba(var(--primary-rgb), 0.18);
}

.form-step.is-current .form-step-label {
    color: var(--primary);
    font-weight: 600;
}

/*
 * A step holding a failed required field. This is what makes the "required
 * field on a hidden step" case visible: submit-validation marks the offending
 * step red, so the reason a save was refused is on screen even before you
 * switch to it. Deliberately NOT a green check on passed steps — a check mark
 * would claim a step is valid when nothing has validated it.
 */
.form-step.is-invalid .form-step-marker {
    /* --danger-FILL, not --danger: the marker is a filled disc carrying a
       number, so it is text on a fill and takes the fill shade. --danger under
       white measured 3.76:1 light and 2.77:1 dark — the step number that says
       WHICH step was refused was the least readable thing in the stepper. */
    border-color: var(--danger-fill);
    background: var(--danger-fill);
    color: #fff;
    box-shadow: 0 0 0 4px rgba(var(--danger-rgb), 0.18);
}

.form-step.is-invalid .form-step-label {
    color: var(--danger-text);
    font-weight: 600;
}

.form-step-btn:focus-visible {
    outline: none;
}

.form-step-btn:focus-visible .form-step-marker {
    outline: 2px solid var(--primary);
    outline-offset: 3px;
}

.form-step-btn:hover .form-step-label {
    color: var(--text);
}

@media (max-width: 767.98px) {
    /*
     * Hide the labels VISUALLY and keep the numbered rail. Four Arabic step
     * labels do not fit across a phone, and wrapping them makes the rail three
     * lines tall.
     *
     * Clipped, NOT `display: none`. The label is the step button's only text —
     * display:none takes it out of the accessibility tree as well as the
     * layout, which would leave every step button on a phone with no
     * accessible name at all, announced as just "button". The marker digit
     * cannot stand in for it: it is aria-hidden, and "3" is not a name.
     */
    .form-stepper {
        gap: 0;
    }

    .form-step-label {
        position: absolute;
        inline-size: 1px;
        block-size: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
}

/* =========================================================
   PWA install card (js/common/pwa-install.js)
   A bottom sheet inviting a phone or tablet visitor to add Tibyan to the home
   screen (plans/implemented/pwa-install-support-plan.md, ported from Mostashar's). Every
   colour is a design token, so it follows the theme AND the design with no
   [data-theme] rule of its own; the two actions are Bootstrap buttons
   (CLAUDE.md rule 36 — no geometry on a button variant). Animated by GSAP,
   so there is no @keyframes here.
   ========================================================= */
.pwa-install {
    position: fixed;
    inset-inline: 0;
    inset-block-end: 0;
    /* Under the phone drawer and its backdrop (--z-fixed, and one below it),
       every modal and every open menu; above the page and the footer.
       Mostashar's --z-modal would sit on an open modal's footer here, because
       this app's overlays live at --z-modal-backdrop. */
    z-index: calc(var(--z-fixed) - 2);
    display: none;
    justify-content: center;
    padding: 0 0.75rem calc(0.75rem + env(safe-area-inset-bottom, 0px));
    pointer-events: none;   /* only the card itself takes a touch */
}

.pwa-install.is-open {
    display: flex;
}

.pwa-install-card {
    pointer-events: auto;
    inline-size: 100%;
    max-inline-size: 480px;
    padding: 1.25rem 1.25rem 1.1rem;
    border-radius: var(--radius-modal);
    background: var(--card);
    border: 1px solid var(--border);
    box-shadow: var(--shadow-xl);
    color: var(--text);
}

.pwa-install-head {
    display: flex;
    align-items: flex-start;
    gap: 0.9rem;
}

.pwa-install-icon {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    inline-size: 56px;
    block-size: 56px;
    border-radius: var(--radius-circle);
    background: color-mix(in srgb, var(--primary) 15%, transparent);
    border: 1px solid color-mix(in srgb, var(--primary) 35%, transparent);
}

.pwa-install-icon img {
    display: block;
    inline-size: 40px;
    block-size: 40px;
    border-radius: var(--radius-card);
}

.pwa-install-text {
    flex: 1 1 auto;
    min-width: 0;
}

.pwa-install-title {
    margin: 0 0 0.3rem;
    font-size: 1.15rem;
    font-weight: 700;
    line-height: 1.3;
    color: var(--text);
}

.pwa-install-desc {
    margin: 0;
    color: var(--text-secondary);
    line-height: 1.6;
}

/* The manual steps — revealed by «تثبيت» where no install dialog exists. */
.pwa-install-steps {
    margin-block-start: 0.9rem;
    padding: 0.75rem 0.9rem;
    border-radius: var(--radius-card);
    background: color-mix(in srgb, var(--primary) 7%, transparent);
    border: 1px dashed color-mix(in srgb, var(--primary) 35%, var(--border));
}

.pwa-install-steps-intro {
    margin: 0 0 0.4rem;
    font-weight: 600;
    color: var(--text);
}

.pwa-install-steps-list {
    margin: 0;
    padding-inline-start: 1.1rem;
    color: var(--text-secondary);
    line-height: 1.7;
}

/* --primary as TEXT on --card is the brand token measured for that job
   (CLAUDE.md, Designs) — never --primary-fill here. */
.pwa-install-step-icon {
    color: var(--primary);
    margin-inline-end: 0.35rem;
}

/* Chrome's install glyph is an inline SVG: sized and seated like the font icons. */
.pwa-install-step-svg {
    inline-size: 1em;
    block-size: 1em;
    vertical-align: -0.125em;
}

/* The copy-link helper inside the «open in Safari» step. */
.pwa-install-copy-btn {
    margin-inline-start: 0.5rem;
    vertical-align: middle;
}

.pwa-install-actions {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-block-start: 1.1rem;
}

.pwa-install-actions .btn {
    min-inline-size: 108px;
}

@media (max-width: 400px) {
    .pwa-install-card {
        padding: 1rem 1rem 0.9rem;
    }

    .pwa-install-actions {
        gap: 0.6rem;
    }

    .pwa-install-actions .btn {
        flex: 1 1 0;
        min-inline-size: 0;
    }
}
