/* Duwam design tokens + shared styles
   Brand: ROYAL PIXEL (2026-07-17, owner-directed) — quiet interface: ~90%
   of every screen is white/ink/gray; royal blue speaks only for the primary
   action, the active state, and live data. Data draws as stacked 12px pixel
   squares (the brand signature). Depth comes from whitespace + hairlines —
   never gradients, never card shadows. All color flows through the tokens
   below; no handwritten hex outside this file's token blocks. */
:root {
  /* ══ ROYAL PIXEL primitives — dark tier (the spec's [data-theme=dark]
     table mapped onto bare :root, since dark is the default here and light
     is the `.light` class; NEVER :root.dark — that class is never applied). */
  --canvas: #0B1526;  --surface-2: #111E36;
  --ink: #EDF2FB;
  --primary-700: #4D90F0;  --primary-600: #2F80ED;  --primary-500: #2F80ED;
  --primary-300: #6E9BEA;  --primary-100: #C9DBF9;  --primary-50:  #16294A;
  --border-strong: #2E4066;
  --chart-1: #24406E;  --chart-2: #2F5FB3;  --chart-3: #4D90F0;  --chart-4: #9DC4F8;
  --viz-violet: rgb(108, 92, 231); /* written as rgb(), not hex, so the retired inline-violet hex family stays retired; theme-invariant: one flat value, no light/dark pair, visual parity only, themeable later */
  --spark: #FFC81A;  --spark-bg: #3A2F0A;  --spark-text: #FFD75E;
  --icon-ink: var(--text-2);            /* dark: icons lift, not sink */
  --icon-idle: var(--icon-ink);
  --icon-stroke: 2;                     /* theme-invariant: base stroke weight for .lucide-ic svg */
  --wa: #25D366; /* WhatsApp channel semantics — third-party color, not decoration */
  --rmm-wall-cap: #302a28; /* v18: cocoa ink — sampled from the Donarg pack's wall outlines (48,42,40); also the vertical wall ink and the facade crest overlay, so strips, posts and crest meet seamlessly. Office game-space structural color, theme-independent, not UI decoration */
  --rmm-void: #232020;     /* v18: the world outside the building envelope — one step darker than the cocoa wall ink so envelope verticals never dissolve into the dark (Donarg Levels float on exactly this kind of mat). Game-space structural, theme-independent */
  /* Surfaces — quiet interface: cards sit on the same canvas; the 1px
     hairline is the only separator (no surface steps, no card shadows). */
  --bg: var(--canvas);
  --bg-2: var(--surface-2);
  --bg-3: color-mix(in srgb, var(--surface-2) 90%, var(--ink) 10%);
  --card: var(--canvas);
  --card-2: var(--surface-2);
  /* Workspace surfaces — the ONE place the quiet-interface rule bends, and
     only for multi-pane workspace shells (CRM inbox today). Those screens
     are three side-by-side scroll regions; hairline-only separation reads
     as one box carved into zones instead of three objects, which is what
     made the inbox feel flat beside HighLevel. Panes lift off a substrate
     and the gutter does the separating. Dark steps UP (navy canvas →
     lifted surface) exactly like the Flutter app's canvas→card pair; light
     steps DOWN (grey substrate → white pane), because white cannot lift
     any further. Dense surfaces — tables, gantt, forms, cards — keep pure
     white and hairlines; they are not workspaces. */
  --workspace-canvas: var(--canvas);
  --workspace-pane: var(--surface-2);
  --border: #23324F;
  --border-2: var(--border-strong);
  --text: var(--ink);
  --text-2: #93A3BF;
  --text-3: #64748B;
  /* Brand (Royal Blue) — `--accent` is the historical token read everywhere;
     it re-points at the Royal Pixel primitives so components flip without
     edits. Primary action = primary-600 + white text, hover/pressed =
     primary-700; links / active icons / live data = primary-500 (spec §2). */
  --accent: var(--primary-600);
  --accent-fg: #FFFFFF;
  --accent-2: var(--primary-700);
  --accent-grad: var(--primary-500); /* progress fills — flat, no gradients */
  --accent-soft: var(--primary-50);
  --accent-soft-border: color-mix(in srgb, var(--primary-600) 34%, transparent);
  --brand: var(--accent);
  --brand-hover: var(--primary-700);
  --brand-pressed: #5FA0F2; /* pressed is a real state, not the hover alias — dark tier LIFTS on press */
  --brand-soft: var(--accent-soft);
  /* Status — success DECOUPLED from brand under Royal Pixel: blue speaks
     only for actions/active/live; states use the semantic set (spec §2). */
  --success: #16A34A;  --success-bg: rgba(22, 163, 74, 0.16);
  --warn: #D97706;     --warn-bg: rgba(217, 119, 6, 0.16);
  --danger: #DC2626;   --danger-bg: rgba(220, 38, 38, 0.16);
  --info: var(--primary-500);
  /* Tag catalog swatches (Wave 3.1 Task 3, 2026-07-26 tags-saved-views plan)
     — 8 fixed, muted hues (routes/crm.js TAG_COLORS is the server-side
     allow-list these mirror 1:1). Deliberately distinct from the danger/
     warn/success semantic set above so a tag chip reads as user-chosen
     categorization, never as a status signal. Quiet fills per the Royal
     Pixel doctrine — same translucent-bg + saturated-fg formula already
     tuned for --warn/--danger. */
  --tag-slate-bg:  rgba(148, 163, 184, .16); --tag-slate-fg:  #94A3B8;
  --tag-blue-bg:   rgba(56, 189, 248, .16);  --tag-blue-fg:   #38BDF8;
  --tag-green-bg:  rgba(74, 222, 128, .16);  --tag-green-fg:  #4ADE80;
  --tag-amber-bg:  rgba(251, 191, 36, .16);  --tag-amber-fg:  #FBBF24;
  --tag-red-bg:    rgba(251, 113, 133, .16); --tag-red-fg:    #FB7185;
  --tag-violet-bg: rgba(196, 181, 253, .16); --tag-violet-fg: #C4B5FD;
  --tag-teal-bg:   rgba(20, 184, 166, .16);  --tag-teal-fg:   #14B8A6;
  --tag-pink-bg:   rgba(244, 114, 182, .16); --tag-pink-fg:   #F472B6;
  /* Legacy aliases — some pages (super/companies, admin/integrations) reference
     --panel/--panel-muted/--primary. Map them onto the real tokens so they
     flip correctly between dark and light. */
  --panel: var(--card);
  --panel-muted: var(--bg-3);
  --primary: var(--accent);
  /* Radius scale — Royal Pixel (2026-07-17, owner-directed): cards 12,
     modals/large frames 16, fields/small controls 10. Buttons, badges and
     avatars are FULL pills (999px) — a core brand fingerprint, applied
     categorically at the component rules. ONE scale on every viewport. */
  --radius: 12px;
  --radius-lg: 16px;
  --radius-card: 12px;
  --radius-sm: 10px;
  /* Shared control height (2026-07-11) — the ONE height where a field and
     the square button beside it meet (.dp-shell picker fields, .btn-ctl).
     Cairo's tall line box makes .input land ≈48px naturally; pin it so
     paired controls stop drifting apart. Compact scopes override locally. */
  --control-h: 48px;
  --topbar-h: 62px;
  /* The in-content page head is gone (2026-08-11 — the window name moved
     back into the topbar). The token stays at zero rather than being
     deleted: .tasks-board and .crm-chat's phone rule both subtract it, and
     zeroing here keeps both formulas correct — and both simply gain the
     58px back — without editing either. */
  --page-head-h: 0px;
  /* Shadow scale — Royal Pixel: ZERO shadow on resting surfaces (the
     hairline does all separation). The single allowed shadow belongs to
     floating layers only: modals, dropdowns, popovers (spec §3). */
  --shadow-1: none;
  --shadow-2: 0 8px 24px rgba(0, 0, 0, .5); /* dark tier: navy-tint alpha .10 vanished against #0B1526 — floating layers need real black depth here */
  /* ── Motion scale (2026-07-07) ──────────────────────────────────────
     One easing + duration vocabulary so every transition in the product
     feels like one hand. Durations: 1 = micro (hover/press feedback),
     2 = standard (most state changes), 3 = larger (panels, settle, view
     morphs). Easings: standard for symmetric moves, out for things that
     enter/expand (fast start, gentle stop), spring for playful lifts
     (drag pickup, drop settle). Prefer these over ad-hoc cubic-beziers;
     they collapse to 0s under prefers-reduced-motion (see below). */
  --dur-1: .12s;
  --dur-2: .18s;
  --dur-3: .28s;
  --ease-standard: cubic-bezier(.4, 0, .2, 1);
  --ease-out: cubic-bezier(.32, .72, 0, 1);
  --ease-spring: cubic-bezier(.34, 1.56, .64, 1);
  /* Glass retired (Duwam Minimal): kept at 0 so any straggling
     blur(var(--blur)) resolves to a no-op instead of frosting. */
  --blur: 0px;
  --ring: var(--primary-500);
  --ring-glow: rgba(47, 128, 237, 0.20);
  /* Typography tokens — Royal Pixel (2026-07-17): IBM Plex Sans Arabic
     leads; Cairo stays loaded as the fallback per spec §4. Inter handles
     Latin + tabular numerics. Use --font-display for headings, --font-body
     for everything else, --font-numeric for KPIs/timers/tables. */
  --font-display: 'IBM Plex Sans Arabic', 'Cairo', 'Inter', system-ui, -apple-system, sans-serif;
  --font-body: 'IBM Plex Sans Arabic', 'Cairo', 'Inter', system-ui, -apple-system, sans-serif;
  --font-numeric: 'Inter', 'IBM Plex Sans Arabic', system-ui, sans-serif;
  /* Marketing display scale — Wise-inspired typographic ladder. Hero copy
     on marketing/landing pages uses these; in-product UI keeps its
     existing scale (semibold 600/700) since 900-weight at small sizes
     reads ink-heavy in a dashboard context.
     Rule: weight 900 for hero, weight 600 for sub-displays. The contrast
     IS the typographic story. */
  --display-mega: 900 clamp(3.5rem, 8vw, 6rem) / 1.05 var(--font-display);   /* up to 96px */
  --display-xxl:  900 clamp(2.8rem, 6vw, 4.5rem) / 1.08 var(--font-display); /* up to 72px */
  --display-xl:   900 clamp(2.2rem, 4.8vw, 3.5rem) / 1.15 var(--font-display); /* up to 56px */
  --display-sub:  600 clamp(1.05rem, 2vw, 1.25rem) / 1.6 var(--font-display); /* sub-display */

  /* ══ CLEAN MINIMAL — mobile design system (2026-07-06) ═══════════════
     Additive tokens ONLY (never re-value an existing token — desktop
     inherits these). The mobile app (≤720px) is a distinct iOS-native
     "Clean Minimal" language built on these; desktop is unaffected. */
  /* 8pt spacing scale */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px; --sp-5: 20px;
  --sp-6: 24px; --sp-8: 32px; --sp-10: 40px; --sp-12: 48px; --sp-16: 64px;
  /* Extra radii — fields 10px (Royal Pixel §3). Pill stays a pill (999px)
     — the categorical rule: buttons/badges/avatars are full pills. */
  --radius-pill: 999px;
  --radius-field: 10px;
  /* Semantic hairline aliases + a mobile-tuned floating shadow (the ONLY
     shadow the pill/CTA/sheets earn — cards on canvas use a hairline). */
  --hairline: var(--border);
  --hairline-strong: var(--border-2);
  --shadow-float: var(--shadow-2);
  --cta-ring: var(--bg);
  /* Semantic type scale — Royal Pixel §4: headings 600, body 400, numbers
     and buttons 500 — NEVER 700+. Arabic line-height floor: paragraphs 1.6,
     headings ≥1.3 so no Arabic ascender/descender ever clips. */
  --t-large-title:   600 1.5rem/1.3 var(--font-display);
  --t-title-2:       600 1.375rem/1.3 var(--font-display);
  --t-headline:      600 1.0625rem/1.35 var(--font-body);
  --t-body:          400 1rem/1.6 var(--font-body);
  --t-callout:       500 0.9375rem/1.6 var(--font-body);
  --t-subhead:       600 0.8125rem/1.45 var(--font-body);
  --t-section-label: 600 0.75rem/1.35 var(--font-body);
  --t-caption:       500 0.75rem/1.4 var(--font-body);
}
/* Tell the browser which tier it is painting NATIVE controls for.
   Without this, every unchecked checkbox, radio, scrollbar and native picker
   in the app rendered with LIGHT chrome on the dark canvas — a near-white box
   floating on #0B1526. The app-wide checkbox rules only ever set
   `accent-color`, which tints the CHECKED fill and nothing else, so the
   unchecked state had no fix at all short of re-skinning every control with
   appearance:none (which two components did, one at a time: .emp-multi-row,
   .login-remember). One declaration per tier replaces all of that, and it is
   the mechanism the datePicker comment further down already alluded to.
   Dark is the default tier here — light is the `.light` class — so the
   bare :root above carries `dark`. */
:root { color-scheme: dark; }
:root.light {
  color-scheme: light;
  /* ══ ROYAL PIXEL primitives — light tier (the spec's :root table).
     Quiet interface: PURE WHITE canvas on every screen, white cards, and
     the 1px hairline as the ONLY separator. surface-2 is for row hover
     and secondary zones exclusively — never a card background. */
  --canvas: #FFFFFF;  --surface-2: #F7F9FC;
  --ink: #0A1F3D;
  --primary-700: #14479F;  --primary-600: #1D5FD6;  --primary-500: #2F80ED;
  --primary-300: #6E9BEA;  --primary-100: #C9DBF9;  --primary-50:  #E7EEFC;
  --border-strong: #D7DEEA;
  --chart-1: #C9DBF9;  --chart-2: #6E9BEA;  --chart-3: #3D77DE;  --chart-4: #14479F;
  --spark: #FFC81A;  --spark-bg: #FFF3C4;  --spark-text: #8A6400;
  --icon-ink: color-mix(in srgb, var(--ink) 78%, var(--text-2)); /* near-navy ≈ #1E3B5E */
  --icon-idle: var(--icon-ink);
  --bg: var(--canvas);
  --bg-2: var(--surface-2);
  --bg-3: var(--surface-2);
  --card: #FFFFFF;
  --card-2: var(--surface-2);
  /* Light workspace substrate — the same grey the Flutter app already uses
     behind its cards (#EFF2F7), so a workspace reads identically on the
     phone and the desktop. Deliberately deeper than --surface-2 #F7F9FC:
     at a 2% delta the panes never separate from the ground. */
  --workspace-canvas: #EFF2F7;
  --workspace-pane: var(--canvas);
  --border: #E6EAF2;
  --border-2: var(--border-strong);
  --text: var(--ink);
  --text-2: #64748B;
  --text-3: #94A3B8;
  --shadow-1: none;
  --shadow-2: 0 8px 24px rgba(10, 31, 61, .10);
  --accent: var(--primary-600);
  --accent-fg: #FFFFFF;
  --accent-2: var(--primary-700);
  --accent-grad: var(--primary-500);
  --accent-soft: var(--primary-50);
  --accent-soft-border: var(--primary-100);
  --brand: var(--accent);
  --brand-hover: var(--primary-700);
  --brand-pressed: #103A85; /* one step below --primary-700 (#14479F) — light tier DARKENS on press */
  --brand-soft: var(--accent-soft);
  --success: #16A34A;  --success-bg: #E8F7EE;
  --warn: #D97706;     --warn-bg: #FEF3E2;
  --danger: #DC2626;   --danger-bg: #FDEAEA;
  --info: var(--primary-500);
  /* Tag catalog swatches — light tier (see the dark-tier block above for
     the doctrine note). Same 8 hues, tinted-100/text-600 formula already
     used for --warn-bg/--danger-bg in this tier. */
  --tag-slate-bg:  #F1F5F9; --tag-slate-fg:  #475569;
  --tag-blue-bg:   #E0F2FE; --tag-blue-fg:   #0284C7;
  --tag-green-bg:  #ECFDF5; --tag-green-fg:  #15803D;
  --tag-amber-bg:  #FEF3C7; --tag-amber-fg:  #B45309;
  --tag-red-bg:    #FFF1F2; --tag-red-fg:    #E11D48;
  --tag-violet-bg: #F5F3FF; --tag-violet-fg: #5B21B6;
  --tag-teal-bg:   #F0FDFA; --tag-teal-fg:   #0D9488;
  --tag-pink-bg:   #FDF2F8; --tag-pink-fg:   #DB2777;
  --ring: var(--primary-500);
  --ring-glow: rgba(47, 128, 237, 0.20);
  /* Clean Minimal — light-mode overrides for the additive tokens. */
  --shadow-float: var(--shadow-2);
}
/* Accessibility: collapse the motion scale to instant for users who ask
   for reduced motion. Any transition/animation that reads --dur-* turns
   off in one place — the payoff for routing durations through tokens. */
@media (prefers-reduced-motion: reduce) {
  :root { --dur-1: 0s; --dur-2: 0s; --dur-3: 0s; }
}
/* Royal Pixel uses ONE radius scale on every viewport (spec §3) — the old
   phone-specific radius restore is gone. Bottom sheets (`.modal` at ≤640px)
   keep their rounded ~20px top corners separately — that's the iOS "sheet"
   signal and must not sharpen. */
* { box-sizing: border-box; }

/* ──────────────────────────────────────────────────────────────────────
   Brand scrollbar — applied globally so every overflowing surface (modals,
   side panels, sub-containers) gets the same subtle, on-brand chrome
   instead of the OS default. Resting state uses --border-2 (low-contrast
   token that already shifts with light/dark theme); hover and active
   states tint royal blue to echo the rest of the UI's interaction language.
   Component-specific overrides further down keep their own widths but
   inherit the colour treatment via these defaults.
   ────────────────────────────────────────────────────────────────────── */
html { scrollbar-width: thin; scrollbar-color: var(--border-2) transparent; }
*::-webkit-scrollbar { width: 8px; height: 8px; background: transparent; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--border-2);
  border-radius: var(--radius-sm);
  border: 2px solid transparent;
  background-clip: padding-box;
  transition: background-color .15s ease;
}
*::-webkit-scrollbar-thumb:hover { background-color: var(--accent); }
*::-webkit-scrollbar-thumb:active { background-color: var(--brand-hover, var(--accent-2)); }
*::-webkit-scrollbar-corner { background: transparent; }
/* Firefox honours scrollbar-color but doesn't repaint on :hover of the
   bar itself — so we tint the whole element's scrollbar when the user is
   actively pointing at the surface. Subtle but matches Webkit's hover. */
*:hover { scrollbar-color: var(--border-2) transparent; }

/* Lock the browser viewport to a single pane: the <body> never scrolls.
   Vertical scrolling happens INSIDE the <main> element (see .shell > main
   below) so the sidebar + topbar stay pinned while only content scrolls.
   overflow-x: hidden on body is a safety net against any child leaking a
   horizontal scrollbar onto the page. */
html, body { margin: 0; padding: 0; background: var(--bg); color: var(--text); font-family: var(--font-body); height: 100vh; overflow: hidden; font-synthesis: none; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
/* Desktop UI density — Asana/Linear-tier type scale (2026-07-10, owner-
   directed). Every component in the app is rem-based, so ONE root step
   (16 → 15px) tightens text, paddings and control heights ~6% across
   the whole system at once. Phones are exempt: the mobile Clean Minimal
   scale keeps the browser default. Deliberately 15px, not Asana's ~13px
   text tier — our audience skews less screen-dense-tolerant; a second
   step to 14.5px stays available if the owner wants full Asana density
   after living with this one. */
@media (min-width: 721px) { html { font-size: 15px; } }
/* Touch devices: kill the grey tap-flash rectangle + the long-press
   callout/selection so buttons, nav items and chips feel native, not web.
   Scoped to pointer:coarse so desktop (mouse) is literally untouched. */
@media (pointer: coarse) {
  html { -webkit-tap-highlight-color: transparent; }
  a, button, .btn, .bottom-nav-item, .chip, [role="button"], .card, .nav a {
    -webkit-tap-highlight-color: transparent; -webkit-touch-callout: none;
  }
  /* Keep text selectable in inputs/content; suppress it on chrome only. */
  .bottom-nav, .topbar, .sidebar { -webkit-user-select: none; user-select: none; }
}
h1, h2, h3, h4, h5, h6 { font-family: var(--font-display); letter-spacing: -0.01em; }
/* Duwam Minimal: the old ambient emerald glows are gone — the canvas is
   one quiet solid so content, not atmosphere, carries the page. */
body > * { position: relative; z-index: 1; }
a { color: var(--accent-2); text-decoration: none; }
a:hover { text-decoration: underline; }

/* Global form-control color inheritance.
   Browsers default `<button>` / `<input>` / `<select>` / `<textarea>` to the
   user-agent system color (buttontext / fieldtext) which doesn't follow our
   theme tokens — that's why dark-on-dark "invisible text" bugs appear on
   every inline-styled <button> we draw with `style="background:var(--bg-2)"`.
   Forcing `color: inherit` makes form controls follow the body's --text by
   default, so every button picks up the right contrast unless a class
   explicitly overrides it (e.g. .btn-primary using --accent-fg). */
button, input, select, textarea {
  color: inherit;
  font-family: inherit;
}

/* Cards — solid minimal surfaces. The border separates; the shadow is a
   whisper. */
.card {
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-1);
}
.card-lg { border-radius: var(--radius-lg); box-shadow: var(--shadow-2); }

/* Collapsible company group cards (super admin Users page) */
.company-group { transition: box-shadow .15s ease; }
.company-group[open] { box-shadow: var(--shadow-2); }
.company-group > summary { list-style: none; cursor: pointer; user-select: none; }
.company-group > summary::-webkit-details-marker { display: none; }
.company-group-head {
  display: flex; align-items: center; gap: .75rem;
  padding: .9rem 1rem; border-radius: var(--radius);
  transition: background .15s ease;
}
.company-group-head:hover { background: var(--bg-3); }
.company-group[open] > summary > .company-group-head { border-bottom: 1px solid var(--border); border-radius: var(--radius) var(--radius) 0 0; }
.company-group-head::after {
  content: '▾'; font-size: .8rem; color: var(--text-2); margin-inline-start: .25rem;
  transition: transform .15s ease;
}
.company-group[open] > summary > .company-group-head::after { transform: rotate(180deg); }

/* Buttons — flat, GHL-inspired. No gradients, no lift-on-hover. Hover
   is communicated via a subtle background/border tone shift + a soft
   box-shadow (no transform). Keeps the UI calm at scale — hundreds of
   buttons don't all lift/shake on a single pointer move. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  padding: .55rem 1.1rem; border-radius: var(--radius-pill); font-weight: 500; font-size: .9rem;
  border: 1px solid var(--border); background: var(--bg-2); color: var(--text);
  cursor: pointer; transition: background .12s, border-color .12s, color .12s;
  line-height: 1.2;
}
.btn:hover:not(:disabled) { background: var(--bg-3); border-color: var(--border-2); }
.btn:active:not(:disabled) { background: var(--card-2); }
.btn:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
/* Royal Pixel §3 — accessibility contract: every interactive element shows
   the same 2px primary focus ring. :where() keeps specificity at zero so
   richer component focus styles still win; :focus-visible keeps pointer
   clicks quiet (keyboard travel lights up). */
:where(a, button, input, select, textarea, summary, [role="button"], [tabindex]:not([tabindex="-1"])):focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

.btn-primary {
  background: var(--accent); color: var(--accent-fg); border-color: var(--accent);
  font-weight: 500;
}
.btn-primary:hover:not(:disabled) {
  background: var(--brand-hover); border-color: var(--brand-hover);
}
.btn-primary:active:not(:disabled) { background: var(--brand-pressed); border-color: var(--brand-pressed); }
.btn-ghost { background: transparent; border-color: transparent; color: var(--text-2); }
.btn-ghost:hover:not(:disabled) { background: var(--bg-2); border-color: var(--border); color: var(--text); }
.btn-danger { background: var(--danger); color: #fff; border-color: var(--danger); font-weight: 500; }
.btn-danger:hover:not(:disabled) { filter: brightness(.92); border-color: var(--danger); background: var(--danger); }
/* Pressed is a real state, not a rerun of :hover's filter dimming — dark
   tier LIFTS toward white, light tier DARKENS toward black (same doctrine
   as --brand-pressed above), so the semantic solid buttons get the same
   token-math depth cue the primary button does. */
.btn-danger:active:not(:disabled) { filter: none; background: color-mix(in srgb, var(--danger) 86%, #fff); border-color: color-mix(in srgb, var(--danger) 86%, #fff); }
:root.light .btn-danger:active:not(:disabled) { filter: none; background: color-mix(in srgb, var(--danger) 86%, #000); border-color: color-mix(in srgb, var(--danger) 86%, #000); }
.btn-info { background: var(--info); color: #fff; border-color: var(--info); font-weight: 500; }
.btn-info:hover:not(:disabled) { filter: brightness(.92); border-color: var(--info); background: var(--info); }
.btn-info:active:not(:disabled) { filter: none; background: color-mix(in srgb, var(--info) 86%, #fff); border-color: color-mix(in srgb, var(--info) 86%, #fff); }
:root.light .btn-info:active:not(:disabled) { filter: none; background: color-mix(in srgb, var(--info) 86%, #000); border-color: color-mix(in srgb, var(--info) 86%, #000); }
.btn-success { background: var(--success); color: #fff; border-color: var(--success); font-weight: 500; }
.btn-success:hover:not(:disabled) { filter: brightness(.92); border-color: var(--success); background: var(--success); }
.btn-success:active:not(:disabled) { filter: none; background: color-mix(in srgb, var(--success) 86%, #fff); border-color: color-mix(in srgb, var(--success) 86%, #fff); }
:root.light .btn-success:active:not(:disabled) { filter: none; background: color-mix(in srgb, var(--success) 86%, #000); border-color: color-mix(in srgb, var(--success) 86%, #000); }
.btn-sm { padding: .35rem .8rem; font-size: .85rem; border-radius: var(--radius-pill); }
/* Icon-only buttons are true squares — a lone ✕/✎/🗑 glyph inside the
   default .btn padding renders a squat 50×35 rectangle that clashes with
   everything beside it. .btn-icon = compact square (table row actions);
   .btn-ctl = field-height square (--control-h) for header controls that
   sit next to an input, so the pair reads as one geometric family. */
.btn-icon { padding: .5rem; border-radius: var(--radius-pill); flex: none; aspect-ratio: 1; justify-content: center; }
.btn-ctl  { width: var(--control-h); height: var(--control-h); padding: 0; flex: none; justify-content: center; }
/* Header-chrome tier — controls that live in a modal/panel header are
   utilities, not form fields: they drop to the 36px density (same tier the
   settings fields use). One local token override keeps every pair locked —
   the field and its ✕ shrink TOGETHER, they can never drift apart again. */
.ctl-compact { --control-h: 36px; }
.ctl-compact .input { height: var(--control-h); padding: .25rem .7rem; font-size: .88rem; }
.btn:disabled { opacity: .6; cursor: not-allowed; }

/* Inputs */
.input, select.input, textarea.input {
  width: 100%; padding: .65rem .9rem; border-radius: var(--radius-field); border: 1px solid var(--border-2);
  background: var(--bg-2); color: var(--text); font-family: inherit; font-size: .95rem; outline: none;
  transition: border-color .15s, box-shadow .15s;
}
.input:focus, select.input:focus, textarea.input:focus { border-color: var(--ring); box-shadow: 0 0 0 3px var(--ring-glow); }
.label { display: block; font-size: .8rem; color: var(--text-2); margin-bottom: .35rem; font-weight: 600; }

/* ── Custom <select> (UI.enhanceSelect) ───────────────────────────────
   Progressive enhancement over native <select>: a styled trigger + a
   body-portaled popover replace the OS dropdown (which opens oversized on
   macOS/Chromium and is unusable for long lists like the 200-country
   picker). The real <select> stays in the DOM — hidden — as the value
   source. Row height, scroll, search + optgroups are all controlled here,
   so every enhanced picker opens at a natural size in both themes. */
.uisel { position: relative; display: block; }
/* Native select kept for its value but taken out of the visual + hit
   layer; the trigger below sits on top and drives everything. */
.uisel-native {
  position: absolute; inset: 0; width: 100%; height: 100%;
  margin: 0 !important; opacity: 0; pointer-events: none;
  -webkit-appearance: none; appearance: none;
}
/* Trigger reuses the .input shell (so it inherits field density per
   context) but lays out as value + caret. */
.uisel-trigger {
  display: flex; align-items: center; justify-content: space-between; gap: .5rem;
  width: 100%; text-align: start; cursor: pointer;
}
.uisel-trigger .uisel-val { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.uisel-trigger.is-placeholder .uisel-val { color: var(--text-3); }
.uisel-caret { display: inline-flex; flex-shrink: 0; color: var(--text-3); transition: transform .15s ease; }
.uisel-caret svg { width: 16px; height: 16px; }
.uisel.is-open .uisel-caret { transform: rotate(180deg); }
.uisel.is-open .uisel-trigger { border-color: var(--ring); box-shadow: 0 0 0 3px var(--ring-glow); }

/* Portaled popover (position:fixed, coords set in JS). */
.uisel-menu {
  position: fixed; z-index: 1000; display: none;
  flex-direction: column; min-width: 0;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-2);
  overflow: hidden;
  animation: uisel-in .14s ease-out;
}
.uisel-menu.is-open { display: flex; }
@keyframes uisel-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
.uisel-menu.opens-up { animation-name: uisel-in-up; }
@keyframes uisel-in-up { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
@media (prefers-reduced-motion: reduce) { .uisel-menu { animation: none; } }
.uisel-search {
  flex: 0 0 auto; padding: .4rem; border-bottom: 1px solid var(--border);
  position: sticky; top: 0; background: var(--card);
}
.uisel-search-in {
  width: 100%; box-sizing: border-box;
  padding: .4rem .6rem; border-radius: var(--radius-sm);
  border: 1px solid var(--border-2); background: var(--bg-2); color: var(--text);
  font: inherit; font-size: .85rem; outline: none;
}
.uisel-search-in:focus { border-color: var(--ring); box-shadow: 0 0 0 3px var(--ring-glow); }
.uisel-list { flex: 1 1 auto; overflow-y: auto; padding: .25rem; scrollbar-width: thin; scrollbar-color: var(--border-2) transparent; }
.uisel-opt {
  padding: .42rem .6rem; border-radius: var(--radius-sm); cursor: pointer;
  font-size: .875rem; color: var(--text); line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.uisel-opt:hover, .uisel-opt.is-active { background: var(--bg-3); }
.uisel-opt.is-selected { background: var(--accent-soft); color: var(--text); font-weight: 600; }
.uisel-opt.is-disabled { color: var(--text-3); cursor: not-allowed; opacity: .6; }
.uisel-opt.is-disabled:hover { background: transparent; }
.uisel-group-label {
  padding: .5rem .6rem .2rem; font-size: .68rem; font-weight: 600;
  color: var(--text-3); text-transform: uppercase; letter-spacing: .06em;
}
.uisel-empty { padding: .9rem; text-align: center; color: var(--text-3); font-size: .85rem; }

/* Native date/time inputs render their *filled value* (e.g. "2026/07/04")
   as LTR numeric tokens, which read fine. Pin the field to LTR so that value
   ordering stays stable regardless of page direction. */
input[type="date"], input[type="time"], input[type="datetime-local"], input[type="month"], input[type="week"] {
  direction: ltr;
  text-align: start;
}
html[dir="rtl"] input[type="date"],
html[dir="rtl"] input[type="time"],
html[dir="rtl"] input[type="datetime-local"],
html[dir="rtl"] input[type="month"],
html[dir="rtl"] input[type="week"] {
  text-align: right;
}

/* ── Localized date/time placeholder overlay ───────────────────────────
   Chrome renders the *empty* placeholder of a native date input using the
   browser's own locale, and in Arabic (ar-SA) it reverses the glyphs into
   garbled text like "موي/رهش/قنس" (يوم/شهر/سنة backwards). No CSS on the
   input or its ::-webkit-datetime-edit shadow parts can fix this — verified
   across direction/unicode-bidi variants. The only reliable fix is to hide
   the native placeholder while the field is empty and paint our own clean
   text over it. date-i18n.js wraps each field in `.dp-shell`, toggles
   `.dp-empty`, and localizes `.dp-ph` via I18N. Once a value is picked the
   `.dp-empty` class drops and the native (clean, numeric) value shows. */
.dp-shell { position: relative; display: block; }
.dp-shell > input[type="date"],
.dp-shell > input[type="time"],
.dp-shell > input[type="datetime-local"],
.dp-shell > input[type="month"],
.dp-shell > input[type="week"] { width: 100%; }
.dp-shell.dp-empty > input::-webkit-datetime-edit { color: transparent; }
/* Fields routed through the emerald UI.datePicker: read as tappable and
   never show the native (blue) calendar indicator / popup. */
.dp-shell > input.dp-emerald { cursor: pointer; }
.dp-shell > input.dp-emerald::-webkit-calendar-picker-indicator { display: none; }
/* One exact height for every picker field. Native month/datetime inputs
   grow ~2px taller than text inputs (shadow datetime-edit chrome) — pin
   the whole family to --control-h so they align with text inputs AND
   with the .btn-ctl squares that sit beside them in headers. */
.dp-shell > input.dp-emerald.input { height: var(--control-h); }
.dp-ph { display: none; }
.dp-shell.dp-empty > .dp-ph {
  display: block;
  position: absolute;
  inset-inline-start: .9rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-subtle, #94a3b8);
  pointer-events: none;
  white-space: nowrap;
  font-family: inherit;
  font-size: .95rem;
}

/* Layout */
/* Shell layout — sidebar floats as a glass card with breathing room.
   The 12px padding-block + padding-inline-start creates the gap between
   the card and the viewport edge; the 12px column gap separates sidebar
   from main. Main keeps its inline-end edge flush with the viewport so
   wide content (kanban boards, tables) still has room. */
.shell {
  display: grid;
  /* 240px — Asana's sidebar width (2026-07-07 density pass). */
  grid-template-columns: 240px minmax(0, 1fr);
  height: 100vh;
  padding-block: 12px;
  padding-inline-start: 12px;
  gap: 12px;
  transition: grid-template-columns .22s ease;
}
.shell.sidebar-collapsed { grid-template-columns: 68px minmax(0, 1fr); }
/* The <main> column is the page's scroll container. Topbar inside it is
   sticky so it stays pinned as the user scrolls content below. Horizontal
   overflow is clipped here so a wide kanban can't push the main column
   past its 1fr allocation (it scrolls inside its own .tasks-viewport). */
.shell > main {
  min-width: 0; min-height: 0;
  overflow-x: hidden; overflow-y: auto;
  display: flex; flex-direction: column;
  scrollbar-width: thin;
}
/* App frame — "the corner at Work" (Asana pattern, 2026-07-10, owner-
   directed). The content pane is THE contained surface of the app: a
   hairline border with rounded corners on the sidebar side only, so a
   single quiet curve marks where the workspace begins — same canvas
   color on both sides, the stroke does all the work. The sticky topbar
   and the in-content page head live inside the pane, completing the
   continuous-surface read. Desktop only: phones are single-column and
   frameless (Clean Minimal). */
@media (min-width: 721px) {
  .shell > main {
    border: 1px solid var(--border);
    /* Linear-tight (2026-07-15): the pane's sidebar-side corners follow the
       structural token now (was a hardcoded 14px) so the whole app frame
       shares one corner language with cards/panels. */
    border-start-start-radius: var(--radius-lg);
    border-end-start-radius: var(--radius-lg);
  }
}
.shell > main::-webkit-scrollbar { width: 8px; }
.shell > main::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 4px; }
.shell > main > .main { flex: 1 0 auto; min-width: 0; }
/* Sidebar — Royal Pixel quiet panel (2026-07-17, owner-directed).
   Supersedes the 2026-07-11 macOS glass treatment: opaque card fill +
   1px hairline + structural radius — NO translucency, NO backdrop blur,
   NO shadow. The panel stays quiet; the ONLY color inside it is the
   active item's primary pill. Desktop only — phones are single-column
   and frameless. */
.sidebar {
  position: relative;
  /* Lift the sidebar above the sticky topbar (z-index: 10) so collapsed-
     mode tooltips that extend out of the sidebar paint *over* the topbar
     instead of behind it. z-index: 20 keeps us safely above the topbar
     yet below dropdown panels (z-index: 60+) and modals. */
  z-index: 20;
  height: 100%;
  padding: .7rem .5rem .8rem;
  /* `overflow: visible` is required so the collapsed-mode tooltip
     (rendered as a ::after pseudo-element on each icon) can extend past
     the sidebar's edge into the main column. Children never reach the
     rounded corners (the nav is inset by the panel padding), so visible
     overflow and border-radius coexist safely. */
  display: flex; flex-direction: column; overflow: visible;
}
@media (min-width: 721px) {
  .sidebar {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
  }
}
.sidebar-head { display: flex; align-items: center; gap: .5rem; padding: .2rem .4rem .9rem; }
.sidebar-toggle {
  background: transparent; border: none; border-radius: var(--radius-sm);
  width: 36px; height: 36px; color: var(--icon-ink); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
  transition: background .15s, color .15s;
}
.sidebar-toggle:hover { background: var(--card-2); color: var(--text); }
.sidebar-toggle .lucide-ic { width: 18px; height: 18px; }
/* Brand wordmark — pairs with the checkmark mark to form the full Duwam
   logo lockup. Cairo (Arabic) and Inter (Latin) both have weight 800
   loaded; tighter letter-spacing + slightly larger size makes the lockup
   read as a *brand* (Linear, Notion, Vercel territory) rather than just
   a header label. The mark + wordmark gap is intentionally a bit roomier
   so neither feels cramped. */
.sidebar .brand {
  font-weight: 600;
  font-size: 1.18rem;
  letter-spacing: -0.025em;
  display: flex; align-items: center; gap: .65rem; min-width: 0;
}
.sidebar .brand-text {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  line-height: 1;
}

/* ── Duwam brand mark ──────────────────────────────────────────────────
   Royal Pixel (spec §6.2): rounded --primary-600 square with the white
   checkmark inside — the ring is retired; the check IS the mark. Flat
   fill, token-driven in both themes.
   See public/assets/js/shell.js _brandMark() for the SVG markup. */
.duwam-mark {
  --mk-size: 32px;
  width: var(--mk-size); height: var(--mk-size);
  border-radius: var(--radius-sm); flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent);
  color: #FFFFFF;
  position: relative;
  overflow: hidden;
}
.duwam-mark > svg {
  width: 100%; height: 100%; display: block; pointer-events: none;
}
.duwam-mark-sm { --mk-size: 26px; border-radius: 7px; }

/* ── Quick launcher — global search + quick-actions (GHL-inspired) ──────
   The sidebar launcher (desktop) pairs a search trigger with a royal-blue
   lightning button between the brand mark and the nav; the SAME popover
   (.quick-menu / .quick-item) is reused in the mobile topbar, where the
   sidebar is hidden. Positioning is scoped to each wrapper — the sidebar
   flyout escapes to the side, the topbar one drops down. */
.sidebar-launch {
  display: flex; align-items: center; gap: .4rem;
  padding: 0 .3rem .7rem;
}
.sidebar-search-trigger {
  flex: 1; min-width: 0;
  display: inline-flex; align-items: center; gap: .45rem;
  height: 32px; padding: 0 .6rem;
  background: var(--bg-2); color: var(--text-3);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  cursor: pointer; text-align: start;
  transition: border-color .15s, background .15s, color .15s;
}
.sidebar-search-trigger:hover {
  border-color: var(--border-2); color: var(--text-2);
  background: var(--card-2);
}
.sidebar-search-ic { display: inline-flex; flex-shrink: 0; color: var(--icon-ink); }
.sidebar-search-ic .lucide-ic { width: 16px; height: 16px; }
.sidebar-search-ph {
  flex: 1; min-width: 0; font-size: .86rem; font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sidebar-search-kbd {
  flex-shrink: 0; font-family: var(--font-numeric, inherit);
  font-size: .68rem; font-weight: 600; line-height: 1;
  padding: .2rem .32rem; color: var(--text-3);
  background: var(--card); border: 1px solid var(--border);
  border-radius: 5px;
}

/* Royal-blue lightning — mirrors the Ask-AI pill's colours (tinted fill +
   blue glyph + blue-tinted hairline), just at the launcher's size. */
.quick-btn {
  flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--brand) 8%, var(--bg-2)); color: var(--brand);
  border: 1px solid color-mix(in srgb, var(--brand) 28%, var(--border)); cursor: pointer;
  transition: background .15s, border-color .15s, transform .06s;
}
.quick-btn:hover { background: color-mix(in srgb, var(--brand) 14%, var(--bg-2)); border-color: color-mix(in srgb, var(--brand) 45%, var(--border)); }
.quick-btn:active { transform: scale(.94); }
.quick-btn .lucide-ic { width: 16px; height: 16px; stroke-width: var(--icon-stroke); }
/* Matched to the search pill — same 32px height, square, --radius-sm radius,
   so the two read as one slim harmonious cluster (GHL pattern). */
.sidebar-quick-btn { width: 32px; height: 32px; border-radius: var(--radius-sm); }
.sidebar-quick.open .sidebar-quick-btn,
.topbar-quick.open .topbar-quick-btn { background: color-mix(in srgb, var(--brand) 18%, var(--bg-2)); border-color: color-mix(in srgb, var(--brand) 45%, var(--border)); }

.sidebar-quick { position: relative; flex-shrink: 0; }
/* Topbar lightning — mobile-only (the sidebar owns it on desktop). */
.topbar-quick { position: relative; display: none; }
.topbar-quick-btn { width: 36px; height: 36px; border-radius: var(--radius-sm); }

/* Shared quick-actions popover. */
.quick-menu {
  position: absolute; z-index: 60; width: 268px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-2);
  padding: .4rem;
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .16s ease, transform .16s ease, visibility .16s;
}
.sidebar-quick.open > .quick-menu,
.topbar-quick.open > .quick-menu {
  opacity: 1; visibility: visible; pointer-events: auto; transform: none;
}
/* Sidebar flyout — to the side, over the main column (inline-start in RTL). */
.sidebar-quick > .quick-menu {
  top: -6px; inset-inline-start: calc(100% + 12px);
  transform: translateX(6px) scale(.98); transform-origin: top;
}
html[dir="ltr"] .sidebar-quick > .quick-menu { transform: translateX(-6px) scale(.98); }
/* Topbar dropdown — drops below the button and right-aligns (its inline-
   start edge pins to the button so it opens INTO the viewport, never off
   the near screen edge). Width is capped on the mobile media query. */
.topbar-quick > .quick-menu {
  top: calc(100% + 8px); inset-inline-start: 0;
  transform: translateY(-6px) scale(.98); transform-origin: top;
}

.quick-menu-head {
  display: flex; align-items: center; gap: .4rem;
  padding: .35rem .55rem .5rem; font-size: .74rem; font-weight: 600;
  letter-spacing: .01em; color: var(--text-3);
}
.quick-menu-head-ic { display: inline-flex; color: var(--brand); opacity: .7; }
.quick-menu-head-ic .lucide-ic { width: 16px; height: 16px; }
.quick-menu-list { display: flex; flex-direction: column; gap: 2px; }
.quick-item {
  display: flex; align-items: center; gap: .65rem;
  padding: .55rem; border-radius: var(--radius-sm);
  text-decoration: none; color: var(--text); cursor: pointer;
  transition: background .13s;
}
.quick-item:hover { background: var(--bg-2); text-decoration: none; }
.quick-item-ic {
  width: 34px; height: 34px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent-soft); color: var(--brand);
  border-radius: var(--radius-sm);
}
.quick-item-ic .lucide-ic { width: 16px; height: 16px; stroke-width: 2; }
.quick-item-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.quick-item-title { font-size: .88rem; font-weight: 600; line-height: 1.25; }
.quick-item-sub {
  font-size: .74rem; color: var(--text-3); line-height: 1.25;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}


/* Keyboard accelerator badge (E/T/P/A…) shown at the trailing edge of each
   quick action — fires while the menu is open (see bindQuickToggle). */
.quick-item-key {
  flex-shrink: 0; margin-inline-start: auto;
  min-width: 20px; padding: .12rem .3rem; text-align: center;
  font-family: var(--font-numeric, ui-sans-serif, system-ui, sans-serif);
  font-size: .72rem; font-weight: 600; line-height: 1.2; color: var(--text-3);
  background: var(--bg-3); border: 1px solid var(--border);
  border-radius: 6px;
}
.quick-item:hover .quick-item-key { color: var(--text-2); border-color: var(--border-2); }
/* Keyboard-focused row (↑↓) — same treatment as hover so the highlight is
   consistent whether you use the mouse or the keyboard. */
.quick-item.is-kb { background: var(--bg-2); }
.quick-item.is-kb .quick-item-key { color: var(--text-2); border-color: var(--border-2); }
/* Contextual: the action matching the current section leads the list and
   gets an emerald wash + a "here" pill instead of its key badge. */
.quick-item.is-current {
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 1px var(--accent-soft-border);
}
.quick-item.is-current .quick-item-ic { background: var(--brand); color: var(--accent-fg); }
.quick-item-here {
  flex-shrink: 0; margin-inline-start: auto;
  padding: .12rem .42rem; font-size: .68rem; font-weight: 600;
  color: var(--brand); background: color-mix(in srgb, var(--brand) 14%, transparent);
  border: 1px solid var(--accent-soft-border); border-radius: 999px;
}

/* ⌘K "create" results — emerald kind badge so create actions read as
   distinct from navigation/search results. */
.cmdk-kind-create { color: var(--brand); background: var(--accent-soft); }
.cmdk-item .cmdk-ic { color: var(--icon-ink); }

/* Collapsed sidebar — stack the two controls, icon-only. The search
   trigger shrinks to a square; the flyout still escapes the rail. */
.shell.sidebar-collapsed .sidebar-launch {
  flex-direction: column; gap: .4rem; padding: 0 0 .7rem; align-items: center;
}
.shell.sidebar-collapsed .sidebar-search-ph,
.shell.sidebar-collapsed .sidebar-search-kbd { display: none; }
.shell.sidebar-collapsed .sidebar-search-trigger {
  width: 34px; height: 34px; flex: 0 0 auto; padding: 0; justify-content: center;
}
.shell.sidebar-collapsed .sidebar-search-ic { color: var(--icon-ink); }
.shell.sidebar-collapsed .sidebar-quick-btn { width: 34px; height: 34px; }
/* Icon-only rail — give the glyphs a touch more presence than the
   expanded 16px so they read clearly at a glance. */
.shell.sidebar-collapsed .sidebar-search-ic .lucide-ic,
.shell.sidebar-collapsed .sidebar-quick-btn .lucide-ic { width: 18px; height: 18px; }

.sidebar .nav { flex: 1; overflow-y: auto; overflow-x: hidden; scrollbar-width: thin; }
.sidebar .nav::-webkit-scrollbar { width: 6px; }
.sidebar .nav::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 3px; }
/* Visual divider inside the nav — used to separate the employee's
   personal items from their permission-gated admin sections, and again
   before Settings at the very bottom. */
.nav .nav-sep {
  height: 1px; background: var(--border); margin: .55rem .3rem;
}
.shell.sidebar-collapsed .nav .nav-sep { margin: .55rem .45rem; }
/* When two separators end up adjacent (e.g., no admin extensions → only
   the Settings separator renders), collapse the second one to avoid a
   double line. */
.nav .nav-sep + .nav-sep { display: none; }

/* Nav rows — Asana density (2026-07-07): flat ~32px rows, 14px text,
   bare 16px icons (the emerald icon TILE was retired with this pass —
   active state is a full-row brand wash + brand-colored glyph). */
.nav a {
  display: flex; align-items: center; gap: .55rem; padding: .3rem .55rem; border-radius: var(--radius-sm); color: var(--text-2);
  min-height: 32px;
  text-decoration: none; margin-bottom: 1px; font-weight: 500; font-size: .875rem; white-space: nowrap;
  border: 1px solid transparent;
  transition: background .18s ease, color .18s ease, border-color .18s ease, border-radius var(--dur-2) var(--ease-out);
  /* No `overflow: hidden` here — that clipped the collapsed-mode ::after
     tooltip so it never escaped the link box. Label truncation is already
     handled at .nav-label below, so removing it from the link is safe. */
}
/* Hover = quiet NEUTRAL row tint (Royal Pixel: hover never speaks blue —
   only the active state does). */
.nav a:hover {
  background: var(--bg-2);
  color: var(--text);
}
/* Active row: the ONE colored element in the sidebar — a full pill with
   --primary-50 wash and --primary-600 content (Royal Pixel §6.1). */
.nav a.active {
  background: var(--accent-soft);
  color: var(--accent); font-weight: 600;
  border-radius: var(--radius-pill);
}

/* Icon — bare glyph, no container. Idle icons route through --icon-ink
   (near-navy light / --text-2 dark, see the token block); only active
   tints. */
.nav a .ic {
  width: 20px; height: 20px; border-radius: 0;
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
  background: transparent; color: var(--icon-idle);
  transition: color var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
}
/* Hover = brighten + a subtle micro-lift. This is the ONE motion cue in
   the sidebar — the "pulse of life" the user wanted, spent on state not
   identity (no per-tool colors). Scale keeps the glyph centered on the
   row baseline; --dur-2 collapses to 0s under prefers-reduced-motion, so
   motion-sensitive users just get the instant color shift. */
.nav a:hover .ic { color: var(--text); transform: scale(1.09); }
.nav a.active .ic { color: var(--accent); }
.nav a .ic .lucide-ic, .nav a .ic svg { width: 16px; height: 16px; stroke-width: var(--icon-stroke); }
.nav a .nav-label { overflow: hidden; text-overflow: ellipsis; flex: 1; }

/* Color-coded admin items — RETIRED under Royal Pixel (spec §6.1): idle
   icons are neutral in both themes; only the active item carries color.
   `--item-color` is still set inline by shell.js navFor() but no longer
   painted here — one color has a voice. */
.nav a.nav-colored .ic { color: var(--icon-idle); }
.nav a.nav-colored:hover .ic { color: var(--text); }
.nav a.nav-colored.active .ic { color: var(--accent); }

/* Collapsed state — icons only.
   `.sidebar` is normally `overflow: hidden` to clip content to the rounded
   glass-card edges, and `.nav` clips horizontally to keep label text from
   spilling. Both clip the icon tooltip when collapsed, so it never reaches
   the user. We relax both axes only in collapsed mode — labels are
   `display:none` here, so nothing else needs the clip. The trade-off is
   losing nav vertical-scroll in collapsed mode (CSS forbids
   `overflow-y: auto` together with `overflow-x: visible`); ten icons at
   40px fit any reasonable viewport, so this is acceptable. */
.shell.sidebar-collapsed .sidebar { overflow: visible; }
.shell.sidebar-collapsed .sidebar .nav { overflow: visible; }
.shell.sidebar-collapsed .sidebar { padding-inline: .45rem; }
.shell.sidebar-collapsed .sidebar-head { padding-inline: .2rem; gap: .3rem; flex-direction: column; align-items: center; }
.shell.sidebar-collapsed .sidebar .brand-text,
.shell.sidebar-collapsed .nav a .nav-label { display: none; }
.shell.sidebar-collapsed .nav a { justify-content: center; padding: .6rem .4rem; }
.shell.sidebar-collapsed .nav a .nav-badge { position: absolute; top: 2px; inset-inline-end: 2px; margin: 0; min-width: 16px; padding: 0 4px; font-size: .62rem; }
.nav a { position: relative; }
.nav-badge {
  margin-inline-start: auto; background: var(--danger); color: #fff; font-size: .7rem;
  font-weight: 600; padding: 1px 7px; border-radius: 999px; line-height: 1.4; min-width: 20px;
  text-align: center;
}
.nav a.active .nav-badge { background: var(--accent); color: var(--accent-fg); box-shadow: none; }
.lucide-ic { display: inline-flex; align-items: center; justify-content: center; width: 1em; height: 1em; vertical-align: -0.15em; color: currentColor; }
.lucide-ic svg { width: 1em; height: 1em; stroke-width: var(--icon-stroke); }
.btn .lucide-ic { width: 1.05em; height: 1.05em; }
.btn-icon .lucide-ic { width: 1.15rem; height: 1.15rem; }
.chip .lucide-ic { width: .95em; height: .95em; }
/* Section header inside the nav — quiet uppercase label that doubles as
   a visual break (replaces the old .nav-sep thin line). The section name
   itself does the separation, so no border/divider is needed. */
.nav-section {
  font-size: .66rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .12em;
  color: var(--text-3);
  padding: .85rem .6rem .3rem;
  user-select: none; pointer-events: none;
}
.nav-section > span { display: inline-block; }
/* First section in the nav doesn't need top padding — it already has
   sidebar-head padding above it. */
.nav > .nav-section:first-child { padding-top: .25rem; }
/* Collapsible section header (the Admin group). A <button>, so the
   .nav-section typography must be re-inherited (buttons don't inherit
   font) and pointer-events re-enabled. */
.nav-section-toggle {
  pointer-events: auto; cursor: pointer;
  appearance: none; background: none; border: 0; width: 100%;
  font-family: inherit; text-align: start;
  display: flex; align-items: center; justify-content: space-between; gap: .4rem;
}
.nav-section-toggle:hover { color: var(--text-2); }
.nav-section-caret { display: inline-flex; color: inherit; }
.nav-section-caret .lucide-ic {
  width: .85rem; height: .85rem;
  transition: transform var(--dur-2) var(--ease-out);
}
/* Folded: chevron turns toward the reading start (mirrors in RTL). */
.nav-section-toggle[aria-expanded="false"] .nav-section-caret .lucide-ic { transform: rotate(-90deg); }
[dir="rtl"] .nav-section-toggle[aria-expanded="false"] .nav-section-caret .lucide-ic { transform: rotate(90deg); }
/* Group body — 0fr/1fr grid trick animates height:auto natively.
   Motion tokens keep it under prefers-reduced-motion's kill switch. */
.nav-group { display: grid; grid-template-rows: 1fr; transition: grid-template-rows var(--dur-2) var(--ease-out); }
.nav-group.collapsed { grid-template-rows: 0fr; }
.nav-group-inner { overflow: hidden; min-height: 0; }
/* Icon-rail mode: the header is just a hairline (no label, no caret),
   so the group is forced open — folded state would strand the icons —
   and the toggle goes inert to match. */
.shell.sidebar-collapsed .nav-group { grid-template-rows: 1fr; }
.shell.sidebar-collapsed .nav-section-caret { display: none; }
.shell.sidebar-collapsed .nav-section-toggle { pointer-events: none; }
/* Collapsed: label text disappears, leaves a faint centered hairline so
   the user still feels the grouping when scanning vertically. */
.shell.sidebar-collapsed .nav-section {
  padding: .5rem .35rem;
  display: flex; align-items: center; justify-content: center;
}
.shell.sidebar-collapsed .nav-section > span { display: none; }
.shell.sidebar-collapsed .nav-section::after {
  content: ''; display: block;
  height: 1px; width: 60%;
  background: var(--border);
}

/* ── Mobile bottom-nav ─────────────────────────────────────────────────
   Floating glass pill at the bottom of the viewport, mobile-only. The
   sidebar is hidden entirely on small screens — bottom-nav replaces it
   for the 4 most-used destinations per role. Active state mirrors the
   sidebar's brand-container pattern so the visual language stays unified
   when the user switches between desktop and mobile. */
.bottom-nav {
  display: none;
  position: fixed;
  bottom: max(8px, calc(env(safe-area-inset-bottom) + 6px));
  /* Stretch to span the viewport with comfortable side margins (16px
     min, expands with safe-area-inset-left/right on devices with side
     cutouts). Items inside space evenly via justify-content so each
     destination gets a generous tap target instead of clustering in a
     tight pill at the centre. */
  left: max(16px, calc(env(safe-area-inset-left) + 16px));
  right: max(16px, calc(env(safe-area-inset-right) + 16px));
  z-index: 50;
  padding: 5px 6px;
  background: var(--card);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
  /* Floating layer → the single mobile-tuned floating shadow (flat, calm). */
  box-shadow: var(--shadow-float);
  align-items: center;
  justify-content: space-evenly;
  gap: 2px;
}
/* Clean Minimal (2026-07-06): each tab is a horizontal row; at rest only
   the outline icon shows. The ACTIVE tab becomes an emerald-soft WASH pill
   and its label animates open beside the icon (Linear/fintech pattern).
   The nav is mobile-only (display:none on desktop) so these base rules are
   already scoped to phones. */
.bottom-nav-item {
  display: inline-flex; flex-direction: row; align-items: center; justify-content: center;
  flex: 0 0 auto;
  padding: 8px;
  border-radius: var(--radius-pill);
  color: var(--text-2);
  text-decoration: none;
  font: var(--t-caption); font-weight: 600;
  transition: background .28s cubic-bezier(.4, 0, .2, 1), color .2s ease, padding .28s ease;
}
.bottom-nav-item:hover { color: var(--text); text-decoration: none; }
.bottom-nav-ic {
  width: 24px; height: 24px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent;
  transition: color .2s ease;
}
.bottom-nav-ic .lucide-ic, .bottom-nav-ic svg { width: 22px; height: 22px; stroke-width: 1.9; }
.bottom-nav-item.active:not(.bottom-nav-cta) {
  color: var(--brand);
  background: var(--brand-soft);
  padding-inline: 12px;
}
.bottom-nav-label {
  max-width: 0; opacity: 0; overflow: hidden; white-space: nowrap;
  margin-inline-start: 0;
  transition: max-width .28s cubic-bezier(.4, 0, .2, 1), opacity .18s ease, margin-inline-start .28s ease;
}
.bottom-nav-item.active:not(.bottom-nav-cta) .bottom-nav-label {
  max-width: 72px; opacity: 1; margin-inline-start: 6px; text-overflow: ellipsis;
}

/* Mobile breakpoint — bottom-nav appears, floating sidebar disappears.
   Shell collapses to a single column so main content gets full width.
   Topbar padding tightens so action cluster doesn't overflow. Main gets
   bottom padding (incl. safe-area inset) so its content doesn't tuck
   under the floating pill or the Android gesture bar. */
@media (max-width: 720px) {
  .shell, .shell.sidebar-collapsed {
    grid-template-columns: minmax(0, 1fr);
    padding: 0;
    gap: 0;
  }
  .sidebar { display: none; }
  .bottom-nav { display: inline-flex; }
  .shell > main > .main {
    /* Clearance for the slimmed floating pill (~66px + 8px offset + margin).
       Was 132px for the old ~84px nav. */
    padding-bottom: calc(104px + env(safe-area-inset-bottom));
  }
  .topbar { padding-inline: .8rem; }
  /* Section headings that repeat the shell's large page title. */
  .section-dup-title { display: none; }
  /* Floating launchers (AI assistant, feedback) are redundant on phones —
     the topbar sparkle opens the assistant and feedback lives in the menu —
     and they floated over the last list row / bottom-nav. Hide on phones so
     content isn't obscured. */
  .agent-fab,
  #duwam-feedback-fab,
  .dw-fab-wrap { display: none !important; }
}

/* ── Sidebar shift card (employee only) ────────────────────────────────
   Compact at-a-glance "are you on shift?" pill above the theme switch.
   Rendered by shell.js _renderShiftCard; live duration ticked once a
   minute by bindShiftCard. Whole card is a link to /app/shift.html for
   full controls. */
.sidebar-shift {
  display: flex; align-items: stretch;
  margin-bottom: .15rem;
  border-radius: var(--radius-field);
  background: var(--bg-3);
  border: 1px solid var(--border);
  overflow: hidden;
  transition: border-color .15s ease;
}
.sidebar-shift:hover {
  border-color: color-mix(in srgb, var(--brand) 25%, var(--border));
}
/* Left zone — navigates to the full attendance page. */
.sidebar-shift-nav {
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: center; gap: .55rem;
  padding: .5rem .6rem;
  color: var(--text); text-decoration: none;
  transition: background .15s ease;
}
.sidebar-shift-nav:hover {
  background: color-mix(in srgb, var(--brand) 8%, var(--bg-3));
  text-decoration: none;
}
/* Hairline divider between the nav zone and the action button. */
.sidebar-shift-sep {
  width: 1px; flex-shrink: 0;
  background: var(--border);
}
/* Right zone — one-tap pause/resume button. Disabled in the off state. */
.sidebar-shift-act {
  flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; padding: 0;
  background: transparent; border: 0;
  color: var(--text-3); cursor: pointer;
  transition: background .15s ease, color .15s ease;
}
.sidebar-shift-act:not(:disabled):hover {
  background: color-mix(in srgb, var(--brand) 10%, var(--bg-3));
  color: var(--text);
}
.sidebar-shift-act:disabled { cursor: default; opacity: .65; }
.sidebar-shift[data-state="on"]     .sidebar-shift-act { color: var(--accent); }
.sidebar-shift[data-state="paused"] .sidebar-shift-act { color: var(--warn); }
.sidebar-shift-act .lucide-ic, .sidebar-shift-act svg { width: 15px; height: 15px; }
/* Status dot — green pulsing when on shift, gray when off. */
.sidebar-shift-dot {
  width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
  background: var(--text-3);
  box-shadow: 0 0 0 0 transparent;
}
.sidebar-shift[data-state="on"] .sidebar-shift-dot {
  background: var(--primary-500);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-500) 22%, transparent);
  animation: shift-pulse 2s ease-in-out infinite;
}
/* Paused — warn tone. Steady (no pulse) because the timer is frozen;
   pulsing would imply active flow. */
.sidebar-shift[data-state="paused"] .sidebar-shift-dot {
  background: var(--warn);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--warn) 20%, transparent);
}
.sidebar-shift[data-state="paused"] .sidebar-shift-label,
.sidebar-shift[data-state="paused"] .sidebar-shift-time { color: var(--warn); }
@keyframes shift-pulse {
  0%, 100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 22%, transparent); }
  50%      { box-shadow: 0 0 0 5px color-mix(in srgb, var(--brand)  8%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .sidebar-shift[data-state="on"] .sidebar-shift-dot { animation: none; }
}
.sidebar-shift-body {
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: baseline; gap: .35rem;
  font-size: .8rem; line-height: 1.2;
}
.sidebar-shift-label { font-weight: 600; color: var(--text); }
.sidebar-shift-time {
  font-weight: 600; font-variant-numeric: tabular-nums;
  color: var(--brand);
}
.sidebar-shift-time:not(:empty)::before {
  content: '•'; margin-inline-end: .35rem; color: var(--text-3); font-weight: 400;
}
.sidebar-shift[data-state="off"] .sidebar-shift-time { display: none; }
.sidebar-shift-ic {
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--icon-ink); flex-shrink: 0;
}
.sidebar-shift-ic .lucide-ic, .sidebar-shift-ic svg { width: 15px; height: 15px; }
/* Loading state — render a quiet skeleton until first fetch arrives. */
.sidebar-shift[data-state="loading"] .sidebar-shift-label,
.sidebar-shift[data-state="loading"] .sidebar-shift-time {
  color: var(--text-3);
}
/* Collapsed sidebar: shrink to a single status dot — full text would
   wrap and crowd a 80px column. The dot still pulses if on-shift, so the
   user keeps the at-a-glance signal. */
.shell.sidebar-collapsed .sidebar-shift-nav { padding: .5rem; justify-content: center; }
.shell.sidebar-collapsed .sidebar-shift-body,
.shell.sidebar-collapsed .sidebar-shift-sep,
.shell.sidebar-collapsed .sidebar-shift-act { display: none; }

/* ── Trial card (sidebar bottom, admins only) ─────────────────────────
   Asana-style countdown: ring + label + days, with a CTA button below.
   Tones shift emerald → amber → danger as the trial nears its end so
   the urgency reads without having to fire a popup. The whole card
   collapses to ring-only in the narrow sidebar. */
.sidebar-trial {
  margin: .35rem .55rem .15rem;
  padding: .6rem .65rem .55rem;
  border-radius: var(--radius-field);
  border: 1px solid color-mix(in srgb, currentColor 22%, var(--border));
  background: color-mix(in srgb, currentColor 6%, transparent);
  display: flex; flex-direction: column; gap: .45rem;
  transition: background .15s, border-color .15s;
}
/* Tone definitions consolidated below — they set both parent color (for
   ring/border via currentColor) and the CTA bg/fg pair. */
.sidebar-trial-row {
  display: flex; align-items: center; gap: .55rem;
  text-decoration: none; color: inherit;
}
.sidebar-trial-ring { flex-shrink: 0; color: inherit; }
.sidebar-trial-body {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; gap: .05rem;
  line-height: 1.15;
}
.sidebar-trial-label {
  font-size: .68rem; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-3);
}
.sidebar-trial-days {
  font-size: .85rem; color: var(--text);
}
.sidebar-trial-days b {
  font-size: 1rem; font-weight: 600; color: inherit;
  font-variant-numeric: tabular-nums;
  margin-inline-end: .15rem;
}
.sidebar-trial.tone-urgent .sidebar-trial-days b,
.sidebar-trial.tone-warning .sidebar-trial-days b { color: currentColor; }
.sidebar-trial-cta {
  display: inline-flex; align-items: center; justify-content: center;
  padding: .45rem .65rem; border-radius: var(--radius-sm);
  /* Each tone supplies its own --cta-bg / --cta-fg pair below. We can't
     `background: currentColor` and then override `color` on the same
     element — the override propagates back into the bg, leaving the
     button invisible (bg = fg). Using two custom properties keeps the
     two values cleanly decoupled. */
  background: var(--cta-bg, var(--accent));
  color: var(--cta-fg, var(--accent-fg));
  font-size: .78rem; font-weight: 600; text-decoration: none;
  letter-spacing: .01em;
  border: 0;
  transition: filter .15s, transform .12s;
}
.sidebar-trial.tone-normal  { color: var(--accent); --cta-bg: var(--accent); --cta-fg: var(--accent-fg); }
.sidebar-trial.tone-warning { color: var(--warn);   --cta-bg: var(--warn);   --cta-fg: color-mix(in srgb, var(--warn) 25%, #000); }
.sidebar-trial.tone-urgent  { color: var(--danger); --cta-bg: var(--danger); --cta-fg: #ffffff; }
.sidebar-trial-cta:hover { filter: brightness(1.06); }
/* Collapsed sidebar: hide labels + CTA, keep ring as a quiet status dot. */
.shell.sidebar-collapsed .sidebar-trial {
  padding: .45rem;
  align-items: center;
}
.shell.sidebar-collapsed .sidebar-trial-body,
.shell.sidebar-collapsed .sidebar-trial-cta { display: none; }
.shell.sidebar-collapsed .sidebar-trial-row { justify-content: center; }
body.focus-mode .sidebar-trial { display: none; }

/* ── Theme pill (sidebar bottom) ───────────────────────────────────────
   Two-segment pill that flips Light/Dark instantly. The active option
   reads as a "raised tab" (white card + tiny shadow) inside a recessed
   track — same metaphor as macOS segmented controls. Keyboard accessible
   via the underlying <button>s. */
/* (Sidebar theme pill removed 2026-07-07 — theme control lives only in
   Settings → Appearance; see .appearance-theme-switch below.) */

/* ── Sidebar user card (sidebar bottom) ────────────────────────────────
   Click → opens a popover above the card with profile/settings/logout.
   Mirrors the topbar avatar menu so users have two equivalent entry
   points (sidebar = primary, topbar = quick action). */
.sidebar-user {
  margin-top: .25rem;
  padding-top: .55rem;
  border-top: 1px solid var(--border);
  position: relative;
}
.sidebar-user-btn {
  display: flex; align-items: center; gap: .55rem; width: 100%;
  padding: .45rem .55rem; border-radius: var(--radius-field);
  background: transparent; border: 1px solid transparent;
  color: var(--text); cursor: pointer; text-align: start;
  font: inherit;
  transition: background .15s ease, border-color .15s ease;
}
.sidebar-user-btn:hover { background: var(--bg-3); border-color: var(--border); }
.sidebar-user-btn .avatar { width: 34px; height: 34px; font-size: .78rem; flex-shrink: 0; }
.sidebar-user-id { flex: 1 1 auto; min-width: 0; line-height: 1.25; }
.sidebar-user-name {
  font-weight: 600; font-size: .85rem; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sidebar-user-role {
  font-size: .72rem; color: var(--text-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sidebar-user-caret {
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-3); flex-shrink: 0;
  transition: transform .2s ease, color .15s ease;
}
.sidebar-user-caret .lucide-ic, .sidebar-user-caret svg { width: 16px; height: 16px; }
.sidebar-user-btn:hover .sidebar-user-caret { color: var(--text-2); }
.sidebar-user-btn[aria-expanded="true"] .sidebar-user-caret { transform: rotate(180deg); }
/* Popover anchored above the card; expanded sidebar = same width as card,
   collapsed sidebar = floats to the side as a compact menu. */
.sidebar-user-menu {
  position: absolute; bottom: calc(100% + .4rem);
  inset-inline-start: .15rem; inset-inline-end: .15rem;
  background: var(--card); border: 1px solid var(--border-2); border-radius: var(--radius-field);
  padding: .35rem; box-shadow: var(--shadow-2);
  display: none; z-index: 60;
  flex-direction: column; gap: 1px;
}
.sidebar-user-menu.open { display: flex; }
.sidebar-user-menu > a, .sidebar-user-menu > button {
  display: flex; align-items: center; gap: .55rem;
  padding: .5rem .65rem; border-radius: var(--radius-sm);
  color: var(--text); text-decoration: none; font-size: .85rem; font-weight: 500;
  background: transparent; border: 0; cursor: pointer; text-align: start;
  font-family: inherit; width: 100%;
  transition: background .15s ease, color .15s ease;
}
.sidebar-user-menu > a:hover, .sidebar-user-menu > button:hover {
  background: var(--bg-3);
}
.sidebar-user-menu > a .lucide-ic,
.sidebar-user-menu > button .lucide-ic { width: 16px; height: 16px; color: var(--icon-ink); }
.sidebar-user-menu .sidebar-user-sep { height: 1px; background: var(--border); margin: .25rem .15rem; }
.sidebar-user-menu .sidebar-user-danger { color: var(--danger); }
.sidebar-user-menu .sidebar-user-danger:hover { background: color-mix(in srgb, var(--danger) 10%, transparent); }
.sidebar-user-menu .sidebar-user-danger .lucide-ic { color: var(--danger); }

/* Collapsed: card shrinks to just the avatar; popover floats to the side. */
.shell.sidebar-collapsed .sidebar-user-btn { padding: .35rem; justify-content: center; }
.shell.sidebar-collapsed .sidebar-user-id,
.shell.sidebar-collapsed .sidebar-user-caret { display: none; }
.shell.sidebar-collapsed .sidebar-user-menu {
  inset-inline-start: calc(100% + .4rem); inset-inline-end: auto;
  bottom: 0; min-width: 220px;
}

/* Topbar — slim glass strip that matches the floating sidebar aesthetic.
   Semi-transparent fill + backdrop blur so content scrolling beneath is
   gently softened (not hidden) as it passes under. Bottom border kept
   intentionally faint so the bar reads as "edge of viewport" rather than
   "framed component". */
/* Three-zone topbar: left cluster (brand/title/trial) — centered search
   — right cluster (actions). The flex children with `flex: 1 1 0` on
   the outer clusters keep them visually balanced so the search field
   centers regardless of trial-chip presence or the role's icon count. */
.topbar {
  /* CSS Grid — Slack / Linear / Notion topbar pattern. Six columns:
       1. brand+title   auto         — natural width on the leading edge
       2. spacer        1fr          — absorbs whitespace (left/right
                                       balance the search cluster)
       3. search        auto         — fixed-width input (440px cap)
       4. ai pill       auto         — sits flush against the search
       5. spacer        1fr          — mirror of column 2
       6. actions       auto         — icon cluster on the trailing edge
     The two 1fr spacers mathematically guarantee the search-and-ask
     cluster is CENTERED between the title and the actions — no matter
     how wide the page title or the action cluster grow. Items are
     placed explicitly via `grid-column` below so they land in the
     right cells even though their DOM order doesn't match (the search
     comes BEFORE the AI pill in DOM, but BOTH belong in the central
     cluster, not the spacers). Zero overlap is structural: each item
     owns its own column. */
  display: grid;
  grid-template-columns: auto 1fr auto auto 1fr auto;
  align-items: center;
  column-gap: .8rem;
  padding: .55rem 1.1rem;
  padding-top: calc(.55rem + env(safe-area-inset-top));
  padding-inline-start: calc(1.1rem + env(safe-area-inset-left));
  padding-inline-end: calc(1.1rem + env(safe-area-inset-right));
  /* Solid canvas strip (Duwam Minimal) — content scrolling underneath
     simply disappears behind it; the hairline border marks the edge. */
  background: var(--bg);
  border: 0;
  border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 10;
  min-height: 56px;
  container-type: inline-size;
  container-name: topbar;
}
/* Grid children — placed EXPLICITLY into columns 1/3/4/6 (DOM order is
   brand → search → ai → actions, so we'd otherwise land in 1/2/3/4
   and skip the spacers we need at 2 and 5). topbar-actions stays a
   flex container internally so its icon buttons stack horizontally
   with their own gap rules. */
.topbar-left {
  grid-column: 1;
  min-width: 0;
  display: flex; align-items: center; gap: .75rem;
}
.topbar-actions {
  grid-column: 6;
  min-width: 0;
  display: flex; align-items: center; justify-content: flex-end; gap: .5rem;
  position: relative;
}
/* Each topbar slot wraps a button + its dropdown panel so the panel
   anchors under its own button instead of the whole topbar row. */
.topbar-slot { position: relative; display: inline-flex; }
/* Group separators — 1px rules chunking the action row into
   AI │ wallet │ comms │ system so eight equal-weight items read as four
   scannable clusters (ClickUp/Asana pattern). Orphan guards: a separator
   disappears with the group it introduces — the AI pill toggles [hidden]
   until agent-widget.js enables it, and #ticker-mount stays :empty for
   roles without a ticker. Container-query tiers and the ≤720px mobile
   collapse hide the rest by id. */
.topbar-sep {
  width: 1px; height: 20px; flex: 0 0 1px;
  background: var(--border);
  margin-inline: .2rem;
}
.topbar-ai[hidden] + .topbar-sep { display: none; }
#ticker-mount:empty + .topbar-sep { display: none; }
/* Brand in the topbar — only shown on mobile widths where the sidebar
   collapses out of view. Reuses the same .logo + .brand-text styling so
   it visually matches the sidebar header. */
.topbar-brand {
  display: none; align-items: center; gap: .55rem;
  font-weight: 600; letter-spacing: -0.025em;
  color: var(--text);
}
.topbar-brand .brand-text { font-size: 1rem; line-height: 1; }
/* Window name in the topbar (2026-08-11, owner-directed — see the retired
   page-head note below). A step quieter than the 1.3rem it wore on the
   canvas: there a title was the loudest thing in its own band, here it
   shares a strip with the search and the system icons, and matching their
   weight is what keeps the row reading as one line rather than a heading
   with clutter beside it.
   `display: none` is the base so PHONES do not get it — their topbar
   already carries the brand and their "where am I" cue is the iOS large
   title below. Desktop turns it on in the ≥721px block further down, the
   same breakpoint the page head used to appear at.
   The cap is sized from the real labels, not guessed: the longest window
   name we ship is 16 characters in Arabic («إدارة المستخدمين», «تقارير
   المحادثات») and 20 in English ("Conversation reports"), so 20ch fits every
   one with room and truncates any future outlier. It is NOT protecting the
   search box — that was the suspicion, and measuring refuted it: the search
   renders 229px at a 1280px viewport whether the title is empty, 16
   characters, or 50. This cell is `auto` in .topbar's grid, but the two 1fr
   spacers absorb the difference before the search ever feels it.
   line-height 1.45 is the Arabic floor — a tighter box clips the dots. */
.topbar-title {
  display: none;
  margin: 0;
  font: 600 1.12rem/1.45 var(--font-display);
  color: var(--text);
  letter-spacing: 0; /* Arabic is never tracked */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 20ch;
}
/* First-paint shimmer until Page.setTitle stamps the real text — carried
   over from .page-head-title, because the empty slot is more conspicuous in
   the topbar than it was on the canvas. */
.topbar-title:empty {
  min-width: 120px;
  background: linear-gradient(90deg, var(--bg-3) 25%, var(--bg-2) 45%, var(--bg-3) 65%);
  background-size: 300% 100%;
  border-radius: var(--radius-sm);
  animation: pt-shimmer 1.4s ease-in-out infinite;
}
@keyframes pt-shimmer { 0% { background-position: 200% 0 } 100% { background-position: -200% 0 } }
@media (prefers-reduced-motion: reduce) {
  .topbar-title:empty { animation: none; background: var(--bg-3); }
}
@media (min-width: 721px) {
  .topbar-title { display: block; }
}
@media (max-width: 720px) {
  .topbar-brand { display: inline-flex; }
  /* When the brand shows, hide the page title to save space — title is
     duplicated by the active sidebar nav item anyway. */
  .page-title { display: none; }
  /* The window-name icon hides on mobile with the name itself — the active
     bottom-nav icon already says where you are. */
  .topbar-title-ic { display: none !important; }
}

/* The icon beside the window name — cloned from the sidebar's active nav item
   by Page.setTitle (see _syncTitleIcon there). 18px against a 1.12rem title so
   the pair reads as one object rather than an icon with a caption. No colour
   of its own beyond the muted default: the cloned SVG uses `currentColor`, so
   it sits a step behind the name instead of competing with it. */
.topbar-title-ic {
  width: 18px; height: 18px; flex: 0 0 18px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--icon-ink);
  opacity: 0; transform: scale(.9);
  transition: opacity var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
}
.topbar-title-ic.is-set { opacity: 1; transform: none; }
.topbar-title-ic svg { width: 100%; height: 100%; display: block; }

/* Trial chip lives in the left cluster (after the title). It must hug
   to the right edge of that cluster but never push the title or the
   actions area off-screen. */
/* The in-content page head is retired (2026-08-11, owner-directed). It
   existed from 2026-07-10 to give the title a home on the canvas while the
   topbar stayed a pure system zone. The read was defensible; the price was
   not. It spent 58px of EVERY window — plus its own row of whitespace — to
   say one word the sidebar's active item was already saying, and the two
   full-height panes (employee chat, the CRM inbox) each had to opt out of it
   by hand. In the topbar's leading cell the same word costs nothing: that
   cell was empty on desktop, and .topbar's own layout comment above has
   called it "brand+title" since the grid was written. See .topbar-title. */
/* ── CRM inbox: an application surface, not a card on a page ──────────
   2026-08-11, owner-directed: "تتوسع المحادثة في الأعلى ونص المحادثة يكون
   فوق الخط… بنفس فكرة هاي لفل في الترتيب والمتانة والحضور."

   The 2026-08-10 pass shortened this window's page head to 40px, which was
   the right move under the wrong frame: it kept treating a full-height
   inbox as an ordinary window that happened to be tall. Above the first
   conversation sat a 62px system strip, a 40px title band with its own
   rule, 1.2rem of page padding, and then the panel's own border and
   corner — four separate edges before any content.

   So the window name goes UP into the topbar's leading cell (the one the
   grid comment on .topbar already calls "brand+title") and everything
   below the topbar's hairline becomes the inbox. That hairline is the
   only remaining line, which is exactly the ask: the title above it, the
   conversation beneath it.

   Later the same day the owner asked for the title hoist everywhere, so
   that half is no longer scoped here — see .topbar-title and the retired
   page-head note above. What stays scoped is the FULL BLEED, and it stays
   scoped on purpose: it suits a window that is one surface, and it would
   hurt a settings form or a report, where the card is what separates one
   subject from the next and an edge-to-edge line is harder to read, not
   easier.

   The height is now flexbox fill, not arithmetic. The old
   `calc(100vh - topbar - page-head - 2.4rem)` silently omitted .shell's
   own 12px padding-block, so the panel overran its pane by ~24px and the
   scroller took up the slack — visible as a conversation row clipped off
   the bottom. `.shell > main` already has a definite height and is a flex
   column; letting the chain fill it is both correct and immune to the
   topbar ever changing height. */
@media (min-width: 721px) {
  /* Full bleed, one link of the flex chain at a time: page padding off,
     each level allowed to shrink (`min-height: 0` — a flex item's automatic
     minimum is its content, which would otherwise pin the panel taller than
     the pane and reintroduce the scroll this rule exists to remove). */
  main:has(.crm-chat) > .main { padding: 0; flex: 1 1 auto; min-height: 0; display: flex; }
  main:has(.crm-chat) > .main > #crm-chat-mount { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; }
  main:has(.crm-chat) .crm-chat {
    flex: 1 1 auto; min-height: 0; height: auto;
    border: 0; border-radius: 0;
    /* …except the app frame's own bottom corner, which the panel now
       reaches. Leaving it square would put a right angle inside the pane's
       curve. The top corner needs no such care: the topbar still owns it. */
    border-end-start-radius: var(--radius-lg);
  }
}
/* An in-content heading that repeats the window's name is redundant on
   every viewport: desktop reads it in the topbar, phones in the iOS large
   title below it. */
.section-dup-title { display: none; }
/* display:none removes the title from the flex row entirely, so without
   this its sibling (the actions cluster, a filter bar, …) would slide to
   the row's leading edge. auto-margin keeps it reading as trailing-aligned
   — the same position it held when the title was still visible. */
.section-dup-title + * { margin-inline-start: auto; }
/* Chat's sidebar large-title is a phone-only pattern (styled in the
   mobile block); on desktop it rendered as a bare unstyled h1 — hide. */
@media (min-width: 721px) { .chat-large-title { display: none; } }

/* Topbar action buttons — "ghost" style. Transparent at rest so the glass
   topbar reads as a clean strip; pillow lifts on hover with a soft tinted
   surface + matching ring. Keeps focal weight on bell badges and content,
   not on the chrome. */
.topbar-bell {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: var(--radius-sm);
  background: transparent; border: 1px solid transparent; color: var(--icon-ink);
  cursor: pointer; transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.topbar-bell:hover {
  background: color-mix(in srgb, var(--brand) 8%, transparent);
  color: var(--text);
  border-color: color-mix(in srgb, var(--brand) 25%, var(--border));
}
.topbar-bell:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.topbar-bell .lucide-ic { width: 18px; height: 18px; }
/* Urgent trigger inherits the neutral ghost color at rest so the topbar
   reads as one calm cluster; the amber accent only surfaces on hover to
   signal "this is the urgent broadcast channel" without shouting. */
#topbar-urgent:hover {
  background: color-mix(in srgb, var(--warn) 12%, transparent);
  border-color: color-mix(in srgb, var(--warn) 35%, var(--border));
  color: var(--warn);
}

/* Bell badge — sits above the icon. We bump z-index and use the page bg
   colour for the ring so the badge reads as "floating" instead of glued
   to a neighbouring button when icons cluster. */
.bell-badge {
  position: absolute; top: -5px; inset-inline-end: -5px;
  min-width: 18px; height: 18px; padding: 0 5px;
  background: var(--danger); color: #fff; font-size: .65rem; font-weight: 600;
  border-radius: 999px;
  /* Halo ring matches the now-solid topbar canvas. */
  box-shadow: 0 0 0 2px var(--bg);
  display: inline-flex; align-items: center; justify-content: center;
  z-index: 2; pointer-events: none;
}
.bell-badge.hidden { display: none; }

/* ── Topbar quick-search trigger (⌘K) ──────────────────────────────────
   Slim "search box"-shaped button sitting at the start of the actions
   cluster. The kbd hint is hidden on narrow screens and on devices
   without a hardware keyboard so it doesn't lie about the shortcut. */
/* Centered global search — the visual primary action of the topbar.
   Pill-shaped, wide (caps at 520px so it doesn't crowd the action icons
   on narrow desktops), with a search icon on the inline-start, faded
   placeholder text in the middle, and a ⌘K hint on the inline-end.
   Click → opens the cmdk overlay (same handler as before since we kept
   the `id="topbar-cmdk"`). On mobile it collapses to an icon-only pill
   so the topbar stays uncluttered when the bottom-nav is the primary
   navigation surface. */
.topbar-search {
  /* Column 3 of the topbar grid; column 4 is the Ask-AI pill. The 1fr
     spacers in columns 2 and 5 push us to the geometric center of the
     row. Width is FIXED-with-cap (no flex-grow magic) so the cluster
     stays visually anchored — Slack / Notion / Linear all use a
     fixed-width search this way. min(440px, 100%) keeps it readable on
     desktop without overflowing on very narrow viewports. */
  grid-column: 3;
  width: min(440px, 100%);
  display: inline-flex; align-items: center; gap: .55rem;
  padding: .5rem .85rem;
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text-3);
  font: inherit; font-size: .85rem; font-weight: 500;
  cursor: text; text-align: start;
  min-width: 0;
  transition: background .15s ease, border-color .15s ease, color .15s ease, box-shadow .15s ease;
}
.topbar-search:hover,
.topbar-search:focus-visible {
  background: var(--bg-3);
  border-color: color-mix(in srgb, var(--brand) 30%, var(--border));
  color: var(--text);
  outline: none;
}
.topbar-search:focus-visible {
  box-shadow: 0 0 0 3px var(--ring-glow);
}
.topbar-search-ic {
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--icon-ink); flex-shrink: 0;
}
.topbar-search-ic .lucide-ic, .topbar-search-ic svg { width: 16px; height: 16px; }
.topbar-search-placeholder {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--text-3);
}
.topbar-search:hover .topbar-search-placeholder,
.topbar-search:focus-visible .topbar-search-placeholder { color: var(--text-2); }
.topbar-search-kbd {
  font-family: ui-sans-serif, system-ui, sans-serif;
  font-size: .68rem; font-weight: 600; letter-spacing: .02em;
  padding: 2px 6px; border-radius: 5px;
  background: var(--card); color: var(--text-3);
  border: 1px solid var(--border);
  flex-shrink: 0;
}
/* Search lives in the topbar CENTER on desktop (2026-07-10, Asana/
   ClickUp/Linear pattern — completes the "topbar = system zone" move
   that relocated the page title into the content pane). The sidebar
   search trigger hides on desktop to avoid a duplicate; its row-mate,
   the quick-actions lightning, stretches to fill the freed row. Phones
   are the inverse (sidebar hidden → topbar search is theirs already). */
@media (min-width: 721px) {
  .sidebar-search-trigger { display: none; }
  /* The workspace switcher is the extended (wide) control on desktop, showing
     the active system name; the quick-actions lightning sits beside it, on the
     left, as an icon-only button. */
  .sidebar-launch .sidebar-switch { flex: 1; min-width: 0; }
  .sidebar-launch .sidebar-switch .ws-switch-btn { width: 100%; justify-content: flex-start; gap: 6px; padding: 0 12px; }
  .sidebar-launch .sidebar-switch .ws-switch-caret { margin-inline-start: auto; }
  .shell.sidebar-collapsed .sidebar-launch .sidebar-switch { flex: 0 0 auto; }
  .shell.sidebar-collapsed .sidebar-launch .sidebar-switch .ws-switch-btn { width: 34px; padding: 0; justify-content: center; }
}
@media (max-width: 720px) {
  .topbar-quick { display: inline-flex; }
  /* Never let the dropdown exceed the viewport on a phone. */
  .topbar-quick > .quick-menu { width: min(268px, calc(100vw - 20px)); }
}
/* ─── Progressive action-cluster collapse ────────────────────────────
   Same pattern Linear / Slack / Notion use: as the topbar narrows,
   the action cluster on the trailing edge sheds its NON-essential
   items in priority order so the search-and-ask cluster in the
   center never has to fight for space. Each drop happens BEFORE the
   next, so the row reflows smoothly instead of jumping at one cliff.

   Tier 1 (≤1280px) — drop the money readout from the ticker chip.
       Points are the daily-feedback signal; SAR has its own wallet page.
   Tier 2 (≤1100px) — hide the entire ticker. The dashboard hero card
       + shift pill already display the same numbers more legibly.
   Tier 3 (≤980px)  — drop secondary admin actions (focus mode toggle
       + urgent-message launcher). Both reachable elsewhere.
   Tier 4 (≤820px)  — drop the contact button. Its panel is reachable
       from the chat section; on a tight topbar it's the least-used icon.

   ALWAYS visible: notifications, theme toggle, user menu, search,
   AI pill. Those are the irreducible essentials. */
/* Tightened thresholds (2026-05-15 round 2): the previous 1280/1100
   numbers fired while there was still 200-300px of slack in the row,
   which read as "items vanishing for no reason". Calibrated against
   the actual widths of each piece:
     brand+title ≈ 120-180px   search 440px   ai pill 140px
     actions cluster ≈ 38px × 5 + gaps ≈ 222px
     gaps (4×.8rem) + padding ≈ 90px
   Sum ≈ 1010-1075px. Below ~1020px the row genuinely runs out of
   room, so that's where Tier 1 now fires. Each tier still leads its
   peer by ~80-100px so the row reflows in stages. */
@container topbar (max-width: 1020px) {
  .tk-chip .tk-money-num, .tk-chip .tk-dot { display: none !important; }
}
@container topbar (max-width: 920px) {
  /* Wallet group gone → its trailing separator goes with it (the AI
     separator stays and now divides AI │ comms). */
  #ticker-mount, #sep-wallet { display: none !important; }
}
@container topbar (max-width: 820px) {
  #topbar-focus, #topbar-urgent { display: none !important; }
}
@container topbar (max-width: 720px) {
  /* Last comms icon gone → the comms│system separator goes with it. */
  #topbar-contact, #sep-comms { display: none !important; }
  /* …and the window name goes with them. Reachable at a wide VIEWPORT — an
     expanded 240px sidebar puts the topbar's own inline-size under 720px at
     around a 980px window — and by this tier the strip is down to search +
     system icons, where a name is the least load-bearing thing in the row:
     the sidebar's active item is still saying where you are. */
  .topbar-title { display: none !important; }
}

/* Fallback for browsers without container-query support (Safari <16).
   The desktop + sidebar layout keeps the topbar at ~viewport-width
   minus ~260px sidebar, so the breakpoints here are bumped by ~260px
   to land in the same visual state. */
@supports not (container-type: inline-size) {
  @media (max-width: 1280px) { .tk-chip .tk-money-num, .tk-chip .tk-dot { display: none !important; } }
  @media (max-width: 1180px) { #ticker-mount, #sep-wallet { display: none !important; } }
  @media (max-width: 1080px) { #topbar-focus, #topbar-urgent { display: none !important; } }
  @media (max-width: 980px)  { #topbar-contact, #sep-comms { display: none !important; } }
}

/* Container-query collapse for the search bar. Triggered by the
   TOPBAR's own width, not the viewport — so the search responds to
   the real space it has (e.g. sidebar open vs collapsed). */
@container topbar (max-width: 640px) {
  .topbar-search-kbd { display: none; }
}
@container topbar (max-width: 540px) {
  /* Final collapse: icon-only pill. Below this width the placeholder
     just truncates to ellipsis anyway, so dropping it is honest. */
  .topbar-search {
    flex: 0 0 38px;
    width: 38px; padding: 0;
    border-radius: var(--radius-sm);
    justify-content: center;
  }
  .topbar-search-placeholder { display: none; }
}

/* Ask-AI pill in the topbar (OpenRouter docs pattern). Sits flush with
   the search bar — same height, same border radius. Brand-tinted to
   draw a clean visual line between "find data" and "ask data". Hidden
   until the agent widget confirms the feature is enabled.

   Responsive strategy uses CONTAINER QUERIES (against the .topbar's
   own width, not viewport) and a `clamp()`-driven search bar that
   fluidly absorbs slack, so the whole row reflows smoothly when the
   browser is dragged narrower instead of jumping at fixed breakpoints.

   Priority order as space gets tight (each step kicks in earlier than
   the next):
     1. Action labels drop (handled inline by .topbar-actions rules)
     2. Search bar's ⌘K hint disappears   (≤640px topbar width)
     3. AI pill's text label disappears    (≤880px topbar width)
     4. Search bar's placeholder hides     (≤540px topbar width)
   By the time all four fire, both pill + search are 38×38 icons — the
   minimum useful state. Any narrower and we're below mobile anyway. */
.topbar-ai {
  /* Lives inside .topbar-actions (the trailing icon cluster) now that the
     desktop search moved to the sidebar — so it groups with the other
     topbar controls instead of floating alone in the old center slot.
     Frame is deliberately the SAME neutral hairline as .topbar-search /
     .tk-chip (bg-2 + --border) so all three framed pills read as one
     system, per the owner's "unified identity" direction. The brand lives
     in the emerald label + sparkle icon, not a saturated chip — and the
     slimmer vertical padding sits it lighter against the 38px icons. */
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .4rem .8rem;
  background: var(--bg-2);
  border: 1px solid var(--border);
  /* --radius-sm, not a 999px pill: matches the Asana corner language of
     the whole desktop shell (8px surfaces / 6px small controls) and the
     ghost icon buttons sitting right beside it. The token adapts per
     breakpoint (6px desktop, 8px on the ≤720px mobile tier). */
  border-radius: var(--radius-sm);
  color: var(--brand);
  font: inherit; font-size: .82rem; font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: background .15s ease, border-color .15s ease, color .15s ease, box-shadow .15s ease, transform .1s ease;
}
.topbar-ai[hidden] { display: none !important; }
.topbar-ai:hover {
  background: var(--bg-3);
  border-color: color-mix(in srgb, var(--brand) 35%, var(--border));
}
.topbar-ai:active { transform: translateY(1px); }
.topbar-ai:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--ring-glow);
}
.topbar-ai-ic {
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--brand); opacity: .7;
}
.topbar-ai-ic .lucide-ic, .topbar-ai-ic svg, .topbar-ai-ic i { width: 16px; height: 16px; }
@container topbar (max-width: 880px) {
  /* AI label drops first — the search bar's "find data" affordance is
     more universally useful than a chat shortcut, so we sacrifice the
     pill's label one step before the search bar's placeholder. */
  .topbar-ai {
    padding: 0; width: 38px; height: 38px;
    justify-content: center; border-radius: var(--radius-sm);
  }
  .topbar-ai-label { display: none; }
}
/* Browsers without container query support (Safari <16, anything pre-
   2023) fall back to a viewport media query that mirrors the same
   thresholds. Modern targets use the @container rules above; this is
   a graceful degradation layer, not the primary path. */
@supports not (container-type: inline-size) {
  @media (max-width: 1023px) {
    .topbar-ai {
      padding: 0; width: 38px; height: 38px;
      justify-content: center; border-radius: var(--radius-sm);
    }
    .topbar-ai-label { display: none; }
  }
  @media (max-width: 720px) {
    .topbar-search { flex: 0 0 38px; width: 38px; padding: 0; border-radius: var(--radius-sm); justify-content: center; }
    .topbar-search-placeholder, .topbar-search-kbd { display: none; }
  }
}

/* ── Command Palette modal (⌘K) ────────────────────────────────────────
   Centered card on top of a blurred backdrop. The list mirrors the
   bell-panel aesthetic so it feels native to the rest of the shell.
   The whole stack lives at z-index 200 so it sits above the bell /
   user-menu / panels — those auto-close when the modal opens because
   ⌘K steals focus into the cmdk input. */
.cmdk-overlay {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(2, 6, 23, 0);
  display: none; align-items: flex-start; justify-content: center;
  padding-top: 12vh;
  transition: background .2s ease;
}
.cmdk-overlay.is-open {
  display: flex;
  background: rgba(2, 6, 23, .55);
}
.cmdk-modal {
  width: min(640px, calc(100vw - 24px));
  max-height: 70vh; overflow: hidden;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-card);
  box-shadow: var(--shadow-2);
  display: flex; flex-direction: column;
  animation: cmdk-in .18s ease-out;
}
@keyframes cmdk-in { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: translateY(0); } }
.cmdk-input-row {
  display: flex; align-items: center; gap: .65rem;
  padding: .9rem 1rem; border-bottom: 1px solid var(--border);
}
.cmdk-input-ic { color: var(--icon-ink); display: inline-flex; }
.cmdk-input-ic .lucide-ic { width: 1.1rem; height: 1.1rem; }
.cmdk-input {
  flex: 1; appearance: none; border: 0; background: transparent;
  font: inherit; font-size: 1rem; color: var(--text);
  outline: none; padding: 0;
}
.cmdk-input::placeholder { color: var(--text-3); }
.cmdk-esc {
  font-family: ui-sans-serif, system-ui, sans-serif;
  font-size: .7rem; font-weight: 600; padding: 2px 6px; border-radius: 4px;
  background: var(--bg-3); color: var(--text-3); border: 1px solid var(--border);
}
.cmdk-list {
  list-style: none; margin: 0; padding: .35rem;
  overflow-y: auto; flex: 1;
}
.cmdk-empty {
  padding: 2rem 1rem; text-align: center;
  color: var(--text-3); font-size: .9rem;
}
.cmdk-item {
  display: flex; align-items: center; gap: .75rem;
  padding: .65rem .8rem; border-radius: var(--radius-sm);
  cursor: pointer; user-select: none;
}
.cmdk-item.is-selected {
  background: color-mix(in srgb, var(--brand) 12%, var(--bg-3));
}
.cmdk-ic {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: var(--radius-sm);
  background: var(--bg-3); color: var(--icon-ink); flex-shrink: 0;
}
.cmdk-ic .lucide-ic { width: 14px; height: 14px; }
.cmdk-item.is-selected .cmdk-ic {
  background: color-mix(in srgb, var(--brand) 22%, transparent);
  color: var(--brand);
}
.cmdk-body { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.cmdk-label {
  font-weight: 600; font-size: .92rem; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cmdk-sub {
  color: var(--text-3); font-size: .76rem; margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cmdk-kind {
  flex-shrink: 0;
  font-size: .68rem; font-weight: 600; letter-spacing: .02em;
  padding: 2px 8px; border-radius: 999px;
  background: var(--bg-3); color: var(--text-3);
  text-transform: uppercase;
}
.cmdk-foot {
  display: flex; gap: 1rem; justify-content: flex-end;
  padding: .55rem .9rem; border-top: 1px solid var(--border);
  background: var(--bg-2); color: var(--text-3); font-size: .72rem;
}
.cmdk-foot kbd {
  font-family: ui-sans-serif, system-ui, sans-serif;
  background: var(--card); border: 1px solid var(--border);
  border-radius: 4px; padding: 1px 5px; margin-inline-end: .25rem;
  font-size: .7rem; color: var(--text-2);
}
body.cmdk-locked { overflow: hidden; }
@media (max-width: 480px) {
  .cmdk-overlay { padding-top: 6vh; }
  .cmdk-foot { display: none; }
  .cmdk-kind { display: none; }
}

/* ── Focus mode toggle ─────────────────────────────────────────────────
   Quiets the topbar without silencing notifications themselves. When
   active, badges + ticker + trial chip fade out so the user can read /
   plan / write without the bell counter pulling at their eye. The
   button reuses the .topbar-bell ghost shell so it sits in the same
   visual cluster as the bell/contact/urgent triggers. */
.topbar-focus { position: relative; }
.topbar-focus .focus-dot {
  position: absolute; bottom: 5px; left: 50%; transform: translateX(-50%);
  width: 4px; height: 4px; border-radius: 50%;
  background: var(--brand);
  opacity: 0; transition: opacity .15s ease;
}
.topbar-focus[aria-pressed="true"] {
  color: var(--brand);
  border-color: color-mix(in srgb, var(--brand) 35%, var(--border));
  background: color-mix(in srgb, var(--brand) 10%, transparent);
}
.topbar-focus[aria-pressed="true"] .focus-dot { opacity: 1; }
body.focus-mode .bell-badge { display: none !important; }
body.focus-mode #ticker-mount, body.focus-mode #sep-wallet { display: none; }

/* Severity tints — JS toggles .is-info (blue, routine) / .is-urgent (red,
   needs attention). Default class stays red so old call-sites still work. */
.bell-badge.is-info   { background: var(--info); }
.bell-badge.is-urgent { background: var(--danger); }

/* Bell ping — short pulse on the trigger when an urgent admin message
   arrives. Soft and quick: peace-of-mind, not anxiety. */
@keyframes bell-ping { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--danger) 55%, transparent); } 70% { box-shadow: 0 0 0 10px transparent; } 100% { box-shadow: 0 0 0 0 transparent; } }
.topbar-bell.bell-ping { animation: bell-ping 1.1s ease-out 2; }

.bell-panel {
  position: absolute; top: calc(100% + 8px); inset-inline-end: 0;
  width: min(380px, calc(100vw - 24px));
  background: var(--card);
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-2);
  z-index: 60; display: none; overflow: hidden;
}
.bell-panel.open { display: block; animation: bell-panel-in .18s ease; }
@keyframes bell-panel-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } }

/* Page-level floating overlays (sticky memos on /app/shift, future
   widgets) defocus the moment the user opens a topbar panel — bell,
   contact, urgent, account menu. We blur each card instead of hiding
   the layer so the notes stay as visual anchors (the page doesn't
   "empty out") while their text becomes unreadable — depth-of-field
   effect signalling "this isn't your focus right now", and a small
   privacy bonus if someone glances over your shoulder while a
   notification opens. The body class is set and cleared by
   watchTopbarPanels() in shell.js. */
/* ── Sticky memo cards (shared between /app/shift.html's full editor
   and the global PinnedMemos layer that follows the user across
   pages). The full editor on shift.html still inlines its own copies
   of these rules — harmless duplication. Placed here so cards
   render correctly on every page that mounts the PinnedMemos layer. */
.note-card {
  position: absolute;
  width: 200px;
  padding: .85rem .85rem 2.1rem;
  border-radius: 4px;
  min-height: 130px;
  color: #1f2937;
  font-size: .88rem;
  line-height: 1.5;
  box-shadow: 0 6px 14px -8px rgba(0,0,0,.35), 0 2px 0 rgba(0,0,0,.04);
  transition: box-shadow .18s ease;
  cursor: grab;
  word-break: break-word;
  user-select: none;
  touch-action: none;
  transform: rotate(var(--note-tilt, 0deg));
}
.note-card:hover { box-shadow: 0 12px 24px -10px rgba(0,0,0,.4); z-index: 4; }
.note-card.pinned { transform: rotate(0); box-shadow: 0 10px 18px -10px rgba(0,0,0,.42), 0 0 0 2px rgba(255,255,255,.3) inset; }
.note-card.dragging {
  cursor: grabbing;
  z-index: 9;
  transform: rotate(0) scale(1.03);
  box-shadow: 0 18px 38px -14px rgba(0,0,0,.55);
  transition: none;
}
.note-card.color-yellow { background: color-mix(in srgb, var(--warn) 35%, #fff); }
.note-card.color-pink   { background: color-mix(in srgb, var(--tag-pink-fg) 25%, #fff); }
.note-card.color-green  { background: color-mix(in srgb, var(--success) 30%, #fff); }
.note-card.color-blue   { background: var(--primary-100); }
.note-card.color-red    { background: color-mix(in srgb, var(--danger) 25%, #fff); }
.note-card.color-purple { background: color-mix(in srgb, var(--viz-violet) 25%, #fff); }
.note-card .note-title { font-weight: 600; margin-bottom: .25rem; font-size: .92rem; }
.note-card .note-body  { white-space: pre-wrap; }
.note-actions { position: absolute; top: .35rem; inset-inline-end: .4rem; display: inline-flex; gap: .15rem; opacity: .35; transition: opacity .2s ease; }
.note-card:hover .note-actions, .note-card.pinned .note-actions { opacity: 1; }
.note-actions button {
  all: unset; cursor: pointer; padding: .2rem .35rem; border-radius: 6px;
  color: rgba(0,0,0,.55);
  transition: background .15s, color .15s;
}
.note-actions button:hover { background: rgba(0,0,0,.1); color: rgba(0,0,0,.85); }
.note-actions button:active { transform: scale(.92); transition: transform .08s; }
/* One-shot flick: all action icons play together the instant the CARD is
   first hovered, then settle back to rest. Triggering on the card (not
   on each button) means the cursor moving between icons doesn't replay
   the animation three times — it fires once per card-hover, then stops. */
.note-card:hover .note-actions button {
  animation: note-icon-flick .22s ease-out;
}
@keyframes note-icon-flick {
  0%   { transform: rotate(0)    scale(1); }
  50%  { transform: rotate(-8deg) scale(1.12); }
  100% { transform: rotate(0)    scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .note-card:hover .note-actions button { animation: none; }
}
.note-pin-icon.active { color: #b91c1c; }
.note-card.priority-urgent::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  box-shadow: 0 0 0 2px var(--danger) inset;
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .note-card, .note-card:hover { transition: none; }
}

/* Global pinned-memos layer (PinnedMemos module) — fixed positioning
   so cards float above page content like the shift-page board, and
   reuses the .note-card styling so a memo looks identical on either
   surface. The layer itself doesn't capture pointer events; only the
   cards do, so clicks on empty page area pass through. */
.pinned-memos-layer { position: fixed; inset: 0; pointer-events: none; z-index: 3; }
.pinned-memos-layer .note-card { pointer-events: auto; }

/* Resize handle — small SE-corner grip (BL in RTL since the card is
   mirrored by its parent dir). The arrow shape is drawn purely with
   borders so we don't ship an extra SVG for one icon. */
.note-card .note-resize {
  position: absolute;
  bottom: 4px;
  inset-inline-end: 4px;
  width: 14px; height: 14px;
  cursor: nwse-resize;
  opacity: .35;
  background:
    linear-gradient(135deg, transparent 0 65%, rgba(0,0,0,.55) 65% 75%, transparent 75% 85%, rgba(0,0,0,.55) 85% 95%, transparent 95%);
  border-radius: 0 0 3px 0;
  transition: opacity .15s ease;
}
[dir="rtl"] .note-card .note-resize {
  /* In RTL the handle is mirrored to the bottom-LEFT corner via
     inset-inline-end. The cursor must follow — nesw-resize points along
     the diagonal from this corner up-and-to-the-right, matching how the
     card actually grows when dragged. */
  cursor: nesw-resize;
  background:
    linear-gradient(225deg, transparent 0 65%, rgba(0,0,0,.55) 65% 75%, transparent 75% 85%, rgba(0,0,0,.55) 85% 95%, transparent 95%);
  border-radius: 0 0 0 3px;
}
.note-card:hover .note-resize { opacity: .85; }
.note-card.resizing { transition: none; }

/* Inline edit affordance — when a title or body becomes editable, give
   it a subtle inset ring so the user sees they're now typing into
   that field. Uses currentColor against the sticky background so it
   reads on every paper colour. */
.note-card .note-title[contenteditable="true"],
.note-card .note-body[contenteditable="true"] {
  outline: none;
  box-shadow: inset 0 0 0 2px rgba(0, 0, 0, .25);
  border-radius: 4px;
  background: rgba(255, 255, 255, .35);
}
body.topbar-panel-open .notes-layer { pointer-events: none; }
/* Pointer-events on the parent layer alone aren't enough — the cards
   set pointer-events:auto themselves so clicks through them would
   still hijack the contact / urgent / bell panels. Disable on the
   cards too while a topbar panel is open. */
body.topbar-panel-open .notes-layer .note-card,
body.topbar-panel-open .notes-layer .note-add-tile {
  /* Push memos far enough out of focus that their colors don't compete
     with the topbar panel's content. They stay as a faint hint of the
     layer underneath rather than legible shapes. */
  filter: blur(14px);
  opacity: .25;
  pointer-events: none;
  transition: filter .2s ease, opacity .2s ease;
}
@media (prefers-reduced-motion: reduce) {
  body.topbar-panel-open .notes-layer .note-card,
  body.topbar-panel-open .notes-layer .note-add-tile {
    transition: none;
  }
}
/* Urgent panel hosts a form (label + select + textarea + button) — the
   default 380px feels cramped. Give it 420px and let it relax. */
.urgent-panel { width: min(420px, calc(100vw - 24px)); }
.urgent-counter { font-variant-numeric: tabular-nums; transition: color .15s ease; }
.urgent-counter.warn { color: var(--warn); font-weight: 600; }
.urgent-counter.over { color: var(--danger); font-weight: 600; }
.bell-panel-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: .8rem 1rem; border-bottom: 1px solid var(--border);
}
.bell-panel-head h4 { margin: 0; font-size: .95rem; font-weight: 600; }
/* Mark-all-read — was a flat link, now a soft pill so it reads as an
   action, not a hint. */
.bell-mark-all {
  background: color-mix(in srgb, var(--accent-2) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent-2) 25%, var(--border));
  color: var(--accent-2);
  cursor: pointer; font-size: .78rem; font-weight: 600;
  padding: .3rem .65rem; border-radius: 999px;
  transition: background .15s ease, border-color .15s ease;
}
.bell-mark-all:hover { background: color-mix(in srgb, var(--accent-2) 18%, transparent); border-color: color-mix(in srgb, var(--accent-2) 45%, var(--border)); }
.bell-panel-tabs {
  display: flex; gap: .4rem; padding: .6rem 1rem;
  border-bottom: 1px solid var(--border);
}
.bell-tab {
  appearance: none; background: transparent; border: 0;
  padding: .4rem .9rem; border-radius: var(--radius-sm); cursor: pointer;
  font-size: .85rem; font-weight: 600; color: var(--text-2);
  transition: background .15s ease, color .15s ease;
}
.bell-tab:hover { background: var(--bg-3); color: var(--text); }
.bell-tab.active { background: var(--accent); color: var(--accent-fg); }
.bell-panel-list { max-height: 480px; overflow-y: auto; }
.bell-panel-empty { padding: 2rem 1rem; text-align: center; color: var(--text-3); font-size: .9rem; }

/* ── Topbar user menu ──────────────────────────────────────────────────
   Avatar button at the end of the topbar opens a small menu (profile,
   settings, sign out). Reuses the .bell-panel base for the dropdown so
   it matches the bell/urgent/contact panels visually. */
.topbar-user {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; padding: 0;
  background: transparent; border: 1px solid var(--border); border-radius: 50%;
  cursor: pointer; overflow: hidden;
  transition: border-color .15s ease, transform .15s ease;
}
.topbar-user:hover { border-color: var(--text-3); }
.topbar-user .avatar { width: 100%; height: 100%; border-radius: 50%; font-size: .72rem; }
.topbar-user:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.user-menu { width: 240px; padding: .35rem; }
.user-menu-head {
  display: flex; align-items: center; gap: .65rem;
  padding: .65rem .55rem .8rem; border-bottom: 1px solid var(--border);
}
.user-menu-name { font-weight: 600; font-size: .92rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-menu-sub  { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: .1rem; }
.user-menu-item {
  display: flex; align-items: center; gap: .65rem;
  padding: .55rem .65rem; margin: .1rem 0;
  background: transparent; border: 0; width: 100%; text-align: start;
  font-size: .88rem; font-weight: 500; color: var(--text);
  text-decoration: none; cursor: pointer; border-radius: var(--radius-sm);
  transition: background .12s ease;
}
.user-menu-item:hover { background: var(--bg-3); }
.user-menu-item .lucide-ic { width: 1rem; height: 1rem; color: var(--icon-ink); }
.user-menu-sep { height: 1px; background: var(--border); margin: .25rem .25rem; }
.user-menu-danger { color: var(--danger); }
.user-menu-danger .lucide-ic { color: var(--danger); }
.user-menu-danger:hover { background: color-mix(in srgb, var(--danger) 12%, transparent); }

/* ── Avatar crop modal ────────────────────────────────────────────────
   The stage is a square clip-area with the canvas behind a circle mask,
   so the user always sees what the round avatar will look like. The
   overlay draws the circle and a soft dimmer over the corners. */
.avatar-crop-stage {
  position: relative; overflow: hidden; border-radius: var(--radius-card);
  background: #000; cursor: grab; user-select: none; touch-action: none;
}
.avatar-crop-stage.is-dragging { cursor: grabbing; }
.avatar-crop-canvas { display: block; }
.avatar-crop-overlay {
  position: absolute; inset: 0; pointer-events: none;
  /* Dim everything outside the circle (radial mask via box-shadow). */
  border-radius: 50%;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.55);
  outline: 1px dashed rgba(255, 255, 255, 0.35);
  outline-offset: -2px;
}

/* ── Topbar mobile layout ──────────────────────────────────────────────
   Below 640px the topbar would otherwise overflow horizontally
   (ticker chips + 3 icon buttons + company chip + page title). We:
     • Hide the company chip's id, keep its name truncated.
     • Tighten the gap and reduce icon button size by 4px.
     • Allow the actions row to shrink (page title can ellipsize).         */
@media (max-width: 720px) {
  .topbar {
    padding-top: calc(.55rem + env(safe-area-inset-top));
    padding-bottom: .55rem;
    padding-inline-start: calc(.8rem + env(safe-area-inset-left));
    padding-inline-end: calc(.8rem + env(safe-area-inset-right));
  }
  .topbar-actions { gap: .35rem; }
  .topbar-bell { width: 34px; height: 34px; border-radius: var(--radius-sm); }
  .topbar-bell .lucide-ic { width: 1rem; height: 1rem; }
  .bell-panel { width: min(340px, calc(100vw - 16px)); }
}
@media (max-width: 480px) {
  .topbar-bell { width: 32px; height: 32px; }
  .bell-panel { width: calc(100vw - 16px); inset-inline-end: -8px; }
}
.bell-item {
  position: relative;
  display: flex; gap: .7rem;
  padding: .75rem 2rem .75rem 1rem;
  padding-inline-start: 1rem; padding-inline-end: 2rem;
  border-bottom: 1px solid var(--border); cursor: pointer;
  transition: background .15s ease;
}
.bell-item:last-child { border-bottom: 0; }
.bell-item:hover { background: var(--bg-3); }
.bell-item::after {
  content: '';
  position: absolute; top: 50%; inset-inline-end: 14px; transform: translateY(-50%);
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--accent-2); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-2) 25%, transparent);
  opacity: 0; transition: opacity .2s ease, transform .2s ease;
  pointer-events: none;
}
.bell-item.unread::after { opacity: 1; }
.bell-item-icon {
  width: 36px; height: 36px; border-radius: var(--radius-sm);
  background: var(--card-2); border: 1px solid var(--border);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--accent-2); flex-shrink: 0;
}
.bell-item.attendance-started .bell-item-icon { color: var(--success); background: color-mix(in srgb, var(--success) 12%, var(--card-2)); }
.bell-item.attendance-ended .bell-item-icon { color: var(--warn); background: color-mix(in srgb, var(--warn) 12%, var(--card-2)); }
.bell-item.task-created .bell-item-icon { color: var(--accent-2); background: color-mix(in srgb, var(--accent-2) 12%, var(--card-2)); }
.bell-item.task-waiting_approval .bell-item-icon { color: var(--warn); background: color-mix(in srgb, var(--warn) 12%, var(--card-2)); }
.bell-item.task-needs_changes .bell-item-icon { color: var(--danger); background: color-mix(in srgb, var(--danger) 12%, var(--card-2)); }
.bell-item.task-approved .bell-item-icon { color: var(--success); background: color-mix(in srgb, var(--success) 12%, var(--card-2)); }
.bell-item-body { flex: 1; min-width: 0; }
.bell-item-title { font-weight: 600; font-size: .88rem; margin: 0 0 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bell-item-sub { font-size: .8rem; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bell-item-time { font-size: .7rem; color: var(--text-3); margin-top: 2px; }
.main { padding: 1.2rem; }

.chip { display: inline-flex; align-items: center; gap: .35rem; padding: .25rem .55rem; border-radius: 999px; border: 1px solid var(--border-2); background: var(--card-2); font-size: .78rem; color: var(--text-2); }
.chip.primary { background: var(--accent); color: var(--accent-fg); border: 0; }
.chip.success { background: var(--success-bg); color: var(--success); border: 1px solid color-mix(in srgb, var(--success) 30%, transparent); }
.chip.warn { background: var(--warn-bg); color: var(--warn); border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent); }
.chip.danger { background: var(--danger-bg); color: var(--danger); border: 1px solid color-mix(in srgb, var(--danger) 30%, transparent); }

/* ── Status badge system ───────────────────────────────────────────────
   Royal Pixel: states speak ONLY through the semantic set (spec §6.5) —
   done = success · in-progress = primary (live work = blue) · late /
   blocked / urgent = danger · awaiting approval (review/onhold) = warn ·
   everything dormant is neutral. Faint tint + dot prefix, never loud
   bars. One rule set serves BOTH themes straight off the token table —
   the old `:root.dark` variant block was dead code (that class is never
   applied; dark is bare :root) and is gone. */
.status-badge {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .22rem .6rem; border-radius: var(--radius-pill);
  font-family: var(--font-numeric); font-size: .72rem; font-weight: 500;
  letter-spacing: .02em; line-height: 1.4;
  border: 1px solid transparent; white-space: nowrap;
}
.status-badge::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor; flex-shrink: 0;
}
.status-badge.is-planned    { background: color-mix(in srgb, var(--text-3) 12%, transparent); color: var(--text-2); border-color: color-mix(in srgb, var(--text-3) 28%, transparent); }
.status-badge.is-progress   { background: var(--accent-soft); color: var(--info); border-color: color-mix(in srgb, var(--info) 28%, transparent); }
.status-badge.is-review     { background: var(--warn-bg); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 28%, transparent); }
.status-badge.is-done       { background: var(--success-bg); color: var(--success); border-color: color-mix(in srgb, var(--success) 28%, transparent); }
.status-badge.is-blocked    { background: var(--danger-bg); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 28%, transparent); }
.status-badge.is-cancelled  { background: color-mix(in srgb, var(--text-3) 12%, transparent); color: var(--text-3); border-color: color-mix(in srgb, var(--text-3) 25%, transparent); text-decoration: line-through; }
.status-badge.is-archived   { background: color-mix(in srgb, var(--text-3) 12%, transparent); color: var(--text-3); border-color: color-mix(in srgb, var(--text-3) 28%, transparent); }
.status-badge.is-draft      { background: transparent; color: var(--text-3); border-color: color-mix(in srgb, var(--text-3) 40%, transparent); border-style: dashed; }
.status-badge.is-scheduled  { background: transparent; color: var(--info); border-color: color-mix(in srgb, var(--info) 40%, transparent); border-style: dashed; }
.status-badge.is-late       { background: var(--danger-bg); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 32%, transparent); }
.status-badge.is-urgent     { background: var(--danger-bg); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 40%, transparent); animation: status-pulse 1.8s ease-in-out infinite; }
.status-badge.is-onhold     { background: var(--warn-bg); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 28%, transparent); }
@keyframes status-pulse {
  0%, 100% { box-shadow: 0 0 0 0 transparent; }
  50%      { box-shadow: 0 0 0 4px color-mix(in srgb, var(--danger) 15%, transparent); }
}
/* Solid variant — for cases where the badge needs maximum visual weight
   (e.g. as the primary status on a hero card). Use sparingly; the soft
   variant above is the default. */
.status-badge.is-solid              { color: #fff; border: 0; }
.status-badge.is-solid.is-planned   { background: var(--text-3); }
.status-badge.is-solid.is-progress  { background: var(--accent); }
.status-badge.is-solid.is-review    { background: var(--warn); }
.status-badge.is-solid.is-done      { background: var(--success); }
.status-badge.is-solid.is-blocked   { background: var(--danger); }
.status-badge.is-solid.is-late      { background: var(--danger); }
.status-badge.is-solid.is-urgent    { background: var(--danger); }
.status-badge.is-solid.is-scheduled { background: var(--info); }
.status-badge.is-solid.is-onhold    { background: var(--warn); }

/* ── KPI strip ─────────────────────────────────────────────────────────
   Power-tool dashboard atop admin pages — Connecteam-style. Shows 3-5
   big tabular numbers above the page content so the admin gets a one-
   glance read of "what's the state of this section right now". Tones
   carry semantic meaning: default = neutral, brand/warn/danger track
   the underlying KPI. Delta arrows show movement vs prior period.
   Use: <div class="kpi-strip"><div class="kpi"><span class="kpi-label">…
   </span><b class="kpi-num">47</b><span class="kpi-delta is-up">+3</span>
   </div>…</div> */
.kpi-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: .9rem;
  margin: 0 0 1.4rem;
}
.kpi {
  position: relative;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1rem 1.15rem 1.05rem;
  display: flex; flex-direction: column; gap: .15rem;
  transition: border-color .15s, transform .15s;
  overflow: hidden;
}
/* Semantic tone lives in the VALUE, not a colored edge rail (2026-07-15).
   The 3px left rail read as an AI-design cliché and duplicated the label;
   removed. Only true alert tones (danger/warn) now tint the number so a
   problem still catches the eye at a glance; brand/info/review stay
   neutral — calm by default, colour reserved for what needs attention. */
.kpi.is-danger .kpi-num { color: var(--danger); }
.kpi.is-warn   .kpi-num { color: var(--warn); }
.kpi:hover {
  border-color: var(--border-2);
  box-shadow: var(--shadow-1);
  transform: translateY(-1px);
}
.kpi-label {
  /* text-2, not text-3: these caption the headline numbers — they're the
     widget's title, not tertiary metadata (2026-07-10 clarity pass). */
  font-size: .76rem; font-weight: 600; color: var(--text-2);
  text-transform: uppercase; letter-spacing: .06em;
  font-family: var(--font-numeric);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.kpi-num {
  /* Royal Pixel §4: stat number 20–28px band, weight 500–600. 1.75rem at
     the 15px desktop root ≈ 26px. */
  font-family: var(--font-numeric);
  font-variant-numeric: tabular-nums;
  font-size: 1.75rem; font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.2;
  color: var(--text);
  direction: ltr;
}
.kpi-num .kpi-unit {
  font-size: .85rem; font-weight: 600;
  color: var(--text-3); margin-inline-start: .25rem;
  letter-spacing: 0;
}
.kpi-delta {
  font-family: var(--font-numeric);
  font-variant-numeric: tabular-nums;
  font-size: .78rem; font-weight: 600;
  display: inline-flex; align-items: center; gap: .2rem;
  padding: .12rem .4rem; border-radius: 6px;
  margin-top: .3rem;
  width: max-content;
}
.kpi-delta.is-up    { background: var(--brand-soft); color: var(--brand); }
.kpi-delta.is-down  { background: var(--danger-bg); color: var(--danger); }
.kpi-delta.is-flat  { background: var(--bg-2); color: var(--text-3); }
.kpi-delta::before {
  font-size: .9em; font-weight: 600; line-height: 1;
}
.kpi-delta.is-up::before   { content: "↑"; }
.kpi-delta.is-down::before { content: "↓"; }
.kpi-delta.is-flat::before { content: "→"; }
@media (max-width: 520px) {
  .kpi-strip { grid-template-columns: 1fr 1fr; gap: .6rem; }
  .kpi { padding: .75rem .85rem .85rem; }
  .kpi-num { font-size: 1.55rem; }
  .kpi-label { font-size: .68rem; }
  /* Odd counts: stretch the orphan KPI instead of leaving a hole. */
  .kpi-strip > .kpi:last-child:nth-child(odd) { grid-column: 1 / -1; }
}

/* ── Side detail panel ─────────────────────────────────────────────────
   Linear/Jira-style slide-in panel from the inline-end of the screen.
   Click a list row → panel reveals full details without navigating away.
   The list stays visible behind a soft backdrop, so the user can close
   the panel and pick another row instantly. Animation: slide + fade,
   300ms, decelerate easing.
   API (in page.js): Page.openDetail({title, body, actions}); closes on
   Escape, backdrop click, or the X button. */
.detail-panel-backdrop {
  position: fixed; inset: 0; z-index: 1100;
  background: rgba(15, 31, 26, 0.30);
  opacity: 0; pointer-events: none;
  transition: opacity 220ms ease;
}
.detail-panel-backdrop.is-open {
  opacity: 1; pointer-events: auto;
}
.detail-panel {
  position: fixed; top: 0; bottom: 0; inset-inline-end: 0;
  width: min(560px, 100vw);
  z-index: 1101;
  background: var(--card);
  border-inline-start: 1px solid var(--border);
  box-shadow: var(--shadow-2);
  display: flex; flex-direction: column;
  transform: translateX(100%);
  transition: transform 320ms cubic-bezier(.16, 1, .3, 1);
  will-change: transform;
}
[dir="rtl"] .detail-panel { transform: translateX(-100%); }
.detail-panel.is-open { transform: translateX(0); }
.detail-panel-head {
  display: flex; align-items: center; gap: .75rem;
  padding: 1.1rem 1.4rem 1rem;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.detail-panel-head .dp-title {
  flex: 1; min-width: 0;
  font-size: 1.05rem; font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.detail-panel-head .dp-close {
  appearance: none; border: 0; background: transparent;
  width: 36px; height: 36px; border-radius: var(--radius-sm);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-2); cursor: pointer;
  transition: background .15s, color .15s;
}
.detail-panel-head .dp-close:hover { background: var(--bg-2); color: var(--text); }
.detail-panel-head .dp-close svg { width: 18px; height: 18px; stroke-width: 2; }
.detail-panel-body {
  flex: 1; overflow-y: auto;
  padding: 1.2rem 1.4rem 1.5rem;
  font-size: .92rem; line-height: 1.65; color: var(--text);
}
.detail-panel-body .dp-row {
  display: grid; grid-template-columns: 110px 1fr;
  gap: .75rem; padding: .55rem 0;
  border-bottom: 1px dashed var(--border);
}
.detail-panel-body .dp-row:last-of-type { border-bottom: 0; }
.detail-panel-body .dp-label {
  font-size: .8rem; font-weight: 600;
  color: var(--text-3);
  text-transform: uppercase; letter-spacing: .04em;
  font-family: var(--font-numeric);
  padding-top: 2px;
}
.detail-panel-body .dp-value {
  font-size: .95rem; color: var(--text);
}
.detail-panel-body h4 {
  margin: 1.4rem 0 .55rem;
  font-size: .82rem; font-weight: 600;
  color: var(--text-3);
  text-transform: uppercase; letter-spacing: .07em;
  font-family: var(--font-numeric);
}
/* Contact edit drawer (Task 5, 2026-07-27 crm-contacts-page-enrich plan) —
   crm-contacts.js embeds the SAME .crm-chat-context-profile block #288
   shipped for the chat sidebar (see the .crm-chat-context-* rules further
   down) inside this generic Page.openDetail panel. Pull it flush to the
   panel's own edges (bleeding past .detail-panel-body's padding) so the
   edit block reads as one header card instead of double-inset content. */
.detail-panel-body .crm-chat-context-profile {
  margin: -1.2rem -1.4rem 1.2rem;
  padding: 14px 1.4rem;
}
@media (max-width: 600px) {
  .detail-panel-body .crm-chat-context-profile { margin: -1rem -1.1rem 1.2rem; padding: 14px 1.1rem; }
}
.detail-panel-foot {
  display: flex; gap: .55rem; justify-content: flex-end;
  padding: 1rem 1.4rem;
  border-top: 1px solid var(--border);
  background: var(--bg-2);
  flex-shrink: 0;
}
@media (max-width: 600px) {
  .detail-panel { width: 100vw; box-shadow: none; }
  .detail-panel-head { padding: 1rem 1.1rem .85rem; }
  .detail-panel-body { padding: 1rem 1.1rem 1.4rem; }
  .detail-panel-foot { padding: .85rem 1.1rem; }
}

/* ── Inline editor popover ─────────────────────────────────────────────
   Notion/Linear-style click-to-edit: tap a status badge or priority chip
   to reveal a compact list of options anchored beneath the cell. Pick →
   commit + close, no modal. Keyboard: Esc closes, ↑↓ navigates, Enter
   commits. Designed to host status/priority/assignee/category lists.
   Use Page.inlinePop({anchorEl, options, current, onPick}) to invoke. */
.inline-pop-anchor {
  cursor: pointer;
  position: relative;
}
.inline-pop-anchor::after {
  content: ""; position: absolute; inset: -3px -5px;
  border-radius: 999px;
  border: 1.5px dashed transparent;
  transition: border-color .15s ease;
  pointer-events: none;
}
.inline-pop-anchor:hover::after,
.inline-pop-anchor:focus-visible::after { border-color: var(--brand); }

/* Editable table cell — softer signal than .inline-pop-anchor (no dashed
   ring) since whole tables of these would feel busy. Just a cursor hint
   plus a faint background tint on hover. The cell still stops row click
   propagation so tapping it edits in place instead of opening the row. */
.editable-cell {
  cursor: pointer;
  transition: background .12s ease;
}
.editable-cell:hover {
  background: var(--bg-2);
}

.inline-pop {
  position: absolute; z-index: 1200;
  min-width: 200px; max-width: 280px;
  background: var(--card); color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-field);
  box-shadow: var(--shadow-2);
  padding: .35rem;
  opacity: 0; transform: translateY(-4px) scale(.97);
  transition: opacity 160ms ease, transform 200ms cubic-bezier(.16,1,.3,1);
  transform-origin: top center;
}
.inline-pop.is-open { opacity: 1; transform: translateY(0) scale(1); }

.inline-pop-search {
  display: block; width: 100%;
  background: var(--bg-2); color: var(--text);
  border: 1px solid var(--border-2); border-radius: var(--radius-sm);
  padding: .45rem .7rem; font: inherit; font-size: .85rem;
  margin-bottom: .35rem; outline: none;
  transition: border-color .15s, box-shadow .15s;
}
.inline-pop-search:focus { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-soft); }

.inline-pop-list {
  list-style: none; margin: 0; padding: 0;
  max-height: 280px; overflow-y: auto;
}
.inline-pop-opt {
  display: flex; align-items: center; gap: .55rem;
  padding: .55rem .65rem;
  border-radius: var(--radius-sm);
  font-size: .9rem; cursor: pointer;
  transition: background .12s;
}
.inline-pop-opt:hover,
.inline-pop-opt.is-active { background: var(--bg-2); }
.inline-pop-opt.is-current::after {
  content: "✓"; margin-inline-start: auto;
  font-family: var(--font-numeric); font-weight: 600;
  color: var(--brand); font-size: .9rem;
}
.inline-pop-opt .ip-dot {
  width: 10px; height: 10px; border-radius: 50%;
  flex-shrink: 0;
}
.inline-pop-opt .ip-label { flex: 1; min-width: 0; }
.inline-pop-empty {
  padding: 1rem; text-align: center;
  color: var(--text-3); font-size: .85rem;
}

/* ── Onboarding checklist ──────────────────────────────────────────────
   Activation card on the admin dashboard for fresh tenants. Five steps
   that walk the new admin from empty workspace to "I'm using the
   product". Each step is a clickable row that takes the admin to the
   relevant page; auto-detection on the backend ticks them off as the
   admin acts. Dismisses on click of the X, or auto-hides at 100%.
   Progress ring on the left mirrors the same emerald hover semantics
   as the trial-card ring so the dashboard feels like one family. */
.onboarding-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-inline-start: 4px solid var(--brand);
  border-radius: var(--radius-lg);
  padding: 1.4rem 1.5rem;
  margin-bottom: 1.2rem;
  position: relative;
  overflow: hidden;
}
.onb-head {
  display: flex; align-items: center; gap: 1rem;
  margin-bottom: 1.2rem; position: relative; z-index: 1;
}
.onb-ring {
  width: 56px; height: 56px; flex-shrink: 0;
  position: relative;
}
.onb-ring svg { width: 56px; height: 56px; transform: rotate(-90deg); }
.onb-ring .ring-bg { stroke: var(--border-2); fill: none; stroke-width: 4; }
.onb-ring .ring-fg {
  stroke: var(--brand); fill: none; stroke-width: 4;
  stroke-linecap: round;
  transition: stroke-dasharray 600ms cubic-bezier(.16,1,.3,1);
}
.onb-ring .ring-pct {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-numeric); font-weight: 600; font-size: .82rem;
  color: var(--brand);
}
.onb-titles { flex: 1; min-width: 0; }
.onb-title {
  font-size: 1.05rem; font-weight: 600; letter-spacing: -0.01em;
  margin: 0 0 .15rem;
}
.onb-sub {
  font-size: .85rem; color: var(--text-2); margin: 0;
}
.onb-dismiss {
  appearance: none; border: 0; background: transparent;
  color: var(--text-3); cursor: pointer;
  width: 32px; height: 32px; border-radius: var(--radius-sm);
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  transition: background .15s, color .15s;
}
.onb-dismiss:hover { background: var(--bg-2); color: var(--text); }
.onb-dismiss svg { width: 16px; height: 16px; stroke-width: 1.75; }

.onb-steps {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: .15rem;
  position: relative; z-index: 1;
}
.onb-step {
  display: flex; align-items: center; gap: .8rem;
  padding: .7rem .85rem; border-radius: var(--radius-sm);
  text-decoration: none; color: var(--text);
  transition: background .15s, transform .12s;
  cursor: pointer;
}
.onb-step:hover {
  background: var(--bg-2);
  text-decoration: none;
  transform: translateX(2px);
}
[dir="rtl"] .onb-step:hover { transform: translateX(-2px); }
.onb-step .onb-tick {
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--bg-2); border: 2px solid var(--border-2);
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0; color: transparent;
  transition: background .2s, border-color .2s, color .2s;
}
.onb-step .onb-tick svg { width: 14px; height: 14px; stroke-width: 2.5; }
.onb-step.is-done .onb-tick {
  background: var(--brand); border-color: var(--brand);
  color: var(--brand-fg);
  animation: onb-pop 350ms cubic-bezier(.16,1.5,.3,1);
}
@keyframes onb-pop {
  0% { transform: scale(.6); }
  60% { transform: scale(1.18); }
  100% { transform: scale(1); }
}
.onb-step .onb-label { flex: 1; font-size: .92rem; font-weight: 600; }
.onb-step.is-done .onb-label { color: var(--text-3); text-decoration: line-through; text-decoration-thickness: 1.5px; }
.onb-step .onb-arrow {
  color: var(--text-3); flex-shrink: 0;
  opacity: 0; transform: translateX(-4px);
  transition: opacity .15s, transform .15s;
}
[dir="rtl"] .onb-step .onb-arrow { transform: translateX(4px); }
.onb-step:hover .onb-arrow { opacity: 1; transform: translateX(0); color: var(--brand); }
.onb-step.is-done .onb-arrow { display: none; }
.onb-step .onb-arrow svg { width: 16px; height: 16px; stroke-width: 2; }

@keyframes onb-celebrate {
  0%   { transform: scale(.95); opacity: 0; }
  60%  { transform: scale(1.04); opacity: 1; }
  100% { transform: scale(1);    opacity: 1; }
}
.onboarding-card.is-celebrating {
  animation: onb-celebrate 500ms cubic-bezier(.16,1,.3,1);
  border-inline-start-color: var(--brand);
}

/* ── Product tour ─────────────────────────────────────────────────────
   Highlighted-element walkthrough for first-time admins. Three pieces:
   (1) a dim overlay that blocks the rest of the UI, (2) a bright emerald
   ring drawn over the highlighted element, (3) a popover with the step
   copy + Next/Back/Skip controls. All three rendered by tour.js with
   no library dependency. */
.duwam-tour-overlay {
  position: fixed; inset: 0; z-index: 2400;
  background: rgba(0, 0, 0, 0); /* transparent when spotlight is shown */
  transition: background 200ms ease;
  pointer-events: auto;
}
.duwam-tour-overlay.is-dimmed { background: rgba(14, 18, 17, 0.62); }

.duwam-tour-spotlight {
  position: absolute; z-index: 2410;
  border-radius: var(--radius-field);
  pointer-events: none;
  /* Big shadow projected outward = darken everything except the ring.
     Inner shadow holds the brand colour against the highlighted element. */
  box-shadow:
    0 0 0 9999px rgba(14, 18, 17, 0.62),
    0 0 0 3px var(--brand),
    0 0 0 6px var(--brand-soft);
  animation: duwam-tour-pulse 2s ease-in-out infinite;
}
@keyframes duwam-tour-pulse {
  0%, 100% { box-shadow: 0 0 0 9999px rgba(14,18,17,.62), 0 0 0 3px var(--brand),   0 0 0 6px var(--brand-soft); }
  50%      { box-shadow: 0 0 0 9999px rgba(14,18,17,.62), 0 0 0 3px var(--brand),   0 0 0 12px var(--brand-soft); }
}

.duwam-tour-popover {
  position: absolute; z-index: 2420;
  width: min(360px, calc(100vw - 40px));
  background: var(--card); color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-2);
  padding: 0;
  font-family: 'Cairo', 'Inter', sans-serif;
  animation: duwam-tour-pop 240ms cubic-bezier(.16, 1, .3, 1);
}
@keyframes duwam-tour-pop {
  0%   { opacity: 0; transform: scale(.94) translateY(6px); }
  100% { opacity: 1; transform: scale(1)   translateY(0); }
}
.duwam-tour-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 1.1rem 1.3rem .6rem; gap: .8rem;
}
.duwam-tour-head h3 {
  margin: 0; font-size: 1.1rem; font-weight: 600;
  letter-spacing: -0.01em; color: var(--text);
}
.duwam-tour-close {
  appearance: none; border: 0; background: transparent;
  width: 28px; height: 28px; border-radius: var(--radius-sm);
  font-size: 22px; line-height: 1; color: var(--text-3);
  cursor: pointer; flex-shrink: 0;
  transition: background .15s, color .15s;
}
.duwam-tour-close:hover { background: var(--bg-2); color: var(--text); }
.duwam-tour-body {
  padding: 0 1.3rem 1.1rem;
  font-size: .95rem; line-height: 1.7; color: var(--text-2);
}
.duwam-tour-body kbd {
  font-family: var(--font-numeric); font-size: .8rem; font-weight: 600;
  background: var(--bg-2); color: var(--text);
  border: 1px solid var(--border-2); border-radius: 6px;
  padding: 1px 6px;
}
.duwam-tour-foot {
  display: flex; align-items: center; justify-content: space-between;
  padding: .85rem 1.3rem 1.1rem; gap: 1rem;
  border-top: 1px solid var(--border);
}
.duwam-tour-dots { display: inline-flex; gap: .35rem; }
.duwam-tour-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--border-2);
  transition: background .15s, transform .15s;
}
.duwam-tour-dot.is-active { background: var(--brand); transform: scale(1.25); }
.duwam-tour-dot.is-done   { background: var(--brand-soft); }

.duwam-tour-actions { display: inline-flex; gap: .5rem; }
.duwam-tour-back, .duwam-tour-next {
  appearance: none; border: 0; cursor: pointer; font: inherit;
  font-weight: 600; font-size: .88rem;
  padding: .55rem 1.1rem; border-radius: var(--radius-sm);
  transition: background .15s, transform .1s;
}
.duwam-tour-back {
  background: transparent; color: var(--text-2); border: 1px solid var(--border-2);
}
.duwam-tour-back:hover { background: var(--bg-2); color: var(--text); }
.duwam-tour-next {
  background: var(--brand); color: var(--brand-fg);
}
.duwam-tour-next:hover { background: var(--brand-hover); }
.duwam-tour-next:active { transform: scale(.97); }

@media (max-width: 520px) {
  .duwam-tour-popover { width: calc(100vw - 24px); }
  .duwam-tour-head { padding: .9rem 1rem .5rem; }
  .duwam-tour-body { padding: 0 1rem .9rem; font-size: .9rem; }
  .duwam-tour-foot { padding: .7rem 1rem .9rem; }
}
@media (prefers-reduced-motion: reduce) {
  .duwam-tour-spotlight { animation: none; }
  .duwam-tour-popover { animation: none; }
}

/* ── Keyboard shortcuts ───────────────────────────────────────────────
   Two surfaces: a transient leader-hint that surfaces while a sequence
   leader (g) is pressed, and the full cheatsheet modal triggered with
   "?". Both follow the brand's emerald + Cairo aesthetic and respect RTL. */
.keymap-leader-hint {
  position: fixed; bottom: 18px; inset-inline-start: 18px; z-index: 1500;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-field); padding: .9rem 1.1rem;
  box-shadow: var(--shadow-2);
  font-size: .85rem; color: var(--text);
  opacity: 0; transform: translateY(6px);
  transition: opacity 140ms ease, transform 200ms cubic-bezier(.16,1,.3,1);
  pointer-events: none;
  max-width: 320px;
}
.keymap-leader-hint.is-shown { opacity: 1; transform: translateY(0); }
.keymap-leader-hint .kh-head {
  font-family: var(--font-numeric); font-weight: 600; font-size: .72rem;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-3); margin-bottom: .55rem;
}
.keymap-leader-hint .kh-line {
  display: flex; gap: .55rem; align-items: center;
  padding: .15rem 0;
  font-size: .82rem;
}
.keymap-leader-hint kbd {
  display: inline-block;
  font-family: var(--font-numeric); font-size: .75rem; font-weight: 600;
  background: var(--bg-2); color: var(--text);
  border: 1px solid var(--border-2); border-radius: 5px;
  padding: 1px 6px; min-width: 18px; text-align: center;
  line-height: 1.4;
}

/* Cheatsheet modal */
.kc-modal {
  background: var(--card); color: var(--text);
  width: min(640px, 92vw); max-height: 80vh;
  border-radius: var(--radius-card); overflow: hidden;
  display: flex; flex-direction: column;
  box-shadow: var(--shadow-2);
}
.kc-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 1.1rem 1.4rem; border-bottom: 1px solid var(--border);
}
.kc-head h2 { margin: 0; font-size: 1.1rem; font-weight: 600; letter-spacing: -0.01em; }
.kc-close {
  appearance: none; border: 0; background: transparent;
  width: 32px; height: 32px; border-radius: var(--radius-sm);
  color: var(--text-3); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .15s, color .15s;
}
.kc-close:hover { background: var(--bg-2); color: var(--text); }
.kc-close svg { width: 16px; height: 16px; stroke-width: 1.75; }
.kc-body {
  padding: 1.2rem 1.4rem 1.6rem;
  overflow-y: auto;
}
.kc-group { margin-bottom: 1.4rem; }
.kc-group:last-child { margin-bottom: 0; }
.kc-group h3 {
  font-family: var(--font-numeric); font-size: .72rem; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-3); margin: 0 0 .55rem;
}
.kc-group ul { list-style: none; margin: 0; padding: 0; }
.kc-group li {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; padding: .55rem .15rem;
  border-bottom: 1px solid var(--border);
}
.kc-group li:last-child { border-bottom: 0; }
.kc-label { font-size: .92rem; color: var(--text); font-weight: 600; }
.kc-keys { display: inline-flex; gap: .4rem; align-items: center; flex-shrink: 0; }
.kc-kbd {
  display: inline-block;
  font-family: var(--font-numeric); font-size: .8rem; font-weight: 600;
  background: var(--bg-2); color: var(--text);
  border: 1px solid var(--border-2); border-radius: 6px;
  padding: 2px 8px; min-width: 22px; text-align: center;
  line-height: 1.5;
}
.kc-then { font-size: .72rem; color: var(--text-3); font-weight: 600; }

/* ── Empty states with personality ─────────────────────────────────────
   Replaces the bland "No data" text with a friendly illustration + a
   clear next-step CTA. Tones:
     • empty-state          — neutral default
     • empty-state.is-success — first-time-positive (no overdue tasks!)
     • empty-state.is-error  — error/disconnected (failed to load)
   Use UI.emptyState() to render with sensible defaults; this CSS is the
   underlying contract. */
.empty-state {
  display: flex; flex-direction: column; align-items: center;
  justify-content: center; text-align: center;
  padding: 3rem 1.5rem 2.6rem;
  max-width: 440px; margin: 1.5rem auto;
  color: var(--text);
}
.empty-state .es-illu {
  width: 128px; height: 128px;
  margin-bottom: 1.4rem;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--brand);
  background: var(--brand-soft);
  border-radius: var(--radius-lg);
  position: relative;
  overflow: hidden;
}
.empty-state .es-illu svg {
  width: 76px; height: 76px;
  position: relative; z-index: 1;
  stroke-width: 1.6;
}
.empty-state.is-success .es-illu { background: var(--success-bg); color: var(--success); }
.empty-state.is-error .es-illu  { background: var(--danger-bg); color: var(--danger); }
/* Royal Pixel signature strip at the foot of every empty state — one of
   the 4 sanctioned mountain spots (spec §5). */
.empty-state .es-mountains { margin-top: 1.6rem; opacity: .9; }
.px-mountains { display: block; margin-inline: auto; max-width: 100%; }
.empty-state .es-title {
  font-size: 1.18rem; font-weight: 600;
  letter-spacing: -0.015em; margin: 0 0 .55rem;
  color: var(--text);
}
.empty-state .es-body {
  font-size: .95rem; line-height: 1.7;
  color: var(--text-2); margin: 0 0 1.4rem;
  max-width: 380px;
}
.empty-state .es-actions {
  display: flex; gap: .55rem; flex-wrap: wrap; justify-content: center;
}
.empty-state .es-actions .btn {
  padding: .6rem 1.2rem; font-weight: 500;
}
/* ── Threshold-surface accents (2026-07-15) ────────────────────────────
   Two "signature genes" borrowed from the Duwam marketing carousel — the
   emerald section pill and the highlighted keyword — deliberately SCOPED
   to live only inside .empty-state (threshold moments: first-run, "add
   your first…", onboarding). Every selector here descends from
   .empty-state on purpose: the carousel's hard-shadow / heavy-outline
   look stays OUT of work surfaces (boards, tables) where calm wins. This
   is the "graduated intensity" decision — same brand hue, lighter dose as
   data density rises. Both are opt-in (eyebrow arg / <span class="hl">). */
.empty-state .es-eyebrow {
  display: inline-flex; align-items: center; gap: .5rem;
  margin-bottom: 1rem;
}
.empty-state .es-eyebrow .eb-chev {
  color: var(--brand); opacity: .5;
  font-weight: 600; font-size: .95rem; line-height: 1;
  /* decorative outward flares, mirror-symmetric under RTL flex reversal */
}
.empty-state .es-eyebrow .eb-pill {
  display: inline-flex; align-items: center; gap: .4rem;
  background: var(--brand); color: var(--accent-fg);
  font-size: .72rem; font-weight: 600; letter-spacing: .02em;
  padding: .32rem .8rem; border-radius: 999px;
  /* filled-emerald recipe mirrors .chip.primary — deep-on-mint in dark,
     white-on-emerald (AA) in light; flips via --brand / --accent-fg */
}
/* Highlighted keyword inside a title — opt-in by wrapping one word in
   <span class="hl"> in the title string. Emphasis via hue only; no weight
   or size change, so the heading stays calm. */
.empty-state .es-title .hl { color: var(--brand); }
@media (max-width: 520px) {
  .empty-state { padding: 2rem 1rem 1.6rem; }
  .empty-state .es-illu { width: 96px; height: 96px; border-radius: var(--radius-card); }
  .empty-state .es-illu svg { width: 56px; height: 56px; }
  .empty-state .es-title { font-size: 1.05rem; }
}

/* ── Row hover actions ─────────────────────────────────────────────────
   Jira/Linear-style quick actions that reveal on row hover. The row stays
   clean by default; pointing at it surfaces 2-4 icon buttons (edit, copy,
   archive, delete). Use class .row-actions on a flex container; mark
   destructive ones with .is-danger so the hover tone shifts red. Focus-
   within keeps actions visible for keyboard navigation. To force-show on
   touch devices or compact lists, drop .always-show-actions on a parent. */
.row-actions {
  display: inline-flex; gap: .15rem; align-items: center;
  opacity: 0; transform: translateX(-4px);
  transition: opacity 150ms ease, transform 150ms ease;
  pointer-events: none;
}
[dir="rtl"] .row-actions { transform: translateX(4px); }
tr:hover .row-actions,
tr:focus-within .row-actions,
.row:hover .row-actions,
.row:focus-within .row-actions,
.always-show-actions .row-actions {
  opacity: 1; transform: translateX(0); pointer-events: auto;
}
.row-actions .ra-btn {
  appearance: none; border: 0; background: transparent;
  width: 30px; height: 30px; border-radius: var(--radius-sm);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--icon-ink); cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.row-actions .ra-btn:hover { background: var(--bg-2); color: var(--text); }
.row-actions .ra-btn:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 1px;
}
.row-actions .ra-btn.is-danger:hover { background: rgba(220,38,38,.12); color: var(--danger); }
.row-actions .ra-btn svg { width: 16px; height: 16px; stroke-width: 1.75; }
/* Touch device fallback — pointing devices never trigger :hover, so on
   coarse pointers we keep the actions always visible at low contrast.
   Users can still tap any action, no muscle memory required. */
@media (hover: none) {
  .row-actions { opacity: .6; transform: none; pointer-events: auto; }
  .row-actions .ra-btn { color: var(--icon-ink); }
}

.grid { display: grid; gap: 1rem; }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
/* Phones: stat strips go 2-up like the dashboard KPI grid instead of a
   long column of full-width single-number cards. (Lives here, after the
   base rule — the shared 720px block earlier in the file would lose the
   same-specificity cascade.) */
@media (max-width: 720px) {
  .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .7rem; }
  .grid-3 .stat .value { font-size: 1.45rem; }
  /* Odd counts: stretch the orphan card instead of leaving a hole.
     (Covers bare .stat cards and [data-kpi]-wrapped ones alike.) */
  .grid-3 > :last-child:nth-child(odd) { grid-column: 1 / -1; }
}

.stat {
  padding: 1rem 1.1rem; border-radius: var(--radius); background: var(--card); border: 1px solid var(--border);
}
.stat .label { font-size: .75rem; color: var(--text-3); text-transform: uppercase; letter-spacing: .05em; }
.stat .value { font-size: 1.8rem; font-weight: 600; margin-top: .25rem; }

/* Compact KPI row atop the Attendance tab — keeps the table above the fold. */
.att-kpis .stat { padding: .6rem .8rem; }
.att-kpis .stat .label { font-size: .7rem; }
.att-kpis .stat .value { font-size: 1.25rem; margin-top: .1rem; }
/* Hover/focus for these cards lives with the rest of the drill-down styling
   (.att-kpi-click, far below) and tints with --brand. An earlier --danger pair
   sat here and, being MORE specific (.att-kpis [data-kpi]:hover .stat, 0-4-0)
   than the brand rule (.att-kpi-click:hover .stat, 0-3-0), it won regardless of
   source order: every attendance KPI outlined RED on hover — «حاضر» included —
   and keyboard focus flashed red with no brand counterpart at all. Two rules
   for one state on one element is the bug; there is now one. */
.att-kpis [data-kpi]:focus { outline: none; }

.table { width: 100%; border-collapse: collapse; font-size: .92rem; }
.table th, .table td { text-align: start; padding: .7rem .75rem; border-bottom: 1px solid var(--border); }
.table th { color: var(--text-3); font-weight: 600; text-transform: uppercase; font-size: .74rem; letter-spacing: .05em; }
.table tr:hover td { background: var(--card-2); }
/* Wrap any .table wider than its card in this: the card's overflow:hidden
   otherwise clips columns unreachably (body blocks page-level x-scroll). */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ── Responsive table → stacked cards (phones) ─────────────────────────
   A 4–7 column table on a 390px screen forces horizontal swiping that
   hides columns. Tables tagged `.table.as-cards` collapse each row into a
   card below 640px: the cell tagged `.td-primary` becomes the card
   header, the rest become "label: value" lines pulling the label from
   each cell's `data-label`, and `.td-actions` sits as a footer row.
   Cells left empty (—) or without a label are hidden to avoid noise. */
@media (max-width: 640px) {
  .table.as-cards,
  .table.as-cards > tbody { display: block; width: 100%; }
  .table.as-cards > thead { display: none; }
  .table.as-cards > tbody > tr {
    display: block;
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    background: var(--card);
    padding: .35rem .9rem .55rem;
    margin-bottom: .6rem;
  }
  .table.as-cards > tbody > tr:hover td { background: transparent; }
  .table.as-cards > tbody > tr > td {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: .5rem 0;
    border: 0;
    border-top: 1px solid var(--border-2);
    text-align: end;
    min-height: 0;
  }
  .table.as-cards > tbody > tr > td::before {
    content: attr(data-label);
    font-weight: 600;
    color: var(--text-3);
    font-size: .78rem;
    text-align: start;
    flex-shrink: 0;
  }
  /* Primary cell (name/title): full-width header row, no label. */
  .table.as-cards > tbody > tr > td.td-primary {
    display: block;
    border-top: 0;
    padding: .5rem 0 .3rem;
  }
  .table.as-cards > tbody > tr > td.td-primary::before { content: none; }
  /* Action cell: buttons flush to the end, no label. */
  .table.as-cards > tbody > tr > td.td-actions {
    justify-content: flex-end;
    padding-top: .6rem;
  }
  .table.as-cards > tbody > tr > td.td-actions::before { content: none; }
  /* Hide value-less cells (empty or just an em-dash placeholder). */
  .table.as-cards > tbody > tr > td.td-empty { display: none; }
  /* Spanning cells (designed empty state, its load-error twin) and skeleton
     rows hold a centered block stack, not a label:value pair. Left in the
     generic cascade above they become shrink-to-fit flex items pushed to one
     edge — and the error state's message and retry button end up side by
     side instead of stacked. Same opt-out shape as .td-primary. */
  .table.as-cards > tbody > tr > td.table-empty,
  .table.as-cards > tbody > tr.crm-ct-skel-row > td {
    display: block;
    text-align: initial;
  }
  .table.as-cards > tbody > tr > td.table-empty::before,
  .table.as-cards > tbody > tr.crm-ct-skel-row > td::before { content: none; }
  /* Totals footer (e.g. payroll) — flatten to a simple summary strip so
     the table-footer-group doesn't fight the block table layout. */
  .table.as-cards > tfoot { display: block; }
  .table.as-cards > tfoot > tr {
    display: flex; flex-wrap: wrap; gap: .3rem 1rem;
    justify-content: space-between; align-items: baseline;
    padding: .7rem .9rem; border-top: 1px solid var(--border);
    font-size: .9rem;
  }
  .table.as-cards > tfoot > tr > td { border: 0; padding: 0; }
  .table.as-cards > tfoot > tr > td:empty { display: none; }
  /* Card mode is full-width — no horizontal scroll needed. */
  .table-scroll:has(> .table.as-cards) { overflow-x: visible; }
}

/* Attendance identity cell (dot + name + id) — one unit on desktop and
   as the card header on mobile. */
.att-id-row { display: inline-flex; align-items: center; gap: .5rem; min-width: 0; }
.att-id-row .att-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.55); display: none; align-items: center; justify-content: center; z-index: 100; padding: 1rem; overflow-y: auto; }
.modal-backdrop.open { display: flex; }
.modal { background: var(--bg-2); border: 1px solid var(--border-2); border-radius: var(--radius-lg); padding: 1.5rem; max-width: 480px; width: 90%; box-shadow: var(--shadow-2); max-height: calc(100vh - 2rem); overflow-y: auto; }
/* Phones: modals become bottom-sheets — anchored to the bottom edge,
   full-width, rounded only on top, sliding up. Reads as native. */
@media (max-width: 640px) {
  .modal-backdrop { align-items: flex-end; padding: 0; }
  .modal {
    width: 100%; max-width: none;
    border-radius: 20px 20px 0 0;
    border-bottom: 0;
    padding: 1.25rem 1.1rem calc(1.25rem + env(safe-area-inset-bottom));
    max-height: 92vh;
    animation: sheet-up .24s cubic-bezier(.22, 1, .36, 1);
  }
  /* Grab-handle affordance at the top of the sheet. */
  .modal::before {
    content: ""; display: block; width: 38px; height: 4px;
    border-radius: 999px; background: var(--border-2);
    margin: -.35rem auto .9rem;
  }
}
@keyframes sheet-up { from { transform: translateY(100%); } to { transform: translateY(0); } }

/* ── Mobile native touches ─────────────────────────────────────────────
   Comfortable tap targets + a sticky section sub-nav so navigation stays
   in reach while scrolling long card lists. */
@media (max-width: 640px) {
  .staff-icon-btn { width: 40px; height: 40px; }
  .table.as-cards td.td-actions { gap: .4rem; }
  .table.as-cards td.td-actions .btn-sm,
  .table.as-cards td.td-actions .staff-icon-btn { min-height: 40px; }
  .table.as-cards td.td-actions .btn-sm { padding-block: .5rem; }

  /* Sticky segmented sub-nav — the pill floats and sticks to the top of
     the scroll area so navigation stays in reach on long card lists.
     A full-bleed backdrop strip sits behind it (via a wide box-shadow in
     the page colour) so scrolled cards don't show around the pill's
     rounded corners. */
  .ws-tabs:not(.tasks-controls .ws-tabs) {
    position: sticky; top: .5rem; z-index: 6;
    box-shadow: 0 0 0 100vmax var(--bg);
    clip-path: inset(-.6rem -100vmax);
  }
}

/* Permissions modal (admin / employees) */
.btn-xs { padding: .2rem .55rem; font-size: .72rem; border-radius: var(--radius-sm); font-weight: 500; }
.perm-modal {
  width: min(900px, 96vw);
  height: min(92vh, 940px); max-height: min(92vh, 940px);
  display: flex; flex-direction: column;
  background: var(--bg-2); border: 1px solid var(--border-2);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-2);
  overflow: hidden;
}
.perm-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 1rem 1.2rem; border-bottom: 1px solid var(--border); gap: 1rem;
  flex-shrink: 0;
}
.perm-head-user { display: flex; align-items: center; gap: .8rem; min-width: 0; flex: 1; }
.perm-head-user-text { min-width: 0; flex: 1; }
.perm-head-title { font-weight: 600; font-size: 1rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.perm-head-sub { display: flex; align-items: center; gap: .4rem; margin-top: .25rem; font-size: .78rem; color: var(--text-3); }
.perm-close {
  appearance: none; background: transparent; border: 0; cursor: pointer;
  width: 34px; height: 34px; border-radius: var(--radius-sm);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-2); transition: background .15s ease, color .15s ease;
  flex-shrink: 0;
}
.perm-close:hover { background: var(--bg-3); color: var(--text); }
.perm-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 1rem 1.2rem; display: flex; flex-direction: column; gap: .9rem; scrollbar-width: thin; scrollbar-color: var(--border-2) transparent; }
.perm-body::-webkit-scrollbar { width: 8px; }
.perm-body::-webkit-scrollbar-track { background: transparent; }
.perm-body::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 4px; }
.perm-body::-webkit-scrollbar-thumb:hover { background: var(--text-3); }
.perm-body-split { flex-direction: row; padding: 0; gap: 0; overflow: hidden; }
.perm-sidebar {
  flex: 0 0 240px; min-width: 240px;
  border-inline-end: 1px solid var(--border);
  background: var(--card-2);
  padding: .6rem .5rem;
  overflow-y: auto; scrollbar-width: thin; scrollbar-color: var(--border-2) transparent;
  display: flex; flex-direction: column; gap: 2px;
}
.perm-sidebar::-webkit-scrollbar { width: 6px; }
.perm-sidebar::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 3px; }
.perm-sidebar-item {
  appearance: none; background: transparent; border: 0; border-radius: var(--radius-sm);
  padding: .65rem .7rem; cursor: pointer; text-align: start;
  display: flex; align-items: center; gap: .65rem;
  color: var(--text-2); font: inherit; font-weight: 600;
  transition: background .15s ease, color .15s ease;
  width: 100%;
}
.perm-sidebar-item:hover { background: var(--bg-3); color: var(--text); }
.perm-sidebar-item.active { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--text); }
.perm-sidebar-item.active .perm-sidebar-icon { color: var(--accent); background: color-mix(in srgb, var(--accent) 18%, var(--bg-2)); }
.perm-sidebar-icon {
  width: 30px; height: 30px; border-radius: var(--radius-sm);
  background: var(--bg-3); color: var(--text-2);
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
  transition: background .15s ease, color .15s ease;
}
.perm-sidebar-label { flex: 1; font-size: .9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.perm-sidebar-count {
  font-size: .72rem; font-weight: 600; color: var(--text-3);
  background: var(--bg-2); border: 1px solid var(--border);
  padding: .12rem .45rem; border-radius: 999px;
  font-variant-numeric: tabular-nums; flex-shrink: 0;
}
.perm-sidebar-item.active .perm-sidebar-count { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }
.perm-content {
  flex: 1 1 auto; min-width: 0; overflow-y: auto;
  padding: 1rem 1.2rem; display: flex; flex-direction: column; gap: .65rem;
  scrollbar-width: thin; scrollbar-color: var(--border-2) transparent;
}
.perm-content::-webkit-scrollbar { width: 8px; }
.perm-content::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 4px; }
.perm-content-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 1rem; padding-bottom: .8rem; margin-bottom: .2rem;
  border-bottom: 1px solid var(--border);
}
.perm-content-head h4 { margin: 0 0 .2rem; font-size: 1.02rem; font-weight: 600; }
.perm-content-head p { margin: 0; font-size: .82rem; color: var(--text-3); line-height: 1.45; }
.perm-content-actions { display: flex; gap: .3rem; flex-shrink: 0; }
@media (max-width: 720px) {
  .perm-body-split { flex-direction: column; }
  .perm-sidebar { flex: 0 0 auto; min-width: 0; width: 100%; border-inline-end: 0; border-bottom: 1px solid var(--border); flex-direction: row; overflow-x: auto; overflow-y: hidden; padding: .5rem .5rem; }
  .perm-sidebar-item { width: auto; flex-shrink: 0; }
  .perm-sidebar-label { max-width: 140px; }
}
.perm-group {
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden;
}
.perm-group-head {
  display: flex; align-items: center; gap: .6rem;
  padding: .7rem .9rem; border-bottom: 1px solid var(--border);
  background: var(--card-2);
}
.perm-group-icon {
  width: 32px; height: 32px; border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--accent-2) 10%, var(--bg-2));
  color: var(--accent-2);
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.perm-group-head h4 { margin: 0; font-size: .9rem; font-weight: 600; }
.perm-group-count { font-size: .75rem; color: var(--text-3); font-variant-numeric: tabular-nums; flex: 1; margin-inline-start: .2rem; }
.perm-group-actions { display: flex; gap: .3rem; flex-shrink: 0; }
.perm-group-body { display: flex; flex-direction: column; padding: .35rem .45rem .5rem; }
.perm-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; padding: .7rem .8rem; border-radius: var(--radius-sm); cursor: pointer;
  transition: background .15s ease;
  width: 100%; box-sizing: border-box;
}
.perm-row + .perm-row { margin-top: 2px; }
.perm-row:hover { background: var(--bg-3); }
.perm-row:focus-visible { outline: 2px solid var(--ring); outline-offset: 1px; }
.perm-row-text { min-width: 0; flex: 1; }
.perm-row-title { font-weight: 600; font-size: .9rem; }
.perm-row-desc { font-size: .78rem; color: var(--text-3); margin-top: 2px; line-height: 1.35; }

/* Permissions toggle (button-based, RTL-safe, no input-absolute trick) */
.perm-switch {
  appearance: none; -webkit-appearance: none;
  border: 1px solid var(--border); background: var(--bg-3);
  width: 44px; height: 24px; border-radius: 999px; padding: 0;
  position: relative; cursor: pointer; flex-shrink: 0;
  transition: background .18s ease, border-color .18s ease;
  display: inline-block; vertical-align: middle;
}
.perm-switch-thumb {
  position: absolute; top: 1px; inset-inline-start: 1px;
  width: 20px; height: 20px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.3);
  transition: inset-inline-start .18s ease;
  pointer-events: none;
}
.perm-switch.on { background: var(--accent); border-color: var(--accent); }
.perm-switch.on .perm-switch-thumb { inset-inline-start: 22px; background: var(--accent-fg); }
.perm-switch:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }

.perm-foot {
  display: flex; align-items: center; justify-content: space-between;
  padding: .9rem 1.2rem; border-top: 1px solid var(--border);
  background: var(--bg-2); flex-shrink: 0;
}
.perm-foot-summary { font-size: .85rem; color: var(--text-2); font-weight: 600; font-variant-numeric: tabular-nums; }
.modal h3 { margin: 0 0 .5rem; }

/* Workspace tabs (segmented horizontal pill) */
.ws-tabs { display: flex; gap: .25rem; padding: .35rem; background: var(--card-2); border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 1.1rem; overflow-x: auto; scrollbar-width: none; }
.ws-tabs::-webkit-scrollbar { display: none; }
.ws-tab { appearance: none; border: 0; background: transparent; padding: .6rem 1rem; border-radius: var(--radius-sm); font-weight: 600; font-size: .9rem; color: var(--text-2); cursor: pointer; transition: background .15s ease, color .15s ease; white-space: nowrap; display: inline-flex; align-items: center; gap: .5rem; flex-shrink: 0; font-family: inherit; }
.ws-tab .lucide-ic, .ws-tab svg { width: 16px; height: 16px; }
.ws-tab:hover { color: var(--text); background: var(--bg-3); }
.ws-tab.active { background: var(--bg); color: var(--text); box-shadow: inset 0 0 0 1px var(--border); }
html.light .ws-tab.active { box-shadow: inset 0 0 0 1px var(--border); }
/* Kill the UA blue focus ring on tap; keep a brand ring for keyboard nav. */
.ws-tab:focus { outline: none; }
.ws-tab:focus-visible { outline: 2px solid var(--ring); outline-offset: 1px; }
/* Phones: the sub-tab row fits any tab count without overflow by showing
   the ACTIVE tab as icon+label and the rest as icons only (a common
   compact tab pattern). The tasks toolbar keeps its own icon-only
   collapse (scoped to .tasks-controls). */
@media (max-width: 640px) {
  .ws-tabs:not(.tasks-controls .ws-tabs) { gap: .15rem; justify-content: space-between; }
  .ws-tabs:not(.tasks-controls .ws-tabs) > .ws-tab {
    justify-content: center; padding-inline: .55rem; font-size: .84rem; gap: .35rem;
  }
  /* Hide labels on inactive tabs — icon carries them; active shows text. */
  .ws-tabs:not(.tasks-controls .ws-tabs) > .ws-tab:not(.active) > span:last-child {
    display: none;
  }
  .ws-tabs:not(.tasks-controls .ws-tabs) > .ws-tab.active {
    flex: 0 1 auto; min-width: 0;
  }
  .ws-tabs:not(.tasks-controls .ws-tabs) > .ws-tab.active > span:last-child {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .ws-tabs:not(.tasks-controls .ws-tabs) > .ws-tab .lucide-ic,
  .ws-tabs:not(.tasks-controls .ws-tabs) > .ws-tab svg { width: 16px; height: 16px; }
}

/* Tasks toolbar — ONE control row (2026-07-04): view tabs · divider ·
 * scope buttons · spacer · project · search · filters · [group] · add.
 * The old 4-row stack (tabs row + filter bar + 2 chips rows) cost
 * ~150px of chrome before any task showed; this is one ~52px row + an
 * optional chips row. Wraps gracefully, folds labels on narrow widths. */
.tasks-controls { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; width: 100%; }
.tasks-controls > .ws-tabs { margin-bottom: 0; }
.tasks-controls-spacer { flex: 1 1 auto; }
.tasks-controls-divider { width: 1px; align-self: stretch; margin: .2rem .15rem; background: var(--border); flex-shrink: 0; }

/* One control height across the toolbar (owner ask 2026-07-10): the
 * project select inherited .input's generous form padding and towered
 * over the search field. 36px Asana-tier density — same decision as the
 * settings-fields pass — for every field and button in the row.
 * 2026-07-11: UI.enhanceSelect swaps the native select for a
 * .uisel-trigger BUTTON, which escaped the select.input selector and
 * towered over the row again — pin the trigger to the same tier. */
.tasks-controls { --control-h: 36px; }
.tasks-controls select.input {
  width: auto; flex: 0 1 200px; min-width: 140px;
  height: var(--control-h); padding-block: 0; font-size: .88rem;
}
.tasks-controls .uisel-trigger.input { height: var(--control-h); padding-block: 0; font-size: .88rem; }
.tasks-controls .task-filter-search > input { height: var(--control-h); padding-block: 0; }
.tasks-controls .task-filter-btn { height: var(--control-h); padding-block: 0; }
.tasks-controls .btn { height: var(--control-h); padding-block: 0; }

/* Scope switch — demoted to bare ghost buttons (no card container) so it
 * reads as a lightweight refinement of the view, not a second segmented
 * control competing with the view tabs. Brand-tinted active state keeps
 * it legible as the *scope* signal. */
.scope-switch { display: inline-flex; gap: .1rem; flex-shrink: 0; }
.scope-btn { appearance: none; border: 0; background: transparent; padding: .5rem .7rem; border-radius: var(--radius-sm); font-weight: 600; font-size: .85rem; color: var(--text-3); cursor: pointer; transition: background .15s ease, color .15s ease; white-space: nowrap; display: inline-flex; align-items: center; gap: .4rem; font-family: inherit; }
.scope-btn .lucide-ic, .scope-btn svg { width: 15px; height: 15px; }
.scope-btn:hover { color: var(--text); background: var(--bg-3); }
.scope-btn.active { background: color-mix(in srgb, var(--brand) 12%, transparent); color: var(--brand); }
html.light .scope-btn.active { background: color-mix(in srgb, var(--brand) 10%, transparent); }

/* Merged chips row (2026-07-04) — quick-filter buttons + active-filter
 * chips + a right-docked results counter and clear-all. Renders only
 * when there's something to show, so an untouched board is a single
 * control row. */
.tasks-chips { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; width: 100%; margin-top: .55rem; }
.tasks-chips .quick-filter-group,
.tasks-chips .active-filter-group { display: inline-flex; align-items: center; gap: .35rem; flex-wrap: wrap; }
.tasks-chips-spacer { flex: 1 1 auto; }
.task-chip-count { font-size: .78rem; color: var(--text-3); white-space: nowrap; }
.task-chip-clear { appearance: none; border: 1px solid var(--border); background: transparent; color: var(--text-2); font-family: inherit; font-size: .8rem; font-weight: 600; border-radius: var(--radius-sm); padding: .3rem .7rem; cursor: pointer; display: inline-flex; align-items: center; gap: .35rem; transition: color .12s, border-color .12s, background .12s; }
.task-chip-clear:hover { color: var(--text); border-color: var(--border-2); background: var(--bg-3); }
.task-chip-clear .lucide-ic, .task-chip-clear svg { width: 14px; height: 14px; }

/* Stages link relocated into the filter-popover footer — a low-emphasis
 * text link, no longer a gear icon sitting beside the primary Add CTA. */
.tf-stages-link { display: inline-flex; align-items: center; gap: .4rem; font-size: .8rem; font-weight: 600; color: var(--text-3); text-decoration: none; transition: color .12s; }
.tf-stages-link:hover { color: var(--brand); }
.tf-stages-link .lucide-ic, .tf-stages-link svg { width: 15px; height: 15px; }

/* Narrow widths — fold the view-tab + scope + add-button text to icons
 * so the single row survives to ~640px before wrapping. */
@media (max-width: 820px) {
  .tasks-controls .ws-tab-text,
  .tasks-controls .scope-btn-text { display: none; }
}
@media (max-width: 560px) {
  .tasks-controls .tf-add-text,
  .tasks-controls .tfb-text { display: none; }
}

.kbd { font-family: ui-monospace, SF Mono, Menlo, monospace; font-size: .85rem; padding: 2px 6px; background: var(--bg-3); border: 1px solid var(--border); border-radius: 6px; }

.toast { position: fixed; bottom: 24px; inset-inline-end: 24px; background: var(--bg-2); border: 1px solid var(--border-2); padding: .9rem 1.1rem; border-radius: var(--radius-field); box-shadow: var(--shadow-2); z-index: 200; opacity: 0; transform: translateY(8px); transition: opacity .2s, transform .2s; }
.toast.show { opacity: 1; transform: translateY(0); }
.toast.error { border-inline-start: 4px solid var(--danger); }
.toast.success { border-inline-start: 4px solid var(--success); }
/* Toast with an action (Undo) — the message and button share one row. */
.toast:has(.toast-action) { display: inline-flex; align-items: center; gap: .9rem; }
.toast-action {
  appearance: none; border: 0; background: transparent;
  color: var(--brand); font-family: inherit; font-weight: 600; font-size: .85rem;
  cursor: pointer; padding: .2rem .5rem; border-radius: var(--radius-sm);
  white-space: nowrap; flex-shrink: 0;
  transition: background .12s ease;
}
.toast-action:hover { background: color-mix(in srgb, var(--brand) 12%, transparent); }

.top-toast-host { position: fixed; top: 14px; inset-inline: 0; display: flex; flex-direction: column; align-items: center; gap: 8px; z-index: 300; pointer-events: none; }
.top-toast { background: var(--bg-2); border: 1px solid var(--border-2); box-shadow: var(--shadow-2); padding: .6rem 1rem; border-radius: 999px; display: inline-flex; align-items: center; gap: .6rem; max-width: 90vw; font-weight: 600; opacity: 0; transform: translateY(-12px); transition: opacity .25s, transform .25s; pointer-events: auto; }
.top-toast.show { opacity: 1; transform: translateY(0); }
.top-toast.success { border-inline-start: 4px solid var(--success); }
.top-toast.info { border-inline-start: 4px solid var(--info, #4ea1ff); }
.top-toast-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--success); box-shadow: 0 0 0 3px rgba(0,200,120,.18); animation: top-toast-pulse 1.4s ease-out infinite; }
.top-toast.info .top-toast-dot { background: var(--info, #4ea1ff); box-shadow: 0 0 0 3px rgba(78,161,255,.18); }
@keyframes top-toast-pulse { 0% { box-shadow: 0 0 0 0 rgba(0,200,120,.35); } 100% { box-shadow: 0 0 0 10px rgba(0,200,120,0); } }

.status-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; background: var(--bg-3); margin-inline-end: .4rem; vertical-align: middle; }
.status-dot.working { background: var(--success); box-shadow: 0 0 0 0 rgba(0,200,120,.55); animation: top-toast-pulse 1.6s ease-out infinite; }
.status-dot.paused { background: var(--warn); }
.status-dot.ended { background: var(--text-subtle, #888); }
.status-dot.completed { background: var(--success); }
.status-dot.overtime { background: var(--info); box-shadow: 0 0 0 0 rgba(78,161,255,.55); animation: top-toast-pulse 1.6s ease-out infinite; }
.status-dot.due { background: var(--warn); box-shadow: 0 0 0 0 rgba(245,158,11,.55); animation: top-toast-pulse 1.4s ease-out infinite; }

/* Shift pill — compact ribbon above the attendance card on the employee
   shift page. Carries a status-dot on the left and a short dynamic label on
   the right. Variants tint the background so the viewer can read state at
   a glance without reading text. */
.shift-pill {
  display: inline-flex; align-items: center; gap: .55rem;
  padding: .5rem .85rem; border-radius: 999px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  font-size: .88rem; font-weight: 600; color: var(--text);
  max-width: 100%;
}
.shift-pill .shift-pill-sub { color: var(--text-2); font-weight: 500; margin-inline-start: .3rem; }
.shift-pill.due {
  border-color: color-mix(in srgb, var(--warn) 55%, var(--border));
  background: color-mix(in srgb, var(--warn) 10%, var(--bg-2));
}
.shift-pill.working {
  border-color: color-mix(in srgb, var(--success) 45%, var(--border));
  background: color-mix(in srgb, var(--success) 10%, var(--bg-2));
}
.shift-pill.completed, .shift-pill.ended {
  border-color: color-mix(in srgb, var(--info) 35%, var(--border));
  background: color-mix(in srgb, var(--info) 8%, var(--bg-2));
}
.shift-pill.overtime {
  border-color: color-mix(in srgb, var(--info) 55%, var(--border));
  background: color-mix(in srgb, var(--info) 12%, var(--bg-2));
}

/* Segmented control — used in admin schedule card to toggle between
   "fixed shift" and "flexible hours" modes. */
.segmented {
  display: inline-flex;
  padding: .25rem;
  border-radius: .6rem;
  background: var(--bg-3);
  border: 1px solid var(--border);
  gap: .25rem;
}
.segmented .seg-btn {
  appearance: none;
  border: 0; background: transparent; cursor: pointer;
  padding: .45rem .9rem; border-radius: .45rem;
  font: inherit; font-weight: 600; color: var(--text-2);
  transition: background .15s ease, color .15s ease;
}
.segmented .seg-btn.is-active {
  background: var(--bg-1, var(--bg-2));
  color: var(--text);
  box-shadow: inset 0 0 0 1px var(--border);
}
.segmented .seg-btn:hover:not(.is-active) { color: var(--text); }

/* Auth / splash */
.splash { min-height: 100vh; display: grid; place-items: center; padding: 1.25rem; }
.auth-card { width: 100%; max-width: 440px; padding: 2rem; }
.auth-card h1 { margin: 0 0 .5rem; font-size: 1.7rem; }
.auth-card p.sub { color: var(--text-2); margin: 0 0 1.5rem; }

/* ── Auth split layout (Linear/Notion style) ───────────────────────────
   Two-column login: brand panel on the leading edge (visually right in
   RTL, left in LTR), form on the trailing edge. The brand panel carries
   a deep emerald gradient, soft aurora glows, the logo, a mission quote,
   and tiny social-proof stats. On phones the brand panel collapses and
   a compact mobile brand block at the top of the form replaces it. */
.auth-split {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 1fr 1.05fr; /* form panel slightly wider for breathing room */
  background: var(--bg);
  color: var(--text);
}

/* ── Aside: brand + quote + stats ─────────────────────────────────── */
.auth-aside {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 3rem 3.2rem;
  color: rgba(255, 255, 255, 0.92);
  /* Royal Pixel: one solid deep-navy panel (the light-tier ink). The
     depth of the blue IS the statement — no gradient, no glows. */
  background: #0A1F3D;
  isolation: isolate;
}

.auth-aside > * { position: relative; z-index: 1; }

.auth-brand {
  display: flex;
  align-items: center;
  gap: 0.85rem;
}
.auth-brand-logo {
  width: 52px;
  height: 52px;
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.20);
  display: grid;
  place-items: center;
}
.auth-brand-logo img,
.auth-brand-logo svg { width: 28px; height: 28px; }
.auth-brand-name {
  font-size: 1.5rem;
  font-weight: 600;
  margin: 0;
  letter-spacing: -0.01em;
  color: #fff;
}
.auth-brand-tagline {
  font-size: 0.8rem;
  font-weight: 500;
  opacity: 0.78;
  margin: 0.1rem 0 0;
}

.auth-quote {
  margin: 0;
  padding: 0;
  max-width: 540px;
}
.auth-quote-mark {
  font-family: 'Georgia', serif;
  font-size: 5rem;
  line-height: 0.6;
  opacity: 0.28;
  margin-bottom: 0.4rem;
  display: block;
  font-weight: 600;
}
.auth-quote-text {
  font-size: 1.55rem;
  font-weight: 600;
  line-height: 1.55;
  color: #fff;
  margin: 0 0 1.5rem;
  letter-spacing: -0.005em;
  /* Soft fade for the rotating-quote swap (handled by JS on the login
     page — every 7s the text fades out, swaps, fades back in). 350ms
     matches the JS swap delay. */
  transition: opacity .35s ease;
}
.auth-quote-cite {
  font-style: normal;
  display: block;
  font-size: 0.95rem;
  font-weight: 600;
  opacity: 0.92;
}
.auth-quote-cite small {
  display: block;
  font-size: 0.78rem;
  font-weight: 500;
  opacity: 0.65;
  margin-top: 0.2rem;
}

.auth-stats {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  font-size: 0.78rem;
  font-weight: 600;
  opacity: 0.78;
  flex-wrap: wrap;
}
.auth-stats-dot {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.55;
}

/* ── Main: form ───────────────────────────────────────────────────── */
.auth-main {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 2rem 3rem;
}
.auth-main-actions {
  position: absolute;
  /* Clear the status bar on edge-to-edge WebViews (env resolves to 0 in a
     normal browser, so desktop is unchanged). */
  top: calc(1.4rem + env(safe-area-inset-top));
  inset-inline-end: calc(1.4rem + env(safe-area-inset-right));
  display: flex;
  gap: 0.35rem;
  z-index: 2;
}
.auth-main-actions .btn-icon {
  width: 36px;
  height: 36px;
  border-radius: var(--radius-sm);
}

.auth-mobile-brand {
  display: none; /* hidden on desktop — aside owns the brand there */
}

.auth-form {
  margin: auto 0;
  width: 100%;
  max-width: 380px;
  align-self: center;
}
.auth-form-title {
  margin: 0 0 0.4rem;
  font-size: 1.7rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text);
}
.auth-form-sub {
  margin: 0 0 2rem;
  font-size: 0.92rem;
  color: var(--text-3);
}
.auth-form .label { font-size: 0.8125rem; font-weight: 600; color: var(--text-2); margin-bottom: 0.35rem; }
/* Compact but accessible: 44px min-height meets the 44dp/44pt touch-target
   floor (Material 48dp is the tappable area, not the visual box) while
   staying tight. 15px text — Android WebViews never focus-zoom, so 16px
   isn't required and reads oversized. */
.auth-form .input { font-size: 0.9375rem; min-height: 44px; padding: 0.55rem 0.85rem; border-radius: var(--radius-field); }

.auth-footer {
  margin-top: auto;
  padding-top: 1.5rem;
  padding-bottom: env(safe-area-inset-bottom);
  font-size: 0.75rem;
  color: var(--text-3);
  text-align: center;
}

/* "Or continue with" divider above the Google sign-in button. Two
   subtle rules with the label centered between them — standard auth
   pattern (Stripe, Linear, Notion all use this). Hidden via [hidden]
   attribute when Google OAuth is not configured server-side. */
.auth-divider {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin: 1.4rem 0 1rem;
  font-size: 0.78rem;
  color: var(--text-3);
}
.auth-divider::before,
.auth-divider::after {
  content: '';
  flex: 1 1 auto;
  height: 1px;
  background: var(--border);
}
.auth-google {
  display: flex;
  justify-content: center;
}
/* GIS injects an iframe — give it some breathing room and let it
   stretch to the form width on narrow screens. */
.auth-google #google-btn-mount {
  width: 100%;
  display: flex;
  justify-content: center;
}

/* "Sign in with email code" button — sibling of the Google button.
   Same outlined-ghost treatment so the optional auth methods feel
   like a coherent group, not a stack of mismatched buttons. */
.auth-otp-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}
.auth-otp-btn .lucide-ic { width: 16px; height: 16px; }

/* Big OTP code field — monospace, generous letter-spacing so the
   six digits read as a clear group. Centered text, larger size than
   the regular .input. */
.otp-code-input {
  font-family: 'SF Mono', 'Menlo', Consolas, monospace !important;
  font-size: 1.6rem !important;
  font-weight: 600 !important;
  text-align: center;
  letter-spacing: 0.4em;
  padding: 0.85rem 0.5rem !important;
}
.otp-code-input::placeholder {
  letter-spacing: 0.4em;
  color: var(--text-3);
  opacity: 0.5;
}

/* Bottom action row on the OTP code step — "change email" on one
   side, "resend code" on the other. */
.auth-otp-actions {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  align-items: center;
}
.auth-otp-actions .btn-sm {
  font-size: 0.78rem;
  padding: 0.4rem 0.6rem;
}

/* ── Mobile (<= 900px): collapse the aside, show compact brand on top ── */
@media (max-width: 900px) {
  .auth-split {
    grid-template-columns: 1fr;
    min-height: 100dvh; /* dvh = real viewport height (handles iOS chrome) */
  }
  .auth-aside {
    display: none;
  }
  /* Edge-to-edge WebView: pad by the system gutters so the brand/toggles
     clear the status bar (top) and the footer clears the gesture bar
     (bottom). env() resolves to 0 in a desktop browser. overflow-y:auto +
     min-height:100dvh keep the sign-up line reachable when the keyboard
     shrinks the viewport. */
  .auth-main {
    padding:
      calc(1.6rem + env(safe-area-inset-top))
      calc(1.5rem + env(safe-area-inset-right))
      calc(2rem + env(safe-area-inset-bottom))
      calc(1.5rem + env(safe-area-inset-left));
    min-height: 100dvh;
    justify-content: center;
    overflow-y: auto;
  }
  /* Compact branded header: emerald logo tile + name stacked. Sizes tuned
     to modern app density (Linear/Notion) — restrained, not oversized. */
  .auth-mobile-brand {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    justify-content: center;
    margin-bottom: 1.5rem;
  }
  .auth-mobile-brand .auth-brand-logo {
    width: 48px;
    height: 48px;
    border-radius: var(--radius-field);
    background: var(--accent);
    border: none;
  }
  .auth-mobile-brand .auth-brand-logo svg,
  .auth-mobile-brand .auth-brand-logo img { width: 27px; height: 27px; color: var(--accent-fg); }
  .auth-mobile-brand .auth-brand-txt { text-align: center; }
  .auth-mobile-brand .auth-brand-name { color: var(--text); font-size: 1.2rem; font-weight: 600; }
  .auth-mobile-brand .auth-brand-tagline { color: var(--text-3); font-size: 0.78rem; margin-top: 0.05rem; }
  .auth-form-title { font-size: 1.35rem; text-align: center; }
  .auth-form-sub { text-align: center; font-size: 0.875rem; margin-bottom: 1.25rem; }
  /* Compact primary CTA (46px) + a clear two-tier button stack. */
  .auth-form #go,
  .auth-form #otp-verify-btn,
  .auth-form #otp-start-btn { min-height: 46px; font-size: 0.9375rem; font-weight: 600; }
  .auth-otp-btn {
    min-height: 44px;
    border: 1px solid var(--border);
    background: var(--card);
    color: var(--text);
    font-size: 0.9375rem;
  }
  .auth-divider { margin: 1.25rem 0 0.85rem; font-size: 0.75rem; }
  .login-remember { font-size: 0.875rem; }
}

/* Tighter for very small phones */
@media (max-width: 380px) {
  .auth-main {
    padding:
      calc(1.2rem + env(safe-area-inset-top))
      calc(1rem + env(safe-area-inset-right))
      calc(1.6rem + env(safe-area-inset-bottom))
      calc(1rem + env(safe-area-inset-left));
  }
  .auth-form-title { font-size: 1.4rem; }
}

/* Avatar */
/* Avatars get an inline `background: hsl(...)` from UI.avatarColor in
   almost all cases, where white initials read well on the medium-lightness
   colored backgrounds. The default fallback (no name hashed yet) uses the
   accent color; --accent-fg keeps initials legible when accent is white. */
.avatar { width: 36px; height: 36px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-weight: 600; color: #fff; background: var(--accent); flex-shrink: 0; font-size: .85rem; }
.avatar:not([style*="background"]) { color: var(--accent-fg); }
.avatar.lg { width: 48px; height: 48px; font-size: 1rem; }

/* ── Task stages settings panel ───────────────────────────────────────
   The "choose how stages look" section at /admin/settings.html#stages.
   Three preview cards + table of status rows with color + toggle + drag. */
.stage-mode-grid {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: .6rem;
}
@media (max-width: 720px) { .stage-mode-grid { grid-template-columns: 1fr; } }
.stage-mode-card {
  background: var(--card); border: 1.5px solid var(--border);
  border-radius: var(--radius-field); padding: .8rem .9rem; cursor: pointer;
  display: flex; flex-direction: column; gap: .4rem; align-items: stretch;
  text-align: start; transition: all .15s;
}
.stage-mode-card:hover { border-color: var(--border-2); }
.stage-mode-card.selected { border-color: var(--ring); box-shadow: 0 0 0 3px var(--ring-glow); }
.stage-mode-preview {
  display: flex; align-items: center; gap: .4rem;
  padding: .5rem .7rem; border-radius: var(--radius-sm);
  background: var(--bg-2); border: 1px solid var(--border);
}
.stage-mode-preview-default .stage-mode-dot { display: none; }
.stage-mode-preview-dot .stage-mode-dot {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--brand); display: inline-block; flex: 0 0 auto;
}
.stage-mode-preview-tint {
  background: color-mix(in srgb, var(--brand) 18%, var(--bg-2));
  border-color: color-mix(in srgb, var(--brand) 35%, var(--border));
}
.stage-mode-preview-tint .stage-mode-dot { display: none; }
.stage-mode-name { font-size: .85rem; color: var(--text); font-weight: 600; }
.stage-mode-label { font-size: .85rem; font-weight: 600; color: var(--text); }
.stage-mode-hint { font-size: .72rem; color: var(--text-3); line-height: 1.45; }

/* Search field inside stages section */
.stage-search {
  position: relative; min-width: 220px; flex: 1 1 240px; max-width: 360px;
}
.stage-search > input {
  width: 100%; padding: .45rem .7rem .45rem 2.1rem;
  border-radius: var(--radius-sm); background: var(--bg-2);
  border: 1px solid var(--border); color: var(--text);
  outline: none; font-size: .85rem;
}
html[dir="rtl"] .stage-search > input { padding: .45rem 2.1rem .45rem .7rem; }
.stage-search > input:focus { border-color: var(--ring); box-shadow: 0 0 0 3px var(--ring-glow); }
.stage-search > .lucide-ic {
  position: absolute; top: 50%; transform: translateY(-50%);
  inset-inline-start: .7rem; width: 1rem; height: 1rem;
  color: var(--icon-ink); pointer-events: none;
}

/* Stage table */
.stage-table {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  overflow: hidden; background: var(--card);
}
.stage-table-head, .stage-row {
  display: grid; grid-template-columns: 30px 1fr 80px 140px;
  gap: .6rem; align-items: center;
  padding: .5rem .7rem;
}
.stage-table-head {
  background: var(--bg-2); font-size: .75rem; color: var(--text-3);
  font-weight: 600; border-bottom: 1px solid var(--border);
}
.stage-row {
  border-bottom: 1px solid var(--border);
  transition: background .12s;
}
.stage-row:last-child { border-bottom: none; }
.stage-row:hover { background: var(--bg-2); }
.stage-row.drag-over { background: var(--bg-3); outline: 2px dashed var(--ring); outline-offset: -4px; }
.td-handle {
  cursor: grab; color: var(--text-3); user-select: none;
  text-align: center; font-size: 1rem; line-height: 1;
}
.td-handle:active { cursor: grabbing; }
.td-name {
  display: flex; align-items: center; gap: .55rem;
  color: var(--text); font-size: .88rem; font-weight: 500;
}
.td-name .stage-dot {
  width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto;
}
.color-swatch {
  display: inline-block; width: 34px; height: 28px;
  border-radius: 6px; cursor: pointer;
  border: 1.5px solid var(--border-2);
  position: relative; overflow: hidden;
}
.color-swatch > input[type="color"] {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; cursor: pointer; border: none;
}
.td-reports { display: flex; align-items: center; }

/* ── Canned replies settings panel ────────────────────────────────────
   CRM-workspace settings → «الردود الجاهزة» (Task 4). A simple bordered
   list of shared saved replies; add/edit happens in a UI.modal, so no
   dedicated form CSS is needed here — just the list rows. */
.canned-list {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  overflow: hidden; background: var(--card);
}
.canned-item {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 1rem; padding: .85rem 1rem;
  border-bottom: 1px solid var(--border);
}
.canned-item:last-child { border-bottom: none; }
.canned-item:hover { background: var(--bg-2); }
.canned-item-main { min-width: 0; flex: 1; }
.canned-item-title { font-weight: 600; font-size: .92rem; color: var(--text); }
.canned-item-meta { display: flex; gap: .35rem; flex-wrap: wrap; margin: .35rem 0; }
.canned-item-body {
  font-size: .82rem; color: var(--text-3); line-height: 1.5;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.canned-item-actions { display: flex; gap: .3rem; flex: 0 0 auto; }
.canned-empty { padding: 2rem; text-align: center; color: var(--text-3); }
/* Delete reuses the same quiet .btn-ghost treatment as edit (Royal Pixel
   quiet-interface / card-declutter doctrine — no loud filled buttons on
   list rows) and only shifts to --danger on hover/focus, matching the
   .sp-rem-del precedent elsewhere in this file. Higher specificity than
   .btn-ghost:hover so it wins regardless of source order. */
.canned-item-actions [data-action="delete"]:hover,
.canned-item-actions [data-action="delete"]:focus-visible {
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
}

/* ── Tag catalog settings panel (Wave 3.1 Task 3, 2026-07-26
   tags-saved-views plan) ────────────────────────────────────────────────
   «الوسوم» — company-shared crm_tags CRUD (routes/crm.js, Tasks 1-2). Rows
   are inline-editable (name input + recolor <select>) rather than the
   canned-replies panel's modal, per the brief's literal "rename" + "recolor
   select" row shape; only delete needs a confirm. `.tag-cat-swatch` reuses
   the SAME 8 --tag-<name>-fg tokens as the chip primitive (duwam.css's
   .crm-tag rules above) as a solid dot, so the swatch always matches
   exactly what the chip itself will render. */
.tag-cat-list {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  overflow: hidden; background: var(--card);
}
.tag-cat-row {
  display: flex; align-items: center; gap: .6rem;
  padding: .6rem .8rem; border-bottom: 1px solid var(--border);
}
.tag-cat-row:last-child { border-bottom: none; }
.tag-cat-row:hover { background: var(--bg-2); }
.tag-cat-swatch { width: 12px; height: 12px; border-radius: 50%; flex: none; }
.tag-cat-swatch.tag-slate  { background: var(--tag-slate-fg); }
.tag-cat-swatch.tag-blue   { background: var(--tag-blue-fg); }
.tag-cat-swatch.tag-green  { background: var(--tag-green-fg); }
.tag-cat-swatch.tag-amber  { background: var(--tag-amber-fg); }
.tag-cat-swatch.tag-red    { background: var(--tag-red-fg); }
.tag-cat-swatch.tag-violet { background: var(--tag-violet-fg); }
.tag-cat-swatch.tag-teal   { background: var(--tag-teal-fg); }
.tag-cat-swatch.tag-pink   { background: var(--tag-pink-fg); }
.tag-cat-name {
  flex: 1 1 auto; min-width: 0; border: 1px solid transparent; background: transparent;
  color: var(--text); font: inherit; font-size: .88rem; font-weight: 600;
  padding: .3rem .5rem; border-radius: var(--radius-sm);
}
.tag-cat-name:hover, .tag-cat-name:focus { border-color: var(--border-2); background: var(--bg-2); outline: none; }
.tag-cat-color-select { flex: none; width: 132px; }
select.tag-cat-color-select.input,
.tag-cat-color-select .uisel-trigger.input {
  height: 32px; padding: 4px 10px; font-size: .8rem; border-radius: var(--radius-sm);
}
.tag-cat-actions { display: flex; gap: .3rem; flex: none; }
.tag-cat-empty { padding: 2rem; text-align: center; color: var(--text-3); }
.tag-cat-add-row {
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  padding: .8rem 1rem; margin-top: .8rem;
  border: 1px dashed var(--border-2); border-radius: var(--radius-sm); background: var(--bg-2);
}
.tag-cat-add-row .input { flex: 1 1 160px; }

/* ── Kanban: per-status colors + display modes ─────────────────────
   Columns read the active display mode from `.kanban[data-mode]` set
   by the render code, and per-column colors from an inline
   `--col-color` CSS variable so the three modes (default/dot/tint)
   share the same DOM but look entirely different. */
/* Tint mode: colored accent bar on top of the header box (GHL-style)
   + gentle tint on the column body. Border stays the neutral gray so the
   rectangle shape remains consistent across all three modes. */
.kanban[data-mode="tint"] .kanban-col > h4 {
  border-top-width: 3px;
  border-top-color: var(--col-color, var(--border-2));
}
.kanban[data-mode="tint"] .kanban-col .col-body {
  background: color-mix(in srgb, var(--col-color, transparent) 6%, transparent);
  border-radius: var(--radius-sm);
}
/* Dot mode: inline colored dot inside the header, before the label. */
.kanban[data-mode="dot"] .kanban-col > h4::before {
  content: ""; display: inline-block;
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--col-color, var(--border-2));
  flex: 0 0 auto;
}
/* Default mode has no color accent — just the plain header box. */
/* Default mode has no color accents — just the plain header text */

/* Tasks page layout — Asana-inspired. The whole board lives inside a
   viewport-bounded flex column so the kanban scrolls internally (per-col)
   instead of pushing the sidebar/topbar around. The columns themselves are
   "containerless" — no border, no card bg — letting task cards float over
   the page background with clean typography-first column headers. */
.tasks-board {
  height: calc(100vh - var(--topbar-h) - var(--page-head-h, 0px) - 2.4rem);
  display: flex; flex-direction: column; gap: .8rem;
  min-height: 0; min-width: 0; width: 100%; max-width: 100%;
}
.tasks-toolbar { flex: 0 0 auto; }
.tasks-viewport {
  flex: 1 1 auto; min-height: 0; min-width: 0; width: 100%;
  display: flex; overflow: hidden;
}

/* View-toggle tabs — flat, underline-on-active, no boxes. */
.view-tabs { display: flex; align-items: center; gap: .2rem; }
.view-tab {
  background: transparent; border: none;
  padding: .45rem .85rem;
  color: var(--text-2); font-size: .92rem; font-weight: 500;
  cursor: pointer; border-radius: var(--radius-sm);
  position: relative; transition: color .15s, background .15s;
}
.view-tab:hover { background: var(--bg-2); color: var(--text); }
.view-tab.active { color: var(--text); font-weight: 600; }
.view-tab.active::after {
  content: ""; position: absolute; left: 12%; right: 12%; bottom: -2px;
  height: 2px; background: var(--accent-grad); border-radius: 2px;
}

/* Kanban — borderless columns, clean headers, cards are the only visible
   surfaces. This matches the Asana/Linear aesthetic where "empty columns
   are invisible" and the focus is on the tasks themselves. */
.kanban {
  display: grid; grid-auto-flow: column; grid-auto-columns: 288px;
  gap: 1.2rem;
  overflow-x: auto; overflow-y: hidden;
  flex: 1 1 auto; height: 100%; padding-bottom: 4px;
  scrollbar-width: thin;
}
.kanban::-webkit-scrollbar { height: 8px; }
.kanban::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 4px; }
.kanban-col {
  background: transparent; border: none; padding: 0;
  display: flex; flex-direction: column;
  max-height: 100%; min-height: 0;
}
.kanban-col h4 {
  /* Column header styled as a thin rounded card (GHL-inspired): soft
     border, subtle background tint, modest corner radius. Stays legible
     in both light + dark via the theme tokens. */
  margin: 0 0 .7rem;
  padding: .5rem .75rem;
  font-size: .92rem; font-weight: 600; text-transform: none; letter-spacing: 0;
  color: var(--text);
  display: flex; justify-content: space-between; align-items: center; gap: .5rem;
  flex: 0 0 auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-2);
}
.kanban-col h4 .col-count {
  background: transparent; border: none;
  color: var(--text-3); font-weight: 500; font-size: .82rem;
  padding: 0; margin: 0;
}
.kanban-col .col-body {
  flex: 1 1 auto; min-height: 0;
  overflow-y: auto; overflow-x: hidden;
  padding: 2px 2px 4px;
  scrollbar-width: thin;
}
.kanban-col .col-body::-webkit-scrollbar { width: 6px; }
.kanban-col .col-body::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 3px; }

/* Task card — subtle bg, very light border, soft shadow on hover.
   The "floating" feel comes from shadow not border weight. */
.kanban-task {
  position: relative;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-field);
  padding: .75rem .85rem;
  margin-bottom: .55rem;
  cursor: pointer;
  transition: border-color .15s, box-shadow .15s, transform .12s, opacity .15s;
}
.kanban-task:hover {
  border-color: var(--border-2);
  box-shadow: var(--shadow-1);
  transform: translateY(-1px);
}
/* ── Kanban fluid drag (2026-07-04) — pointer engine visuals ─────────
   Native HTML5 DnD is gone; these style the engine's three actors:
   the GHOST (fixed clone under the pointer), the PLACEHOLDER (dashed
   slot that FLIP-slides through the columns), and the column states. */
/* touch: vertical swipes keep scrolling the column natively — the
   long-press lift is what commits a finger to dragging. */
.kanban-task { touch-action: pan-y; }
.kanban-ghost {
  /* !important is deliberate: the ghost clones the card's classes/attrs,
     so higher-specificity .kanban-task[...] rules could leak position
     and drop it into document flow (see the proj-color stripe rule). */
  position: fixed !important;
  z-index: 1000;
  margin: 0;
  pointer-events: none;
  /* The "picked up" read: slight tilt + growth + tier-2 shadow. The
     rotation lives in the engine's transform so the settle animation
     can unwind it back to 0 on drop. */
  box-shadow: var(--shadow-2);
  opacity: .98;
  will-change: transform;
}
.kanban-placeholder {
  border: 1.5px dashed var(--border-2);
  border-radius: var(--radius-field);               /* mirrors .kanban-task */
  margin-bottom: .55rem;             /* mirrors .kanban-task rhythm */
  background: color-mix(in srgb, var(--brand) 6%, transparent);
}
/* The slot swallows the empty-state label while it's visiting. */
.col-body:has(> .kanban-placeholder) .col-empty { display: none; }
body.kb-dragging { cursor: grabbing; user-select: none; }
body.kb-dragging .kanban-task { cursor: grabbing; }
/* Due chip on cards is editable — reads as a control on hover, not text. */
.kanban-task .kb-due { cursor: pointer; border-radius: 6px; transition: background .12s; }
.kanban-task .kb-due:hover { background: var(--bg-2); }
.kb-hidden { display: none !important; }
/* Column under the ghost — quiet emerald wash, no border box-jump. */
.kanban-col.drag-over .col-body {
  background: color-mix(in srgb, var(--brand) 6%, transparent);
  border-radius: var(--radius-field);
}
/* Pipeline tasks can't change status by drag — stages own that flow.
   The whole column mutes instead of toasting after the drop. */
.kanban-col.kb-blocked { opacity: .55; }
.kanban-col.kb-blocked .col-body { cursor: not-allowed; }

/* Pickup pop (owner ask 2026-07-06) — the ghost blooms into its lifted
 * pose over 130ms instead of snapping, so grabbing a card FEELS like
 * picking it up. transform stays JS-owned (it tracks the pointer); the
 * animation covers the shadow/opacity bloom only. */
@keyframes kb-lift-in {
  from { box-shadow: 0 1px 2px rgba(15, 23, 42, .10); opacity: .9; }
  to   { box-shadow: var(--shadow-2); opacity: .98; }
}
.kanban-ghost { animation: kb-lift-in .13s ease-out; }

/* Landing pulse — a soft emerald ring blooms and fades where the card
 * touched down. Pure decoration: no layout impact, degrades to nothing
 * if the class is dropped mid-animation by a re-render. */
@keyframes kb-land-ring {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--brand) 45%, transparent); }
  100% { box-shadow: 0 0 0 10px color-mix(in srgb, var(--brand) 0%, transparent); }
}
.kanban-task.kb-landed { animation: kb-land-ring .5s ease-out; }

/* 9-dot grip — the drag affordance replacing the pencil. Grab cursor
 * telegraphs the gesture; slightly muted so it reads as chrome, not an
 * action that "does something" on click. */
.kanban-task .t-grip { cursor: grab; }
.kanban-task .t-grip:hover { background: var(--bg-3); color: var(--text-2); border-color: var(--border-2); }
body.kb-dragging .t-grip { cursor: grabbing; }
.kanban-task .t-title { font-weight: 600; margin-bottom: .3rem; font-size: .92rem; line-height: 1.35; padding-inline-end: 1.6rem; }
.kanban-task .t-meta {
  display: flex; justify-content: space-between; align-items: center;
  color: var(--text-3); font-size: .76rem;
  margin-top: .4rem;
}

/* Hover action icons (pen / like) — appear top-left in RTL, top-right in LTR. */
.kanban-task .t-actions {
  position: absolute; top: .55rem; inset-inline-end: .55rem;
  display: flex; gap: .25rem;
  opacity: 0; transition: opacity .15s;
}
.kanban-task:hover .t-actions { opacity: 1; }
.kanban-task .t-act {
  width: 24px; height: 24px; border-radius: 6px;
  background: var(--bg-2); border: 1px solid var(--border);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-2); cursor: pointer;
  transition: background .12s, color .12s;
}
.kanban-task .t-act:hover { background: var(--accent); color: var(--accent-fg); border-color: transparent; }
.kanban-task .t-act .lucide-ic { width: .85rem; height: .85rem; }

/* Empty-column placeholder — even lighter now. */
.kanban-col .col-empty {
  font-size: .78rem; color: var(--text-3); text-align: center;
  padding: 1.2rem .4rem; opacity: .5;
}

/* ── Bulk action bar (Phase 1B — admin/tasks List view) ──────────────
   Sticky pill that appears above the table when any rows are selected.
   Tinted with the brand emerald so the user immediately knows they're
   in "acting on a selection" mode. Action controls inline-flex so the
   bar stays one row on desktop, wraps on narrow widths. */
/* Bulk action bar — Linear/Asana-style floating pill at the bottom-
 * center of the viewport. Compact horizontal layout (no wrapping), so
 * it never grows into the giant rectangle the old layout produced when
 * many controls were in play. Fixed position keeps it visible during
 * scroll without pushing table content. */
.bulk-action-bar {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 60;
  display: inline-flex; align-items: center; gap: .6rem;
  padding: .5rem .7rem;
  background: var(--card);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-2);
  border-radius: 999px;
  animation: bulk-bar-in .18s ease-out;
}
@keyframes bulk-bar-in {
  from { opacity: 0; transform: translate(-50%, 8px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}
.bulk-bar-count {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .25rem .65rem;
  background: color-mix(in srgb, var(--brand) 14%, transparent);
  color: var(--brand);
  border-radius: 999px;
  font-size: .82rem; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.bulk-bar-count strong { font-size: .95rem; color: var(--brand); }
.bulk-bar-actions { display: inline-flex; gap: .35rem; align-items: center; }
.bulk-bar-actions .input {
  width: auto; padding: .35rem .55rem; font-size: .82rem;
  background: var(--bg-2); border: 1px solid var(--border);
}
.bulk-bar-actions .btn-sm { padding: .35rem .65rem; font-size: .82rem; }

/* Group-by toolbar button — sits next to "Filters" in the top filter
 * bar. The .is-on modifier paints the button in a soft brand tint so
 * the user can see at a glance that grouping is active without having
 * to read the label. Pairs with the .group-menu popover below. */
.task-filter-btn.is-on {
  background: color-mix(in srgb, var(--brand) 14%, transparent);
  color: var(--brand);
  border-color: color-mix(in srgb, var(--brand) 30%, transparent);
}
.task-filter-btn.is-on:hover {
  background: color-mix(in srgb, var(--brand) 20%, transparent);
}

/* Group-by popover — reuses the .task-filter-menu shell so the menu
 * frame matches the existing Filters menu. The option-list layout
 * (.gm-opt / .tgo-check / .tgo-label) is unique to this popover. */
.task-filter-menu.group-menu {
  padding: .5rem;
  min-width: 220px;
}
.task-filter-menu.group-menu .group { padding: 0; }
.task-filter-menu.group-menu .label {
  padding: .3rem .55rem .4rem;
  font-size: .7rem; font-weight: 600;
  color: var(--text-3);
  text-transform: uppercase;
  letter-spacing: .04em;
}
.gm-opt {
  appearance: none; border: 0; background: transparent;
  width: 100%;
  display: inline-flex; align-items: center; gap: .55rem;
  padding: .55rem .65rem;
  border-radius: var(--radius-sm);
  color: var(--text); cursor: pointer;
  font-family: inherit; font-size: .88rem; font-weight: 600;
  text-align: start;
  transition: background .12s;
}
.gm-opt:hover { background: var(--bg-3); }
.gm-opt.is-active {
  background: color-mix(in srgb, var(--brand) 12%, transparent);
  color: var(--brand);
}
.tgo-check {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; flex-shrink: 0;
  color: var(--brand);
}
.tgo-check svg, .tgo-check .lucide-ic { width: 14px; height: 14px; }
.tgo-label { flex: 1; }

/* ── Gantt "Display" toolbar button + upward-opening popover ──────────
 * Replaces the old row of [أسبوع] [شهر] [ربع] [اذهب لليوم] buttons —
 * a single pill shows the active zoom; click opens a small popover
 * *above* the button with the zoom radio + a "jump to today" row.
 * Matches the Group-by popover's visual language so both view-modifier
 * controls feel like one system. */
.gantt-display-btn {
  display: inline-flex; align-items: center; gap: .45rem;
  padding: .4rem .8rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text-2);
  font-family: inherit; font-size: .82rem; font-weight: 600;
  cursor: pointer;
  transition: background .12s, border-color .12s, color .12s;
}
.gantt-display-btn:hover {
  background: var(--bg-3);
  border-color: var(--border-2);
  color: var(--text);
}
.gantt-display-btn .gd-label b {
  font-weight: 600; color: var(--text);
  margin-inline-start: .2rem;
}
.gantt-display-btn .gd-chev {
  display: inline-flex; opacity: .55; margin-inline-start: .15rem;
}
.gantt-display-btn .gd-chev svg,
.gantt-display-btn .gd-chev .lucide-ic { width: 13px; height: 13px; }
.gantt-display-btn .lucide-ic { width: 14px; height: 14px; }

.task-filter-menu.gantt-display-menu {
  padding: .5rem;
  min-width: 240px;
  animation: gdm-in .15s ease-out;
}
@keyframes gdm-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
.gdm-divider {
  height: 1px; background: var(--border);
  margin: .35rem -.5rem;
}

/* Row-level select cell + selected-state highlight on the row. */
.bulk-select-cell input[type="checkbox"] { accent-color: var(--brand); cursor: pointer; }
tr.is-selected { background: color-mix(in srgb, var(--brand) 8%, transparent); }
tr.is-selected:hover { background: color-mix(in srgb, var(--brand) 14%, transparent); }

/* Filter-menu toggle row (e.g. "Hide completed"). Sits inside a .group
 * so it inherits the existing padding/dividers — keeps the menu rhythm. */
.tf-hidecompleted-row { padding-block: .35rem; }
.tf-toggle {
  display: inline-flex; align-items: center; gap: .55rem;
  font-size: .88rem; font-weight: 600;
  color: var(--text-2);
  cursor: pointer;
  user-select: none;
}
.tf-toggle input[type="checkbox"] {
  width: 16px; height: 16px;
  accent-color: var(--brand);
  cursor: pointer;
}
.tf-toggle:hover { color: var(--text); }

/* ═════════════════════════════════════════════════════════════════════
 *  Completed-task visual demotion — universal across views
 *  ───────────────────────────────────────────────────────────────────
 *  Three layers, applied via .is-task-completed (any view) and
 *  .is-aged-completed (>7 days old, computed in tasks-page.js):
 *    1. Strikethrough on the primary title text
 *    2. Reduced opacity on the container (.65 fresh, .35 aged)
 *    3. Avatars + status chips remain at full opacity so the row stays
 *       parseable at a glance ("who did it? what's it called?")
 *  Mirrors the Linear/Asana convention — done work fades into the
 *  background without disappearing, freeing the eye for active work.
 *  Universal selectors so the rule applies to list rows, kanban cards,
 *  gantt labels, today cards — anywhere we mark a row as completed. */
.is-task-completed { opacity: .72; }
.is-task-completed:hover { opacity: 1; }
.is-task-completed.is-aged-completed { opacity: .38; }
.is-task-completed.is-aged-completed:hover { opacity: .9; }

/* Strikethrough the primary title across all views. The list-view row
 * uses <strong>; kanban uses .kanban-task-title; today uses h6; gantt
 * label uses .gantt-label-text. Cover all four. */
.is-task-completed > td > strong,
.is-task-completed .kanban-task-title,
.is-task-completed .gantt-label-text,
.is-task-completed h6 {
  text-decoration: line-through;
  text-decoration-color: var(--text-3);
  text-decoration-thickness: 1.5px;
}

/* Avatars + status chip stay at full opacity inside a completed row so
 * the eye still locks onto "who" + "what state" quickly. */
.is-task-completed .row-av,
.is-task-completed .row-av-stack,
.is-task-completed .row-av-more,
.is-task-completed .gantt-avatar-stack,
.is-task-completed .status-badge,
.is-task-completed .gantt-status-pill { opacity: 1; }

/* Gantt-specific — bars are already striped via .is-completed (the
 * existing style). The aged bar dims further so 30-day-old "done"
 * items don't compete with active work. */
.gantt-bar.is-completed.is-aged-completed { opacity: .35; }

/* Empty-state celebrate icon — replaces the previous 🎉 glyph. Sized to
   match the previous 3rem visual weight, brand-tinted. */
.empty-icon-celebrate { color: var(--brand); }
.empty-icon-celebrate .lucide-ic { width: 48px; height: 48px; }

/* Task type badge alignment in side panel + title-with-icon helper. */
.task-type-badge { display: inline-flex; align-items: center; gap: .3rem; }
.task-title-with-icon { display: inline-flex; align-items: center; gap: .35rem; }
.task-title-with-icon .lucide-ic { flex: 0 0 auto; }
/* Pipeline blank-row name with leading "+" icon. */
.pr-name-blank { display: inline-flex; align-items: center; gap: .35rem; }
/* Today section heading icon sizing (warn/calendar/lightning/list). */
.today-section-head .lucide-ic { width: 1rem; height: 1rem; }
/* Team-progress mini count: avatar-sized icon next to the assignee total. */
.team-progress-icon { display: inline-flex; align-items: center; gap: .25rem; }

/* ── Task filter controls (search + dropdowns) ───────────────────────
   These now live directly in the single .tasks-controls row (2026-07-04
   merge). Project select matches the search/filters cluster so the row
   reads as one unit. */
.tasks-controls > select.input {
  width: auto; min-width: 150px; max-width: 200px;
  padding: .5rem 1.9rem .5rem .75rem;
  border-radius: var(--radius-sm); border-color: var(--border);
  font-size: .88rem; line-height: 1.2;
}
html[dir="rtl"] .tasks-controls > select.input {
  padding: .5rem .75rem .5rem 1.9rem;
}
/* Enhanced (UI.enhanceSelect) filter keeps the toolbar sizing the raw
   `> select.input` rule above gave it — that selector stops matching once
   the <select> is wrapped in .uisel, so mirror the width intent here. */
.tasks-controls > .uisel { width: auto; min-width: 150px; max-width: 220px; }
.task-filter-search {
  flex: 1 1 220px; min-width: 160px; max-width: 340px;
  position: relative;
}
.task-filter-search > input {
  width: 100%; padding: .5rem .75rem .5rem 2.1rem;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg-2); color: var(--text);
  outline: none; font-size: .88rem;
  transition: border-color .12s, box-shadow .12s;
}
html[dir="rtl"] .task-filter-search > input { padding: .5rem 2.1rem .5rem .75rem; }
.task-filter-search > input:focus { border-color: var(--ring); box-shadow: 0 0 0 3px var(--ring-glow); }
.task-filter-search > .lucide-ic {
  position: absolute; top: 50%; transform: translateY(-50%);
  inset-inline-start: .7rem; color: var(--icon-ink);
  width: 1rem; height: 1rem; pointer-events: none;
}
.task-filter-search > button.clear {
  position: absolute; top: 50%; transform: translateY(-50%);
  inset-inline-end: .5rem; background: transparent; border: none;
  color: var(--text-3); cursor: pointer; padding: .2rem; border-radius: 6px;
  display: inline-flex;
}
.task-filter-search > button.clear:hover { color: var(--text); background: var(--bg-3); }

.task-filter-dropdown { position: relative; }
.task-filter-btn {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .5rem .75rem; background: var(--bg-2); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  cursor: pointer; font-size: .88rem;
}
.task-filter-btn:hover { border-color: var(--border-2); }
/* Active state for "view modifier" buttons (filters, group-by). Subtle
 * emerald tint signals the modifier is engaged at a glance — no need
 * to read the label. */
.task-filter-btn.is-on {
  background: color-mix(in srgb, var(--brand) 12%, transparent);
  border-color: color-mix(in srgb, var(--brand) 36%, transparent);
  color: var(--brand);
  font-weight: 600;
}
.task-filter-btn.is-on svg, .task-filter-btn.is-on .lucide-ic { color: var(--brand); }
.task-filter-btn .count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; border-radius: 999px;
  background: var(--accent); color: var(--accent-fg);
  font-size: .7rem; font-weight: 600; padding: 0 5px;
}

/* Group-by popover — single-select list of options. Active row gets a
 * check mark + emerald tint so the current axis is obvious. */
.task-group-menu .gm-opt {
  display: flex; align-items: center; gap: .55rem;
  width: 100%;
  padding: .45rem .65rem;
  background: transparent; border: 0; cursor: pointer;
  font-family: inherit; font-size: .88rem;
  color: var(--text);
  border-radius: var(--radius-sm);
  transition: background .12s;
}
.task-group-menu .gm-opt:hover { background: var(--bg-3); }
.task-group-menu .gm-opt.is-active {
  background: color-mix(in srgb, var(--brand) 10%, transparent);
  color: var(--brand); font-weight: 600;
}
.task-group-menu .tgo-check {
  width: 18px; height: 18px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--brand);
}
.task-group-menu .tgo-check svg, .task-group-menu .tgo-check .lucide-ic { width: 14px; height: 14px; }
.task-group-menu .tgo-label { flex: 1; text-align: start; }
.task-filter-menu {
  position: absolute; top: calc(100% + 6px); inset-inline-start: 0;
  min-width: 280px; max-width: 380px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-field); box-shadow: var(--shadow-2);
  padding: .8rem; z-index: 40;
  animation: fade-in .14s ease;
}
.task-filter-menu > .group { margin-bottom: .8rem; }
.task-filter-menu > .group:last-child { margin-bottom: 0; }
.task-filter-menu > .group > .label {
  font-size: .78rem; color: var(--text-3); margin-bottom: .35rem;
  font-weight: 600;
}
.task-filter-menu .chip-row { display: flex; flex-wrap: wrap; gap: .3rem; }
.task-filter-menu .chip-row > button {
  padding: .25rem .55rem; background: var(--bg-2); color: var(--text-2);
  border: 1px solid var(--border); border-radius: 999px;
  font-size: .76rem; cursor: pointer; transition: all .12s;
}
.task-filter-menu .chip-row > button.on {
  background: var(--accent); color: var(--accent-fg); border-color: transparent;
}
.task-filter-menu select, .task-filter-menu input[type="date"] {
  width: 100%; padding: .4rem .55rem; border-radius: var(--radius-sm);
  background: var(--bg-2); border: 1px solid var(--border);
  color: var(--text); outline: none; font-size: .85rem;
}
.task-filter-menu .range { display: grid; grid-template-columns: 1fr 1fr; gap: .4rem; }
.task-filter-menu .menu-foot {
  display: flex; justify-content: space-between; align-items: center;
  margin-top: .7rem; padding-top: .7rem; border-top: 1px solid var(--border);
}

/* Active-filter chips — now live in .active-filter-group inside the
 * merged .tasks-chips row (2026-07-04). */
.active-filter-group > .chip {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .2rem .55rem; background: color-mix(in srgb, var(--accent) 14%, var(--bg-2));
  border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--border));
  color: var(--text); font-size: .76rem;
}
.active-filter-group > .chip > button {
  background: transparent; border: none; color: var(--text-2);
  cursor: pointer; padding: 0; line-height: 1; font-size: .9rem;
}
.active-filter-group > .chip > button:hover { color: var(--text); }

/* Quick-filter chips (A3, 2026-05-17) — one-click pill-buttons in the
 * merged chips row. Outlined by default; filled with brand tint when ON.
 * The count badge inside each chip uses bg-3 (neutral) when off and
 * brand-tinted when on, so an active filter reads as one cohesive
 * brand-colored unit. */
.quick-filter {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .3rem .65rem .3rem .55rem;
  background: transparent;
  border: 1px solid var(--border);
  /* System identity radius, not a pill — chips are buttons and sit in the
     same row as radius-sm buttons (owner call 2026-07-11). The circular
     count badge inside stays round: badges are a separate idiom. */
  border-radius: var(--radius-sm);
  color: var(--text-2);
  font-size: .76rem; font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition: background .12s ease, color .12s ease, border-color .12s ease;
}
.quick-filter:hover {
  background: var(--bg-3);
  color: var(--text);
  border-color: color-mix(in srgb, var(--text-3) 35%, var(--border));
}
.quick-filter svg, .quick-filter .lucide-ic {
  width: 13px; height: 13px;
  flex-shrink: 0;
}
/* Compressed label (2026-07-07) — resting chips show icon + count only;
 * the text reveals on hover or when the chip is ON, so the row stays
 * calm but one glance/hover still names every filter. title carries it
 * for AT + touch. max-width transition avoids a reflow jump. */
.quick-filter .qf-label {
  max-width: 0; overflow: hidden; white-space: nowrap;
  opacity: 0;
  transition: max-width .18s ease, opacity .14s ease, margin .18s ease;
}
.quick-filter:hover .qf-label,
.quick-filter.is-on .qf-label,
.quick-filter:focus-visible .qf-label {
  max-width: 140px; opacity: 1;
  margin-inline: .1rem .15rem;
}
.quick-filter .qf-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 5px;
  border-radius: 999px;
  background: var(--bg-3);
  color: var(--text-3);
  font-size: .65rem; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.quick-filter.is-on {
  background: color-mix(in srgb, var(--brand) 14%, transparent);
  border-color: color-mix(in srgb, var(--brand) 40%, transparent);
  color: var(--brand);
}
.quick-filter.is-on:hover {
  background: color-mix(in srgb, var(--brand) 20%, transparent);
}
.quick-filter.is-on .qf-count {
  background: var(--brand);
  color: var(--brand-fg, #fff);
}

/* Inline finish flow (employee tasks, 2026-07-05) — the handoff-note row
 * that swaps in place of the finish button, replacing the old native
 * prompt(). Input stretches; confirm/cancel hug the end. */
.tp-finish-form {
  display: flex; align-items: center; gap: .4rem;
  width: 100%;
}
.tp-finish-form > input {
  flex: 1; min-width: 0;
  padding: .45rem .6rem;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg-2); color: var(--text);
  font: inherit; font-size: .85rem;
  outline: none;
}
.tp-finish-form > input:focus { border-color: var(--ring); box-shadow: 0 0 0 3px var(--ring-glow); }

/* ── Completed column: monthly groups ─────────────────────────────────
   Current month's tasks show inline (list of cards). Older months
   collapse into <details> accordions labeled "Month Year · N" so the
   column stays tidy once a team has hundreds of finished tasks. */
.completed-month-group {
  margin-bottom: .55rem; border-radius: var(--radius-sm); overflow: hidden;
  border: 1px solid var(--border);
}
.completed-month-group > summary {
  list-style: none; cursor: pointer; user-select: none;
  padding: .5rem .65rem;
  display: flex; align-items: center; justify-content: space-between;
  font-size: .82rem; font-weight: 600; color: var(--text-2);
  background: var(--bg-2);
  transition: background .12s;
}
.completed-month-group > summary::-webkit-details-marker { display: none; }
.completed-month-group > summary:hover { background: var(--bg-3); color: var(--text); }
.completed-month-group > summary::before {
  content: "▸"; margin-inline-end: .5rem; font-size: .7rem;
  transition: transform .15s; display: inline-block;
}
html[dir="rtl"] .completed-month-group > summary::before { content: "◂"; }
.completed-month-group[open] > summary::before { transform: rotate(90deg); }
html[dir="rtl"] .completed-month-group[open] > summary::before { transform: rotate(-90deg); }
.completed-month-group > summary > .mg-count {
  background: var(--bg-3); padding: .05rem .45rem; border-radius: 999px;
  font-size: .72rem; color: var(--text-3); font-weight: 500;
}
.completed-month-group > .mg-body { padding: .55rem .35rem .3rem; }

/* Current month — same accordion but with a subtle accent on the header
   so the admin can tell at a glance which month is "live" without
   reading dates. Still collapsible on click. */
.completed-month-group.current > summary {
  background: color-mix(in srgb, var(--success) 8%, var(--bg-2));
  color: var(--text);
}
.completed-month-group.current > summary:hover {
  background: color-mix(in srgb, var(--success) 14%, var(--bg-2));
}

/* Bottom "+ Add task" row inside each column — Asana-style subtle button. */
.kanban-col .col-add {
  margin-top: .35rem; padding: .55rem .6rem;
  background: transparent; border: 1px dashed transparent;
  color: var(--text-3); font-size: .82rem;
  display: flex; align-items: center; gap: .4rem;
  cursor: pointer; border-radius: var(--radius-sm);
  transition: background .12s, color .12s, border-color .12s;
}
.kanban-col .col-add:hover {
  background: var(--bg-2); color: var(--text); border-color: var(--border-2);
}

/* ── Baton bar (pipeline task visualization) ───────────────────────────
   A row of dots connected by lines, one per stage. Works inside task
   cards (compact) and task detail modals (with labels below). Uses logical
   properties so it mirrors correctly in RTL/LTR. */
.baton-bar { display: flex; align-items: center; gap: 0; margin-top: .55rem; }
.baton-bar.with-labels { flex-wrap: wrap; }
.baton-seg { flex: 1 1 0; display: flex; align-items: center; min-width: 0; }
.baton-seg:last-child { flex: 0 0 auto; }
.baton-dot {
  width: 14px; height: 14px; border-radius: 50%;
  background: transparent; border: 2px solid var(--border-2);
  flex: 0 0 auto; position: relative; transition: all .2s;
}
.baton-dot.done {
  background: var(--brand); border-color: transparent;
}
.baton-dot.active {
  background: var(--accent); border-color: transparent;
  box-shadow: 0 0 0 4px var(--ring-glow);
  animation: baton-pulse 1.8s ease-in-out infinite;
}
.baton-dot.submitted {
  background: var(--warn); border-color: transparent;
  box-shadow: 0 0 0 4px rgba(245, 158, 11, .22);
  animation: baton-pulse-amber 1.8s ease-in-out infinite;
}
@keyframes baton-pulse-amber {
  0%,100% { box-shadow: 0 0 0 4px rgba(245, 158, 11, .22); }
  50%     { box-shadow: 0 0 0 7px rgba(245, 158, 11, .08); }
}
.baton-dot.returned {
  background: rgba(239,68,68,.15); border-color: var(--danger);
}
@keyframes baton-pulse {
  0%,100% { box-shadow: 0 0 0 4px var(--ring-glow); }
  50%     { box-shadow: 0 0 0 7px var(--accent-soft); }
}
.baton-line {
  flex: 1 1 auto; height: 2px; background: var(--border-2); margin: 0 2px;
  min-width: 10px;
}
.baton-line.done { background: var(--brand); }

/* Labels under baton bar (used in modal, not in card) */
.baton-labels {
  display: flex; gap: 2px; margin-top: .35rem;
  font-size: .72rem; color: var(--text-3);
}
.baton-labels > span {
  flex: 1 1 0; min-width: 0; text-align: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  padding: 0 .15rem;
}
.baton-labels > span.active { color: var(--text); font-weight: 600; }

/* Stage chip on the card (shows "Now at: [stage name]") */
.stage-now-chip {
  display: inline-flex; align-items: center; gap: .3rem;
  padding: .2rem .5rem; border-radius: var(--radius-sm);
  background: var(--accent-soft); color: var(--text);
  border: 1px solid var(--accent-soft-border);
  font-size: .72rem; font-weight: 600;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.stage-now-chip.sla-breached {
  background: rgba(239,68,68,.12); color: var(--danger);
  border-color: rgba(239,68,68,.3);
}

/* Team task progress (N/M completed) */
.team-progress { display: flex; align-items: center; gap: .4rem; font-size: .75rem; color: var(--text-2); }
/* Royal Pixel progress recipe (spec §2): --primary-500 fill on a
   --primary-50 track — the purple-era styling is gone system-wide. */
.team-progress .bar { flex: 1 1 auto; height: 4px; background: var(--accent-soft); border-radius: 2px; overflow: hidden; min-width: 40px; }
.team-progress .bar > span { display: block; height: 100%; background: var(--accent-grad); border-radius: 2px; transition: width .3s; }

/* ── Task-type picker (3-mode modal) ───────────────────────────────── */
.mode-pick { display: grid; grid-template-columns: repeat(3, 1fr); gap: .7rem; margin-bottom: 1rem; }
@media (max-width: 720px) { .mode-pick { grid-template-columns: 1fr; } }
.mode-card {
  border: 1.5px solid var(--border); border-radius: var(--radius); padding: 1rem .8rem;
  background: var(--card); cursor: pointer; text-align: center;
  transition: all .18s; display: flex; flex-direction: column; gap: .35rem;
  align-items: center;
}
.mode-card:hover { border-color: var(--ring); }
.mode-card.selected {
  border-color: var(--ring); background: var(--bg-2);
  box-shadow: 0 0 0 3px var(--ring-glow);
}
/* Mode-card icon — was raw emoji; now hosts a Lucide SVG via UI.icon().
   Wrapped in a brand-tinted rounded square so the picker echoes the
   sidebar's active-nav-state pattern (consistent visual language). The
   selected card swaps to a filled brand container — see selector below. */
.mode-card .mode-icon {
  width: 44px; height: 44px;
  margin: 0 auto .4rem;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-field);
  background: color-mix(in srgb, var(--brand) 12%, transparent);
  color: var(--brand);
  transition: background .18s ease, color .18s ease, box-shadow .2s ease;
}
.mode-card .mode-icon .lucide-ic,
.mode-card .mode-icon svg { width: 22px; height: 22px; stroke-width: 2; }
.mode-card:hover .mode-icon { background: color-mix(in srgb, var(--brand) 20%, transparent); }
.mode-card.selected .mode-icon {
  background: var(--brand);
  color: var(--accent-fg);
}
.mode-card .mode-title { font-weight: 600; font-size: .95rem; }
.mode-card .mode-desc { font-size: .78rem; color: var(--text-2); line-height: 1.45; }
.mode-card .mode-example { font-size: .72rem; color: var(--text-3); font-style: italic; margin-top: .3rem; }

/* ── Bonus category picker (admin → Employees → Rewards → إكرامية) ───
   3-column chip grid (collapses to 2 on narrow). Each chip carries its
   category color via a CSS variable on the icon container; selecting a
   chip lifts the whole card to the brand-tinted "active" state so the
   choice reads instantly. Colors mirror the admin section nav palette
   for cross-app consistency. */
.bonus-cats {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: .5rem; margin-bottom: .35rem;
}
@media (max-width: 480px) { .bonus-cats { grid-template-columns: repeat(2, 1fr); } }
.bonus-cat {
  display: flex; flex-direction: column; align-items: center; gap: .35rem;
  padding: .65rem .4rem;
  border: 1px solid var(--border); border-radius: var(--radius-field);
  background: var(--card);
  color: var(--text-2);
  cursor: pointer; font: inherit; font-size: .78rem; font-weight: 600;
  transition: background .15s ease, border-color .15s ease, color .15s ease, transform .12s ease;
}
.bonus-cat:hover { border-color: var(--text-3); color: var(--text); }
.bonus-cat:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.bonus-cat-ic {
  width: 36px; height: 36px; border-radius: var(--radius-sm);
  display: inline-flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--cat-color, var(--brand)) 14%, transparent);
  color: var(--cat-color, var(--brand));
  transition: background .18s ease, color .18s ease, box-shadow .2s ease;
}
.bonus-cat-ic .lucide-ic, .bonus-cat-ic svg { width: 18px; height: 18px; stroke-width: 2; }
.bonus-cat-lbl { line-height: 1.2; }
/* Selected — chip becomes a tinted "card" with the category color, and
   its icon container fills solid for unmistakable focus. */
.bonus-cat.selected {
  background: color-mix(in srgb, var(--cat-color, var(--brand)) 10%, var(--card));
  border-color: color-mix(in srgb, var(--cat-color, var(--brand)) 45%, var(--border));
  color: var(--text);
}
.bonus-cat.selected .bonus-cat-ic {
  background: var(--cat-color, var(--brand));
  color: #FFFFFF;
}
/* Per-category color tokens — applied via attribute selector so the JS
   only needs to set data-cat (no inline style needed). Hues curated to
   match the admin section nav palette for visual cohesion. */
.bonus-cat[data-cat="excellence"]   { --cat-color: var(--warn); }
.bonus-cat[data-cat="attendance"]   { --cat-color: var(--brand); }
.bonus-cat[data-cat="innovation"]   { --cat-color: var(--chart-4); }
.bonus-cat[data-cat="teamwork"]     { --cat-color: var(--success); }
.bonus-cat[data-cat="appreciation"] { --cat-color: #EC4899; }
.bonus-cat[data-cat="growth"]       { --cat-color: #0891B2; }

/* Industry template picker grid */
.industry-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: .55rem; }
.industry-card {
  border: 1px solid var(--border); border-radius: var(--radius-field); padding: .7rem .5rem;
  background: var(--card); cursor: pointer; text-align: center;
  transition: all .15s; display: flex; flex-direction: column; gap: .2rem;
  align-items: center; position: relative;
}
.industry-card:hover:not(.disabled) { border-color: var(--ring); }
.industry-card.selected { border-color: var(--ring); background: var(--bg-2); box-shadow: 0 0 0 3px var(--ring-glow); }
.industry-card.disabled { opacity: .5; cursor: not-allowed; }
.industry-card .ic-icon { font-size: 1.6rem; }
.industry-card .ic-name { font-weight: 600; font-size: .82rem; }
.industry-card .ic-badge {
  position: absolute; top: 4px; inset-inline-end: 4px;
  background: var(--warn); color: #fff; font-size: .6rem;
  padding: 1px 5px; border-radius: 999px; font-weight: 600;
}

/* Pipeline list inside template detail */
.pipeline-row {
  padding: .65rem .8rem; border: 1px solid var(--border); border-radius: var(--radius-sm);
  cursor: pointer; margin-bottom: .4rem; background: var(--card);
  transition: all .15s;
}
.pipeline-row:hover { border-color: var(--ring); }
.pipeline-row .pr-name { font-weight: 600; margin-bottom: .25rem; }
.pipeline-row .pr-desc { font-size: .78rem; color: var(--text-3); margin-bottom: .35rem; }
.pipeline-row .pr-stages { display: flex; flex-wrap: wrap; gap: .3rem; font-size: .72rem; color: var(--text-2); }
.pipeline-row .pr-stages > span { padding: .15rem .5rem; background: var(--bg-2); border: 1px solid var(--border-2); border-radius: 6px; }

/* Stage editor — compact row layout (Trello-style): each stage is a click-
   target that opens a rich detail modal. The row shows enough context to
   reorder/scan (number + name + team chip + SLA chip + completion signals)
   without cluttering the creation flow with every sub-field inline. */
.stage-editor { display: flex; flex-direction: column; gap: .5rem; }
.stage-editor-row {
  display: flex; align-items: center; gap: .6rem;
  padding: .6rem .7rem;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--card);
  cursor: pointer; transition: border-color .12s, box-shadow .12s, transform .1s;
  position: relative;
}
.stage-editor-row:hover {
  border-color: var(--border-2);
  box-shadow: 0 2px 8px rgba(0,0,0,.12);
}
.stage-editor-row.warning {
  border-color: rgba(245,158,11,.5);
  background: rgba(245,158,11,.04);
}
.stage-editor-row .se-num {
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--accent); color: var(--accent-fg);
  display: flex; align-items: center; justify-content: center;
  font-weight: 600; font-size: .78rem; flex: 0 0 auto;
}
.stage-editor-row .se-body {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column; gap: .25rem;
}
.stage-editor-row .se-title {
  font-weight: 600; font-size: .92rem; line-height: 1.3;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.stage-editor-row .se-title.empty { color: var(--text-3); font-style: italic; }
.stage-editor-row .se-meta {
  display: flex; flex-wrap: wrap; gap: .3rem;
  font-size: .74rem; color: var(--text-3);
}
.stage-editor-row .se-meta > span {
  display: inline-flex; align-items: center; gap: .25rem;
  padding: .1rem .45rem; border-radius: 6px;
  background: var(--bg-2); border: 1px solid var(--border);
}
.stage-editor-row .se-meta > span.warn {
  background: rgba(245,158,11,.1); color: var(--warn);
  border-color: rgba(245,158,11,.35);
}
.stage-editor-row .se-del {
  background: transparent; border: none; color: var(--text-3);
  cursor: pointer; padding: .35rem; border-radius: 6px; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
}
.stage-editor-row .se-del:hover { color: var(--danger); background: var(--bg-2); }
.stage-editor-row .se-edit-hint {
  color: var(--text-3); font-size: 1rem; flex: 0 0 auto;
  opacity: 0; transition: opacity .12s;
}
.stage-editor-row:hover .se-edit-hint { opacity: 1; }

.stage-editor-add {
  display: flex; gap: .45rem; align-items: center;
  padding: .5rem .65rem;
  border: 1px dashed var(--border-2); border-radius: var(--radius-sm);
  background: transparent; color: var(--text-3); cursor: pointer;
  font-size: .85rem; justify-content: center;
  transition: all .15s;
}
.stage-editor-add:hover { color: var(--text); border-color: var(--ring); background: var(--bg-2); }

.stage-editor-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: .6rem;
}
.stage-editor-head > strong { font-size: .95rem; }
.stage-editor-head .back-btn {
  background: transparent; border: none; color: var(--text-3);
  font-size: .82rem; cursor: pointer; padding: .2rem .5rem; border-radius: 6px;
}
.stage-editor-head .back-btn:hover { color: var(--text); background: var(--bg-2); }

/* ── Side panel (task detail) ─────────────────────────────────────────
   Slides in from the inline-end side (left in RTL, right in LTR) instead
   of a modal. Keeps the kanban visible so the user sees where the task
   sits in the flow. ESC + overlay click to close. */
.side-panel-backdrop {
  position: fixed; inset: 0; z-index: 1000;
  background: rgba(0,0,0,.45);
  animation: fade-in .18s ease;
}
.side-panel {
  position: fixed; top: 0; bottom: 0; inset-inline-end: 0;
  width: min(720px, 95vw);
  background: var(--bg); border-inline-start: 1px solid var(--border);
  box-shadow: var(--shadow-2);
  display: flex; flex-direction: column;
  animation: side-slide .22s cubic-bezier(.2,.8,.2,1);
  z-index: 1001;
}
@keyframes side-slide {
  from { transform: translateX(var(--slide-from, 100%)); }
  to   { transform: translateX(0); }
}
html[dir="rtl"] .side-panel { --slide-from: -100%; }
@keyframes fade-in { from { opacity: 0 } to { opacity: 1 } }

.side-panel-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: .75rem 1rem; border-bottom: 1px solid var(--border);
  gap: .5rem; flex-wrap: wrap;
}
.side-panel-head .sp-actions { display: flex; gap: .3rem; flex-shrink: 0; align-items: center; }
.side-panel-head .sp-close,
.side-panel-head .sp-icon-btn {
  /* Reset the global .sp-close absolute positioning (defined in the
   * marketing-modal styles around line 7725) — inside the side-panel
   * head we want both action buttons to participate in flex flow so
   * they sit side-by-side with a real gap, not stacked on top of each
   * other in the corner. */
  position: static;
  top: auto; inset-inline-end: auto;
  background: transparent; border: none; color: var(--text-2);
  width: 34px; height: 34px; border-radius: var(--radius-sm); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  z-index: auto;
  font-size: 1rem;
}
.side-panel-head .sp-close:hover,
.side-panel-head .sp-icon-btn:hover { background: var(--bg-2); color: var(--text); }
.side-panel-head .sp-close svg, .side-panel-head .sp-close .lucide-ic,
.side-panel-head .sp-icon-btn svg, .side-panel-head .sp-icon-btn .lucide-ic { width: 16px; height: 16px; }

.side-panel-body {
  flex: 1 1 auto; overflow-y: auto; padding: 1rem 1.2rem 2rem;
  scrollbar-width: thin;
}
.side-panel-body::-webkit-scrollbar { width: 7px; }
.side-panel-body::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 4px; }

/* Title input (inline-editable) — Trello-inspired: big, soft-bordered. */
.sp-title {
  font-size: 1.75rem; font-weight: 600; line-height: 1.25;
  background: transparent; border: 2px solid transparent;
  color: var(--text); width: 100%; padding: .5rem .6rem;
  border-radius: var(--radius-sm); margin-bottom: 1.1rem; outline: none;
  transition: background .15s, border-color .15s;
  font-family: inherit;
}
.sp-title:hover { background: var(--bg-2); }
.sp-title:focus { background: var(--bg-2); border-color: var(--ring); box-shadow: 0 0 0 3px var(--ring-glow); }
.sp-title.empty::before { content: attr(data-placeholder); color: var(--text-3); }

/* Field rows — label on start, value on end, aligned on baseline */
.sp-field {
  display: grid; grid-template-columns: 120px 1fr; gap: .8rem;
  align-items: center; padding: .5rem 0;
  border-bottom: 1px solid transparent;
}
.sp-field:hover { border-bottom-color: var(--border); }
.sp-field > .sp-label {
  color: var(--text-3); font-size: .82rem; font-weight: 500;
}
.sp-field > .sp-value {
  font-size: .9rem; color: var(--text);
  display: flex; align-items: center; gap: .4rem; flex-wrap: wrap;
}
.sp-field > .sp-value .chip { font-size: .78rem; }
.sp-field > .sp-value > button { all: unset; cursor: pointer; color: var(--text-3); font-size: .85rem; }
.sp-field > .sp-value > button:hover { color: var(--accent); }

.sp-section {
  margin-top: 1.3rem; padding-top: 1rem; border-top: 1px solid var(--border);
}
.sp-section > h4 {
  font-size: .95rem; font-weight: 600; margin: 0 0 .6rem;
}

/* ── Date / time picker popover ─────────────────────────────────────── */
/* Due-date trigger — a button wearing the .input skin so it matches the
   priority/estimate fields beside it in the create-task grid, but opens
   the branded Arabic calendar (UI.datePicker) instead of the native OS
   one. Start-aligned like a real input; the placeholder label dims until
   a date is picked. */
.dp-field-trigger {
  text-align: start;
  cursor: pointer;
  display: flex; align-items: center;
}
.dp-field-trigger .dp-field-label.is-empty { color: var(--text-3); }

/* ── Date picker — the system-wide calendar (2026-07-11) ─────────────
   One visual language for every date pick in Duwam: the planner's
   schedule-popover design (.pl-sched-pop is the source). Pill fields on
   top (date + time), Saturday-first month grid or 15-min time list as
   the body, مسح/حفظ footer. Theme-aware via tokens; RTL-safe via
   logical properties. */
.date-picker {
  /* Topmost popover: must clear EVERY surface it can be opened from —
   * the stage-detail sheet stacks at 1101 (above the task side panel),
   * so at 1100 the picker rendered BEHIND its own field and read as
   * "broken" (2026-07-12 pinnedStart bug). 2500 clears the 2400-tier. */
  position: absolute; z-index: 2500;
  width: 320px;
  background: var(--bg-2); border: 1px solid var(--border-2);
  border-radius: var(--radius-field, 12px); box-shadow: var(--shadow-2);
  overflow: hidden;
  font-size: .85rem; color: var(--text);
  animation: fade-in .14s ease;
}
/* Single-panel arrangements — same theme, narrower footprint. A lone
   clock (shift start, message schedule…) doesn't need calendar width. */
.date-picker.dp-mode-time { width: 220px; }
.date-picker.dp-mode-month { width: 264px; }

/* Top row of pill fields */
.date-picker .dp-fields {
  display: flex; align-items: center; gap: .35rem;
  padding: .5rem;
  border-bottom: 1px solid var(--border);
}
.date-picker .dp-field {
  flex: 1;
  display: inline-flex; align-items: center; justify-content: center; gap: .35rem;
  padding: .45rem .35rem;
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-sm);
  cursor: pointer; color: var(--text-2);
  font-weight: 600; font-size: .8rem;
  font-variant-numeric: tabular-nums; white-space: nowrap;
  transition: background .12s, color .12s, border-color .12s;
}
.date-picker .dp-field:hover { background: var(--bg-3); color: var(--text); }
.date-picker .dp-field.is-active {
  background: var(--bg-3); border-color: var(--border-2); color: var(--text);
  box-shadow: inset 0 0 0 1px var(--accent-soft-border);
}
.date-picker .dp-field.is-empty span { color: var(--text-3); font-weight: 600; }
.date-picker .dp-field .lucide-ic { width: 12px; height: 12px; opacity: .75; }

/* Body — calendar OR time list (mutually exclusive) */
.date-picker .dp-body {
  padding: .55rem;
  max-height: 280px; overflow: auto;
  scrollbar-width: thin;
}
.date-picker .dp-body::-webkit-scrollbar { width: 6px; }
.date-picker .dp-body::-webkit-scrollbar-thumb { background: var(--bg-3); border-radius: 3px; }

/* Calendar */
.date-picker .dp-cal-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: .15rem .25rem .55rem;
  font-weight: 600; font-size: .85rem; color: var(--text);
}
.date-picker .dp-nav {
  display: inline-grid; place-items: center;
  width: 24px; height: 24px;
  background: transparent; border: 0; border-radius: 6px;
  color: var(--text-3); cursor: pointer;
}
.date-picker .dp-nav:hover { background: var(--bg-3); color: var(--text); }
.date-picker .dp-nav .lucide-ic { width: 14px; height: 14px; }
.date-picker .dp-cal-grid {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px;
}
.date-picker .dp-dow {
  font-size: .65rem; color: var(--text-3); text-align: center;
  padding: .15rem 0; font-weight: 600;
}
.date-picker .dp-day {
  background: transparent; border: 0; border-radius: 6px;
  padding: .35rem 0;
  font-size: .78rem; font-variant-numeric: tabular-nums;
  color: var(--text-2); cursor: pointer;
  transition: background .12s, color .12s;
}
.date-picker .dp-day:hover { background: var(--bg-3); color: var(--text); }
.date-picker .dp-day.is-out { color: var(--text-3); opacity: .35; }
.date-picker .dp-day.is-today { color: var(--text); font-weight: 600; position: relative; }
.date-picker .dp-day.is-today::after {
  content: '';
  position: absolute; inset-inline: 0; bottom: 3px; margin: 0 auto;
  width: 4px; height: 4px; border-radius: 50%;
  background: var(--danger);
}
.date-picker .dp-day.is-sel {
  background: var(--bg-3); color: var(--text); font-weight: 600;
  outline: 1px solid var(--border-2);
}
.date-picker .dp-day.is-sel.is-today::after { background: var(--text); }

/* Month grid (mode 'month') — same treatment as the day cells */
.date-picker .dp-months { display: grid; grid-template-columns: repeat(3, 1fr); gap: 3px; }
.date-picker .dp-month {
  background: transparent; border: 0; border-radius: 6px;
  padding: .55rem .2rem;
  font: inherit; font-size: .8rem; font-weight: 600;
  color: var(--text-2); cursor: pointer;
  transition: background .12s, color .12s;
}
.date-picker .dp-month:hover { background: var(--bg-3); color: var(--text); }
.date-picker .dp-month.is-today { color: var(--text); font-weight: 600; }
.date-picker .dp-month.is-sel {
  background: var(--bg-3); color: var(--text); font-weight: 600;
  outline: 1px solid var(--border-2);
}

/* Time list */
.date-picker .dp-times { display: flex; flex-direction: column; gap: 1px; }
.date-picker .dp-time {
  background: transparent; border: 0; border-radius: 6px;
  padding: .45rem .65rem;
  text-align: start; font-size: .85rem;
  font-variant-numeric: tabular-nums; font-weight: 600;
  color: var(--text-2); cursor: pointer;
  transition: background .12s, color .12s;
}
.date-picker .dp-time:hover { background: var(--bg-3); color: var(--text); }
.date-picker .dp-time.is-sel {
  background: var(--bg-3); color: var(--text);
  outline: 1px solid var(--border-2);
}
.date-picker .dp-time-none {
  color: var(--text-3); font-size: .78rem;
  border-bottom: 1px solid var(--border); border-radius: 0;
  position: sticky; top: 0; background: var(--bg-2); z-index: 1;
}

/* Footer — مسح / حفظ */
.date-picker-foot {
  display: flex; justify-content: space-between; align-items: center;
  padding: .5rem; gap: .4rem;
  border-top: 1px solid var(--border);
}
.date-picker-foot .dp-clear,
.date-picker-foot .dp-save {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .45rem .8rem;
  border-radius: var(--radius-sm); border: 1px solid var(--border);
  font-weight: 600; font-size: .8rem;
  cursor: pointer;
  transition: background .12s, color .12s, border-color .12s;
}
.date-picker-foot .dp-clear { background: transparent; color: var(--text-3); }
.date-picker-foot .dp-clear:hover { background: var(--bg-3); color: var(--text); }
.date-picker-foot .dp-save { background: var(--bg); color: var(--text); }
.date-picker-foot .dp-save:hover { background: var(--bg-3); }
.date-picker-foot .dp-save:disabled,
.date-picker-foot .dp-save[disabled] { opacity: .5; cursor: not-allowed; }
.date-picker-foot .dp-clear .lucide-ic,
.date-picker-foot .dp-save .lucide-ic { width: 13px; height: 13px; }

/* ── Assignee picker (search + sticky footer) ───────────────────────── */
.assign-picker {
  width: 280px; padding: 0; display: flex; flex-direction: column;
  max-height: min(70vh, 440px);
}
.assign-picker .ap-head {
  padding: .55rem .6rem; border-bottom: 1px solid var(--border);
}
.assign-picker .ap-search {
  width: 100%; padding: .4rem .6rem; font-size: .85rem;
  border-radius: var(--radius-sm);
}
.assign-picker .ap-list {
  flex: 1 1 auto; overflow-y: auto; padding: .35rem .35rem;
  scrollbar-width: thin;
}
.assign-picker .ap-list::-webkit-scrollbar { width: 7px; }
.assign-picker .ap-list::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 4px; }
.assign-picker .ap-item {
  display: flex; align-items: center; gap: .55rem;
  padding: .45rem .55rem; cursor: pointer;
  border-radius: var(--radius-sm);
}
.assign-picker .ap-item:hover { background: var(--bg-2); }
.assign-picker .ap-item input[type="checkbox"] { accent-color: var(--accent); cursor: pointer; }
.assign-picker .ap-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.assign-picker .ap-empty {
  padding: 1rem; text-align: center; color: var(--text-3); font-size: .85rem;
}
.assign-picker .ap-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: .5rem; padding: .55rem .6rem;
  border-top: 1px solid var(--border);
  background: var(--card);
  border-radius: 0 0 var(--radius) var(--radius);
}

/* ── Task side-panel: Trello-style sections ─────────────────────────── */
.sp-quick-row {
  display: flex; gap: .4rem; flex-wrap: wrap; margin: 0 0 1rem;
}
.sp-quick-btn {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .45rem .7rem; border-radius: var(--radius-sm);
  background: var(--bg-2); border: 1px solid var(--border);
  color: var(--text-2); font-size: .82rem; font-weight: 500;
  cursor: pointer; transition: all .15s;
}
.sp-quick-btn:hover { background: var(--card); color: var(--text); border-color: var(--border-2); }
.sp-quick-btn.on { background: var(--accent-soft); color: var(--text); border-color: var(--accent-soft-border); }
.sp-quick-btn .lucide-ic { width: .95rem; height: .95rem; }

/* Labels bar */
.sp-labels { display: flex; flex-wrap: wrap; gap: .3rem; margin: .25rem 0; }
.sp-label {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .18rem .55rem; border-radius: 6px;
  font-size: .76rem; font-weight: 600; color: #fff;
  cursor: default;
}
.sp-label > button {
  all: unset; cursor: pointer; opacity: .7; font-size: .8rem;
}
.sp-label > button:hover { opacity: 1; }

.sp-label-add {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .18rem .55rem; border-radius: 6px;
  background: var(--bg-2); border: 1px dashed var(--border-2);
  color: var(--text-3); font-size: .76rem; cursor: pointer;
}
.sp-label-add:hover { color: var(--text); border-color: var(--ring); }

.sp-label-palette {
  display: grid; grid-template-columns: repeat(8, 1fr); gap: .3rem;
  margin-top: .4rem; margin-bottom: .5rem;
}
.sp-label-swatch {
  width: 100%; aspect-ratio: 1; border-radius: 6px;
  border: 2px solid transparent; cursor: pointer;
  transition: transform .12s, border-color .12s;
}
.sp-label-swatch:hover { transform: scale(1.06); }
.sp-label-swatch.selected { border-color: var(--text); }

/* Description block */
.sp-desc-view {
  padding: .6rem .7rem; border-radius: var(--radius-sm);
  background: transparent; border: 1px solid transparent;
  color: var(--text-2); cursor: pointer; min-height: 70px;
  white-space: pre-wrap; word-break: break-word;
  transition: background .15s, border-color .15s;
  font-size: .9rem; line-height: 1.55;
}
.sp-desc-view:hover { background: var(--bg-2); }
.sp-desc-view.empty { color: var(--text-3); font-style: italic; }
.sp-desc-edit {
  width: 100%; min-height: 120px;
  padding: .6rem .7rem; border-radius: var(--radius-sm);
  background: var(--bg-2); border: 1px solid var(--ring);
  color: var(--text); resize: vertical; outline: none;
  font-family: inherit; font-size: .9rem; line-height: 1.55;
  box-shadow: 0 0 0 3px var(--ring-glow);
}
.sp-desc-actions { display: flex; gap: .4rem; margin-top: .5rem; justify-content: flex-end; }

/* Checklist */
.sp-checklist-progress {
  height: 6px; border-radius: 3px; background: var(--border-2);
  overflow: hidden; margin-bottom: .6rem;
}
.sp-checklist-progress > span {
  display: block; height: 100%; background: var(--brand);
  transition: width .3s;
}
.sp-checklist-item {
  display: flex; align-items: center; gap: .55rem;
  padding: .4rem .15rem; border-radius: 6px;
  transition: background .12s;
}
.sp-checklist-item:hover { background: var(--bg-2); }
.sp-checklist-item > input[type="checkbox"] { width: 18px; height: 18px; accent-color: var(--accent); cursor: pointer; }
.sp-checklist-item > .cli-text {
  flex: 1 1 auto; font-size: .9rem; color: var(--text);
  background: transparent; border: none; outline: none;
}
.sp-checklist-item.done > .cli-text { text-decoration: line-through; color: var(--text-3); }
.sp-checklist-item > .cli-del {
  opacity: 0; background: transparent; border: none; color: var(--text-3);
  cursor: pointer; padding: .2rem; border-radius: 4px;
  transition: opacity .12s, color .12s;
}
.sp-checklist-item:hover > .cli-del { opacity: 1; }
.sp-checklist-item > .cli-del:hover { color: var(--danger); }

.sp-checklist-add {
  display: flex; gap: .4rem; margin-top: .3rem;
}
.sp-checklist-add > input {
  flex: 1 1 auto; padding: .45rem .65rem; border-radius: var(--radius-sm);
  background: var(--bg-2); border: 1px solid var(--border);
  color: var(--text); outline: none; font-size: .88rem;
}
.sp-checklist-add > input:focus { border-color: var(--ring); box-shadow: 0 0 0 3px var(--ring-glow); }

/* Attachments */
.sp-attachments { display: grid; gap: .5rem; }
.sp-att-item {
  display: flex; align-items: center; gap: .7rem;
  padding: .55rem .7rem; border: 1px solid var(--border);
  border-radius: var(--radius-sm); background: var(--card);
  transition: border-color .15s, box-shadow .15s;
}
.sp-att-item:hover { border-color: var(--border-2); box-shadow: 0 2px 8px rgba(0,0,0,.15); }
.sp-att-thumb {
  width: 48px; height: 48px; border-radius: var(--radius-sm); flex: 0 0 auto;
  background: var(--bg-2);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; color: var(--text-2); font-size: 1.3rem;
}
.sp-att-thumb > img { width: 100%; height: 100%; object-fit: cover; }
.sp-att-info { flex: 1 1 auto; min-width: 0; }
.sp-att-name {
  font-weight: 600; font-size: .88rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sp-att-meta { font-size: .75rem; color: var(--text-3); }
.sp-att-actions { display: flex; gap: .3rem; flex: 0 0 auto; }
.sp-att-actions a, .sp-att-actions button {
  all: unset; cursor: pointer; color: var(--text-2);
  padding: .35rem; border-radius: 6px; display: inline-flex;
}
.sp-att-actions a:hover { color: var(--accent); background: var(--bg-2); }
.sp-att-actions button:hover { color: var(--danger); background: var(--bg-2); }

.sp-att-dropzone {
  padding: 1.1rem .8rem; border: 2px dashed var(--border-2);
  border-radius: var(--radius-field); text-align: center; color: var(--text-3);
  cursor: pointer; transition: all .15s; font-size: .85rem;
}
.sp-att-dropzone:hover, .sp-att-dropzone.drag-over {
  background: var(--bg-2); border-color: var(--ring); color: var(--text);
}

/* Comments */
.sp-comment {
  display: flex; gap: .7rem; padding: .6rem 0;
  border-bottom: 1px solid var(--border);
}
.sp-comment:last-child { border-bottom: none; }
.sp-comment-body { flex: 1 1 auto; min-width: 0; }
.sp-comment-head {
  display: flex; align-items: baseline; gap: .5rem;
  margin-bottom: .25rem;
}
.sp-comment-author { font-weight: 600; font-size: .88rem; }
.sp-comment-time { font-size: .72rem; color: var(--text-3); }
.sp-comment-text { font-size: .88rem; color: var(--text); white-space: pre-wrap; word-break: break-word; }

.sp-comment-add {
  display: flex; gap: .6rem; margin-top: .7rem; align-items: flex-start;
}
.sp-comment-add > textarea {
  flex: 1 1 auto; min-height: 44px; max-height: 180px;
  padding: .55rem .7rem; border-radius: var(--radius-sm);
  background: var(--bg-2); border: 1px solid var(--border);
  color: var(--text); outline: none; font-family: inherit;
  font-size: .88rem; resize: vertical;
}
.sp-comment-add > textarea:focus { border-color: var(--ring); box-shadow: 0 0 0 3px var(--ring-glow); }

/* ── Stage history timeline inside task modal ─────────────────────── */
.stage-timeline { display: flex; flex-direction: column; gap: .55rem; margin-top: .5rem; }
.stage-timeline-item {
  padding: .65rem .8rem; border-radius: var(--radius-sm); border: 1px solid var(--border);
  background: var(--card);
}
.stage-timeline-item.done { border-left: 3px solid var(--brand); }
html[dir="rtl"] .stage-timeline-item.done { border-left: 1px solid var(--border); border-right: 3px solid var(--brand); }
.stage-timeline-item.active { border-left: 3px solid var(--accent); background: var(--accent-soft); }
html[dir="rtl"] .stage-timeline-item.active { border-left: 1px solid var(--border); border-right: 3px solid var(--accent); background: var(--accent-soft); }
.stage-timeline-item.submitted { border-left: 3px solid var(--warn); background: rgba(245,158,11,.06); }
html[dir="rtl"] .stage-timeline-item.submitted { border-left: 1px solid var(--border); border-right: 3px solid var(--warn); background: rgba(245,158,11,.06); }
.stage-timeline-item.returned { border-left: 3px solid var(--danger); background: rgba(239,68,68,.05); }
html[dir="rtl"] .stage-timeline-item.returned { border-left: 1px solid var(--border); border-right: 3px solid var(--danger); background: rgba(239,68,68,.05); }
.stage-timeline-item .st-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: .25rem; }
.stage-timeline-item .st-name { font-weight: 600; font-size: .92rem; }
.stage-timeline-item .st-status { font-size: .72rem; color: var(--text-3); }
.stage-timeline-item .st-meta { font-size: .76rem; color: var(--text-2); }
.stage-timeline-item .st-notes { margin-top: .4rem; padding-top: .35rem; border-top: 1px dashed var(--border-2); font-size: .78rem; color: var(--text-2); }
.stage-timeline-item .st-note { padding: .15rem 0; }
.stage-timeline-item .st-note.return { color: var(--danger); }

/* ═════════════════════════════════════════════════════════════════════
 *  Timeline (الجدول) — Jira-grade build, 2026-05-15
 *  ───────────────────────────────────────────────────────────────────
 *  Layered structure inside one scrollable canvas:
 *    .gantt-grid                  2-col, 3-row CSS Grid (LABEL_W × TOTAL_W)
 *      .gantt-corner-banner       sticky top-start, empty (RTL-correct)
 *      .gantt-months              row 1 col 2 — month banners, sticky top:0
 *      .gantt-corner-days         sticky top-start, says "المهام"
 *      .gantt-days                row 2 col 2 — day cells + today pill
 *      .gantt-labels              row 3 col 1 — rich left column, sticky-start
 *      .gantt-tracks              row 3 col 2 — bars + overlays (today/deps)
 *
 *  All bar fills tint from `--bar-color` (set inline from
 *  companyStageSettings.colors[status]) so Kanban + Timeline share one
 *  palette. Hover/completed/overdue/milestone variants compose on top.
 * ═════════════════════════════════════════════════════════════════════ */

.gantt-toolbar {
  display: flex; align-items: center; gap: .6rem;
  padding: .4rem 0 .6rem; flex-wrap: wrap;
}
.gantt-zoom-group { display: inline-flex; gap: .35rem; flex-wrap: wrap; }

/* Single "Display" button replacing the 4 inline buttons (Week/Month/
 * Quarter/Today). Compact label "العرض: شهر" + chevron — opens the
 * popover above the button. The bolded active zoom name surfaces
 * current state without needing the user to open the popover. */
.gantt-display-btn {
  display: inline-flex; align-items: center; gap: .45rem;
  padding: .4rem .75rem;
  border: 1px solid var(--border);
  background: var(--card);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-family: inherit; font-size: .85rem; font-weight: 600;
  cursor: pointer;
  transition: background .12s, border-color .12s;
}
.gantt-display-btn:hover {
  background: var(--bg-3);
  border-color: var(--border-2);
}
.gantt-display-btn .gd-label { display: inline-flex; gap: .25rem; }
.gantt-display-btn .gd-label b { font-weight: 600; color: var(--brand); }
.gantt-display-btn .gd-chev { display: inline-flex; opacity: .7; }
.gantt-display-btn .gd-chev svg,
.gantt-display-btn .gd-chev .lucide-ic { width: 13px; height: 13px; }
.gantt-display-btn > svg:first-child,
.gantt-display-btn > .lucide-ic:first-child { width: 15px; height: 15px; }

/* Display popover — same option-row shell as .group-menu (.gm-opt/
 * .tgo-check/.tgo-label), but opens UPWARD (JS positions it above the
 * button) since the Gantt toolbar sits low on the page. Divider
 * separates the zoom section from the navigation row. */
.task-filter-menu.gantt-display-menu {
  padding: .5rem;
  min-width: 240px;
}
.task-filter-menu.gantt-display-menu .group { padding: 0; }
.task-filter-menu.gantt-display-menu .label {
  padding: .3rem .55rem .4rem;
  font-size: .7rem; font-weight: 600;
  color: var(--text-3);
  text-transform: uppercase;
  letter-spacing: .04em;
}
.gdm-divider {
  height: 1px;
  background: var(--border);
  margin: .35rem .25rem;
}

.gantt-scroll {
  overflow: auto;
  /* Auto-fit: shrink to content when there are few rows, cap at viewport
   * height when there are many. After the standalone toolbar was removed
   * (2026-05-15, zoom + today moved into the canvas corner cells), the
   * canvas reclaims that height — no offset subtraction needed. */
  max-height: 100%;
  width: 100%;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  scrollbar-color: var(--border-2) transparent;
}

.gantt-grid {
  display: grid;
  grid-template-rows: 32px 40px auto;
  min-width: min-content;
}

/* ── Top-start corner cells (sticky in both axes) ─────────────────── */
.gantt-corner {
  background: var(--card-2);
  border-inline-end: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  position: sticky;
  inset-inline-start: 0;
}
.gantt-corner-banner {
  top: 0; z-index: 6;
  height: 32px;
  display: flex; align-items: center;
  padding: 0 .4rem;
}
.gantt-corner-days {
  top: 32px; z-index: 5;
  height: 40px;
  display: flex; align-items: center;
  padding: 0 .4rem;
}

/* Corner buttons (Zoom + Today). Compact pills that turn the previously
 * empty top-start corner into a functional control surface — this is
 * what reclaims the ~56px of vertical real estate the old toolbar used.
 * Owner directive 2026-05-15: kill the toolbar, move controls here. */
.gantt-corner-btn {
  appearance: none;
  border: 1px solid transparent;
  background: transparent;
  display: inline-flex; align-items: center; gap: .3rem;
  width: auto;
  height: 24px;
  padding: 0 .45rem;
  border-radius: 7px;
  font-size: .74rem; font-weight: 600;
  color: var(--text-2);
  cursor: pointer;
  font-family: inherit;
  transition: background .12s, border-color .12s, color .12s;
  white-space: nowrap;
}
.gantt-corner-btn:hover {
  background: var(--bg-3);
  border-color: var(--border);
  color: var(--text);
}
.gantt-corner-btn svg, .gantt-corner-btn .lucide-ic { width: 12px; height: 12px; flex-shrink: 0; }
.gantt-corner-btn .gcb-label {
  min-width: 0;
  overflow: hidden; text-overflow: ellipsis;
  text-align: start;
}
.gantt-corner-btn .gcb-chev { opacity: .55; display: inline-flex; }
.gantt-today-corner {
  color: var(--brand);
}
.gantt-today-corner:hover {
  background: color-mix(in srgb, var(--brand) 10%, transparent);
  border-color: color-mix(in srgb, var(--brand) 35%, transparent);
  color: var(--brand);
}

/* ── Sidebar collapse (2026-07-07, owner ask) ───────────────────────
 * ClickUp-style fold: the 280px label column collapses to a 44px rail.
 * Bars already carry name + avatars (≥130px → inside the bar, narrower
 * → floating name beside it, wide bars park content under the eye), so
 * folding loses nothing — it hands ~236px back to the canvas. The rail
 * keeps every corner control alive (expand / zoom / today), stacked
 * vertically — the VS-Code-activity-bar idiom. */
.gantt-side-toggle { margin-inline-start: auto; }
/* Chevron points where the column will GO: toward the near edge when
 * expanded (fold), into the canvas when collapsed (unfold). The base
 * Lucide chevron points down; ±90° turns it sideways, mirrored per
 * writing direction so the metaphor survives RTL⇄LTR. */
.gantt-side-toggle svg, .gantt-side-toggle .lucide-ic { transition: transform var(--dur-2) var(--ease-standard); transform: rotate(-90deg); }
.gantt-side-toggle.is-expand svg, .gantt-side-toggle.is-expand .lucide-ic { transform: rotate(90deg); }
[dir="ltr"] .gantt-side-toggle svg, [dir="ltr"] .gantt-side-toggle .lucide-ic { transform: rotate(90deg); }
[dir="ltr"] .gantt-side-toggle.is-expand svg, [dir="ltr"] .gantt-side-toggle.is-expand .lucide-ic { transform: rotate(-90deg); }

/* Collapsed rail — sticky just under the two frozen header rows
 * (32px banner + 40px days), so the controls stay reachable however
 * far the canvas scrolls vertically. */
.gantt-side-rail {
  position: sticky; top: 78px;
  display: flex; flex-direction: column; align-items: center;
  gap: .35rem; padding: .45rem .2rem;
}
.gantt-side-rail .gantt-corner-btn {
  width: 34px; min-height: 30px;
  justify-content: center; padding: .25rem .15rem;
  border: 1px solid var(--border);
  background: var(--card-2);
}
.gantt-side-rail .gantt-corner-btn:hover { background: var(--bg-3); }
.gantt-side-rail .gantt-zoom-corner .gcb-label {
  font-size: .6rem; font-weight: 600; max-width: 30px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gantt-side-rail .gantt-zoom-corner .gcb-chev { display: none; }
/* NOTE: no overflow:hidden on .gantt-labels here — an overflow-clipping
 * ancestor becomes the scrollport for sticky descendants (same trap the
 * edge-cue overlay documents), which would anchor the rail's top:78px to
 * the column instead of the scroller and float it ~150px down. */

/* Column drag-resize (2026-07-07) — 7px invisible seam handle on the
 * label column's inline-end edge; glows brand on hover/drag. Dblclick
 * resets to the 280px default. */
.gantt-col-resizer {
  position: absolute; inset-block: 0; inset-inline-end: -3px;
  width: 7px; cursor: col-resize; z-index: 7;
  border-radius: 3px;
  transition: background .12s;
}
.gantt-col-resizer:hover,
.gantt-grid.is-col-resizing .gantt-col-resizer {
  background: color-mix(in srgb, var(--brand) 30%, transparent);
}
.gantt-grid.is-col-resizing { user-select: none; cursor: col-resize; }

/* Column wrapper for the kanban view — #view (.tasks-viewport) is a
 * flex ROW, so the customize bar + board must stack inside one
 * flex-column child or they'd render side by side. */
.kb-wrap {
  flex: 1 1 auto; min-width: 0; min-height: 0;
  display: flex; flex-direction: column;
}
.kb-wrap > .kanban { flex: 1 1 auto; min-height: 0; }

/* ── Table summary row (2026-07-07, Monday roll-up) ──────────────────
 * The list view foots with a live per-column aggregation. Sticky to the
 * scroll container's bottom so it stays in view while the body scrolls.
 * A muted per-group variant (.list-group-summary) closes each group. */
.table tfoot .list-summary-row td {
  position: sticky; bottom: 0; z-index: 2;
  background: var(--card-2);
  border-top: 2px solid var(--border);
  font-size: .78rem; color: var(--text-2);
  padding-block: .5rem;
}
.table tr.list-group-summary td {
  background: color-mix(in srgb, var(--bg-3) 45%, transparent);
  border-top: 1px dashed color-mix(in srgb, var(--border) 80%, transparent);
  font-size: .74rem; color: var(--text-3);
  padding-block: .3rem;
}
.col-summary { white-space: nowrap; vertical-align: middle; }
.col-summary .cs-val { font-weight: 600; font-variant-numeric: tabular-nums; }
.col-summary.cs-count .cs-val { color: var(--text); }
.col-summary .cs-alert { color: var(--danger); }
/* Completion mini-bar in the status column summary. */
.cs-complete { display: inline-flex; align-items: center; gap: .4rem; }
.cs-bar {
  width: 46px; height: 5px; border-radius: 999px;
  background: color-mix(in srgb, var(--text-3) 22%, transparent);
  overflow: hidden; flex: none;
}
.cs-bar-fill { display: block; height: 100%; border-radius: 999px; background: var(--brand); }
/* Cyclable numeric/money aggregate — a button that advances the mode. */
.cs-agg {
  appearance: none; border: 1px solid transparent; background: transparent;
  display: inline-flex; align-items: center; gap: .3rem;
  padding: .1rem .3rem; border-radius: 7px;
  cursor: pointer; color: inherit; font: inherit;
  transition: background .12s, border-color .12s;
}
.cs-agg:hover { background: var(--bg-3); border-color: var(--border); }
.cs-agg-glyph {
  font-size: .68rem; font-weight: 600; color: var(--brand);
  min-width: 12px; text-align: center;
}
.list-group-summary .cs-bar { width: 32px; }

/* ── Import modal (2026-07-07, Phase 1 Item 3) ───────────────────────── */
.import-modal { min-width: min(560px, 86vw); max-width: 640px; }
.import-lbl { display: block; font-size: .78rem; font-weight: 600; color: var(--text-2); margin: 0 0 .3rem; }
.import-textarea { width: 100%; min-height: 130px; resize: vertical; font-family: ui-monospace, 'JetBrains Mono', monospace; font-size: .8rem; line-height: 1.5; }
.import-filerow { display: flex; align-items: center; gap: .6rem; margin-top: .55rem; }
.import-actions { display: flex; justify-content: flex-end; gap: .5rem; margin-top: 1.2rem; }
.import-map-grid {
  display: grid; grid-template-columns: 120px 1fr; gap: .5rem .8rem;
  align-items: center; margin-bottom: 1rem;
}
.import-map-lbl { font-size: .84rem; font-weight: 600; color: var(--text); }
.import-req { color: var(--danger); }
.import-sample-wrap { overflow: auto; max-height: 200px; border: 1px solid var(--border); border-radius: 10px; }
.import-sample { width: 100%; font-size: .76rem; }
.import-sample th { position: sticky; top: 0; background: var(--card-2); font-weight: 600; white-space: nowrap; }
.import-sample td { color: var(--text-2); max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.import-done { text-align: center; padding: 1rem 0 .4rem; }
.import-done-icon {
  width: 54px; height: 54px; margin: 0 auto .3rem;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; background: color-mix(in srgb, var(--brand) 14%, transparent); color: var(--brand);
}
.import-done-icon svg, .import-done-icon .lucide-ic { width: 26px; height: 26px; }
.import-stat { font-size: .9rem; margin: .15rem 0; }
.import-stat b { font-size: 1.05rem; color: var(--text); }

/* ── Nested subtasks (2026-07-07, Phase 1 Item 4) ────────────────────── */
.sp-subtasks .sp-sub-list { display: flex; flex-direction: column; gap: .1rem; margin: .5rem 0; }
.sp-sub-item {
  display: flex; align-items: center; gap: .55rem;
  padding: .32rem .3rem; border-radius: var(--radius-sm);
  transition: background .12s;
}
.sp-sub-item:hover { background: var(--bg-3); }
.sp-sub-check {
  appearance: none; flex: none;
  width: 18px; height: 18px; border-radius: 50%;
  border: 1.5px solid var(--border-2, var(--border));
  background: var(--card); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  color: #fff; transition: background .12s, border-color .12s;
}
.sp-sub-check:hover { border-color: var(--brand); }
.sp-sub-check.is-done { background: var(--brand); border-color: var(--brand); }
.sp-sub-check svg, .sp-sub-check .lucide-ic { width: 11px; height: 11px; }
.sp-sub-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--sd, #94a3b8); flex: none; }
.sp-sub-title { flex: 1; min-width: 0; cursor: pointer; font-size: .88rem; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sp-sub-title:hover { color: var(--brand); text-decoration: underline; }
.sp-sub-item.is-done .sp-sub-title { text-decoration: line-through; color: var(--text-3); }
.sp-sub-av { flex: none; }
.sp-sub-av .avatar { width: 22px; height: 22px; font-size: .62rem; }

/* Subtask roll-up chip on list rows / cards. */
.row-subtask-chip {
  display: inline-flex; align-items: center; gap: .2rem;
  margin-inline-start: .4rem; padding: .05rem .4rem;
  border-radius: 999px; font-size: .68rem; font-weight: 600;
  background: var(--bg-3); color: var(--text-3);
  vertical-align: middle; font-variant-numeric: tabular-nums;
}
.row-subtask-chip.is-complete { background: color-mix(in srgb, var(--brand) 15%, transparent); color: var(--brand); }
.row-subtask-chip svg, .row-subtask-chip .lucide-ic { width: 11px; height: 11px; }

/* ── Watchers + reminders (2026-07-07, Phase 1 Item 5) ───────────────── */
.sp-watch-btn { position: relative; }
.sp-watch-btn.is-on { color: var(--brand); background: color-mix(in srgb, var(--brand) 12%, transparent); }
.sp-watch-count {
  position: absolute; top: -3px; inset-inline-end: -3px;
  min-width: 15px; height: 15px; padding: 0 3px;
  border-radius: 999px; background: var(--brand); color: #fff;
  font-size: .6rem; font-weight: 600; line-height: 15px; text-align: center;
}
.sp-rem-list { display: flex; flex-direction: column; gap: .25rem; margin: .5rem 0; }
.sp-rem-item { display: flex; align-items: center; gap: .5rem; padding: .35rem .5rem; border-radius: var(--radius-sm); background: var(--bg-3); font-size: .82rem; }
.sp-rem-item.is-past:not(.is-fired) { border: 1px solid color-mix(in srgb, var(--danger) 40%, transparent); }
.sp-rem-item.is-fired { opacity: .6; }
.sp-rem-when { display: inline-flex; align-items: center; gap: .3rem; color: var(--text); font-weight: 600; white-space: nowrap; }
.sp-rem-when svg, .sp-rem-when .lucide-ic { width: 13px; height: 13px; }
.sp-rem-note { flex: 1; min-width: 0; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sp-rem-flag { font-size: .68rem; font-weight: 600; color: var(--brand); flex: none; }
.sp-rem-del { appearance: none; border: 0; background: transparent; cursor: pointer; color: var(--text-3); flex: none; padding: .1rem; border-radius: 6px; }
.sp-rem-del:hover { color: var(--danger); background: color-mix(in srgb, var(--danger) 12%, transparent); }
.sp-rem-del svg, .sp-rem-del .lucide-ic { width: 13px; height: 13px; }
.sp-rem-presets { display: flex; flex-wrap: wrap; gap: .35rem; margin-top: .3rem; }
.sp-rem-custom { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .5rem; align-items: center; }
.sp-rem-custom .input { flex: 1; min-width: 120px; }

/* Recurrence picker (Phase 2) — freq select + interval, inline in the panel. */
.sp-recur { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap; }
.sp-recur .sp-recur-freq { width: auto; min-width: 110px; padding-block: .25rem; }
.sp-recur .sp-recur-every { display: inline-flex; align-items: center; gap: .35rem; font-size: .82rem; color: var(--text-2); }
.sp-recur .sp-recur-interval { width: 62px; padding-block: .25rem; text-align: center; }
.sp-recur-every[hidden] { display: none; }
/* Recurring badge on list rows / cards. */
.row-recur-chip { display: inline-flex; align-items: center; gap: .2rem; margin-inline-start: .4rem; color: var(--text-3); font-size: .7rem; font-weight: 600; vertical-align: middle; }
.row-recur-chip svg, .row-recur-chip .lucide-ic { width: 12px; height: 12px; }

/* Board bar above the kanban — hosts تخصيص الحالات when a single
 * project is selected. Slim so it doesn't eat board height. */
.kb-board-bar {
  display: flex; justify-content: flex-end;
  padding: 0 .15rem .45rem;
  flex: 0 0 auto;
}
.kb-statuses-btn { display: inline-flex; align-items: center; gap: .35rem; white-space: nowrap; }
.kb-statuses-btn svg, .kb-statuses-btn .lucide-ic { width: 14px; height: 14px; }

/* Custom-status editor rows (openStatusEditor modal). */
.se-row {
  display: flex; align-items: center; gap: .5rem;
  padding: .3rem 0;
}
.se-row .se-label { flex: 1; min-width: 0; }
.se-row .se-cat { width: 118px; flex: none; }
.se-row .se-color {
  width: 30px; height: 30px; padding: 2px; flex: none;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--card); cursor: pointer;
}
.se-move { display: inline-flex; flex-direction: column; gap: 2px; flex: none; }
.se-mv {
  appearance: none; border: 1px solid var(--border); background: var(--card-2);
  border-radius: 6px; width: 22px; height: 15px;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; color: var(--text-2); padding: 0;
}
.se-mv:hover:not(:disabled) { background: var(--bg-3); color: var(--text); }
.se-mv:disabled { opacity: .35; cursor: default; }
.se-mv svg, .se-mv .lucide-ic { width: 11px; height: 11px; }
.se-mv.se-up svg, .se-mv.se-up .lucide-ic { transform: rotate(180deg); }
.se-del { flex: none; }

/* ── Month banner row ──────────────────────────────────────────────── */
.gantt-months {
  display: flex; flex-direction: row;
  position: sticky; top: 0; z-index: 4;
  background: var(--card-2);
  border-bottom: 1px solid var(--border);
  height: 32px;
}
.gantt-month-banner {
  display: flex; align-items: center; justify-content: center;
  font-size: .8rem; font-weight: 600;
  color: var(--text);
  border-inline-end: 1px solid var(--border);
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
  /* No tracking — Arabic month names are connected script. */
  letter-spacing: 0;
}
.gantt-month-banner > span {
  /* inline-start inset clears the 280px sticky labels column — without
   * it the pinned month label hides underneath the column whenever its
   * banner extends past the viewport edge (common now that the window
   * covers task history). */
  position: sticky; inset-inline-start: calc(280px + .8rem); inset-inline-end: .8rem;
  white-space: nowrap;
}

/* ── Day cells row ─────────────────────────────────────────────────── */
.gantt-days {
  display: flex; flex-direction: row;
  position: sticky; top: 32px; z-index: 3;
  background: var(--card-2);
  border-bottom: 1px solid var(--border);
  height: 40px;
}
.gantt-day-cell {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1px;
  flex-shrink: 0;
  /* Hairline at 45% (was 60%) — the day strip reads as one calm band,
   * not a picket fence, now that every cell carries two text lines. */
  border-inline-end: 1px solid color-mix(in srgb, var(--border) 45%, transparent);
  font-variant-numeric: tabular-nums;
}
.gantt-day-num {
  font-size: .78rem; font-weight: 600; color: var(--text);
  line-height: 1.15;
}
.gantt-day-dow {
  font-size: .58rem; font-weight: 600;
  color: var(--text-3); text-transform: uppercase;
  letter-spacing: 0;
  line-height: 1;
}
.gantt-day-cell.is-weekend { background: color-mix(in srgb, var(--text-3) 5%, transparent); }
.gantt-day-cell.is-weekend .gantt-day-num,
.gantt-day-cell.is-weekend .gantt-day-dow { color: var(--text-3); }
/* Today (2026-07-10 redesign) — the calendar-app idiom: a filled brand
 * disc around the day number (white numeral), weekday letter in brand.
 * Replaces the old full-cell wash, which stamped a colored column on
 * the header; the disc is a point cue that pairs with the today line
 * dropping beneath it. */
.gantt-day-cell.is-today { background: transparent; }
.gantt-day-cell.is-today .gantt-day-num {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 21px; height: 21px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--brand);
  color: #fff;
  font-weight: 600;
  box-shadow: 0 1px 3px color-mix(in srgb, var(--brand) 40%, transparent);
}
.gantt-day-cell.is-today .gantt-day-dow { color: var(--brand); font-weight: 600; }

/* Day-zoom — each day is 480px wide so we have room to surface hour
 * markers under the date label. Sub-day positioning (xOf in JS) has
 * always been there in calc; this is the first zoom that gives those
 * fractions enough screen space to read.
 *
 * Owner ask 2026-05-15: "اقترح اضافة الساعات للايام في الجدول ليتم
 * الحساب بالساعات" — calculation by hours, surfaced visually here. */
.gantt-day-cell.is-day-zoom {
  padding: 4px 8px 6px;
  align-items: stretch;
  justify-content: space-between;
}
.gantt-day-cell.is-day-zoom .gantt-day-num {
  text-align: center;
  font-size: .82rem;
  font-weight: 600;
}
.gantt-day-hours {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 .25rem;
}
.gantt-hour-tick {
  font-size: .58rem;
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
  letter-spacing: .02em;
}

/* Today's day-cell at day zoom — brand tint + bolder name so the eye
 * lands on "today" without hunting for the today line. Mirrors how
 * Asana / Monday paint a "current day" pill in their day view. */
.gantt-day-cell.is-day-zoom.is-today {
  background: color-mix(in srgb, var(--brand) 8%, transparent);
}
.gantt-day-cell.is-day-zoom.is-today .gantt-day-num {
  color: var(--brand);
  font-weight: 600;
}
.gantt-day-cell.is-day-zoom.is-today .gantt-hour-tick {
  color: color-mix(in srgb, var(--brand) 70%, var(--text-3));
}

/* ── Rich left column (labels) ─────────────────────────────────────── */
.gantt-labels {
  position: sticky; inset-inline-start: 0; z-index: 5;
  background: var(--card);
  border-inline-end: 1px solid var(--border);
  display: flex; flex-direction: column;
}
/* z-index hierarchy at the canvas (2026-05-18):
 *   today-line / now-line ........ 0   (behind everything)
 *   dep arrows .................... 1
 *   .gantt-bar / .gantt-stage-bar . 2
 *   .gantt-labels ................. 5   (always rides ABOVE bars so
 *                                        scrolled bars pass UNDER the
 *                                        sticky card, not over it)
 *   .gantt-months / .gantt-days ... 3-4 (sticky day-header rows)
 *   .gantt-corner-* ............... 5-6 (top-start sticky corner cells)
 */
.gantt-label {
  position: relative;             /* anchors the ::before/::after rail pseudos */
  display: flex; align-items: center; gap: .65rem;
  padding: .5rem .9rem;
  height: 56px;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  font-size: .85rem; color: var(--text);
  cursor: pointer;
  transition: background .12s;
  min-width: 0;
}
.gantt-label:hover,
.gantt-label.is-row-hover {
  background: var(--bg-3);
}

/* Drag-to-reorder handle (2026-05-18) — 4 faint dots in a 2×2 grid on
 * the leading edge of the row. Opacity climbs from .3 → 1 on row hover
 * so the affordance only signals when the user's pointer is on the row.
 * The handle IS the drag initiator (owner 2026-07-07): pressing it arms
 * the row's draggable flag; pressing anywhere else is a plain click. It
 * stretches full row height + a little horizontal padding so it's an
 * easy grab target while the dots stay centered. */
.gantt-drag-handle {
  flex-shrink: 0;
  /* Breathing room from the row's inline-start padding so the dots don't
   * hug the column wall (visible most clearly in cascade rows where the
   * chevron's divider compounds the "squeezed" feel). 2026-05-18. */
  margin-inline-start: .35rem;
  align-self: stretch;          /* full-height grab target */
  display: grid;
  grid-template-columns: 3px 3px;
  grid-template-rows: 3px 3px;
  place-content: center;        /* keep the 2×2 dots centered in that height */
  gap: 2px;
  padding-inline: 3px;
  cursor: grab;
  opacity: .3;
  transition: opacity var(--dur-1) var(--ease-standard);
  touch-action: none;           /* let the grip own the pointer gesture */
}
.gantt-drag-handle i {
  display: block;
  width: 3px; height: 3px;
  border-radius: 50%;
  background: var(--text-2);
}
.gantt-label:hover .gantt-drag-handle,
.gantt-label.is-row-hover .gantt-drag-handle { opacity: 1; }
.gantt-label.is-dragging { opacity: .45; cursor: grabbing; }
.gantt-label.is-dragging .gantt-drag-handle { cursor: grabbing; }
/* Drop cues — a 2px brand line painted on the leading edge of the
 * target row, ABOVE or BELOW, telling the user exactly where the
 * dragged row will land. Pseudo-element so it doesn't reflow the row. */
.gantt-label.is-drop-above::before,
.gantt-label.is-drop-below::before {
  content: '';
  position: absolute;
  inset-inline-start: 0; inset-inline-end: 0;
  height: 2px;
  background: var(--brand);
  z-index: 3;
  pointer-events: none;
}
.gantt-label.is-drop-above::before { top: -1px; }
.gantt-label.is-drop-below::before { bottom: -1px; }
/* Stage child labels + phantoms don't participate in reorder, so they
 * don't get a handle. Phantom rows hover-add already; stages move with
 * their parent. (Defense-in-depth: even if a handle leaked in, the CSS
 * here makes it invisible.) */
.gantt-label-stage .gantt-drag-handle,
.gantt-label-phantom .gantt-drag-handle { display: none; }

/* Restore button (2026-05-18) — icon-only on archived rows. Square,
 * brand-tinted, with the full "إرجاع من الأرشيف" label carried by
 * title + aria-label. Class name stays .gantt-restore-pill so the
 * existing JS markup + tests keep matching. */
.gantt-restore-pill {
  /* Sits in the FIXED leading rail (type icon → pill → title) so the
   * pills align vertically across rows — the flex gap spaces it. */
  flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px;
  padding: 0;
  background: color-mix(in srgb, var(--brand) 12%, transparent);
  color: var(--brand);
  border: 1px solid color-mix(in srgb, var(--brand) 35%, transparent);
  border-radius: 7px;
  cursor: pointer;
  transition: background .12s ease, border-color .12s ease;
}
.gantt-restore-pill:hover {
  background: var(--brand);
  color: var(--brand-fg, #fff);
  border-color: var(--brand);
}
.gantt-restore-pill svg,
.gantt-restore-pill .lucide-ic { width: 14px; height: 14px; }

/* Archive-row compaction (2026-05-18) — at archive scope a 280px label
 * column hosts chevron + handle + body + restore-pill. We drop content
 * that's redundant in this read-only context to free body space:
 *   • status pill: "مكتملة" — archived implies closed, the badge is noise
 *   • comments badge: not actionable in archive review
 *   • avatar stack → single avatar: last toucher carries the story */
.gantt-label.is-archived-row .gantt-status-dot,
.gantt-label.is-archived-row .gantt-label-comments { display: none; }
.gantt-label.is-archived-row .gantt-avatar-stack .gantt-avatar:not(:first-child),
.gantt-label.is-archived-row .gantt-avatar-stack .gantt-avatar-more { display: none; }

/* ── Archive chapter (2026-07-10) — the archive as a first-class room ──
 * List rows, board cards, scope-pill count, month sections, skeleton and
 * empty state. Same emerald restore language as .gantt-restore-pill so
 * استرجاع reads identically in every canvas. */

/* Scope pill count — «الأرشيف (312)» before a single row is fetched. */
.scope-btn .scope-count {
  font-size: .72rem; font-weight: 600;
  font-family: var(--font-numeric, inherit);
  background: var(--bg-3); color: var(--text-3);
  border-radius: 999px; padding: .05rem .4rem;
  line-height: 1.2;
}
.scope-btn.active .scope-count {
  background: color-mix(in srgb, var(--brand) 18%, transparent);
  color: var(--brand);
}

/* List restore pill — the row's primary action in the archive. Leads
 * the title cell (fixed column edge → pills align vertically, beside
 * the type-icon column) with the task name after it. */
.row-restore-pill {
  display: inline-flex; align-items: center; gap: .3rem;
  margin-inline-end: .5rem; padding: .12rem .5rem;
  font-size: .75rem; font-weight: 600; font-family: inherit;
  background: color-mix(in srgb, var(--brand) 12%, transparent);
  color: var(--brand);
  border: 1px solid color-mix(in srgb, var(--brand) 35%, transparent);
  border-radius: 999px;
  cursor: pointer;
  vertical-align: middle;
  transition: background .12s ease, border-color .12s ease, color .12s ease;
}
.row-restore-pill:hover { background: var(--brand); color: var(--brand-fg, #fff); border-color: var(--brand); }
.row-restore-pill svg, .row-restore-pill .lucide-ic { width: 13px; height: 13px; }

/* Archived list rows — quiet, and visibly not-live: content dims, the
 * restore pill stays at full strength as the one actionable element. */
.table tr.is-archived-row td { opacity: .75; }
.table tr.is-archived-row td .row-restore-pill { opacity: 1; }

/* Archive month section head — reuses .list-group-head chrome; neutral
 * stripe (months are chronology, not semantics). */
.list-group-head.archive-month-head { --group-color: var(--text-3); }
.list-group-head.archive-month-head .lgh-toggle { transition: transform var(--dur-1, .15s) var(--ease-standard, ease); }
.list-group-head.archive-month-head.is-collapsed-head .lgh-toggle { transform: rotate(-90deg); }

/* Frozen board card — archived implies read-only until restored. */
.kanban-task.is-archived-card { opacity: .78; }
.kanban-task.is-archived-card:active { cursor: default; }
.card-archived-chip { display: inline-flex; align-items: center; gap: .3rem; font-size: .7rem; padding: .1rem .45rem; border-radius: 999px; border: 1px solid; }
.card-archived-chip svg, .card-archived-chip .lucide-ic { width: 12px; height: 12px; }

/* Archive skeleton — shimmer rows while ?archivedOnly=1 is in flight. */
.archive-skeleton { display: flex; flex-direction: column; gap: .55rem; padding: 1.2rem; }
.archive-skeleton-row {
  height: 40px; border-radius: var(--radius-sm);
  background: var(--bg-3);
  animation: archive-shimmer 1.2s ease-in-out infinite;
}
@keyframes archive-shimmer { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .archive-skeleton-row { animation: none; } }

/* Current-stage action row — lives INSIDE the stage timeline card
 * (2026-07-12): the decision buttons sit with the state they act on. */
.stage-timeline-item .st-actions {
  margin-top: .55rem; padding-top: .55rem;
  border-top: 1px dashed var(--border);
}

/* Archive empty / error states. */
.col-empty.archive-state { text-align: center; padding: 1.6rem 1.2rem; }
.archive-empty-ic { display: inline-flex; margin-bottom: .5rem; color: var(--icon-ink); }
.archive-empty-ic svg, .archive-empty-ic .lucide-ic { width: 26px; height: 26px; }

/* Task-type icon (2026-07-07, owner pick) — a small MONOCHROME icon right
 * before the title: person = solo, people = team, workflow = pipeline.
 * The icon SHAPE carries the type, so it needs no colour and no legend;
 * the colour channel is freed for the priority stripe. */
.gantt-type-ic {
  flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--icon-ink);
}
.gantt-type-ic svg, .gantt-type-ic .lucide-ic { width: 15px; height: 15px; }

/* Priority edge stripe (2026-07-07) — 3px bar flush to the row's leading
 * edge. Only urgent/high get a colour (red / amber) so the eye lands on
 * what needs attention; normal priorities keep a faint neutral rail so
 * the edge stays a consistent visual anchor. Absolute so it never
 * competes for flex space; the grip sits just inside it. */
.gantt-prio-stripe {
  position: absolute; inset-block: 0; inset-inline-start: 0;
  width: 3px; border-radius: 0 2px 2px 0;
  flex-shrink: 0; pointer-events: none;
}
.gantt-prio-stripe.is-urgent { background: var(--danger); }
.gantt-prio-stripe.is-high   { background: var(--warn); }
.gantt-prio-stripe.is-normal { background: var(--border-2); }
/* Status as a color dot — the name lives in the tooltip; the bar in the
 * canvas carries the same colour, so row + bar agree without a text pill. */
.gantt-status-dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--sd, #94a3b8);
  flex-shrink: 0;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--sd, #94a3b8) 18%, transparent);
}

/* Two-line label body — title + avatars on line 1, status + priority
 * on line 2. Mirrors Jira/Linear/Asana density. Title gets room to
 * breathe instead of fighting status pill + avatars on a single line. */
.gantt-label-body {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column;
  gap: 3px;
  justify-content: center;
}
/* Single-line body (2026-07-07) — after dropping the text status pill the
 * label collapses to one row: status dot + title …… priority · comments ·
 * avatars. Denser, and the eye tracks one baseline instead of two. */
.gantt-label-body.is-single {
  flex-direction: row; align-items: center; gap: .5rem;
}
.gantt-label-body.is-single .gantt-label-fill { flex: 1; min-width: .25rem; }
.gantt-label-body.is-single .gantt-label-text { flex: 0 1 auto; }
.gantt-label-line1 {
  display: flex; align-items: center; gap: .5rem;
  min-width: 0;
}
.gantt-label-line2 {
  display: inline-flex; align-items: center; gap: .45rem;
  min-width: 0;
}
.gantt-label-text {
  flex: 1; min-width: 0;
  font-weight: 600; font-size: .9rem;
  color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  line-height: 1.25;
}
.gantt-prio-dot {
  width: 7px; height: 7px; border-radius: 50%;
  flex-shrink: 0;
}
.gantt-status-pill {
  font-size: .66rem; font-weight: 600;
  padding: 1px 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--pill-color) 16%, transparent);
  color: var(--pill-color);
  white-space: nowrap;
  letter-spacing: .01em;
  line-height: 1.5;
}
.gantt-avatar-stack {
  display: inline-flex; align-items: center;
}
.gantt-avatar-stack .gantt-avatar { margin-inline-start: -6px; }
.gantt-avatar-stack .gantt-avatar:first-child { margin-inline-start: 0; }
.gantt-avatar .avatar {
  width: 22px; height: 22px; font-size: .62rem;
  border: 2px solid var(--card);
  box-sizing: content-box;
}
.gantt-avatar-more {
  width: 22px; height: 22px;
  border-radius: 50%;
  background: var(--bg-3);
  color: var(--text-2);
  font-size: .62rem; font-weight: 600;
  display: inline-flex; align-items: center; justify-content: center;
  border: 2px solid var(--card);
}

/* ── Track column (bars + overlays) ────────────────────────────────── */
.gantt-tracks {
  position: relative;
  /* 2026-05-18: paint the day-boundary grid on the tracks container
   * itself so the visual continues BELOW the last task row, filling
   * the empty canvas with the same vertical rhythm as the populated
   * rows. The JS now sets --cell inline on this element so the grid
   * matches the active zoom exactly (was using a hardcoded 36px
   * fallback that read wrong at day/week/quarter zooms). The today
   * line + weekend bands also extend into this area. */
  background:
    repeating-linear-gradient(
      to right,
      transparent 0,
      transparent calc(var(--cell) - 1px),
      color-mix(in srgb, var(--border) 40%, transparent) calc(var(--cell) - 1px),
      color-mix(in srgb, var(--border) 40%, transparent) var(--cell)
    );
  /* Fill the visible scroll height so the grid extends visually even
   * when there are very few tasks. Tracks always get the absolute
   * `min-height` from JS based on real rows; this floor protects the
   * "near-empty" case. */
  min-height: 360px;
}
/* Workday tint (2026-05-18) — Day-zoom only. Soft warm overlay across
 * the 8 AM → 6 PM band signals "this is the work window" without
 * dictating it (off-hours are still fully usable, just visually
 * quieter). z-index: 0 keeps it behind bars (z:2) and dep arrows
 * (z:1). Pointer-events: none so it never blocks clicks on bars. */
.gantt-workday-tint {
  position: absolute;
  top: 0; bottom: 0;
  /* Cream tint — warm enough to read as "daytime", subtle enough to
   * not compete with the bars / status colors on top. Switches to a
   * deeper amber in dark mode for the same warm-day feel. */
  background: color-mix(in srgb, #fef3c7 50%, transparent);
  pointer-events: none;
  z-index: 0;
}
html.dark .gantt-workday-tint {
  background: color-mix(in srgb, #78350f 18%, transparent);
}
/* Caption pill naming the band — sits at its inline-start top so it
 * never overlaps a bar body. Neutral chrome (not brand) so it reads as
 * a label, not an interactive element. */
.gantt-workday-caption {
  position: absolute;
  top: 4px;
  inset-inline-start: 6px;
  z-index: 1;
  padding: 1px 8px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text-2);
  font-size: .62rem;
  font-weight: 600;
  white-space: nowrap;
  pointer-events: none;
}
/* Display-popover legend — decodes the canvas overlays. */
.gantt-legend .gl-row {
  display: flex; align-items: center; gap: .5rem;
  padding: .25rem .1rem;
  font-size: .78rem;
  color: var(--text-2);
}
.gantt-legend .gl-swatch {
  width: 14px; height: 14px; border-radius: 4px;
  border: 1px solid var(--border);
  flex-shrink: 0;
}
.gantt-legend .gl-work    { background: color-mix(in srgb, #fef3c7 50%, transparent); }
html.dark .gantt-legend .gl-work { background: color-mix(in srgb, #78350f 40%, transparent); }
.gantt-legend .gl-weekend { background: color-mix(in srgb, var(--text-3) 12%, transparent); }
.gantt-legend .gl-now     { background: var(--brand); border-color: var(--brand); }

/* Now-line (2026-05-18) — precise current-time marker, day-zoom only.
 * Brand-colored, slightly thicker than the today-line so it reads as
 * "this is THIS MOMENT" not just "this is today". The HH:MM label
 * sits at the TOP of the line as a small pill — distinguishes the
 * now-line from the today-line at a glance. Updates every 60s via
 * the JS minute-ticker. */
.gantt-now-line {
  position: absolute;
  top: 0; bottom: 0;
  width: 2px;
  background: var(--brand);
  z-index: 2;
  pointer-events: none;
}
.gantt-now-label {
  position: absolute;
  top: -22px;
  inset-inline-start: 50%;
  transform: translateX(-50%);
  background: var(--brand);
  color: var(--brand-fg, #fff);
  font-size: .62rem;
  font-weight: 600;
  padding: 2px 7px;
  border-radius: 999px;
  white-space: nowrap;
  letter-spacing: .03em;
  font-variant-numeric: tabular-nums;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--brand) 70%, #000);
}
html[dir="rtl"] .gantt-now-label { transform: translateX(50%); }

/* Phantom rows (2026-05-18) — ghost rows below the last task that
 * accept clicks to open the add-task wizard. The label-column halves
 * stay quiet (no content) until hovered, at which point a faint dashed
 * "+ مهمّة جديدة" hint appears at the inline-start edge. The track
 * halves continue the day-grid pattern so the visual feels like
 * "more canvas, ready for work". */
.gantt-label-phantom,
.gantt-track-phantom {
  height: 52px;
  cursor: pointer;
  position: relative;
  transition: background-color .12s ease;
}
.gantt-label-phantom {
  border-bottom: 1px dashed color-mix(in srgb, var(--border) 75%, transparent);
}
.gantt-track-phantom {
  border-bottom: 1px dashed color-mix(in srgb, var(--border) 55%, transparent);
}
/* "+ مهمّة" hint at the inline-start edge. Now that phantom rows tile
 * to fill the whole canvas (2026-07-04), a hint on every row would be a
 * wall of repeated ghost labels — so only the FIRST phantom shows it at
 * rest; the others reveal on :hover. 2026-05-18 owner ask (empty area
 * shouldn't look dead) is still met by that one resting invitation. */
.gantt-label-phantom::after {
  content: '+ ' attr(data-add-label);
  position: absolute;
  top: 50%;
  inset-inline-start: 1rem;
  transform: translateY(-50%);
  font-size: .78rem;
  font-weight: 600;
  color: var(--brand);
  letter-spacing: .01em;
  opacity: 0;
  transition: opacity .12s ease;
  pointer-events: none;
}
.gantt-label-phantom.is-first-phantom::after { opacity: .42; }
.gantt-label-phantom:hover {
  background: color-mix(in srgb, var(--brand) 6%, transparent);
}
.gantt-label-phantom:hover::after { opacity: 1; }
/* Pair the hover state — hovering EITHER half lights up both, since
 * the JS click handler is on both. */
.gantt-track-phantom:hover ~ .gantt-track-phantom,
.gantt-track-phantom:hover {
  background: color-mix(in srgb, var(--brand) 5%, transparent);
}

/* ── Archive month headers (2026-07-06) ─────────────────────────────
 * Collapsible section rows that bucket archived tasks by month of
 * archival, so a crowded archive folds to a handful of month rows.
 * The label half carries the chevron + month name + count pill; the
 * track half is a matching tinted band so both columns stay in
 * vertical step. Only rendered in the archive scope. Chevron follows
 * the .gantt-cascade-btn convention: down = open, sideways = folded. */
.gantt-month-header {
  position: relative;
  display: flex; align-items: center; gap: .55rem;
  padding: 0 .9rem;
  height: 44px;
  cursor: pointer;
  background: color-mix(in srgb, var(--brand) 7%, var(--bg-2));
  border-top: 1px solid color-mix(in srgb, var(--border) 45%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
  font-weight: 600;
  color: var(--text);
  user-select: none;
  transition: background .12s ease;
}
.gantt-month-header:hover { background: color-mix(in srgb, var(--brand) 12%, var(--bg-2)); }
.gantt-month-header:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.gantt-month-chevron {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; flex-shrink: 0;
  color: var(--brand);
}
.gantt-month-chevron svg, .gantt-month-chevron .lucide-ic {
  width: 14px; height: 14px;
  transition: transform .15s ease;
  transform: rotate(0deg);            /* open → arrow points down */
}
.gantt-month-header.is-collapsed .gantt-month-chevron svg { transform: rotate(-90deg); }
[dir="rtl"] .gantt-month-header.is-collapsed .gantt-month-chevron svg { transform: rotate(90deg); }
.gantt-month-title {
  font-size: .9rem;
  letter-spacing: .01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  min-width: 0;
}
.gantt-month-count {
  flex-shrink: 0;
  margin-inline-start: auto;
  min-width: 22px;
  padding: .12rem .5rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--brand) 16%, transparent);
  color: var(--brand);
  font-size: .74rem;
  font-weight: 600;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.gantt-month-band {
  height: 44px;
  background: color-mix(in srgb, var(--brand) 4%, var(--bg-2));
  border-top: 1px solid color-mix(in srgb, var(--border) 35%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
}

/* Day-zoom (1 day full, cell=1440): paint hour ticks PLUS the day
 * boundary on the container so the empty area reads exactly like the
 * populated rows. 24 hour ticks per day at 60px each. */
.gantt-grid.is-day-zoom .gantt-tracks {
  background:
    repeating-linear-gradient(
      to right,
      transparent 0,
      transparent calc(var(--cell) / 24 - 1px),
      color-mix(in srgb, var(--border) 22%, transparent) calc(var(--cell) / 24 - 1px),
      color-mix(in srgb, var(--border) 22%, transparent) calc(var(--cell) / 24)
    ),
    repeating-linear-gradient(
      to right,
      transparent 0,
      transparent calc(var(--cell) - 1px),
      color-mix(in srgb, var(--border) 60%, transparent) calc(var(--cell) - 1px),
      color-mix(in srgb, var(--border) 60%, transparent) var(--cell)
    );
}

.gantt-track {
  position: relative;
  height: 56px;
  /* Clip bars that start before the visible window so they don't bleed
   * into the labels grid cell. Today/now/deps overlays are siblings, not
   * children, so they are unaffected. */
  overflow: hidden;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  background:
    repeating-linear-gradient(
      to right,
      transparent 0,
      transparent calc(var(--cell) - 1px),
      color-mix(in srgb, var(--border) 70%, transparent) calc(var(--cell) - 1px),
      color-mix(in srgb, var(--border) 70%, transparent) var(--cell)
    );
}
.gantt-track.is-row-hover {
  background-color: color-mix(in srgb, var(--bg-3) 100%, transparent);
}

/* Day-zoom hour grid (2026-05-16) — owner ask: "لا توجد الساعات لليوم
 * كامل". Adds 24 lighter vertical ticks per day-cell on top of the
 * heavier day boundaries, giving every hour its own visual anchor.
 * Stage tracks get the same treatment via the .gantt-track-stage
 * rule below — they were paint-overriding the base .gantt-track
 * background. */
.gantt-grid.is-day-zoom .gantt-track,
.gantt-grid.is-day-zoom .gantt-track-stage {
  background:
    /* Hour ticks at every 1/24 of a day cell (= 20px at cell=480) */
    repeating-linear-gradient(
      to right,
      transparent 0,
      transparent calc(var(--cell) / 24 - 1px),
      color-mix(in srgb, var(--border) 22%, transparent) calc(var(--cell) / 24 - 1px),
      color-mix(in srgb, var(--border) 22%, transparent) calc(var(--cell) / 24)
    ),
    /* Day boundaries (stronger) */
    repeating-linear-gradient(
      to right,
      transparent 0,
      transparent calc(var(--cell) - 1px),
      color-mix(in srgb, var(--border) 70%, transparent) calc(var(--cell) - 1px),
      color-mix(in srgb, var(--border) 70%, transparent) var(--cell)
    );
}
/* Stage-track variant preserves its indent tint + pipeline backdrop
 * (B2) AND adds the hour grid on top. The is-pipeline-row layer is
 * always painted last so it sits behind the grids. */
.gantt-grid.is-day-zoom .gantt-track-stage.is-pipeline-row {
  background:
    /* Indent tint at inline-start edge anchors children to parent */
    linear-gradient(to right,
      color-mix(in srgb, var(--bg-3) 60%, transparent) 0,
      color-mix(in srgb, var(--bg-3) 60%, transparent) 14px,
      transparent 14px),
    /* Hour ticks every 1/24 of day cell */
    repeating-linear-gradient(
      to right,
      transparent 0,
      transparent calc(var(--cell) / 24 - 1px),
      color-mix(in srgb, var(--border) 22%, transparent) calc(var(--cell) / 24 - 1px),
      color-mix(in srgb, var(--border) 22%, transparent) calc(var(--cell) / 24)
    ),
    /* Day boundaries */
    repeating-linear-gradient(
      to right,
      transparent 0,
      transparent calc(var(--cell) - 1px),
      color-mix(in srgb, var(--border) 70%, transparent) calc(var(--cell) - 1px),
      color-mix(in srgb, var(--border) 70%, transparent) var(--cell)
    ),
    color-mix(in srgb, var(--bg-3) 22%, transparent);
}

/* ── Off-screen edge cues (2026-07-03) ─────────────────────────────────
 * Per-row sticky chips shown when the row's bar sits fully outside the
 * horizontal viewport. The strip spans the whole track; the two chips
 * stick to the scrollport edges (start chip just past the sticky labels
 * column). JS toggles [hidden] per scroll frame + handles the click. */
.gantt-cues-overlay {
  position: absolute; inset: 0;
  pointer-events: none;
  z-index: 3;
}
.gantt-cue-strip {
  position: absolute; inset-inline: 0;
  display: flex; align-items: center;
  pointer-events: none;
}
/* Auto margins (not space-between) so each chip keeps its own edge even
 * when the sibling chip is hidden. */
.gantt-edge-cue-start { margin-inline-end: auto; }
.gantt-edge-cue-end { margin-inline-start: auto; }
.gantt-edge-cue[hidden] { display: none; }
/* Owner ask 2026-07-04: the cue should be unmistakable — it's the only
 * way to find a bar parked off-screen. Filled pill with a white chevron
 * and a soft glow. 2026-07-05: the fill takes the TASK'S state color
 * (--cue-color set per-strip by the JS: danger red when overdue, green
 * when completed, else the status color) so the chip says WHY to jump
 * before you jump. Falls back to brand when unset. */
.gantt-edge-cue {
  appearance: none;
  pointer-events: auto;
  position: sticky;
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px;
  border-radius: 999px;
  border: 2px solid var(--card);
  background: var(--cue-color, var(--brand));
  color: #fff;
  cursor: pointer;
  box-shadow: 0 2px 10px color-mix(in srgb, var(--cue-color, var(--brand)) 40%, transparent), 0 1px 3px rgba(15, 23, 42, .14);
  transition: transform .12s ease, box-shadow .12s ease;
}
.gantt-edge-cue:hover {
  transform: scale(1.12);
  box-shadow: 0 3px 14px color-mix(in srgb, var(--cue-color, var(--brand)) 55%, transparent), 0 1px 4px rgba(15, 23, 42, .18);
}
.gantt-edge-cue svg, .gantt-edge-cue .lucide-ic { width: 14px; height: 14px; }
.gantt-edge-cue-start { inset-inline-start: 288px; }
.gantt-edge-cue-end { inset-inline-end: 8px; }
/* Chevron glyph points down by default — rotate toward the off-screen
 * bar. Same convention as .gantt-cascade-btn: -90° = inline-end in LTR. */
.gantt-edge-cue-start svg { transform: rotate(90deg); }
[dir="rtl"] .gantt-edge-cue-start svg { transform: rotate(-90deg); }
.gantt-edge-cue-end svg { transform: rotate(-90deg); }
[dir="rtl"] .gantt-edge-cue-end svg { transform: rotate(90deg); }

/* Weekend bands — absolute overlay so they paint over every row track
 * without needing per-row CSS. pointer-events:none keeps bars clickable. */
.gantt-weekend-overlay {
  position: absolute; inset: 0;
  pointer-events: none;
  z-index: 0;
}
.gantt-weekend-band {
  position: absolute;
  top: 0; bottom: 0;
  background: color-mix(in srgb, var(--text-3) 5%, transparent);
}

/* Full-height today line — spans every visible row. The day-header cue
 * is carried by the .is-today day-cell wash + the corner Today button,
 * so no marker is duplicated in the day row. */
.gantt-today-line {
  position: absolute;
  top: 0; bottom: 0;
  /* 2px brand line (2026-05-17, owner ask for stronger today presence;
   * 2026-07-10 refresh: vertical gradient — stronger at the header where
   * the eye anchors, calmer through the rows — plus a whisper of glow so
   * it reads as "live" instead of a ruled line). z-index:0 keeps it
   * BEHIND bars (z-index:2) per the standing Jira-grade rule — a task
   * that starts today is never visually bisected by the indicator. */
  width: 2px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--brand) 80%, transparent),
    color-mix(in srgb, var(--brand) 45%, transparent));
  box-shadow: 0 0 8px color-mix(in srgb, var(--brand) 22%, transparent);
  z-index: 0;
  pointer-events: none;
  /* Pair the line with a soft "today column" wash via a wide pseudo
   * element centered on it. The wash fades out toward BOTH edges (was a
   * hard-edged 34px band that read as a painted column) so it melts into
   * the grid instead of stamping a stripe on it. */
}
.gantt-today-line::before {
  content: '';
  position: absolute;
  top: 0; bottom: 0;
  inset-inline-start: -17px;
  width: 36px;
  background: linear-gradient(90deg,
    transparent,
    color-mix(in srgb, var(--brand) 7%, transparent) 50%,
    transparent);
  pointer-events: none;
  z-index: -1;
}

/* ── Bars ──────────────────────────────────────────────────────────── *
 * 2026-07-10 refresh: the color still carries the meaning, but depth now
 * comes from a light-source model instead of a dark outline — a whisper
 * of top-light gradient, a 1px top inset highlight, and a soft ambient
 * shadow tinted with the bar's own color. The old 72%-black inset ring
 * read as "outlined enterprise chart"; this reads as a physical chip
 * sitting on the canvas (Linear/Asana vocabulary) while staying calm. */
.gantt-bar {
  position: absolute;
  top: 12px; bottom: 12px;
  min-width: 14px;
  border-radius: 6px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--bar-color) 90%, #fff) 0%,
    var(--bar-color) 60%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .16),
    inset 0 0 0 1px color-mix(in srgb, #000 10%, transparent),
    0 1px 2px color-mix(in srgb, var(--bar-color) 22%, transparent);
  color: #fff;
  overflow: hidden;
  z-index: 2;
  transition:
    box-shadow var(--dur-1) var(--ease-standard),
    filter var(--dur-1) var(--ease-standard),
    transform var(--dur-1) var(--ease-out);
}

/* Bar whose real start precedes the visible window (older than the
 * per-zoom lead-in cap). The leading edge fades out instead of ending
 * in a hard square — reads as "continues earlier in time". Gradient
 * direction can't be logical, so RTL gets its own rule. */
.gantt-bar.is-clipped-start,
.gantt-stage-bar.is-clipped-start {
  border-start-start-radius: 0;
  border-end-start-radius: 0;
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 14px);
          mask-image: linear-gradient(to right, transparent 0, #000 14px);
}
[dir="rtl"] .gantt-bar.is-clipped-start,
[dir="rtl"] .gantt-stage-bar.is-clipped-start {
  -webkit-mask-image: linear-gradient(to left, transparent 0, #000 14px);
          mask-image: linear-gradient(to left, transparent 0, #000 14px);
}
.gantt-bar-content {
  position: absolute; inset: 0;
  display: flex; align-items: center; gap: .35rem;
  padding: 0 .5rem;
  pointer-events: none; /* let mousedown reach the bar for drag */
}
.gantt-bar-title {
  flex: 1; min-width: 0;
  font-size: .78rem; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  /* NO letter-spacing: Arabic is a connected script — any tracking
   * opens visible gaps between joined letterforms (2026-07-10). */
  letter-spacing: 0;
  text-shadow: 0 1px 1px rgba(0, 0, 0, .12);
}
.gantt-bar-content .gantt-avatar { display: inline-flex; }
.gantt-bar-content .gantt-avatar .avatar {
  width: 18px; height: 18px; font-size: .55rem;
  border-width: 1.5px;
  border-color: rgba(255, 255, 255, .75);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .18);
}
.gantt-bar-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 14px; height: 14px;
  opacity: .85;
}
.gantt-bar-icon svg, .gantt-bar-icon .lucide-ic { width: 12px; height: 12px; }
.gantt-bar-check {
  background: rgba(255,255,255,.35);
  border-radius: 50%;
  padding: 1px;
  width: 16px; height: 16px;
  opacity: 1;
}

/* Spacer that pushes the duration + status-pill to the bar's
 * inline-end, leaving the avatar + title anchored at the start. */
.gantt-bar-spacer { flex: 1; min-width: 0; }

/* Duration text inside a bar (C1, 2026-05-15). Tabular nums so the
 * column stays steady when bars stack vertically; tight inline-end
 * padding so the pill that follows it doesn't crowd. */
.gantt-bar-duration {
  font-size: .68rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: rgba(255, 255, 255, .85);
  white-space: nowrap;
  padding-inline-end: .15rem;
}

/* Status pill (C2, 2026-05-15). A compact rounded chip with glyph
 * (✓ / ▶ / ○) and optional label. The pill uses a subtle white tint
 * so the bar's stage-color stays the dominant cue; the pill enriches,
 * doesn't compete. On narrow bars only the glyph survives. */
.gantt-bar-pill {
  display: inline-flex; align-items: center; gap: .25rem;
  padding: 1.5px 7px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .18);
  /* Hairline ring instead of a heavier fill — the pill reads as etched
   * glass on the bar rather than a sticker fighting the bar color. */
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .3);
  color: #fff;
  font-size: .64rem; font-weight: 600;
  white-space: nowrap;
  flex-shrink: 0;
}
.gantt-bar-pill .gbp-glyph {
  font-size: .72rem;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.gantt-bar-pill .gbp-label { letter-spacing: 0; }
/* Variant tints — same shape, different background intensity so the
 * eye can scan a board of mixed states without reading every label. */
.gantt-bar-pill.is-done    { background: rgba(255, 255, 255, .26); }
.gantt-bar-pill.is-ongoing { background: rgba(255, 255, 255, .18); }
.gantt-bar-pill.is-upcoming{
  background: rgba(0, 0, 0, .14);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .18);
  color: rgba(255, 255, 255, .92);
}

/* On is-future stage bars (hollow + dashed), the bar fill is transparent
 * so a white-tinted pill would be invisible. Switch to a tinted-fill
 * version that uses the bar color directly. */
.gantt-stage-bar.is-future .gantt-bar-pill {
  background: color-mix(in srgb, var(--bar-color) 18%, transparent);
  color: var(--bar-color);
}
.gantt-stage-bar.is-future .gantt-bar-duration { color: var(--text-2); }

.gantt-bar.is-completed {
  /* Done bars desaturate + acquire a faint diagonal stripe so they
   * read as "shipped" at a glance without losing the stage hue. */
  background:
    repeating-linear-gradient(135deg,
      rgba(255,255,255,.18) 0 6px,
      transparent 6px 12px),
    var(--bar-color);
  opacity: .75;
}
.gantt-bar.is-overdue {
  /* Overdue: red, no pulse — same light-source ramp as .gantt-bar above,
   * on --danger. Keep a real 2-stop ramp: --danger IS #DC2626 already,
   * so a raw #dc2626 2nd stop is DEGENERATE (same color into itself). */
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--danger) 90%, #fff) 0%,
    var(--danger) 60%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .18),
    inset 0 0 0 1px rgba(0, 0, 0, .1),
    0 1px 2px rgba(220, 38, 38, .3);
}

.gantt-bar.is-draggable {
  cursor: grab;
  user-select: none;
}
/* Direct hover lifts the bar 1px off the canvas (premium "pick me up"
 * affordance); row-hover from the label side only brightens — a whole
 * row of bars levitating at once would be noise. */
.gantt-track.is-row-hover .gantt-bar {
  filter: brightness(1.06);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .16),
    inset 0 0 0 1px color-mix(in srgb, #000 10%, transparent),
    0 2px 6px color-mix(in srgb, var(--bar-color) 28%, transparent);
}
.gantt-bar.is-draggable:hover {
  filter: brightness(1.06);
  transform: translateY(-1px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .16),
    inset 0 0 0 1px color-mix(in srgb, #000 10%, transparent),
    0 3px 10px color-mix(in srgb, var(--bar-color) 32%, transparent);
}
.gantt-bar.is-dragging {
  cursor: grabbing;
  opacity: .92;
  z-index: 5;
  transform: translateY(-1px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .16),
    inset 0 0 0 1px color-mix(in srgb, #000 10%, transparent),
    0 6px 16px rgba(0, 0, 0, .22);
}

/* Pipeline bars segment internally — each stage paints a time-sized
 * slice, positioned absolutely with an explicit inset-inline-start so
 * RTL flips for free. Since 2026-07-04 each slice is shaped into an
 * interlocking process-arrow (clip-path below) whose tip nests into the
 * next stage's notch, replacing the old floating chevron glyph. */
.gantt-bar.is-pipeline { background: var(--bg-3); box-shadow: inset 0 0 0 1px var(--border); }
/* Interlocking chevron-flow depth — the arrow tip / notch size. Kept in
 * sync with CHEV_PX in tasks-page.js (_renderStageBars). */
.gantt-bar.is-pipeline { --chev: 9px; }
/* Each stage carries its avatar + name inside, and is shaped into an
 * interlocking process-arrow via clip-path (see .is-seg-* below). The
 * padding clears the trailing-edge notch so the avatar never sits under
 * the concave cut. */
.gantt-bar-seg {
  position: absolute; top: 0; bottom: 0;
  display: flex;
  align-items: center;
  gap: 4px;
  padding-inline: calc(var(--chev) + 7px) 9px;
  overflow: hidden;
  color: #fff;
  /* Same top-light as full bars — inset shadows survive the clip-path,
   * so each chevron slice keeps the chip-on-canvas depth. */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .14);
}
/* ── Interlocking arrow shapes ──────────────────────────────────────
 * Flow points inline-end (toward the next stage). clip-path is physical,
 * so LTR (tip on the right) and RTL (tip on the left) are mirrored. The
 * tip of each stage overlaps into the notch of the next → seamless flow.
 * is-seg-first: tip only · is-seg-mid: tip + trailing notch · is-seg-last:
 * notch only. */
.gantt-bar-seg.is-seg-first { clip-path: polygon(0 0, calc(100% - var(--chev)) 0, 100% 50%, calc(100% - var(--chev)) 100%, 0 100%); }
.gantt-bar-seg.is-seg-mid   { clip-path: polygon(0 0, calc(100% - var(--chev)) 0, 100% 50%, calc(100% - var(--chev)) 100%, 0 100%, var(--chev) 50%); }
.gantt-bar-seg.is-seg-last  { clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, var(--chev) 50%); }
[dir="rtl"] .gantt-bar-seg.is-seg-first { clip-path: polygon(100% 0, var(--chev) 0, 0 50%, var(--chev) 100%, 100% 100%); }
[dir="rtl"] .gantt-bar-seg.is-seg-mid   { clip-path: polygon(100% 0, var(--chev) 0, 0 50%, var(--chev) 100%, 100% 100%, calc(100% - var(--chev)) 50%); }
[dir="rtl"] .gantt-bar-seg.is-seg-last  { clip-path: polygon(0 0, 100% 0, calc(100% - var(--chev)) 50%, 100% 100%, 0 100%); }
/* Stage separation comes from a REAL breathing gap (owner ask
 * 2026-07-04): the JS extends each tip by CHEV_PX minus CHEV_GAP_PX, so
 * a thin parallel channel of the track background shows between every
 * pair of stages — cleaner than color-on-color contact and it separates
 * same-status neighbours for free. No seam shadow needed. */
.gantt-bar-seg-avatar { display: inline-flex; flex-shrink: 0; }
.gantt-bar-seg-avatar .avatar {
  width: 18px; height: 18px;
  font-size: .55rem;
  border: 1.5px solid rgba(255, 255, 255, .55);
  box-sizing: content-box;
}
.gantt-bar-seg-avatar.is-expected .avatar {
  opacity: .55;
  outline: 2px dashed rgba(255, 255, 255, .55);
  outline-offset: -2px;
}
/* Team-chip variant inside cluster segments — same square shape as
 * the standalone team chip, sized to fit the bar's height. */
.gantt-bar-seg-team {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px;
  border-radius: 4px;
  background: rgba(255, 255, 255, .22);
  color: #fff;
  font-size: .55rem; font-weight: 600;
  flex-shrink: 0;
  letter-spacing: .02em;
}
.gantt-bar-seg-name {
  font-size: .72rem; font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
/* Outer caps — the first stage's trailing edge and the last stage's
 * leading edge are the only straight (un-chevroned) edges, so round just
 * those corners for a finished ribbon silhouette. clip-path preserves
 * these because the polygon's outer corners sit on the box corners. */
.gantt-bar.is-pipeline .gantt-bar-seg.is-seg-first { border-start-start-radius: 4px; border-end-start-radius: 4px; }
.gantt-bar.is-pipeline .gantt-bar-seg.is-seg-last  { border-start-end-radius: 4px;   border-end-end-radius: 4px; }

/* Milestones — diamond marker centered on a single day. Flat solid
 * fill (no drop shadow), inset border for definition. */
.gantt-milestone {
  position: absolute;
  top: 50%; transform: translateY(-50%) rotate(45deg);
  width: 18px; height: 18px;
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--milestone-color) 88%, #fff),
    var(--milestone-color) 65%);
  border-radius: 4px;
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, .22),
    0 1px 3px color-mix(in srgb, var(--milestone-color) 35%, transparent);
  cursor: pointer;
  z-index: 2;
  display: flex; align-items: center; justify-content: center;
  transition: filter var(--dur-1) var(--ease-standard), transform var(--dur-1) var(--ease-spring);
}
.gantt-milestone:hover {
  filter: brightness(1.08);
  transform: translateY(-50%) rotate(45deg) scale(1.12);
}
.gantt-milestone svg, .gantt-milestone .lucide-ic {
  width: 11px; height: 11px;
  color: #fff;
  transform: rotate(-45deg);
}

/* ── Dependency arrows (SVG overlay) ───────────────────────────────── */
.gantt-deps-overlay {
  position: absolute;
  inset-inline-start: 0;
  top: 0;
  pointer-events: none;
  /* Arrows sit between today-line (z:0) and bars (z:2) so they pass
   * behind bars at endpoints — looks "anchored" to the bar edges
   * instead of floating on top of them. */
  z-index: 1;
  overflow: visible;
  color: color-mix(in srgb, var(--text-3) 30%, transparent);
}
.gantt-dep-path {
  fill: none;
  stroke: color-mix(in srgb, var(--text-3) 38%, transparent);
  stroke-width: 1.5;
  stroke-linecap: round;
  transition: stroke var(--dur-1) var(--ease-standard),
              stroke-width var(--dur-1) var(--ease-standard);
}
/* Hover on either endpoint row brightens the arc to full opacity so the
 * relationship becomes obvious without permanently cluttering the view. */
.gantt-dep-path.is-hover {
  stroke: color-mix(in srgb, var(--text-2) 95%, transparent);
  stroke-width: 2;
}

/* Draw-in — every connector path carries pathLength="1" (set in JS), so
 * dasharray:1 covers the whole path and animating dashoffset 1→0 draws
 * the line from its source to its arrowhead. Runs once per render;
 * motion tokens collapse to 0 under prefers-reduced-motion, which turns
 * this into a plain appear. The opacity ramp keeps the arrowhead marker
 * (which is visible from frame 0) from floating alone at the target. */
@keyframes gantt-path-draw {
  0%   { stroke-dashoffset: 1; opacity: 0; }
  25%  { opacity: 1; }
  100% { stroke-dashoffset: 0; opacity: 1; }
}
.gantt-dep-path,
.gantt-handoff-path {
  stroke-dasharray: 1;
  stroke-dashoffset: 0;
  animation: gantt-path-draw var(--dur-3) var(--ease-out) backwards;
}
.gantt-handoff-dot {
  animation: gantt-fade-in var(--dur-2) var(--ease-out) backwards;
}
@keyframes gantt-fade-in { from { opacity: 0; } to { opacity: 1; } }
/* In-place re-render (same view + zoom — realtime events, saves): no
 * replay. The draw-in is a first-impression cue, not a refresh cue;
 * replaying it on every remote update reads as the screen flashing. */
.gantt-deps-overlay.is-repaint .gantt-dep-path,
.gantt-deps-overlay.is-repaint .gantt-handoff-path,
.gantt-deps-overlay.is-repaint .gantt-handoff-dot {
  animation: none;
}

/* ── Pipeline swimlane backdrop (B2, 2026-05-16) ──────────────────────
 * Subtle tinted background painted across EVERY row of a pipeline
 * group (parent + cascade children). Reads as one cohesive section
 * the way Notion / Linear paint subtle block containers. Color is
 * pulled from the bg-3 token at very low opacity so the tint sits
 * BELOW any hover state (which uses bg-3 at full strength). */
.gantt-label.is-pipeline-row,
.gantt-track.is-pipeline-row {
  background-color: color-mix(in srgb, var(--bg-3) 22%, transparent);
}
/* Stage-tracked variants keep their existing indent-tint + grid */
.gantt-track-stage.is-pipeline-row {
  background:
    /* Indent tint at the inline-start edge anchors children to parent */
    linear-gradient(to right,
      color-mix(in srgb, var(--bg-3) 60%, transparent) 0,
      color-mix(in srgb, var(--bg-3) 60%, transparent) 14px,
      transparent 14px),
    repeating-linear-gradient(
      to right,
      transparent 0,
      transparent calc(var(--cell) - 1px),
      color-mix(in srgb, var(--border) 50%, transparent) calc(var(--cell) - 1px),
      color-mix(in srgb, var(--border) 50%, transparent) var(--cell)
    ),
    /* Group backdrop sits behind everything else, faintest layer */
    color-mix(in srgb, var(--bg-3) 22%, transparent);
}
/* Hover still wins — same hover rules as before but more specific so
 * the tint disappears on the active row. */
.gantt-label.is-pipeline-row:hover,
.gantt-label.is-pipeline-row.is-row-hover {
  background-color: var(--bg-3);
}

/* ── Pipeline cascade: chevron + child stage rows + handoff connectors ─
 * Cascade is auto-driven by completion data (see _computeStageSpans).
 * The chevron in the label column lets the user fold the cascade back
 * into a single bunched bar — useful when many pipelines are in flight
 * and the row count would otherwise balloon.
 *
 * The chevron sits in its OWN gutter at the inline-start edge of the
 * label, with a 1px vertical divider (border-inline-end) between it
 * and the rest of the chip. Owner ask 2026-05-15: "خط يقسم بين مساحة
 * السهم ومساحة الشريحة". */
.gantt-cascade-btn {
  appearance: none;
  border: 0;
  background: transparent;
  width: 22px;
  align-self: stretch;            /* take full row height — divider runs floor-to-ceiling */
  display: inline-flex; align-items: center; justify-content: center;
  border-inline-end: 1px solid var(--border);
  margin-inline-end: .55rem;
  color: var(--text-3);
  cursor: pointer;
  flex-shrink: 0;
  transition: color .12s, background .12s;
}
.gantt-cascade-btn:hover { color: var(--text); background: color-mix(in srgb, var(--bg-3) 60%, transparent); }
.gantt-cascade-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.gantt-cascade-btn svg, .gantt-cascade-btn .lucide-ic {
  width: 12px; height: 12px;
  transition: transform var(--dur-1) var(--ease-standard);
}
/* Collapsed: arrow points sideways (toward the inline-end edge in LTR,
 * which mirrors to inline-start in RTL via the dir-aware rotation). */
.gantt-cascade-btn svg { transform: rotate(-90deg); }
[dir="rtl"] .gantt-cascade-btn svg { transform: rotate(90deg); }
/* Expanded: arrow points down — same orientation in both LTR and RTL. */
.gantt-cascade-btn.is-expanded svg,
[dir="rtl"] .gantt-cascade-btn.is-expanded svg { transform: rotate(0deg); }

/* Labels with a chevron get zero inline-start padding so the chevron
 * gutter is flush against the column edge. Without this, the chevron
 * would float free of the column's start border. */
.gantt-label.has-cascade { padding-inline-start: 0; }

/* Pipeline rail — vertical brand-tinted line connecting an expanded
 * parent to all its child stage rows, with an L-hook into each tee.
 * Drawn via pseudo-elements so no extra DOM is needed.
 *
 * Layout:
 *   Parent (expanded):   rail starts at chevron mid-height, drops to row bottom
 *   Stage child (mid):   rail spans full row height + horizontal hook at mid
 *   Stage child (last):  rail spans top to mid only + horizontal hook (terminates)
 *
 * Position 11px = center of the 22px chevron column, so the rail
 * visually emerges from the chevron and meets the tee on each child. */
.gantt-label.has-cascade.is-cascade-expanded::before {
  content: '';
  position: absolute;
  inset-inline-start: 11px;
  top: 50%;
  bottom: -1px;                   /* overlap next row by 1px for continuity */
  width: 2px;
  background: color-mix(in srgb, var(--brand) 35%, transparent);
  pointer-events: none;
  z-index: 0;
}
.gantt-label-stage::before {
  /* Vertical rail spanning the full row height (intermediate child). */
  content: '';
  position: absolute;
  inset-inline-start: 11px;
  top: 0;
  bottom: -1px;
  width: 2px;
  background: color-mix(in srgb, var(--brand) 35%, transparent);
  pointer-events: none;
  z-index: 0;
}
.gantt-label-stage.is-last-child::before {
  /* Last child: the trunk stops just above mid where the rounded elbow
   * (::after) takes over the turn — no straight overshoot through the
   * curve, no siblings below to reach. */
  bottom: calc(50% + 7px);
}
.gantt-label-stage::after {
  /* Rounded elbow (owner 2026-07-07: الخط المكسّر → منحنى بسيط بنفس ميلان
   * حواف النظام). Was a straight horizontal bar meeting the vertical rail
   * at a hard 90° — that sharp corner is what read as "broken". Now it's
   * a bordered box (short vertical on the rail + horizontal toward the
   * tee) joined by a border-radius, so the trunk sweeps smoothly into the
   * hook. The box ends exactly at mid so it lines up with the stage dot. */
  content: '';
  position: absolute;
  inset-inline-start: 11px;
  top: 50%;
  transform: translateY(-100%);
  width: 17px;
  height: 9px;
  border-inline-start: 2px solid color-mix(in srgb, var(--brand) 35%, transparent);
  border-block-end:   2px solid color-mix(in srgb, var(--brand) 35%, transparent);
  border-end-start-radius: 8px;
  pointer-events: none;
  z-index: 0;
}

/* Hide the existing ASCII └ glyph — the rail's L-hook is the new affordance. */
.gantt-label-stage .gantt-stage-tee { display: none; }

/* Handoff exit node — hollow "port" (2026-07-10, was a solid brand dot):
 * card-colored core + 2px brand ring, the Figma-connector idiom. Reads
 * as "the wire plugs in HERE" and stays legible on any bar color. */
.gantt-handoff-dot {
  fill: var(--card);
  stroke: var(--brand);
  stroke-width: 2;
}

/* Avatar placeholder — used when a user has no name (deleted, race
 * during onboarding, etc.). Neutral gray with a "?" so it reads as
 * "intentionally blank" instead of a colored empty circle. */
.gantt-avatar-empty {
  background: var(--bg-3) !important;
  color: var(--text-3) !important;
  font-weight: 600;
  font-size: .8rem;
}

/* Expected-but-not-yet-claimed avatar (2026-05-18) — used on stage
 * rows when the user is only set via defaultAssigneeId. Visual: faded
 * + dashed ring so it reads as "pre-assigned, not actively working".
 * Tells the viewer "this is who's expected here" without claiming
 * the work has actually started. */
.gantt-avatar.is-expected .avatar,
.gantt-avatar.is-expected.gantt-stage-avatar .avatar {
  opacity: .55;
  outline: 2px dashed color-mix(in srgb, var(--text-3) 60%, transparent);
  outline-offset: -2px;
}

/* Team-chip fallback (2026-05-18) — when a stage is assigned to a TEAM
 * queue with no individual default, show team initials in a square
 * (vs. circle for individuals) so the user reads "anyone on this team"
 * not "one specific person". Color from neutral palette + tinted with
 * the stage's bar color so it harmonizes with the surrounding row. */
.gantt-team-chip {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px;
  border-radius: 5px;
  background: color-mix(in srgb, var(--text-3) 18%, transparent);
  color: var(--text);
  font-size: .6rem; font-weight: 600;
  font-family: inherit;
  letter-spacing: .02em;
  flex-shrink: 0;
  border: 1.5px solid var(--card);
}
.gantt-bar-content .gantt-team-chip {
  width: 18px; height: 18px;
  font-size: .55rem;
  border-width: 1.5px;
}

/* Floating stage name — appears OUTSIDE the bar (after its trailing
 * edge, forward in time) when the bar is too narrow to show the name
 * inside without ellipsis truncation. Microsoft Project / GanttPro
 * pattern. Neutral muted color so it doesn't compete with the bar's
 * own visual weight. */
.gantt-stage-floating-name {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  font-size: .75rem;
  font-weight: 600;
  color: var(--text-2);
  white-space: nowrap;
  pointer-events: none;
  /* Card-tinted plate + z-index above the connector overlay (z:1): a
   * handoff wire exiting the same trailing edge now passes BEHIND a
   * legible label instead of striking through the letters (2026-07-10,
   * Figma edge-label idiom). */
  z-index: 3;
  background: color-mix(in srgb, var(--card) 88%, transparent);
  padding: 2px 7px;
  border-radius: 6px;
  /* Cap the floating label to ~200px so a 50-character stage name
   * doesn't run off the row into the next bar. CSS ellipsis handles
   * overflow gracefully. */
  max-width: 240px;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Child stage label — sized to match the 52px stage track row. Indent
 * + tee glyph + colored status dot make the parent-child relationship
 * visible at a glance without needing a separate icon block. */
.gantt-label-stage {
  padding-inline-start: 2rem;
  padding-inline-end: .8rem;
  gap: .5rem;
  font-size: .82rem;
  color: var(--text-2);
  border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  background:
    linear-gradient(to right,
      color-mix(in srgb, var(--bg-3) 60%, transparent) 0,
      color-mix(in srgb, var(--bg-3) 60%, transparent) 14px,
      transparent 14px),
    var(--card);
}
.gantt-label-stage:hover,
.gantt-label-stage.is-row-hover {
  background: color-mix(in srgb, var(--bg-3) 80%, transparent);
}
.gantt-stage-tee {
  color: var(--text-3);
  font-size: .9rem;
  margin-inline-end: .2rem;
  user-select: none;
}
.gantt-stage-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--bg) 70%, transparent);
}
.gantt-stage-name {
  flex: 1; min-width: 0;
  font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Tier 1+2 stage row chrome (2026-05-18) — two-line layout for stage
 * rows: name + assignee on line 1, timing + SLA micro-line on line 2.
 * Slim rows skip the meta so the visual budget stays honored. */
.gantt-stage-body {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column;
  gap: 1px;
  justify-content: center;
}
.gantt-stage-line1 {
  display: flex; align-items: center; gap: .5rem;
  min-width: 0;
}
.gantt-stage-num {
  display: inline-block;
  margin-inline-end: .2rem;
  font-weight: 600;
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
  letter-spacing: .01em;
}
.gantt-stage-meta {
  display: inline-flex; align-items: center;
  gap: .55rem;
  font-size: .68rem;
  color: var(--text-3);
  line-height: 1.3;
}
.gantt-stage-time { white-space: nowrap; }
.gantt-stage-sla {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.gantt-stage-sla.is-overdue {
  color: var(--danger);
  font-weight: 600;
}

/* Stage state — completed: fade name + light strikethrough so the eye
 * skips past finished work and lands on the active stage. */
.gantt-label-stage.is-stage-completed .gantt-stage-name {
  color: var(--text-3);
  text-decoration: line-through;
  text-decoration-color: color-mix(in srgb, var(--text-3) 45%, transparent);
}
.gantt-label-stage.is-stage-completed .gantt-stage-num { color: var(--text-3); }

/* Stage state — in_progress: 3px brand stripe on the inline-start edge
 * and a brand-colored number so the active stage reads at a glance. */
.gantt-label-stage.is-stage-in_progress {
  box-shadow: inset 3px 0 0 var(--brand);
}
html[dir="rtl"] .gantt-label-stage.is-stage-in_progress {
  box-shadow: inset -3px 0 0 var(--brand);
}
.gantt-label-stage.is-stage-in_progress .gantt-stage-num,
.gantt-label-stage.is-stage-in_progress .gantt-stage-name {
  color: var(--text);
}

/* Pipeline progress badge — "٢⁄٤" near the parent title. Brand-tinted
 * pill, compact, tabular nums so the digit pair always aligns. */
.gantt-stage-progress-badge {
  display: inline-flex; align-items: center;
  height: 18px;
  padding: 0 .4rem;
  margin-inline-start: .35rem;
  border-radius: 5px;
  background: color-mix(in srgb, var(--brand) 12%, transparent);
  color: var(--brand);
  font-size: .65rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: .02em;
  flex-shrink: 0;
}
.gantt-stage-progress-badge .gspb-slash {
  margin: 0 1px;
  opacity: .55;
  font-weight: 600;
}
/* Archived rows already drop status/comments — also hide the progress
 * badge here, since the archive context implies the final state and the
 * "٢⁄٤" running counter is meaningless once the work is closed. */
.gantt-label.is-archived-row .gantt-stage-progress-badge { display: none; }
.gantt-stage-avatar .avatar {
  width: 20px; height: 20px; font-size: .55rem;
  border: 1.5px solid var(--card);
}

/* Stage track + stage bar (height: 52px row). Bar styling mirrors the
 * main .gantt-bar contract — same chunkiness, same flat fill, same rich
 * content (avatar + name + ✓). The only visual distinction is the row
 * background, which carries a subtle indent-tint so a busy timeline
 * still reads as "these belong to the parent above". */
.gantt-track-stage {
  background:
    /* Indent tint: short faint vertical stripe along the inline-start
     * edge that visually anchors the child rows to their parent. */
    linear-gradient(to right,
      color-mix(in srgb, var(--bg-3) 60%, transparent) 0,
      color-mix(in srgb, var(--bg-3) 60%, transparent) 14px,
      transparent 14px),
    repeating-linear-gradient(
      to right,
      transparent 0,
      transparent calc(var(--cell) - 1px),
      color-mix(in srgb, var(--border) 50%, transparent) calc(var(--cell) - 1px),
      color-mix(in srgb, var(--border) 50%, transparent) var(--cell)
    );
  border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent);
}
.gantt-stage-bar {
  position: absolute;
  top: 10px; bottom: 10px;       /* matches .gantt-bar inset of ~12 */
  /* Visual minimum scales with zoom so the bar always has room for
   * Avatar + stage name + ✓, while staying within ~3 day-cells at the
   * smallest zoom (quarter, cell=14) where 100px would be misleadingly
   * wide. clamp(28, 3*cell, 100) → ~42px at quarter, 100px everywhere
   * else. Owner ask 2026-05-15: chips → proper bars. */
  min-width: clamp(28px, calc(var(--cell) * 3), 100px);
  border-radius: 6px;
  /* Same light-source model as .gantt-bar (2026-07-10): top-light
   * gradient + inset highlight + color-tinted ambient shadow replace
   * the old dark outline ring. */
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--bar-color) 90%, #fff) 0%,
    var(--bar-color) 60%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .16),
    inset 0 0 0 1px color-mix(in srgb, #000 10%, transparent),
    0 1px 2px color-mix(in srgb, var(--bar-color) 22%, transparent);
  color: #fff;
  overflow: hidden;
  cursor: pointer;
  z-index: 2;
  transition:
    filter var(--dur-1) var(--ease-standard),
    box-shadow var(--dur-1) var(--ease-standard),
    transform var(--dur-1) var(--ease-out);
}
.gantt-stage-bar:hover {
  filter: brightness(1.06);
  transform: translateY(-1px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .16),
    inset 0 0 0 1px color-mix(in srgb, #000 10%, transparent),
    0 3px 10px color-mix(in srgb, var(--bar-color) 32%, transparent);
}
.gantt-stage-bar .gantt-bar-content {
  position: absolute; inset: 0;
  display: flex; align-items: center; gap: .35rem;
  padding: 0 .4rem;
  pointer-events: none;
}
.gantt-stage-bar .gantt-bar-content .avatar {
  width: 22px; height: 22px; font-size: .6rem;
  border: 1.5px solid rgba(255, 255, 255, .75);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .18);
}
.gantt-stage-bar .gantt-bar-title {
  flex: 1; min-width: 0;
  font-size: .75rem; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Completed stage: recede by desaturating + fading, NOT darkening —
 * brightness(.92) turned emerald into a murky slab (2026-07-10). */
.gantt-stage-bar.is-completed { filter: saturate(.6); opacity: .85; }
.gantt-stage-bar.is-ongoing {
  /* In-progress stage: solid fill. The "how-much-done" affordance is
   * now the dedicated .gantt-stage-progress overlay (C4, 2026-05-16);
   * the earlier trailing-edge gradient was a vague hint at best and is
   * dropped in favor of the explicit progress fill. */
  background: color-mix(in srgb, var(--bar-color) 35%, var(--card));
}
.gantt-stage-bar.is-overdue-stage {
  /* Overdue: bar background mutes to gray-on-card so the inner red
   * progress fill is the visual focus. The 1px inset border picks up
   * red-600 to call attention at the bar edges. */
  background: color-mix(in srgb, var(--danger) 12%, var(--card));
  box-shadow: inset 0 0 0 1px #dc2626;
}

/* Inner progress fill — sits inside .gantt-stage-bar at the BOTTOM as
 * an explicit "% elapsed" indicator. Width is set inline from JS to
 * (elapsed / total-span) × 100 — bounded 0–100. Color shifts to red
 * on overdue stages to match the surrounding border tint. */
.gantt-stage-progress {
  position: absolute;
  inset-inline-start: 0;
  inset-block-end: 0;
  height: 100%;
  background: color-mix(in srgb, var(--bar-color) 100%, transparent);
  border-radius: inherit;
  transition: width .6s ease;
  pointer-events: none;
  /* Make sure the bar content (title + pill) renders on TOP of the
   * fill — z-index works because .gantt-bar-content is inside the
   * same positioning context. */
  z-index: 0;
}
.gantt-stage-bar.is-overdue-stage .gantt-stage-progress {
  background: #dc2626;
}
.gantt-stage-bar .gantt-bar-content { position: absolute; inset: 0; z-index: 1; }

/* ── Slim-mode rows (طي المكتمل, 2026-05-16 · reworked 2026-07-05) ──
 * A completed task/stage row collapses to 24px tall — a thin colored
 * line that occupies a fraction of the vertical space a full row would.
 *
 * NO-REFLOW HOVER (owner report 2026-07-05): the old hover expanded the
 * row 24px → 56px, which SHIFTED every row below it — while the SVG
 * handoff connectors keep their render-time y coordinates, so the wires
 * visibly detached from the bars they anchor to. Rows now NEVER change
 * height. Hover emphasis happens INSIDE the fixed 24px: the 4px line
 * thickens to 14px with the shared brightness/ring treatment, and the
 * compact slim title brightens. Geometry is sacred — nothing on this
 * canvas may move on hover, ever, or the wires lie. */
.gantt-label.is-slim,
.gantt-track.is-slim {
  overflow: hidden;
}

/* Task bar inside a slim track: 4px thin line at rest; thickens to a
 * 14px ribbon on hover — comfortably inside the 24px row, animated on
 * the insets only (no layout impact outside the row). */
.gantt-track.is-slim .gantt-bar,
.gantt-track.is-slim .gantt-stage-bar {
  top: 10px; bottom: 10px;
  transition: top .16s ease, bottom .16s ease;
}
.gantt-track.is-slim:hover .gantt-bar,
.gantt-track.is-slim.is-row-hover .gantt-bar,
.gantt-track.is-slim:hover .gantt-stage-bar,
.gantt-track.is-slim.is-row-hover .gantt-stage-bar {
  top: 5px; bottom: 5px;
}

/* Bar content stays hidden in slim mode — a 14px ribbon has no room
 * for avatars/pills, and revealing them mid-hover caused overflow
 * flashes. The slim title on the label side carries the identity. */
.gantt-track.is-slim .gantt-bar-content,
.gantt-track.is-slim .gantt-stage-progress {
  opacity: 0;
}

/* Label in slim mode: the multi-line body stays hidden (it needs the
 * full 52px row); the compact attr(title) line IS the slim identity. */
.gantt-label.is-slim .gantt-label-body,
.gantt-label.is-slim .gantt-type-icon,
.gantt-label.is-slim .gantt-cascade-btn,
.gantt-label.is-slim .gantt-avatar-stack,
.gantt-label.is-slim .gantt-stage-tee,
.gantt-label.is-slim .gantt-stage-dot,
.gantt-label.is-slim .gantt-stage-avatar {
  opacity: 0;
}

/* Slim title — a single muted line sourced from the row's `title`
 * attribute. Brightens on hover (the no-reflow replacement for the old
 * expansion): the row answers "what is this?" without moving a pixel. */
.gantt-label.is-slim::after {
  content: attr(title);
  position: absolute;
  inset-inline-start: 2rem;
  top: 50%;
  transform: translateY(-50%);
  font-size: .72rem;
  color: var(--text-3);
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: calc(100% - 3rem);
  pointer-events: none;
  transition: color .15s ease;
}
.gantt-label.is-slim:hover::after,
.gantt-label.is-slim.is-row-hover::after {
  color: var(--text);
}

/* Aged-completed slim labels: even more muted so they recede further. */
.gantt-label.is-slim.is-aged-completed::after { opacity: .6; }
.gantt-stage-bar.is-future {
  /* Future stage: hollow + dashed outline so it reads as "projected, not
   * yet started". Content stays inside but with muted color so the row
   * doesn't look fully populated. */
  background: transparent;
  box-shadow: none;
  border: 1.5px dashed color-mix(in srgb, var(--bar-color) 65%, transparent);
  color: var(--text-2);
}
.gantt-stage-bar.is-future .gantt-bar-title { color: var(--text-2); }
.gantt-stage-bar.is-future .avatar { opacity: .85; }

/* Pinned stage — the outline goes SOLID (not the projected dashed) to
 * signal "this date is fixed, not auto-flowing", and a pin glyph leads the
 * content. */
.gantt-stage-bar.is-pinned.is-future {
  border-style: solid;
  border-color: color-mix(in srgb, var(--bar-color) 80%, transparent);
}
.gantt-bar-pin {
  display: inline-flex; align-items: center; flex-shrink: 0;
  color: var(--bar-color);
  margin-inline-end: 3px;
}
.gantt-bar-pin svg, .gantt-bar-pin .lucide-ic { width: 11px; height: 11px; }
.gantt-stage-bar:not(.is-future) .gantt-bar-pin { color: #fff; opacity: .9; }
/* Pinned segment inside a bunched (segmented) pipeline bar. */
.gantt-bar-seg.is-pinned { box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, .55); }
.gantt-seg-pin {
  display: inline-flex; align-items: center; flex-shrink: 0;
  color: #fff; opacity: .95; margin-inline-end: 2px;
}
.gantt-seg-pin svg, .gantt-seg-pin .lucide-ic { width: 10px; height: 10px; }

/* Handoff connectors — Git-branch-icon aesthetic (2026-05-18).
 * Single Bezier curve between two filled dots, mimicking the visual
 * vocabulary of git's branch icon: clear node-to-node connection,
 * no arrowhead (the cascade direction + row indent imply flow),
 * brand-colored at full saturation so the line pops on busy boards.
 * Pairs with .gantt-handoff-dot which paints the endpoint nodes. */
.gantt-handoff-path {
  fill: none;
  stroke: var(--brand);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: stroke-width var(--dur-1) var(--ease-standard),
              filter var(--dur-1) var(--ease-standard);
}
/* Soft brand glow under the whole handoff layer — one drop-shadow on the
 * group (cheap: a handful of paths) gives the wires layered depth over
 * the grid instead of sitting flat on it. */
.gantt-handoffs {
  filter: drop-shadow(0 1px 2px color-mix(in srgb, var(--brand) 28%, transparent));
}
/* Hover lift + glow if the user mouses over a stage row — surfaces the
 * connection without needing to chase thin lines. */
.gantt-track-stage:hover ~ .gantt-deps-overlay .gantt-handoff-path,
.gantt-track-stage.is-row-hover ~ .gantt-deps-overlay .gantt-handoff-path {
  stroke-width: 2.5;
  filter: drop-shadow(0 0 4px color-mix(in srgb, var(--brand) 40%, transparent));
}

/* ── Hover popover ─────────────────────────────────────────────────── */
.gantt-popover {
  position: fixed;
  z-index: 1000;
  min-width: 240px; max-width: 340px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-2);
  padding: .8rem .9rem;
  font-size: .85rem;
  pointer-events: none;
  animation: gantt-popover-in .15s ease-out;
}
@keyframes gantt-popover-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.gantt-popover-title {
  font-weight: 600; font-size: .92rem;
  color: var(--text);
  margin-bottom: .35rem;
  line-height: 1.3;
}
.gantt-popover-sub {
  font-size: .72rem;
  color: var(--text-3);
  margin-bottom: .55rem;
  padding-bottom: .55rem;
  border-bottom: 1px solid var(--border);
  line-height: 1.4;
}
/* When the popover has no sub-line (plain task), the title carries
 * the separator itself instead. */
.gantt-popover-title:last-of-type {
  margin-bottom: .55rem;
  padding-bottom: .55rem;
  border-bottom: 1px solid var(--border);
}
.gantt-popover-row {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 1rem;
  padding: 3px 0;
  font-size: .78rem;
}
.gantt-popover-row > span { color: var(--text-3); }
.gantt-popover-row > b { color: var(--text); font-weight: 600; }

/* Smart-timing badge (C3, 2026-05-15). Top-of-popover banner that
 * states the bar's timing diagnosis at a glance: on-time / running
 * late / at risk / scheduled. Each state has its own color, all keyed
 * to the existing semantic tokens so theme + dark-mode are free. */
.gantt-popover-badge {
  display: flex; align-items: center; gap: .45rem;
  padding: 7px 11px;
  border-radius: 7px;
  font-size: .78rem; font-weight: 600;
  margin-bottom: .6rem;
  line-height: 1.3;
}
.gantt-popover-badge .gpb-icon {
  font-size: .9rem;
  line-height: 1;
  flex-shrink: 0;
}
.gantt-popover-badge.is-done,
.gantt-popover-badge.is-on-track {
  background: color-mix(in srgb, var(--success) 14%, transparent);
  color: var(--success);
}
.gantt-popover-badge.is-late,
.gantt-popover-badge.is-overdue {
  background: color-mix(in srgb, var(--danger) 14%, transparent);
  color: var(--danger);
}
.gantt-popover-badge.is-at-risk {
  background: color-mix(in srgb, var(--warn) 16%, transparent);
  color: var(--warn);
}
.gantt-popover-badge.is-ongoing {
  background: color-mix(in srgb, var(--brand) 14%, transparent);
  color: var(--brand);
}
.gantt-popover-badge.is-scheduled {
  background: var(--bg-3);
  color: var(--text-3);
}

/* ── Empty state ───────────────────────────────────────────────────── */
.gantt-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .35rem;
  padding: 4rem 2rem;
  min-height: 320px;
  text-align: center;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-2);
}
.gantt-empty .empty-icon-celebrate { color: var(--text-3); }
.gantt-empty-title {
  font-size: 1.05rem; font-weight: 600;
  color: var(--text);
  margin-top: .6rem;
}
.gantt-empty-sub {
  font-size: .85rem; color: var(--text-3);
  max-width: 360px; line-height: 1.5;
}

/* ═════════════════════════════════════════════════════════════════════
 *  Admin tasks LIST view — Jira-grade polish, 2026-05-15
 *  ───────────────────────────────────────────────────────────────────
 *  Adds: column-header icons, real assignee avatars, directional
 *  priority arrows, smart relative dates, group-head with status chip
 *  + chevron collapse, inline "+ add in group" row. Pairs with the
 *  list rendering in admin/tasks-page.js → renderList().
 * ═════════════════════════════════════════════════════════════════════ */

/* Column header — icon + label + sort indicator, all on one baseline. */
.th-cell {
  display: inline-flex; align-items: center; gap: .4rem;
  font-weight: 600; font-size: .78rem;
  color: var(--text-2);
  text-transform: uppercase;
  letter-spacing: .03em;
  white-space: nowrap;
}
.th-icon {
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-3);
}
.th-icon svg, .th-icon .lucide-ic { width: 14px; height: 14px; }
.th-label { white-space: nowrap; }
th.sortable { cursor: pointer; user-select: none; }
th.sortable:hover .th-label { color: var(--text); }

/* Project identity color (2026-05-17) — per-project hue derived from
 * project.name (see _projectColor). Surfaces on three views so a
 * multi-project board reads "which project does each row belong to?"
 * at first glance.
 *
 * Forms:
 *   .project-dot     → 8px round dot (inline)
 *   .project-chip    → dot + project name (only in wide rows)
 *   --card-proj-color → 3px inline-start stripe on kanban cards
 */
.project-dot {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--proj-color, var(--text-3));
  flex-shrink: 0;
  vertical-align: middle;
}
.project-chip {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: .72rem; font-weight: 600;
  background: color-mix(in srgb, var(--proj-color, var(--text-3)) 12%, transparent);
  color: var(--proj-color, var(--text-2));
}

/* Kanban card project stripe — inset accent pill tinted to project.
 * Sits on the card's start edge as a quiet identity marker.
 *
 * Inset PILL geometry (2026-07-06, owner screenshot): a 3px band that
 * spans the full card height can never follow the card's 12px corner
 * arc — the outline pulls in up to 12px while the stripe only curves
 * within its own 3px, so its ends always poked outside the silhouette.
 * Vertically insetting the stripe past the corner zones and rounding it
 * into a capsule (Linear/Notion accent style) removes the corner
 * interaction entirely — nothing left to bleed, in either direction. */
/* Kanban card project stripe REMOVED (2026-07-15). The 3px edge rail read
   as an AI-design cliché (accent bar on a rounded card); project identity
   now rides an inline .project-dot at the start of .t-title instead — a
   quieter, high-level marker. (The card still carries --card-proj-color
   inline; harmless, no consumer now. Historical note: the old
   [style*="--card-proj-color"] rule had to avoid `position` or its 0,2,0
   specificity leaked `relative` onto the drag clone and hid the ghost.) */

/* Gantt label project dot — sits just before the type icon, separated
 * by a small gap. Stays visible at every zoom level. */
.gantt-label .project-dot {
  margin-inline-end: .35rem;
}

/* File-type thumbnails (Monday-style, 2026-05-16) — color-coded small
 * squares per attachment family (image=purple, pdf=red, sheet=green,
 * doc=blue, slides=orange, video=pink, audio=teal, archive=amber,
 * code=slate, neutral). Stack with -4px overlap so they read as a row
 * even when 3+ files. The +N overflow chip uses the neutral surface
 * so it doesn't shout from the rainbow. */
.file-thumb-stack {
  display: inline-flex;
  align-items: center;
  vertical-align: middle;
  margin-inline-start: .4rem;
}
.file-thumb {
  width: 18px; height: 18px;
  border-radius: 4px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--thumb-color, #64748b);
  color: #fff;
  margin-inline-start: -3px;
  box-shadow: 0 0 0 1.5px var(--card);
  flex-shrink: 0;
}
.file-thumb:first-child { margin-inline-start: 0; }
.file-thumb svg, .file-thumb .lucide-ic { width: 10px; height: 10px; }
.file-thumb.file-thumb-more {
  background: var(--bg-3);
  color: var(--text-2);
  font-size: .62rem;
  font-weight: 600;
}

/* Comment-count chip (Monday-style "discussion at a glance", 2026-05-16).
 * Used by List view (inline next to title) and Gantt label (in the meta
 * line2 strip). Tiny round-pill, neutral surface — communicates "this
 * task has discussion" without competing for attention with status. */
.row-comment-chip,
.gantt-label-comments {
  display: inline-flex;
  align-items: center;
  gap: .2rem;
  margin-inline-start: .4rem;
  padding: 1px 6px;
  font-size: .68rem;
  font-weight: 600;
  color: var(--text-2);
  background: var(--bg-3);
  border-radius: 999px;
  line-height: 1.4;
  vertical-align: middle;
}
.row-comment-chip svg, .row-comment-chip .lucide-ic,
.gantt-label-comments svg, .gantt-label-comments .lucide-ic {
  width: 10px; height: 10px;
}

/* Assignee column — compact stack of 22px avatars with -6px overlap.
 * Empty state is a discreet em-dash so the column never looks broken. */
.row-av-stack { display: inline-flex; align-items: center; }
.row-av { margin-inline-start: -6px; line-height: 0; }
.row-av:first-child { margin-inline-start: 0; }
.row-av .avatar {
  width: 26px; height: 26px; font-size: .68rem;
  border: 2px solid var(--card);
  box-sizing: border-box;
}
.row-av-more {
  width: 26px; height: 26px;
  border-radius: 50%;
  background: var(--bg-3); color: var(--text-2);
  font-size: .65rem; font-weight: 600;
  display: inline-flex; align-items: center; justify-content: center;
  border: 2px solid var(--card);
  margin-inline-start: -6px;
}
.row-av-empty { color: var(--text-3); font-size: .85rem; }

/* Priority — directional arrow + label. Tone classes paint both the
 * arrow and the label so the cell reads at a glance.
 *   urgent → red double-up,  high → orange up,
 *   medium → blue equals,    low  → gray down. */
.priority-arrow {
  display: inline-flex; align-items: center; gap: .35rem;
  font-size: .82rem; font-weight: 600;
  white-space: nowrap;
}
.priority-arrow .pa-sym {
  font-weight: 600; font-size: 1rem;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.priority-arrow.is-urgent { color: #dc2626; }
.priority-arrow.is-high   { color: #ea580c; }
.priority-arrow.is-medium { color: #0891b2; }
.priority-arrow.is-low    { color: #64748b; }

/* Smart relative date cell — calendar glyph + tone-tinted label. */
.rd-cell { white-space: nowrap; }
.rel-date {
  display: inline-flex; align-items: center; gap: .35rem;
  font-size: .82rem; font-weight: 600;
}
.rel-date .rd-icon { display: inline-flex; opacity: .85; }
.rel-date .rd-icon svg, .rel-date .rd-icon .lucide-ic { width: 13px; height: 13px; }
.rel-date.is-today   { color: var(--brand); }
.rel-date.is-soon    { color: #ea580c; }
.rel-date.is-overdue { color: #dc2626; }
.rel-date.is-future  { color: var(--text-2); }
.rd-empty { color: var(--text-3); font-size: .85rem; }

/* Group head row — Monday-grade colored section banner. The left edge
 * gets a 4px tint matching the group's identity (status color, priority
 * color, neutral for CF/other axes). Sticky-positioned so the header
 * stays visible while scrolling a long list — the user always knows
 * which bucket the rows on screen belong to. Section spacing above
 * each head (except the first) creates visual breathing room. */
tr.list-group-head > td {
  background: var(--card-2);
  padding: .75rem .9rem;
  border-bottom: 1px solid var(--border);
  border-top: 1px solid var(--border);
  border-inline-start: 4px solid var(--group-color, var(--text-3));
  position: sticky;
  top: 0;
  z-index: 4;
}
/* Section spacing — every group-head except the FIRST in the table gets
 * extra top padding so each section breathes. Using sibling combinator
 * is the cleanest way to skip the first-of-type without JS. */
tr.list-group-head ~ tr.list-group-add + tr.list-group-head > td,
tr.list-group-head + tr ~ tr.list-group-head > td {
  /* fallback that triggers when at least one row precedes this head */
  padding-top: 1.1rem;
  box-shadow: inset 0 8px 0 0 var(--card);
}
.lgh-toggle {
  appearance: none; border: 0; background: transparent;
  padding: 0; margin-inline-end: .55rem;
  width: 22px; height: 22px;
  border-radius: 6px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-2); cursor: pointer;
  transition: background .12s, transform .15s;
}
.lgh-toggle:hover { background: var(--bg-3); color: var(--text); }
.lgh-toggle svg, .lgh-toggle .lucide-ic { width: 14px; height: 14px; }
/* Collapsed: rotate chevron from pointing-down to pointing-end. */
tr.list-group-head.is-collapsed .lgh-toggle svg,
tr.list-group-head.is-collapsed .lgh-toggle .lucide-ic { transform: rotate(-90deg); }
html[dir="rtl"] tr.list-group-head.is-collapsed .lgh-toggle svg,
html[dir="rtl"] tr.list-group-head.is-collapsed .lgh-toggle .lucide-ic { transform: rotate(90deg); }
/* The count chip mirrors the group color at 14% opacity — same family
 * as status chips so a "Done · 5" header reads consistently with the
 * Done chip on individual rows. */
.lgh-count {
  display: inline-flex; align-items: center; justify-content: center;
  margin-inline-start: .55rem;
  min-width: 22px; height: 22px; padding: 0 .55rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--group-color, var(--text-3)) 14%, transparent);
  color: var(--group-color, var(--text-2));
  font-size: .72rem; font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* Inline "+ add in group" — quiet by default, full brand tint on hover.
 * The button stretches the full cell so the hit target is generous. */
tr.list-group-add > td {
  background: var(--card);
  padding: 0;
  border-bottom: 1px solid var(--border);
}
.lga-btn {
  appearance: none; border: 0; background: transparent;
  width: 100%;
  display: inline-flex; align-items: center; gap: .45rem;
  padding: .55rem .9rem;
  color: var(--text-3); cursor: pointer;
  font-family: inherit; font-size: .85rem; font-weight: 600;
  transition: color .12s, background .12s;
}
.lga-btn:hover {
  background: color-mix(in srgb, var(--brand) 6%, transparent);
  color: var(--brand);
}
.lga-btn svg, .lga-btn .lucide-ic { width: 14px; height: 14px; }

/* Collapse mechanic — hide every row marked collapsed via JS. */
tr.is-collapsed:not(.list-group-head) { display: none; }

/* Chat layout — Phase 1 */
/* Use 100dvh on mobile so the composer doesn't get hidden behind the
   browser's collapsing address bar / Android gesture bar. Falling back
   to 100vh keeps desktop / older browsers unchanged. */
main:has(.chat-shell) { display: flex; flex-direction: column; height: 100vh; height: 100dvh; min-height: 100vh; min-height: 100dvh; overflow: hidden; }
main:has(.chat-shell) > .main { flex: 1; min-height: 0; padding: 0; display: flex; }
.chat-shell { display: grid; grid-template-columns: var(--chat-sb-w, 240px) 1fr; height: 100%; width: 100%; min-height: 0; }
.chat-sidebar { border-inline-end: 1px solid var(--border); display: flex; flex-direction: column; overflow: hidden; min-height: 0; position: relative; }
/* Unified sidebar (Slack/GHL-style): single scroll area with a pinned
   search box at the top and collapsible Channels + Direct-messages
   sections below. Replaces the old dual-section layout. */
.chat-sidebar-unified { padding: 0; }
.chat-sidebar-search {
  position: relative; padding: .7rem .75rem;
  border-bottom: 1px solid var(--border);
  flex: 0 0 auto;
}
.chat-sidebar-search > input {
  width: 100%;
  /* Extra inline-end padding leaves room for the two icon buttons
     (bookmarks + scheduled) anchored at the trailing edge. */
  padding: .5rem 4rem .5rem 2.1rem;
  border-radius: var(--radius-sm);
  background: var(--bg-2);
  border: 1px solid var(--border);
  color: var(--text); font-size: .88rem; outline: none;
}
html[dir="rtl"] .chat-sidebar-search > input { padding: .5rem 2.1rem .5rem 4rem; }
.chat-sidebar-search > input:focus { border-color: var(--ring); box-shadow: 0 0 0 3px var(--ring-glow); }
.chat-sidebar-search-icon {
  position: absolute; top: 50%; transform: translateY(-50%);
  inset-inline-start: 1.1rem; color: var(--text-3);
  pointer-events: none; display: inline-flex;
}
.chat-sidebar-search-icon .lucide-ic { width: 1rem; height: 1rem; }
.chat-list-empty { padding: 1rem; text-align: center; font-size: .85rem; }
.chat-sidebar-resize { position: absolute; top: 0; bottom: 0; inset-inline-end: -3px; width: 6px; cursor: col-resize; z-index: 5; user-select: none; }
.chat-sidebar-resize::before { content: ''; position: absolute; top: 0; bottom: 0; inset-inline-end: 2px; width: 2px; background: transparent; transition: background .15s ease; }
.chat-sidebar-resize:hover::before, body.chat-sb-dragging .chat-sidebar-resize::before { background: var(--accent); }
body.chat-sb-dragging { cursor: col-resize !important; user-select: none; }
body.chat-sb-dragging * { cursor: col-resize !important; }
.chat-sidebar-section { display: flex; flex-direction: column; min-height: 0; flex: 1 1 0; border-bottom: 1px solid var(--border); overflow: hidden; }
.chat-sidebar-section:last-child { border-bottom: 0; }
.chat-sidebar-section:not(.open) { flex: 0 0 auto; }
.chat-sidebar-section:not(.open) .chat-sidebar-body { display: none; }
.chat-sidebar-body { display: flex; flex-direction: column; flex: 1 1 0; min-height: 0; overflow: hidden; }
.chat-sidebar-title {
  display: flex; align-items: center; justify-content: space-between; gap: .4rem;
  padding: .55rem .8rem; font-weight: 600; font-size: .8rem;
  letter-spacing: .02em; color: var(--text-2);
  background: var(--card-2); border: 0; border-bottom: 1px solid var(--border);
  cursor: pointer; user-select: none; flex: 0 0 auto; width: 100%; text-align: start; font-family: inherit;
}
.chat-sidebar-title:hover { background: var(--bg-3); color: var(--text); }
.chat-sidebar-chev { display: inline-flex; transition: transform .18s ease; color: var(--text-3); }
.chat-sidebar-chev .lucide-ic { width: 1rem; height: 1rem; }
.chat-sidebar-section.open > .chat-sidebar-title .chat-sidebar-chev { transform: rotate(180deg); }
.chat-sidebar-body .input { margin: .4rem .6rem; width: calc(100% - 1.2rem); font-size: .85rem; padding: .45rem .7rem; flex: 0 0 auto; }
.chat-list-inner { overflow-y: auto; overflow-x: hidden; flex: 1 1 0; min-height: 0; scrollbar-width: thin; }
.chat-list-inner::-webkit-scrollbar { width: 8px; }
.chat-list-inner::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 4px; }
.chat-list-inner::-webkit-scrollbar-track { background: transparent; }

.chat-row { padding: .6rem .9rem; border-bottom: 1px solid var(--border); cursor: pointer; display: flex; gap: .6rem; align-items: center; transition: background .15s; }
.chat-row:hover { background: var(--card-2); }
.chat-row.active { background: var(--card-2); }
.chat-row-info { flex: 1; min-width: 0; }
.chat-row-top { display: flex; justify-content: space-between; gap: .3rem; align-items: center; }
.chat-row-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-row-time { font-size: .7rem; color: var(--text-3); flex-shrink: 0; font-variant-numeric: tabular-nums; }
.chat-row-bottom { display: flex; justify-content: space-between; gap: .4rem; align-items: center; }
.chat-row-preview { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-3); font-size: .85rem; flex: 1 1 auto; min-width: 0; }
.chat-row-unread {
  flex-shrink: 0;
  min-width: 18px; height: 18px;
  padding: 0 .35rem;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--brand);
  color: var(--accent-fg);
  border-radius: 999px;
  font-size: .7rem; font-weight: 600;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.chat-row.active .chat-row-name { color: var(--brand); }

/* Mobile: WhatsApp / Telegram-style large rows so each contact is a
   comfortable thumb target with name + last message + time at a glance. */
@media (max-width: 720px) {
  .chat-row {
    padding: .8rem 1rem;
    gap: .85rem;
    min-height: 64px;
  }
  .chat-row > .avatar,
  .chat-row > .chat-avatar,
  .chat-row .avatar {
    width: 48px; height: 48px;
    font-size: .95rem;
    flex-shrink: 0;
  }
  .chat-row-hash {
    width: 48px; height: 48px;
    flex-shrink: 0;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 50%;
    background: color-mix(in srgb, var(--brand) 14%, var(--card-2));
    color: var(--brand);
    font-size: 1.4rem; font-weight: 600;
  }
  .chat-row-name { font-size: 1rem; }
  .chat-row-preview { font-size: .88rem; }
  .chat-row-time { font-size: .72rem; }
  /* Empty channels list: collapse to a single hint row instead of the
     "No channels" box taking ~80px. The auto-collapse rule in JS hides
     the body entirely; this just keeps the empty fallback compact when
     the user manually opens it. */
  .chat-list-empty { padding: .6rem .9rem; font-size: .82rem; }
  /* Member row mirrors the chat-row sizing so the DM section reads
     uniformly when it includes both initiated DMs + uninitiated peers. */
  .member-row {
    padding: .8rem 1rem;
    gap: .85rem;
    min-height: 64px;
  }
  .member-row .avatar { width: 48px; height: 48px; font-size: .95rem; }
  .member-name { font-size: 1rem; font-weight: 600; }
}

.member-row { padding: .5rem .9rem; cursor: pointer; display: flex; gap: .6rem; align-items: center; border-bottom: 1px solid var(--border); transition: background .15s; }
.member-row:hover { background: var(--card-2); }
.member-info { flex: 1; min-width: 0; }
.member-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.chat-body { display: flex; flex-direction: column; min-height: 0; }
.chat-header { padding: .7rem 1rem; border-bottom: 1px solid var(--border); background: var(--card-2); min-height: 56px; display: flex; align-items: center; }
.chat-header-info { display: flex; align-items: center; gap: .6rem; cursor: pointer; padding: .2rem .3rem; border-radius: var(--radius-sm); transition: background .15s; user-select: none; flex: 1; min-width: 0; }
.chat-header-info:hover { background: var(--card); }
.chat-header-info:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.chat-header-actions { display: flex; align-items: center; gap: .25rem; margin-inline-start: auto; }
/* Meet-in-chat M1: channels with virtual-office access swap the ring-call
   icon for a solid Meet pill (owner's reference) that walks into the
   channel's persistent room on the office floor. */
.chat-meet-btn {
  display: inline-flex; align-items: center; gap: .4rem;
  height: 34px; padding: 0 .85rem; border: 0; border-radius: 999px;
  background: var(--accent); color: #fff; cursor: pointer;
  font: inherit; font-size: .85rem; font-weight: 600;
  transition: background .12s;
}
.chat-meet-btn:hover { background: color-mix(in srgb, var(--accent) 86%, #000); }
.chat-meet-btn:disabled { opacity: .6; cursor: default; }
.chat-meet-btn .lucide-ic { width: 16px; height: 16px; }
/* M2: live presence beside the Meet pill — overlapping avatar bubbles +
   a green-dot headcount (the reference's «3●»). */
.chat-meet-group { display: inline-flex; align-items: center; gap: .45rem; }
.chat-meet-presence { display: inline-flex; align-items: center; }
.chat-meet-presence[hidden] { display: none; }
.chat-meet-presence .cmp-av {
  width: 22px; height: 22px; border-radius: 50%; object-fit: cover;
  border: 2px solid var(--card); background: var(--card-2);
  margin-inline-start: -6px;
}
.chat-meet-presence .cmp-av:first-child { margin-inline-start: 0; }
.chat-meet-presence .cmp-av-txt {
  display: inline-flex; align-items: center; justify-content: center;
  font-size: .65rem; font-weight: 600; color: var(--text-2);
}
.chat-meet-presence .cmp-count {
  display: inline-flex; align-items: center; gap: .25rem;
  margin-inline-start: .35rem; font-size: .78rem; font-weight: 600;
  color: var(--text-2);
}
.chat-meet-presence .cmp-dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--success, #16A34A);
}
/* M3: the meeting's trace in the thread — a centered reference-style card
   (title + duration + attendee cluster), sibling of .msg-call-log. */
.msg-meeting { display: flex; justify-content: center; padding: .45rem 0; }
.msg-meeting-card {
  min-width: 260px; max-width: 380px;
  background: var(--card); border: 1px solid var(--border-2);
  border-radius: var(--radius-field); padding: .7rem .85rem;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .05);
  display: flex; flex-direction: column; gap: .5rem;
}
.msg-meeting-card .mtg-head { display: flex; align-items: center; gap: .45rem; }
.msg-meeting-card .mtg-head .lucide-ic { width: 16px; height: 16px; color: var(--accent); flex-shrink: 0; }
.msg-meeting-card .mtg-title { font-weight: 600; font-size: .9rem; }
.msg-meeting-card .mtg-time { margin-inline-start: auto; font-size: .72rem; color: var(--text-3); }
.msg-meeting-card .mtg-meta { display: flex; align-items: center; gap: .8rem; font-size: .8rem; color: var(--text-2); }
.msg-meeting-card .mtg-dur { display: inline-flex; align-items: center; gap: .3rem; }
.msg-meeting-card .mtg-dur .lucide-ic { width: 13px; height: 13px; }
.msg-meeting-card .mtg-people { display: flex; align-items: center; }
.msg-meeting-card .mtg-av {
  width: 26px; height: 26px; border-radius: 50%; object-fit: cover;
  border: 2px solid var(--card); background: var(--card-2);
  margin-inline-start: -7px;
}
.msg-meeting-card .mtg-av:first-child { margin-inline-start: 0; }
.msg-meeting-card .mtg-av-txt {
  display: inline-flex; align-items: center; justify-content: center;
  font-size: .72rem; font-weight: 600; color: var(--text-2);
}
.msg-meeting-card .mtg-more { margin-inline-start: .4rem; font-size: .75rem; font-weight: 600; color: var(--text-2); }

/* In-chat search */
.chat-search-bar { border-bottom: 1px solid var(--border); background: var(--card-2); display: flex; flex-direction: column; max-height: 50%; }
.csb-input-wrap { display: flex; align-items: center; gap: .5rem; padding: .5rem .8rem; border-bottom: 1px solid var(--border); }
.csb-input-wrap .lucide-ic { color: var(--icon-ink); width: 18px; height: 18px; flex-shrink: 0; }
.csb-input-wrap input { flex: 1; border: 0; background: transparent; color: var(--text); font: inherit; outline: none; padding: .3rem 0; font-size: .95rem; }
.csb-close { background: transparent; border: 0; cursor: pointer; color: var(--text-2); width: 28px; height: 28px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; }
.csb-close:hover { background: var(--card); color: var(--text); }
.csb-results { overflow-y: auto; flex: 1; max-height: 320px; }

/* Advanced search — filter chips row + collapsible sender/date panel.
   Chips are mutually exclusive (radio behaviour) so the active type
   is always visible at a glance. The filter button toggles the
   secondary row underneath; on mobile the chips row scrolls
   horizontally to keep the bar from wrapping. */
.csb-filter-btn {
  background: transparent; border: 0; cursor: pointer;
  width: 28px; height: 28px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-3); border-radius: 6px;
  flex-shrink: 0;
}
.csb-filter-btn:hover { color: var(--brand); background: var(--bg-3); }
.csb-filter-btn.active { color: var(--brand); background: var(--bg-3); }
.csb-filter-btn .lucide-ic { width: 16px; height: 16px; }

.csb-types {
  display: flex;
  gap: .35rem;
  padding: .5rem .8rem;
  overflow-x: auto;
  scrollbar-width: none;
  border-bottom: 1px solid var(--border);
  background: color-mix(in srgb, var(--card-2) 65%, var(--card));
}
.csb-types::-webkit-scrollbar { display: none; }
.csb-type-chip {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .35rem .75rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text-2);
  font: inherit;
  font-size: .82rem;
  font-weight: 600;
  cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
  white-space: nowrap;
}
.csb-type-chip:hover {
  background: color-mix(in srgb, var(--brand) 8%, var(--card));
  border-color: color-mix(in srgb, var(--brand) 30%, var(--border));
}
.csb-type-chip.active {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--accent-fg);
}
.csb-type-chip .lucide-ic { width: 14px; height: 14px; }

.csb-filters {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr auto;
  gap: .55rem .65rem;
  padding: .65rem .8rem .75rem;
  border-bottom: 1px solid var(--border);
  background: var(--card-2);
  align-items: end;
}
.csb-filter { display: flex; flex-direction: column; gap: .2rem; min-width: 0; }
.csb-filter-lbl {
  font-size: .72rem;
  font-weight: 600;
  color: var(--text-3);
}
.csb-filter { --control-h: 38px; } /* compact filters keep their density */
.csb-filter .input { padding: .35rem .55rem; font-size: .82rem; min-width: 0; }
.csb-filters .csb-clear {
  align-self: end; padding: .4rem .6rem; font-size: .78rem;
}

.csb-hit-tag {
  display: inline-block;
  padding: .15rem .55rem;
  border-radius: 999px;
  background: var(--card-2);
  font-size: .78rem;
  color: var(--text-2);
  font-weight: 600;
}

@media (max-width: 720px) {
  .csb-filters {
    grid-template-columns: 1fr 1fr;
  }
  .csb-filter:nth-child(1) { grid-column: 1 / -1; } /* sender full width */
  .csb-filters .csb-clear { grid-column: 1 / -1; align-self: stretch; }
  .csb-types { padding: .4rem .65rem; }
  .csb-type-chip { font-size: .78rem; padding: .3rem .65rem; }
}
.csb-empty { padding: 1rem; text-align: center; font-size: .88rem; }
.csb-hit { display: block; width: 100%; text-align: start; background: transparent; border: 0; border-bottom: 1px solid var(--border); padding: .55rem .9rem; cursor: pointer; color: inherit; font: inherit; }
.csb-hit:hover { background: var(--card); }
.csb-hit-top { display: flex; justify-content: space-between; gap: .5rem; margin-bottom: .15rem; }
.csb-hit-name { font-weight: 600; font-size: .85rem; color: var(--accent); }
.csb-hit-snippet { font-size: .88rem; color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.csb-hit-snippet mark { background: var(--accent-soft); color: var(--text); padding: 0 2px; border-radius: 3px; }

.pinned-banner { display: flex; align-items: center; gap: .5rem; padding: .5rem .9rem; background: var(--card-2); border-bottom: 1px solid var(--border); color: var(--text-2); font-size: .85rem; }
.pinned-banner .lucide-ic { color: var(--accent); }
.chat-messages, .msgs { flex: 1; overflow-y: auto; padding: 1rem; display: flex; flex-direction: column; gap: .35rem; scrollbar-width: thin; }
.chat-messages::-webkit-scrollbar, .msgs::-webkit-scrollbar { width: 10px; }
.chat-messages::-webkit-scrollbar-thumb, .msgs::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 5px; }
.chat-messages::-webkit-scrollbar-track, .msgs::-webkit-scrollbar-track { background: transparent; }

.msg-row { display: flex; gap: .5rem; align-items: flex-end; }
.msg-row.mine { flex-direction: row-reverse; }
.msg-row .avatar { width: 28px; height: 28px; font-size: .75rem; flex-shrink: 0; }
.msg-avatar-btn { appearance: none; background: transparent; border: 0; padding: 0; border-radius: 50%; cursor: pointer; display: inline-flex; flex-shrink: 0; transition: transform .12s ease, box-shadow .12s ease; }
.msg-avatar-btn:hover { transform: scale(1.08); box-shadow: 0 0 0 2px var(--accent) inset; }
.msg-avatar-btn:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }

.msg { max-width: 70%; padding: .5rem .8rem; border-radius: var(--radius); background: var(--card-2); border: 1px solid var(--border-2); position: relative; word-wrap: break-word; }
.msg.mine { margin-inline-start: auto; background: var(--accent); color: var(--accent-fg); border: 0; }
.msg .sender-name { font-size: .75rem; font-weight: 600; margin-bottom: .15rem; opacity: .85; color: var(--accent); }
.msg .msg-text { white-space: pre-wrap; word-break: break-word; }
.msg .meta { font-size: .7rem; opacity: .75; margin-top: .25rem; display: flex; gap: .3rem; align-items: center; justify-content: flex-end; }
.msg .ticks { display: inline-flex; align-items: center; color: rgba(255,255,255,.85); }
.msg .ticks svg { width: 16px; height: 16px; stroke-width: 2.5; }
.msg .ticks.double svg { color: #fff; }
.msg.deleted { opacity: .6; font-style: italic; }

.typing-indicator { font-size: .8rem; opacity: .7; padding: .25rem 1rem; font-style: italic; min-height: 1.2rem; }
.composer { border-top: 1px solid var(--border); padding: .6rem .7rem; display: flex; gap: .5rem; align-items: flex-end; background: var(--card-2); }
.composer textarea { flex: 1; resize: none; min-height: 38px; max-height: 120px; padding: .5rem .7rem; border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--card); color: inherit; font: inherit; line-height: 1.4; }
.composer textarea:focus { outline: none; border-color: var(--ring); box-shadow: 0 0 0 3px var(--ring-glow); }

/* Chat Phase 2: context menu, reactions, reply, edit */
.msg-dots { position: absolute; top: 4px; inset-inline-end: 4px; background: transparent; border: 0; cursor: pointer; color: inherit; opacity: 0; font-size: 1rem; padding: 0 .3rem; border-radius: 4px; line-height: 1; }
.msg:hover .msg-dots { opacity: .7; }
.msg-dots:hover { opacity: 1 !important; background: rgba(0,0,0,.15); }
.msg-ctx-menu { position: absolute; z-index: 50; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: .3rem; min-width: 160px; box-shadow: var(--shadow-2); display: flex; flex-direction: column; }
.msg-ctx-menu button { background: transparent; border: 0; padding: .5rem .7rem; text-align: start; cursor: pointer; color: inherit; border-radius: 6px; display: flex; gap: .5rem; align-items: center; font: inherit; }
.msg-ctx-menu button:hover { background: var(--card-2); }
.msg-ctx-menu button.danger { color: var(--danger); }
.reaction-picker { position: absolute; top: -40px; inset-inline-end: 0; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: .3rem .5rem; display: flex; gap: .3rem; box-shadow: var(--shadow-2); z-index: 20; }
.reaction-picker button { background: transparent; border: 0; font-size: 1.15rem; cursor: pointer; padding: 0 .2rem; }
.msg .reactions { display: flex; gap: .25rem; flex-wrap: wrap; margin-top: .3rem; }
.msg .reaction-chip { background: rgba(0,0,0,.15); border-radius: var(--radius-field); padding: .1rem .45rem; font-size: .78rem; cursor: pointer; user-select: none; }
.msg .reaction-chip.mine { background: rgba(124,58,237,.45); }
.msg.mine .reaction-chip { background: rgba(255,255,255,.22); }
.msg.mine .reaction-chip.mine { background: rgba(255,255,255,.4); }
.msg .reply-quote { display: flex; gap: .5rem; align-items: center; border-inline-start: 3px solid currentColor; padding: .3rem .5rem; margin-bottom: .3rem; font-size: .85rem; border-radius: 6px; background: rgba(0,0,0,.12); cursor: pointer; }
.msg.mine .reply-quote { background: rgba(255,255,255,.15); }
.reply-thumb { flex-shrink: 0; width: 36px; height: 36px; border-radius: 6px; overflow: hidden; display: inline-flex; align-items: center; justify-content: center; background: rgba(0,0,0,.2); }
.reply-thumb img { width: 100%; height: 100%; object-fit: cover; }
.reply-thumb-icon { font-size: 1.2rem; }
.reply-quote-body { min-width: 0; flex: 1; }
.reply-quote-name { font-weight: 600; font-size: .8rem; opacity: .95; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.reply-quote-text { opacity: .85; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.msg .edited-tag { font-size: .68rem; opacity: .7; font-style: italic; }
.reply-preview { padding: .4rem .8rem; background: var(--card-2); border-inline-start: 3px solid var(--accent); display: flex; justify-content: space-between; align-items: center; gap: .5rem; }
.reply-preview .close { background: transparent; border: 0; cursor: pointer; color: inherit; padding: .2rem .4rem; border-radius: 4px; }
.reply-preview .close:hover { background: rgba(0,0,0,.1); }

/* Jump-to-message flash */
.msg.flash { animation: msg-flash 1.4s ease-out; }
@keyframes msg-flash {
  0% { box-shadow: 0 0 0 3px rgba(124,58,237,.7); background-color: rgba(124,58,237,.25); }
  100% { box-shadow: 0 0 0 0 rgba(124,58,237,0); background-color: transparent; }
}

/* Drag & drop overlay */
.chat-body { position: relative; }
.chat-body .drop-overlay { position: absolute; inset: 0; z-index: 30; display: none; align-items: center; justify-content: center; background: rgba(124,58,237,.18); border: 3px dashed var(--accent); border-radius: var(--radius-field); margin: .5rem; pointer-events: none; }
.chat-body.dragging .drop-overlay { display: flex; }
.chat-body .drop-hint { display: flex; flex-direction: column; align-items: center; gap: .5rem; color: var(--accent); font-weight: 600; font-size: 1.1rem; background: var(--card); padding: 1rem 1.5rem; border-radius: var(--radius-sm); box-shadow: var(--shadow-2); }
.chat-body .drop-hint svg { width: 28px; height: 28px; }

/* Telegram-style voice player */
.voice-msg { display: flex; align-items: center; gap: .6rem; min-width: 240px; padding: .15rem 0; }
.voice-msg .voice-play { flex-shrink: 0; width: 40px; height: 40px; border-radius: 50%; border: 0; cursor: pointer; background: #fff; color: var(--accent); display: inline-flex; align-items: center; justify-content: center; box-shadow: 0 1px 3px rgba(0,0,0,.2); }
.msg:not(.mine) .voice-msg .voice-play { background: var(--accent); color: var(--accent-fg); }
.voice-msg .voice-play svg { width: 18px; height: 18px; }
.voice-msg .voice-body { flex: 1; display: flex; flex-direction: column; gap: .2rem; min-width: 0; }
.voice-msg .voice-wave { position: relative; display: flex; align-items: center; justify-content: space-between; gap: 2px; height: 28px; cursor: pointer; overflow: hidden; }
.voice-msg .voice-wave .vb { display: inline-block; width: 2.5px; min-height: 4px; border-radius: 2px; background: rgba(255,255,255,.55); transition: background .1s; flex: 0 0 auto; }
.msg:not(.mine) .voice-msg .voice-wave .vb { background: rgba(124,58,237,.35); }
.voice-msg .voice-progress { position: absolute; inset-block: 0; inset-inline-start: 0; width: 0%; background: rgba(255,255,255,.25); pointer-events: none; border-radius: 3px; mix-blend-mode: overlay; }
.msg:not(.mine) .voice-msg .voice-progress { background: rgba(124,58,237,.25); }
.voice-msg .voice-meta { display: flex; align-items: center; gap: .3rem; font-size: .75rem; font-variant-numeric: tabular-nums; opacity: .85; }
.voice-msg .voice-time { font-weight: 600; }

/* Telegram-style composer */
.tg-composer { align-items: center; gap: .5rem; padding: .35rem .55rem; background: var(--card-2); border: 1px solid var(--border); border-radius: var(--radius); margin: .6rem 1rem 1rem; }
/* Composer channel toggle — reply over WhatsApp vs in-app (two-way inbox). */
/* Channel bar — GHL Conversations layout: a tinted strip across the TOP of the
   composer holds the channel selector as a white chip (icon + channel name +
   caret). The chip tints green when WhatsApp is the active channel — a quiet
   "you're about to send over WhatsApp" cue. The bar only renders in
   WhatsApp-capable DMs (renderChannelToggle toggles #comp-channel-bar). */
.comp-channel-bar {
  display: flex;
  align-items: center;
  gap: .4rem;
  padding: .3rem .45rem;
  background: color-mix(in srgb, var(--card-2) 55%, var(--card));
  border-bottom: 1px solid var(--border);
  border-start-start-radius: calc(var(--radius-field) - 1px);
  border-start-end-radius: calc(var(--radius-field) - 1px);
}
.reply-channel-wrap { position: relative; display: inline-flex; align-items: center; }
.reply-channel {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .28rem .6rem;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text-2);
  cursor: pointer;
  border-radius: var(--radius-field);
  font-size: .8rem;
  font-weight: 600;
  transition: background var(--dur-1, .15s) var(--ease-out, ease), color var(--dur-1, .15s) var(--ease-out, ease), border-color var(--dur-1, .15s) var(--ease-out, ease);
}
.reply-channel:hover { border-color: var(--text-3); }
.reply-channel .rc-ic { display: inline-flex; }
.reply-channel .rc-ic svg { width: 16px; height: 16px; }
.reply-channel .rc-label { line-height: 1; }
.reply-channel .rc-caret { width: 12px; height: 12px; opacity: .5; }
.reply-channel.is-wa { background: rgba(37,211,102,.1); border-color: rgba(37,211,102,.38); color: #128C4B; }
.reply-channel.is-wa .rc-ic svg { color: var(--wa); }
:root:not(.light) .reply-channel.is-wa { color: var(--wa); background: color-mix(in srgb, var(--wa) 14%, transparent); }
/* Channel picker menu — opens upward from the composer chip. */
.reply-channel-menu { position: absolute; bottom: calc(100% + 6px); inset-inline-start: 0; min-width: 200px; padding: .3rem; background: var(--card); border: 1px solid var(--border); border-radius: 14px; box-shadow: 0 12px 34px rgba(0,0,0,.16); z-index: 40; display: flex; flex-direction: column; gap: 2px; }
.reply-channel-menu[hidden] { display: none; }
.reply-channel-item { display: flex; align-items: center; gap: .55rem; width: 100%; padding: .5rem .6rem; border: 0; background: transparent; border-radius: 10px; cursor: pointer; color: var(--text); font-size: .85rem; font-weight: 500; text-align: start; transition: background var(--dur-1, .15s) var(--ease-out, ease); }
.reply-channel-item:hover { background: var(--card-2); }
.reply-channel-item.is-active { color: var(--brand); }
.reply-channel-item .rc-ic { display: inline-flex; width: 18px; justify-content: center; color: var(--icon-ink); }
.reply-channel-item .rc-ic svg { width: 16px; height: 16px; }
.reply-channel-item .rc-ic.is-wa svg { color: var(--wa); }
.reply-channel-item .rc-label { flex: 1; }
.reply-channel-item .rc-check { display: inline-flex; width: 16px; justify-content: center; color: var(--brand); }
.reply-channel-item .rc-check .lucide-ic { width: 15px; height: 15px; }
/* Full-height section dividers (GHL-style): crisp lines spanning the composer
   border-to-border, sectioning it into [channel] | [input icons] | [send].
   align-self:stretch only reaches the padding box, so the negative block margins
   (matching the composer's .25rem block padding) extend the line to the frame. */
.tg-composer .comp-sep { align-self: stretch; flex: 0 0 auto; width: 1px; margin-block: -.25rem; background: var(--border); }
/* Send button + its Send/Send-Later dropdown chevron. */
.tg-composer .send-group { position: relative; display: inline-flex; align-items: center; gap: 1px; flex: 0 0 auto; }
.tg-composer .comp-send-more { display: none; background: transparent; border: 0; width: 20px; height: 32px; cursor: pointer; color: var(--text-3); align-items: center; justify-content: center; border-radius: var(--radius-sm); transition: background .12s, color .12s; }
.tg-composer:has(#send-btn.is-send) .comp-send-more { display: inline-flex; }
.tg-composer .comp-send-more:hover { background: var(--card-2); color: var(--text); }
.tg-composer .comp-send-more .lucide-ic { width: 15px; height: 15px; }

/* Split send (owner reference 2026-07-17, resized softer 2026-07-18):
   once there's text the round mic spot becomes ONE blue body — plane
   segment plus a slimmer chevron segment behind a light seam. Owner
   round 2: «كبير وحاد» → compact 28px height with HALF-HEIGHT pill
   radii (Royal Pixel's pill language kills the sharpness; the modest
   scale matches the reference). Radii are logical so the pair mirrors
   under RTL. NB no overflow:hidden on .send-group — the send-later menu
   pops from inside it. Recording keeps its red pulse (that rule is
   !important and outranks the id selector here). */
.tg-composer .send-group:has(#send-btn.is-send) { gap: 0; }
.tg-composer #send-btn.is-send {
  width: 38px;
  height: 28px;
  border-radius: 0;
  border-start-start-radius: 999px;
  border-end-start-radius: 999px;
  background: var(--accent);
  color: #fff;
}
.tg-composer #send-btn.is-send .lucide-ic,
.tg-composer #send-btn.is-send svg { width: 15px; height: 15px; }
.tg-composer #send-btn.is-send:hover { background: color-mix(in srgb, var(--accent) 86%, #000); color: #fff; }
.tg-composer:has(#send-btn.is-send) .comp-send-more {
  width: 22px;
  height: 28px;
  border-radius: 0;
  border-start-end-radius: 999px;
  border-end-end-radius: 999px;
  background: var(--accent);
  color: rgba(255, 255, 255, .9);
  border-inline-start: 1px solid rgba(255, 255, 255, .28);
}
.tg-composer:has(#send-btn.is-send) .comp-send-more .lucide-ic { width: 12px; height: 12px; }
.tg-composer:has(#send-btn.is-send) .comp-send-more:hover,
.tg-composer:has(#send-btn.is-send) .comp-send-more[aria-expanded="true"] {
  background: color-mix(in srgb, var(--accent) 86%, #000);
  color: #fff;
}
.send-menu { position: absolute; bottom: calc(100% + 8px); inset-inline-end: 0; min-width: 190px; padding: .3rem; background: var(--card); border: 1px solid var(--border); border-radius: 14px; box-shadow: 0 12px 34px rgba(0,0,0,.16); z-index: 40; display: flex; flex-direction: column; gap: 2px; }
.send-menu[hidden] { display: none; }
.send-menu button { display: flex; align-items: center; gap: .55rem; width: 100%; padding: .5rem .6rem; border: 0; background: transparent; border-radius: 10px; cursor: pointer; color: var(--text); font-size: .85rem; font-weight: 500; text-align: start; transition: background .12s; }
.send-menu button:hover { background: var(--card-2); }
.send-menu button svg, .send-menu button .lucide-ic { width: 16px; height: 16px; color: var(--icon-ink); flex-shrink: 0; }
/* Doubled class on purpose: the legacy `.composer textarea` border/ring
   rules tie a bare `.tg-composer textarea` on specificity (0,1,1) and a
   merge reorder already flipped that coin once. (0,2,1) wins outright. */
.composer.tg-composer textarea { border: 0; background: transparent; padding: .4rem .2rem; }
.composer.tg-composer textarea:focus { outline: none; box-shadow: none; }
.comp-btn { background: transparent; border: 0; width: 38px; height: 38px; border-radius: 50%; cursor: pointer; color: var(--text-2); font-size: 1.2rem; display: inline-flex; align-items: center; justify-content: center; transition: background .12s, color .12s; }
.comp-btn:hover { background: var(--border); color: var(--text); }
.comp-primary { color: var(--accent); }
.comp-primary.is-send { color: var(--accent-fg); background: var(--accent); }
.comp-primary.recording { color: var(--accent-fg); background: var(--accent); }
@keyframes pulse-rec { 0%,100% { box-shadow:0 0 0 0 rgba(239,68,68,.6); } 50% { box-shadow:0 0 0 8px rgba(239,68,68,0); } }
.comp-cancel { color: var(--danger); }
.comp-cancel:hover { background: rgba(239,68,68,.12); color: var(--danger); }
.rec-ui { flex: 1; display: flex; align-items: center; gap: .6rem; padding: .4rem .2rem; font-variant-numeric: tabular-nums; font-weight: 600; color: var(--text); }
.rec-ui #rec-time { margin-inline-start: auto; }
.rec-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--danger); animation: rec-blink 1s infinite; flex-shrink: 0; }
@keyframes rec-blink { 0%,100% { opacity: 1; } 50% { opacity: .3; } }

/* Anchored to its own button (send-group pattern) — the old hard-coded
   bottom/inset offsets dated from the single-row composer and left the
   menu floating at the far corner of the pane after the redesigns. */
.attach-wrap { position: relative; display: inline-flex; }
.attach-menu { position: absolute; bottom: calc(100% + 8px); inset-inline-start: 0; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-field); padding: .4rem; box-shadow: var(--shadow-2); z-index: 40; flex-direction: column; min-width: 190px; }
/* Soft icon weight across the composer's action row (owner reference:
   1.5-ish stroke reads calmer than Lucide's default 2). */
.tg-composer .comp-actions svg { stroke-width: 1.75; }
/* …extended to the whole chat window chrome — header actions (search,
   focus, wave, meet), composer buttons, send-menu — so every icon in the
   chat frame speaks the same soft-stroke family. Scoped to chat classes
   only; icons outside the chat window keep Lucide's default 2. */
.chat-header-actions .comp-btn svg, .chat-header-actions .chat-meet-btn svg, .tg-composer .comp-btn svg, .send-menu button svg { stroke-width: 1.75; }
/* .comp-btn's own display:inline-flex (author) beats the UA [hidden]
   rule — this keeps the hidden attribute honest (mention-btn is
   channel-only and toggled via hidden per chat open). */
.comp-btn[hidden] { display: none; }
.attach-menu button { background: transparent; border: 0; padding: .6rem .8rem; text-align: start; cursor: pointer; color: inherit; display: flex; gap: .6rem; align-items: center; border-radius: var(--radius-sm); font: inherit; }
.attach-menu button:hover { background: var(--card-2); }

.emoji-pop { position: fixed; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: .35rem; box-shadow: var(--shadow-2); display: grid; grid-template-columns: repeat(8, 1fr); gap: .15rem; z-index: 60; max-width: 320px; }
.emoji-pop button { background: transparent; border: 0; font-size: 1.25rem; cursor: pointer; padding: .25rem; border-radius: 6px; }
.emoji-pop button:hover { background: var(--card-2); }

/* Date separator */
.date-sep { display: flex; justify-content: center; margin: .6rem 0; }
.date-sep span { background: rgba(0,0,0,.45); color: #fff; font-size: .78rem; padding: .2rem .7rem; border-radius: var(--radius-field); font-weight: 600; }
html.light .date-sep span { background: rgba(31,35,40,.75); color: #fff; }

/* Telegram-style context menu wrap (reactions row + actions) */
.msg-ctx-wrap { position: absolute; z-index: 60; display: flex; flex-direction: column; gap: .4rem; align-items: flex-start; }
.msg-ctx-wrap .msg-ctx-menu { position: static; }
.ctx-reactions { display: flex; gap: .2rem; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: .3rem .5rem; box-shadow: var(--shadow-2); align-self: flex-start; }
.ctx-reactions button { background: transparent; border: 0; font-size: 1.25rem; cursor: pointer; padding: .15rem .25rem; border-radius: 50%; transition: transform .12s; }
.ctx-reactions button:hover { transform: scale(1.25); }

/* Attachment styles */
.att-image { max-width: 280px; max-height: 320px; border-radius: var(--radius-sm); display: block; cursor: pointer; }
.att-video { max-width: 320px; max-height: 320px; border-radius: var(--radius-sm); display: block; }
.att-file { display: flex; align-items: center; gap: .5rem; padding: .4rem .6rem; background: rgba(0,0,0,.12); border-radius: var(--radius-sm); color: inherit; text-decoration: none; }
.att-file:hover { background: rgba(0,0,0,.2); }
.forwarded-tag { font-size: .75rem; opacity: .7; font-style: italic; margin-bottom: .2rem; }

/* Utilities */
.flex { display: flex; } .items-center { align-items: center; } .justify-between { justify-content: space-between; }
.gap-1 { gap: .25rem; } .gap-2 { gap: .5rem; } .gap-3 { gap: .75rem; } .gap-4 { gap: 1rem; }
.w-full { width: 100%; } .text-sm { font-size: .88rem; } .text-xs { font-size: .78rem; }
.mt-1 { margin-top: .25rem; } .mt-2 { margin-top: .5rem; } .mt-3 { margin-top: .75rem; } .mt-4 { margin-top: 1rem; } .mt-6 { margin-top: 1.5rem; }
.mb-2 { margin-bottom: .5rem; } .mb-3 { margin-bottom: .75rem; } .mb-4 { margin-bottom: 1rem; }
.text-muted { color: var(--text-2); } .text-subtle { color: var(--text-3); }
.hidden { display: none !important; }
/* Royal Pixel loading signature (spec §5) — three 12px pixel squares
   pulsing in sequence on the blue ladder. Single-element anatomy: the
   element is the middle square, ::before/::after are the flanks, so
   every existing .spinner converts with zero JS changes. */
.spinner {
  width: 12px; height: 12px; border: 0; border-radius: 2px;
  background: var(--primary-500);
  position: relative; display: inline-block;
  animation: px-pulse .9s var(--ease-standard) infinite;
  animation-delay: -.15s;
}
.spinner::before, .spinner::after {
  content: ''; position: absolute; top: 0; width: 12px; height: 12px;
  border-radius: 2px;
  animation: px-pulse .9s var(--ease-standard) infinite;
}
.spinner::before { inset-inline-start: -14px; background: var(--chart-4); animation-delay: -.3s; }
.spinner::after  { inset-inline-start: 14px;  background: var(--primary-300); animation-delay: 0s; }
@keyframes px-pulse { 0%, 100% { opacity: .25; transform: scale(.8); } 40% { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .spinner, .spinner::before, .spinner::after { animation-duration: 2.4s; }
}

/* ── App loader: three wave dots in accent color + subtle label ────── */
.app-loader {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .75rem;
  padding: 2rem 1rem;
  animation: app-loader-fade .3s ease both;
}
.app-loader-inline { padding: 0; gap: 0; flex-direction: row; }

/* Royal Pixel: every UI.loader() call site renders the brand 3-square
   pulse (spec §5) — 12px units on the blue ladder, deep→mid→light. The
   em sizing keeps the sm/lg variants working. */
.app-loader-dots {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-size: 12px; /* the Royal Pixel unit; size variants scale via em */
  line-height: 0;
}
.app-loader-dots > span {
  display: inline-block;
  width: 1em;
  height: 1em;
  border-radius: 2px;
  background: var(--primary-500);
  animation: px-pulse .9s var(--ease-standard) infinite;
}
.app-loader-dots > span:nth-child(1) { background: var(--chart-4); animation-delay: -.3s; }
.app-loader-dots > span:nth-child(2) { animation-delay: -.15s; }
.app-loader-dots > span:nth-child(3) { background: var(--primary-300); animation-delay: 0s; }

.app-loader-sm .app-loader-dots { font-size: 7px; gap: .4em; }
.app-loader-md .app-loader-dots { font-size: 10px; }
.app-loader-lg .app-loader-dots { font-size: 14px; gap: .5em; }

.app-loader-text {
  font-size: .85rem; font-weight: 500; letter-spacing: .02em;
  color: var(--text-3);
  opacity: .85;
}

@keyframes app-loader-fade { from { opacity: 0; } to { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
  .app-loader-dots > span {
    animation: app-loader-pulse-reduced 1.4s ease-in-out infinite;
    transform: none;
  }
}
@keyframes app-loader-pulse-reduced {
  0%, 100% { opacity: .45; }
  50%      { opacity: 1; }
}

/* ── Offline banner ─────────────────────────────────────────────────────
   Slim status pill that drops in from the top when the device loses
   connectivity. Phase-1 offline-shell UX: the app keeps working,
   navigation still works, but the user sees a clear "why data isn't
   refreshing" hint instead of a blank screen or a generic error toast.
   Hidden by default; shell.js adds .is-visible when offline. */
.duwam-offline-banner {
  position: fixed; top: 0; left: 50%; transform: translate(-50%, -120%);
  z-index: 9999;
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .55rem 1rem; margin-top: .55rem;
  border-radius: 999px;
  background: var(--warn);
  color: color-mix(in srgb, var(--warn) 25%, #000);
  font-weight: 600; font-size: .85rem;
  box-shadow: var(--shadow-2);
  pointer-events: none; user-select: none;
  /* Hidden by default — opacity:0 prevents the box-shadow from leaking
     into the viewport while the element sits just above the top edge.
     Without this you can see a faint horizontal shadow band near the
     top of the page even when fully connected. */
  opacity: 0;
  transition: transform .35s cubic-bezier(.2,.8,.2,1), opacity .25s ease-out;
}
.duwam-offline-banner.is-visible { transform: translate(-50%, 0); opacity: 1; }

/* Read-only mode: any control that creates/sends/deletes data should
   carry data-needs-online, and gets dimmed + click-blocked while the
   device is offline. Read flows (filtering, scrolling cached lists,
   navigating between pages) stay fully interactive. */
body.is-offline [data-needs-online] {
  opacity: .45;
  pointer-events: none;
  filter: saturate(.6);
  cursor: not-allowed;
}
.duwam-offline-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: color-mix(in srgb, var(--warn) 25%, #000);
  box-shadow: 0 0 0 0 rgba(26, 20, 8, .6);
  animation: duwam-offline-pulse 1.6s ease-out infinite;
}
@keyframes duwam-offline-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(26, 20, 8, .55); }
  70%  { box-shadow: 0 0 0 7px rgba(26, 20, 8, 0); }
  100% { box-shadow: 0 0 0 0 rgba(26, 20, 8, 0); }
}

/* ── Skeleton screens ───────────────────────────────────────────────────
   Shaped placeholders that mirror the layout of the content about to land
   (Asana / Notion / Linear style). Tuned to be quiet — base sits at --bg-2
   (only a hair above the canvas, so the bar feels "missing content" not
   "loading effect") and the shimmer is a 4% neutral sweep at a relaxed
   2.2s cadence. Crucially we use --text mixed into the bg, NOT the brand
   emerald, so the loading state never competes with real interactive
   accents on the page. */
.sk {
  position: relative; overflow: hidden;
  background: var(--bg-2);
  display: block;
}
.sk::before {
  content: '';
  position: absolute; inset: 0;
  background: linear-gradient(
    90deg,
    transparent 0%,
    color-mix(in srgb, var(--text) 4%, transparent) 50%,
    transparent 100%
  );
  transform: translateX(-100%);
  animation: sk-shimmer 2.2s ease-in-out infinite;
}
.sk-bar { /* alias so existing markup stays clean */ }
.sk-card { border: 1px solid var(--border); }
.sk-list .sk-row:last-child { border-bottom: 0; }

@keyframes sk-shimmer {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(100%); }
}
/* RTL: shimmer should sweep right→left to match reading direction. */
html[dir="rtl"] .sk::before { animation-direction: reverse; }

@media (prefers-reduced-motion: reduce) {
  .sk::before {
    animation: sk-pulse-reduced 2.4s ease-in-out infinite;
    transform: none;
    background: color-mix(in srgb, var(--text) 4%, transparent);
  }
}
@keyframes sk-pulse-reduced {
  0%, 100% { opacity: .25; }
  50%      { opacity: .8;  }
}

/* In-button loader — the Royal Pixel 3-square pulse scaled to em units.
   Inherits currentColor so it works on .btn-primary (white), ghost/
   outline variants, icon buttons. */
.btn-spinner {
  display: inline-block;
  width: .5em; height: .5em; border: 0; border-radius: 1px;
  background: currentColor;
  position: relative;
  vertical-align: -0.05em;
  margin-inline: .7em;
  animation: px-pulse .9s var(--ease-standard) infinite;
  animation-delay: -.15s;
}
.btn-spinner::before, .btn-spinner::after {
  content: ''; position: absolute; top: 0; width: .5em; height: .5em;
  border-radius: 1px; background: currentColor;
  animation: px-pulse .9s var(--ease-standard) infinite;
}
.btn-spinner::before { inset-inline-start: -.7em; animation-delay: -.3s; }
.btn-spinner::after  { inset-inline-start: .7em;  animation-delay: 0s; }
.btn-busy { cursor: progress !important; opacity: .92; pointer-events: none; }
@media (prefers-reduced-motion: reduce) {
  .btn-spinner, .btn-spinner::before, .btn-spinner::after { animation-duration: 2.4s; }
}

.fade-in { animation: fadeIn .25s ease both; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }

/* Chat info side panel */
.info-panel {
  position: absolute; top: 0; bottom: 0; inset-inline-end: 0; width: 340px; max-width: 90%;
  background: var(--bg-2); border-inline-start: 1px solid var(--border);
  display: flex; flex-direction: column; z-index: 40;
  transform: translateX(100%); transition: transform .22s ease;
  box-shadow: var(--shadow-2);
}
html[dir="rtl"] .info-panel { transform: translateX(-100%); }
.info-panel.open,
html[dir="rtl"] .info-panel.open { transform: translateX(0); }
.info-panel-head { display: flex; align-items: center; gap: .6rem; padding: .7rem 1rem; border-bottom: 1px solid var(--border); background: var(--card-2); min-height: 56px; }
.info-panel-title { font-weight: 600; font-size: 1rem; flex: 1; min-width: 0; }
.info-close { background: transparent; border: 0; cursor: pointer; color: var(--text); width: 36px; height: 36px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; }
.info-close:hover { background: var(--card); }
/* Mobile-only: relocates the call action that used to live in the chat
   header. Kept invisible on desktop because the chat header already
   carries its own call button there — duplicating would just add noise. */
.info-call-btn { display: none; background: transparent; border: 0; cursor: pointer; color: var(--brand); width: 38px; height: 38px; border-radius: 50%; align-items: center; justify-content: center; transition: background .15s ease; }
.info-call-btn:hover { background: color-mix(in srgb, var(--brand) 12%, transparent); }
.info-call-btn .lucide-ic { width: 18px; height: 18px; }
@media (max-width: 768px) { .info-call-btn { display: inline-flex; } }
.info-panel-body { flex: 1; overflow-y: auto; padding: 1rem; }
.info-hero { display: flex; flex-direction: column; align-items: center; text-align: center; gap: .4rem; padding: 1rem 0 1.2rem; border-bottom: 1px solid var(--border); }
.info-hero .avatar.lg { width: 96px; height: 96px; font-size: 2rem; }
.info-hero .avatar.lg img, .info-hero img.avatar.lg { width: 96px; height: 96px; object-fit: cover; }
.info-avatar-btn { position: relative; padding: 0; border: 0; background: transparent; cursor: pointer; border-radius: 50%; line-height: 0; }
.info-avatar-btn .avatar.lg { display: block; }
.info-avatar-overlay { position: absolute; inset: 0; border-radius: 50%; background: rgba(0,0,0,.45); color: #fff; display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity .15s ease; }
.info-avatar-overlay svg { width: 26px; height: 26px; }
.info-avatar-btn:hover .info-avatar-overlay, .info-avatar-btn:focus-visible .info-avatar-overlay { opacity: 1; }
.info-title { font-size: 1.25rem; font-weight: 600; }
.info-title-row { display: flex; align-items: center; justify-content: center; gap: .5rem; flex-wrap: wrap; }
.info-rename-btn { background: transparent; border: 0; cursor: pointer; color: var(--text-3); width: 30px; height: 30px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; }
.info-rename-btn:hover { background: var(--card-2); color: var(--text); }
.info-rename-btn svg { width: 16px; height: 16px; }
.info-rename-input { max-width: 220px; text-align: center; font-weight: 600; }
.info-subtitle { color: var(--text-3); font-size: .85rem; }
.info-section-title { font-size: .75rem; text-transform: uppercase; color: var(--text-3); letter-spacing: .08em; margin: 1rem 0 .4rem; font-weight: 600; }
.msg-system { display: flex; justify-content: center; margin: .5rem 0; }
.msg-system span { background: var(--card-2); color: var(--text-3); border: 1px solid var(--border); border-radius: var(--radius-field); padding: .25rem .7rem; font-size: .8rem; max-width: 80%; text-align: center; }
/* Call-log entry — centered chat divider matching .msg-system. Default
   tone is the accent (completed / outgoing call); .is-missed flips it to
   danger so missed calls read as actionable at a glance. */
.msg-call-log { display: flex; justify-content: center; margin: .55rem 0; }
.msg-call-log-pill {
  display: inline-flex; align-items: center; gap: .4rem;
  background: var(--card-2); color: var(--text-2);
  border: 1px solid var(--border);
  border-radius: 999px; padding: .3rem .85rem;
  font-size: .78rem; max-width: 80%;
}
.msg-call-log-pill .lucide-ic { width: 14px; height: 14px; }
.msg-call-log-pill.is-missed {
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 35%, var(--border));
  background: color-mix(in srgb, var(--danger) 6%, var(--card-2));
}
.att-image { cursor: zoom-in; }
.lightbox { position: fixed; inset: 0; background: rgba(0,0,0,.92); z-index: 9999; display: flex; align-items: center; justify-content: center; }
.lightbox-stage { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; overflow: hidden; cursor: zoom-out; }
.lightbox-img { max-width: 92vw; max-height: 92vh; object-fit: contain; transition: transform .15s ease; user-select: none; -webkit-user-drag: none; will-change: transform; }
.lightbox-toolbar { position: absolute; top: 14px; right: 14px; display: flex; gap: .35rem; z-index: 2; }
.lightbox-toolbar button { background: rgba(255,255,255,.08); border: 0; color: #fff; width: 38px; height: 38px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; font-size: .85rem; }
.lightbox-toolbar button:hover { background: rgba(255,255,255,.18); }
.lightbox-toolbar svg { width: 18px; height: 18px; }
.lightbox-nav { position: absolute; top: 50%; transform: translateY(-50%); background: rgba(255,255,255,.08); border: 0; color: #fff; width: 44px; height: 44px; border-radius: 50%; cursor: pointer; font-size: 1.6rem; line-height: 1; z-index: 2; }
.lightbox-nav:hover { background: rgba(255,255,255,.18); }
.lightbox-nav.prev { left: 18px; }
.lightbox-nav.next { right: 18px; }
.lightbox-counter { position: absolute; bottom: 14px; left: 50%; transform: translateX(-50%); color: rgba(255,255,255,.8); font-size: .85rem; background: rgba(0,0,0,.4); padding: .25rem .6rem; border-radius: var(--radius-field); }
html[dir="rtl"] .lightbox-toolbar { right: auto; left: 14px; }
html[dir="rtl"] .lightbox-nav.prev { left: auto; right: 18px; }
html[dir="rtl"] .lightbox-nav.next { right: auto; left: 18px; }
.info-section-row { display: flex; align-items: center; justify-content: space-between; gap: .5rem; margin: 1rem 0 .4rem; }
.info-section-row .info-section-title { margin: 0; }
.info-add-btn { display: inline-flex; align-items: center; gap: .3rem; background: transparent; border: 1px solid var(--border); color: var(--text); border-radius: var(--radius-sm); padding: .25rem .55rem; cursor: pointer; font-size: .8rem; }
.info-add-btn:hover { background: var(--card-2); }
.info-add-btn svg { width: 14px; height: 14px; }
.info-details { display: flex; flex-direction: column; gap: .5rem; }
.info-row { padding: .5rem .6rem; background: var(--card-2); border-radius: var(--radius-sm); border: 1px solid var(--border); }
.info-row-label { font-size: .7rem; color: var(--text-3); text-transform: uppercase; letter-spacing: .05em; }
.info-row-value { font-size: .95rem; color: var(--text); word-break: break-word; }
.info-members { display: flex; flex-direction: column; gap: .2rem; }
.info-member { display: flex; align-items: center; gap: .6rem; padding: .5rem .4rem; border-radius: var(--radius-sm); }
.info-member:hover { background: var(--card-2); }
.info-member-info { flex: 1; min-width: 0; }
.info-member-av { background: transparent; border: 0; padding: 0; line-height: 0; border-radius: 50%; }
.info-member-dm { cursor: pointer; transition: transform .12s ease, box-shadow .12s ease; }
.info-member-dm:hover { transform: scale(1.06); box-shadow: 0 0 0 2px var(--accent) inset; }
.info-member-name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.info-member-remove { background: transparent; border: 0; color: var(--text-3); width: 28px; height: 28px; border-radius: 50%; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; opacity: 0; transition: opacity .15s ease, background .15s ease; }
.info-member-remove svg { width: 14px; height: 14px; }
.info-member:hover .info-member-remove { opacity: 1; }
.info-member-remove:hover { background: color-mix(in srgb, var(--danger, #e25), 15% transparent); color: var(--danger, #e25); }
.info-member-promote { background: transparent; border: 0; color: var(--text-3); width: 28px; height: 28px; border-radius: 50%; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; opacity: 0; transition: opacity .15s ease, background .15s ease, color .15s ease; }
.info-member-promote svg { width: 15px; height: 15px; }
.info-member:hover .info-member-promote { opacity: 1; }
.info-member-promote[data-state="on"] { color: var(--accent); opacity: 1; }
.info-member-promote:hover { background: var(--card-2); color: var(--accent); }
.info-member-badge { display: inline-block; font-size: .65rem; font-weight: 600; padding: .1rem .4rem; border-radius: var(--radius-sm); margin-inline-start: .35rem; vertical-align: middle; }
.info-member-badge.owner { background: color-mix(in srgb, var(--accent), 80% transparent); color: var(--accent); border: 1px solid color-mix(in srgb, var(--accent), 60% transparent); }
.info-member-badge.admin { background: color-mix(in srgb, var(--accent), 88% transparent); color: var(--accent); }

/* Telegram-style delete chat dialog */
.delete-chat-backdrop { background: rgba(0,0,0,.55); }
.delete-chat-dialog {
  background: #1f232b; color: #fff; border-radius: var(--radius); padding: 1.1rem 1.25rem 0;
  width: min(92vw, 420px); box-shadow: var(--shadow-2); overflow: hidden;
}
html.light .delete-chat-dialog { background: #fff; color: var(--text); }
.delete-chat-dialog .dcd-head { display: flex; align-items: center; gap: .7rem; margin-bottom: .7rem; }
.delete-chat-dialog .dcd-head .avatar { width: 44px; height: 44px; font-size: 1rem; }
.delete-chat-dialog .dcd-title { font-size: 1.15rem; font-weight: 600; }
.delete-chat-dialog .dcd-body { font-size: .95rem; line-height: 1.4; opacity: .92; padding-bottom: .5rem; }
.delete-chat-dialog .dcd-actions { display: flex; flex-direction: column; align-items: stretch; margin: 0 -1.25rem; }
.delete-chat-dialog .dcd-btn {
  background: transparent; border: 0; padding: .95rem 1.25rem; text-align: end;
  font: inherit; font-weight: 600; font-size: .95rem; letter-spacing: .04em;
  text-transform: uppercase; cursor: pointer; color: var(--accent);
  border-top: 1px solid rgba(255,255,255,.06);
}
html.light .delete-chat-dialog .dcd-btn { border-top-color: rgba(0,0,0,.08); }
.delete-chat-dialog .dcd-btn:hover { background: rgba(255,255,255,.04); }
html.light .delete-chat-dialog .dcd-btn:hover { background: rgba(0,0,0,.04); }
.delete-chat-dialog .dcd-btn.danger { color: var(--danger); }
.delete-chat-dialog .dcd-btn.primary { color: var(--accent); }

/* Day chips (work / vacation toggles) */
.day-chips { display: flex; gap: .35rem; flex-wrap: wrap; }
.day-chip {
  appearance: none; border: 1px solid var(--border); background: var(--card-2);
  color: var(--text-2); padding: .4rem .7rem; border-radius: 999px;
  font: inherit; font-size: .8rem; font-weight: 600; cursor: pointer;
  transition: background .15s ease, color .15s ease, border-color .15s ease, transform .12s ease;
  user-select: none;
}
.day-chip:hover { border-color: var(--border-2); }
.day-chip.on {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}
.day-chip.off {
  background: var(--card-2); color: var(--text-3);
  border-color: var(--border); opacity: .85;
  text-decoration: line-through;
}

/* Chat compose FAB (lives in chat header) */
.chat-shell { position: relative; }
.chat-header-side { display: inline-flex; align-items: center; gap: .35rem; }
.chat-fab {
  width: 38px; height: 38px; border-radius: 50%; border: 0;
  background: var(--accent); color: var(--accent-fg); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: filter .15s ease, background .15s ease;
  flex-shrink: 0;
}
.chat-fab:hover { filter: brightness(.94); }
.chat-fab:active { filter: brightness(.90); }
.chat-fab .lucide-ic { width: 18px; height: 18px; }
.chat-fab-sm { width: 36px; height: 36px; }
.chat-fab-menu {
  position: absolute; top: calc(100% + 6px); inset-inline-end: 0; z-index: 30;
  min-width: 200px; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-field);
  box-shadow: var(--shadow-2);
  padding: .35rem; opacity: 0; pointer-events: none; transform: translateY(-4px);
  transition: opacity .14s ease, transform .14s ease;
}
.chat-fab-menu.open { opacity: 1; pointer-events: auto; transform: translateY(0); }
.chat-fab-menu button {
  display: flex; align-items: center; gap: .55rem; width: 100%;
  padding: .55rem .7rem; border: 0; background: transparent; color: var(--text);
  border-radius: var(--radius-sm); cursor: pointer; font: inherit; text-align: start;
}
.chat-fab-menu button:hover { background: var(--card-2); color: var(--accent); }
.chat-fab-menu button .lucide-ic { width: 18px; height: 18px; flex-shrink: 0; }

/* Member picker (used by new-group / new-chat modals) */
.new-chat-modal { display: flex; flex-direction: column; max-height: 70vh; }
.pick-list {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  max-height: 320px; overflow-y: auto; background: var(--card-2);
}
.pick-list .pick-row {
  display: flex; align-items: center; gap: .6rem;
  padding: .5rem .7rem; border-bottom: 1px solid var(--border);
  cursor: pointer; transition: background .12s ease;
}
.pick-list .pick-row:last-child { border-bottom: 0; }
.pick-list .pick-row:hover { background: var(--card); }
.pick-row input[type="checkbox"] { flex-shrink: 0; }
.pick-row-av .avatar { width: 32px; height: 32px; font-size: .8rem; }
.pick-row-info { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.pick-row-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pick-row-sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pick-row-btn { width: 100%; border: 0; background: transparent; color: inherit; font: inherit; text-align: start; }

/* ── Push-to-talk mic button (admin) ─────────────────────────────────── */
.mic-btn { user-select: none; touch-action: none; transition: background .15s ease, color .15s ease, transform .12s ease; }
.mic-btn:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--accent); }
.mic-btn.recording { background: var(--danger); color: white; transform: scale(1.05); animation: ptt-pulse 1.2s ease-out infinite; }
@keyframes ptt-pulse { 0% { box-shadow: 0 0 0 0 rgba(255,72,72,.55); } 100% { box-shadow: 0 0 0 14px rgba(255,72,72,0); } }

/* Admin HUD shown while holding the mic button */
.ptt-hud { position: fixed; inset: auto 0 1.2rem 0; display: grid; place-items: center; z-index: 110; pointer-events: none; }
.ptt-hud-card {
  pointer-events: auto;
  background: var(--bg-2); border: 1px solid var(--border-2); border-radius: var(--radius);
  padding: .7rem 1rem; box-shadow: var(--shadow-2);
  display: flex; align-items: center; gap: .8rem;
}
.ptt-dot { width: 12px; height: 12px; border-radius: 50%; background: var(--danger); box-shadow: 0 0 0 0 rgba(255,72,72,.55); animation: ptt-pulse 1.2s ease-out infinite; }
.ptt-text { display: flex; flex-direction: column; min-width: 0; }
.ptt-time { font-family: ui-monospace, monospace; font-weight: 600; font-size: 1.05rem; }
.ptt-hint { font-size: .75rem; color: var(--text-3); margin-inline-start: .4rem; }

/* Overlay the employee sees while admin talks */
.admin-voice-overlay {
  position: fixed; inset: 1rem; top: 1rem; left: 50%; transform: translateX(-50%);
  z-index: 200; display: flex; align-items: flex-start; justify-content: center;
  pointer-events: none;
}
.admin-voice-overlay .avl-card {
  pointer-events: auto; min-width: 320px; max-width: 460px;
  background: var(--bg-2); border: 1px solid var(--accent);
  border-radius: var(--radius-card); padding: 1rem 1.1rem;
  box-shadow: 0 8px 32px rgba(0,0,0,.35);
  display: grid; grid-template-columns: auto 1fr auto; gap: .8rem; align-items: center; position: relative;
}
.admin-voice-overlay .avl-pulse {
  position: absolute; inset-inline-start: 1rem; top: 1rem; width: 48px; height: 48px; border-radius: 50%;
  background: radial-gradient(circle, color-mix(in srgb, var(--accent) 50%, transparent) 0%, transparent 70%);
  animation: ptt-pulse 1.4s ease-out infinite; pointer-events: none;
}
.admin-voice-overlay .avl-avatar { position: relative; }
.admin-voice-overlay .avl-title { font-weight: 600; font-size: .95rem; }
.admin-voice-overlay .avl-name { font-size: .85rem; color: var(--text-2); }
.admin-voice-overlay .avl-status { font-size: .75rem; color: var(--accent); margin-top: .2rem; }
.admin-voice-overlay .avl-close { appearance: none; background: transparent; border: 0; color: var(--text-2); width: 30px; height: 30px; border-radius: var(--radius-sm); cursor: pointer; }
.admin-voice-overlay .avl-close:hover { background: var(--bg-3); }
.admin-voice-overlay .avl-play { grid-column: 1 / -1; margin-top: .5rem; }

/* Contact admin panel (employee topbar) */
.contact-panel .bell-panel-list { padding: .3rem .3rem .1rem; }
.contact-row {
  display: flex; align-items: center; gap: .7rem;
  padding: .55rem .7rem; border-radius: var(--radius-sm);
  transition: background .12s ease;
}
.contact-row + .contact-row { border-top: 1px solid var(--border); }
.contact-row:hover { background: var(--bg-3); }
.contact-row-av .avatar { width: 34px; height: 34px; font-size: .82rem; }
.contact-row-info { flex: 1; min-width: 0; }
.contact-row-name { font-weight: 600; font-size: .9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.contact-row-sub { font-size: .72rem; color: var(--text-3); }
.contact-panel-hint {
  padding: .55rem .9rem; border-top: 1px solid var(--border);
  font-size: .72rem; color: var(--text-3); text-align: center; background: var(--card-2);
}
.contact-group + .contact-group { margin-top: .2rem; border-top: 1px solid var(--border); }
.contact-group-head {
  width: 100%; display: flex; align-items: center; gap: .5rem;
  padding: .6rem .9rem; border: 0; background: var(--card-2);
  font-weight: 600; font-size: .82rem; color: var(--text-2); cursor: pointer;
  text-align: start; font-family: inherit;
  transition: background .12s ease;
}
.contact-group-head:hover { background: var(--bg-3); }
.contact-group-title { flex: 1; }
.contact-group-count {
  background: var(--bg-2); border: 1px solid var(--border);
  padding: .08rem .5rem; border-radius: 999px;
  font-size: .72rem; font-weight: 600; color: var(--text-3);
}
.contact-group-caret { color: var(--text-3); transform: rotate(0); transition: transform .18s ease; }
.contact-group-caret .lucide-ic { width: .95rem; height: .95rem; }
.contact-group-head.collapsed .contact-group-caret { transform: rotate(-90deg); }
.contact-group-body { padding: .15rem 0; }

/* Urgent message — admin send panel */
.urgent-panel .urgent-body { padding: .9rem 1rem 1rem; }
.urgent-panel textarea.input { resize: vertical; min-height: 90px; max-height: 240px; }

/* Urgent message — employee-side pinned stack (bottom-inline-end) */
.urgent-stack {
  position: fixed; bottom: 1rem; inset-inline-end: 1rem;
  display: flex; flex-direction: column; gap: .6rem;
  z-index: 180; max-width: min(380px, calc(100vw - 2rem));
  pointer-events: none;
}
.urgent-card {
  pointer-events: auto;
  background: var(--bg-2);
  border: 1px solid var(--danger);
  border-inline-start: 4px solid var(--danger);
  border-radius: var(--radius); padding: .85rem 1rem;
  box-shadow: var(--shadow-2);
  animation: urgent-in .22s ease;
  display: flex; flex-direction: column; gap: .6rem;
}
@keyframes urgent-in { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
.urgent-card-head { display: flex; align-items: center; gap: .6rem; }
.urgent-card-who { min-width: 0; flex: 1; }
.urgent-card-name { font-weight: 600; font-size: .92rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.urgent-card-sub { font-size: .72rem; color: var(--danger); font-weight: 600; }
.urgent-card-text { font-size: .92rem; line-height: 1.45; white-space: pre-wrap; word-break: break-word; color: var(--text); }
.urgent-card-actions { display: flex; gap: .4rem; justify-content: flex-end; }
.urgent-card .btn-primary { background: var(--danger); border-color: var(--danger); }
.urgent-card .btn-primary:hover { filter: brightness(1.08); }

/* Monthly payroll/attendance report table — sticky header + highlighted totals.
   The wrap is the scroll container: rounding + overflow clip the header and
   totals bands to the brand radius, and the sticky header/footer stick to the
   wrap's own edges (top:0 / bottom:0) instead of the page topbar — the latter
   left the opaque header painting over the first data row. */
#rep-table-wrap {
  overflow: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
@media (min-width: 900px) {
  #rep-table-wrap { max-height: min(68vh, 720px); }
}
.report-table thead th {
  position: sticky;
  top: 0;
  background: var(--card-2);
  z-index: 5;
  font-size: .75rem; letter-spacing: .04em; text-transform: uppercase; color: var(--text-3);
  box-shadow: 0 1px 0 var(--border), 0 6px 12px -10px rgba(0,0,0,.4);
}
.report-table tbody td { vertical-align: middle; }
.report-table tfoot .report-totals td {
  position: sticky; bottom: 0; z-index: 4;
  background: color-mix(in srgb, var(--accent) 8%, var(--card-2));
  border-top: 2px solid var(--border-2);
  padding-top: .85rem; padding-bottom: .85rem;
  font-size: .92rem;
  box-shadow: 0 -6px 12px -10px rgba(0,0,0,.4);
}

/* Conditional-formatting "heat" cells — a sequential single-hue tint scaled by
   magnitude (--heat, 0..1, set per cell in JS). The fill carries the semantic
   (red = shortfall, blue = overtime); the number stays in high-contrast ink so
   readability holds in both themes (dataviz: text wears ink, the mark carries
   color). Dark surfaces take a stronger mix to stay visible on the dark card. */
.report-table td.heat-danger,
.report-table td.heat-info { color: var(--text); font-weight: 600; }
.report-table td.heat-danger { background: color-mix(in srgb, var(--danger) calc(var(--heat, 0) * 22%), transparent); }
.report-table td.heat-info   { background: color-mix(in srgb, var(--info)   calc(var(--heat, 0) * 22%), transparent); }
:root:not(.light) .report-table td.heat-danger { background: color-mix(in srgb, var(--danger) calc(var(--heat, 0) * 30%), transparent); }
:root:not(.light) .report-table td.heat-info   { background: color-mix(in srgb, var(--info)   calc(var(--heat, 0) * 30%), transparent); }

/* KPI headline band above the report table. Flex (not grid) so that when the
   5 cards wrap on a narrow content column, the wrapped card grows to fill its
   row instead of leaving an orphaned gap. */
.report-kpis { display: flex; flex-wrap: wrap; gap: .8rem; }
.report-kpis .stat { flex: 1 1 170px; padding: .7rem .9rem; }
.report-kpis .stat .label { font-size: .7rem; }
.report-kpis .stat .value { font-size: 1.5rem; margin-top: .15rem; white-space: nowrap; }
.report-kpis .kpi-unit { font-size: .8rem; font-weight: 600; color: var(--text-3); margin-inline-start: .1rem; }

/* Sortable column headers */
.report-table th.th-sort { cursor: pointer; user-select: none; transition: color .12s ease; }
.report-table th.th-sort:hover { color: var(--text-2); }
.report-table th.th-sort:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 6px; }
.report-table th.th-sort .th-sort-in { display: inline-flex; align-items: center; gap: .3rem; }
/* Caret is a CSS-drawn triangle so no glyph/emoji font dependency. Hidden
   until the column is the active sort, then points up (asc) or down (desc). */
.report-table th.th-sort .sort-caret {
  width: 0; height: 0;
  border-inline-start: 4px solid transparent;
  border-inline-end: 4px solid transparent;
  border-top: 5px solid currentColor;
  opacity: 0; transition: opacity .12s ease, transform .12s ease;
}
.report-table th.th-sort:hover .sort-caret { opacity: .35; }
.report-table th.th-sort.is-sorted { color: var(--accent); }
.report-table th.th-sort.is-sorted .sort-caret { opacity: 1; color: var(--accent); }
.report-table th.th-sort.is-sorted.is-asc .sort-caret { transform: rotate(180deg); }

/* Rows with dues still owed for the period get a subtle attention accent.
   All first cells reserve the 3px inline-start border (transparent) so the
   accent never shifts column alignment; logical prop keeps it on the outer
   edge in both LTR and RTL. */
.report-table tbody td:first-child { border-inline-start: 3px solid transparent; }
.report-table tbody tr.is-pending td:first-child { border-inline-start-color: var(--accent); }

/* Report loading state with progress counter */
.rep-loading { display: flex; flex-direction: column; align-items: center; gap: .6rem; padding: 2.5rem 1rem; }
.rep-loading-count { font-family: ui-monospace, monospace; font-weight: 600; }

/* Royal Pixel signature at the foot of exported reports — one of the 4
   sanctioned mountain spots (spec §5). Screen hides it; print reveals it
   and forces exact colors so the blues survive the printer dialog. */
.rep-print-foot { display: none; }
@media print {
  body * { visibility: hidden; }
  #rep-table-wrap, #rep-table-wrap * { visibility: visible; }
  .rep-print-foot {
    display: block; text-align: center; margin-top: 8mm;
  }
  .rep-print-foot .px-mountains rect {
    print-color-adjust: exact;
    -webkit-print-color-adjust: exact;
  }
  /* Undo the on-screen scroll container so the whole table prints unclipped */
  #rep-table-wrap { position: absolute; left: 0; top: 0; width: 100%; max-height: none; overflow: visible; border: 0; border-radius: 0; }
  .topbar, .sidebar, .ws-tabs { display: none !important; }

  /* Free-tier watermark — only renders when shell.js sets
     body[data-watermark="1"] (mirrors planLimits.hasWatermark on the
     server). Lives in @media print so it appears on browser-print → PDF
     and on physical paper, but never during normal screen viewing. The
     ::after element overrides the parent's `visibility: hidden` so it's
     visible alongside the report content above. Bilingual + URL by
     design — Saudi printouts often circulate beyond the originating
     organization, so we keep the brand and the destination both
     readable in any context. */
  body[data-watermark="1"]::after {
    content: 'مدعوم من دوام  ·  Powered by Duwam  ·  duwam.com';
    visibility: visible !important;
    position: fixed; bottom: 8mm; left: 0; right: 0;
    text-align: center; font-size: 8pt; font-weight: 600;
    color: #6B7280; letter-spacing: 0.02em;
    padding-top: 4mm;
    border-top: 1px solid #D1D5DB;
  }
}

/* ── Date range picker (GHL-inspired) ─────────────────────────────── */
.dr-wrap { position: relative; display: inline-flex; align-items: center; gap: .5rem; direction: ltr; }
.dr-field {
  display: inline-flex; align-items: center; gap: .6rem;
  background: var(--card); border: 1.5px solid var(--accent);
  border-radius: var(--radius-sm); padding: .35rem .7rem; cursor: pointer;
  font-family: ui-monospace, monospace; font-weight: 600; font-size: .9rem;
}
.dr-field:hover { background: var(--card-2); }
.dr-date { color: var(--text); min-width: 9ch; text-align: center; }
.dr-arrow { color: var(--text-3); display: inline-flex; }
.dr-arrow .lucide-ic { width: .9rem; height: .9rem; transform: rotate(-90deg); }
.dr-toggle {
  appearance: none; background: transparent; border: 0; padding: .2rem;
  color: var(--text-2); cursor: pointer; display: inline-flex;
}
.dr-toggle:hover { color: var(--text); }
.dr-reload {
  appearance: none; border: 0; background: var(--accent); color: var(--accent-fg);
  width: 38px; height: 38px; border-radius: var(--radius-sm); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: filter .15s ease;
}
.dr-reload:hover { filter: brightness(1.08); }
/* Report toolbar: drop the heavy emerald outline on the date field (reads as a
   permanent "active" state) for a neutral field, and give the field + refresh
   button one shared height so the row lines up with the other toolbar buttons. */
.rep-range-host .dr-field {
  border: 1px solid var(--border);
  height: 38px; padding-top: 0; padding-bottom: 0; box-sizing: border-box;
}
.rep-range-host .dr-field:hover { border-color: var(--border-2); }
.rep-range-host .dr-reload { height: 38px; }
.dr-popover {
  position: absolute; top: calc(100% + 8px); inset-inline-start: 0;
  z-index: 80; direction: ltr;
  background: var(--card); border: 1px solid var(--border-2);
  border-radius: var(--radius); box-shadow: var(--shadow-2);
  padding: .75rem; min-width: 500px; max-width: 92vw;
  max-height: min(520px, 85vh); overflow-y: auto;
}
.dr-popover.dr-pop-flip-x { inset-inline-start: auto; inset-inline-end: 0; }
.dr-popover.dr-pop-flip-y { top: auto; bottom: calc(100% + 8px); }
.dr-calendars { display: grid; grid-template-columns: 1fr 1fr; gap: .7rem; margin-bottom: .6rem; }
.dr-cal { min-width: 0; }
.dr-cal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: .35rem; gap: .3rem; }
.dr-cal-title { font-weight: 600; font-size: .88rem; color: var(--text); }
.dr-cal-nav {
  appearance: none; background: transparent; border: 0; padding: .2rem; border-radius: 6px;
  color: var(--text-2); cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px;
}
.dr-cal-nav:hover { background: var(--bg-3); color: var(--text); }
.dr-cal-nav .lucide-ic { width: .9rem; height: .9rem; }
.dr-cal-nav[data-nav="-1"] .lucide-ic { transform: rotate(90deg); }
.dr-cal-nav[data-nav="1"] .lucide-ic { transform: rotate(-90deg); }
.dr-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; }
.dr-dow { font-size: .68rem; color: var(--text-3); text-align: center; padding: .25rem 0 .2rem; font-weight: 600; }
.dr-day {
  appearance: none; background: transparent; border: 0;
  padding: .3rem 0; font: inherit; font-size: .8rem; color: var(--text);
  cursor: pointer; border-radius: 6px; text-align: center;
  transition: background .1s ease, color .1s ease;
  min-width: 0;
}
.dr-day:hover { background: var(--bg-3); }
.dr-day.outside { color: var(--text-3); opacity: .45; }
.dr-day.in-range { background: var(--accent-soft); border-radius: 0; color: var(--text); }
.dr-day.start, .dr-day.end {
  background: var(--accent); color: var(--accent-fg); font-weight: 600;
}
.dr-day.start { border-start-end-radius: 0; border-end-end-radius: 0; }
.dr-day.end { border-start-start-radius: 0; border-end-start-radius: 0; }
.dr-day.single { border-radius: 999px; }
.dr-presets {
  display: flex; gap: .3rem; flex-wrap: wrap;
  padding: .55rem 0 .4rem; border-top: 1px solid var(--border);
  margin-top: .3rem;
}
.dr-preset {
  appearance: none; background: var(--card-2); border: 1px solid var(--border);
  border-radius: 999px; padding: .3rem .7rem; font: inherit; font-size: .76rem;
  color: var(--text-2); cursor: pointer; transition: background .12s ease, color .12s ease, border-color .12s ease;
  white-space: nowrap;
}
.dr-preset:hover { background: var(--bg-3); color: var(--text); border-color: var(--border-2); }
.dr-footer { display: flex; justify-content: flex-end; gap: .4rem; padding-top: .45rem; border-top: 1px solid var(--border); }
.dr-footer .btn { padding: .4rem .9rem; font-size: .82rem; }

@media (max-width: 560px) {
  .dr-popover { min-width: 280px; }
  .dr-calendars { grid-template-columns: 1fr; }
}

/* ── Single-day picker (DayPicker) ─────────────────────────────────────
   Same visual language as the range picker but with a single selection,
   a compact popover, and the brand's light-blue accent (--accent-2). */
.dp-wrap { position: relative; display: inline-flex; align-items: center; }
.dp-field {
  display: inline-flex; align-items: center; gap: .5rem;
  background: color-mix(in srgb, var(--accent-2) 12%, var(--card));
  border: 1.5px solid color-mix(in srgb, var(--accent-2) 55%, var(--border-2));
  color: var(--text);
  border-radius: 999px; padding: .35rem .9rem;
  font: inherit; font-size: .85rem; font-weight: 600;
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease, transform .08s ease;
}
.dp-field:hover {
  background: color-mix(in srgb, var(--accent-2) 20%, var(--card));
  border-color: var(--accent-2);
}
.dp-field:active { transform: translateY(1px); }
.dp-field .dp-icon { color: var(--accent-2); display: inline-flex; }
.dp-field .dp-icon .lucide-ic { width: 1rem; height: 1rem; }
.dp-field .dp-chev { color: var(--accent-2); display: inline-flex; opacity: .8; }
.dp-field .dp-chev .lucide-ic { width: .85rem; height: .85rem; }
.dp-label { white-space: nowrap; }

.dp-popover {
  position: absolute; top: calc(100% + 8px); inset-inline-end: 0;
  z-index: 80; direction: ltr;
  background: var(--card); border: 1px solid var(--border-2);
  border-radius: var(--radius); box-shadow: var(--shadow-2);
  padding: .7rem; min-width: 280px; max-width: 92vw;
}
.dp-popover.dp-pop-flip-x { inset-inline-end: auto; inset-inline-start: 0; }
.dp-popover.dp-pop-flip-y { top: auto; bottom: calc(100% + 8px); }
.dp-cal { min-width: 0; }
.dp-cal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: .4rem; gap: .3rem; }
.dp-cal-title { font-weight: 600; font-size: .9rem; color: var(--text); }
.dp-cal-nav {
  appearance: none; background: transparent; border: 0; padding: .2rem; border-radius: 6px;
  color: var(--text-2); cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px;
}
.dp-cal-nav:hover { background: color-mix(in srgb, var(--accent-2) 15%, transparent); color: var(--accent-2); }
.dp-cal-nav .lucide-ic { width: .95rem; height: .95rem; }
.dp-cal-nav[data-nav="-1"] .lucide-ic { transform: rotate(90deg); }
.dp-cal-nav[data-nav="1"] .lucide-ic { transform: rotate(-90deg); }
.dp-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.dp-dow { font-size: .68rem; color: var(--text-3); text-align: center; padding: .25rem 0 .3rem; font-weight: 600; }
.dp-day {
  appearance: none; background: transparent; border: 0;
  padding: .4rem 0; font: inherit; font-size: .82rem; color: var(--text);
  cursor: pointer; border-radius: var(--radius-sm); text-align: center;
  transition: background .12s ease, color .12s ease;
  min-width: 0;
}
.dp-day:hover:not(.disabled):not(.selected) {
  background: color-mix(in srgb, var(--accent-2) 18%, transparent);
  color: var(--accent-2);
}
.dp-day.outside { color: var(--text-3); opacity: .45; }
.dp-day.selected {
  background: var(--accent); color: var(--accent-fg); font-weight: 600;
  box-shadow: 0 2px 8px color-mix(in srgb, var(--accent) 35%, transparent);
}
.dp-day.disabled { color: var(--text-3); opacity: .3; cursor: not-allowed; }

.dp-presets {
  display: flex; gap: .3rem; flex-wrap: wrap;
  padding-top: .55rem; margin-top: .45rem;
  border-top: 1px solid var(--border);
}
.dp-preset {
  appearance: none; background: transparent;
  border: 1px solid var(--border);
  border-radius: 999px; padding: .28rem .7rem;
  font: inherit; font-size: .75rem; color: var(--text-2);
  cursor: pointer; white-space: nowrap;
  transition: background .12s ease, color .12s ease, border-color .12s ease;
}
.dp-preset:hover {
  background: color-mix(in srgb, var(--accent-2) 15%, transparent);
  color: var(--accent-2);
  border-color: color-mix(in srgb, var(--accent-2) 50%, var(--border));
}
.dp-preset.active {
  background: color-mix(in srgb, var(--accent-2) 22%, transparent);
  color: var(--accent-2);
  border-color: var(--accent-2);
  font-weight: 600;
}

/* ── Employee multiselect (GHL-inspired) ───────────────────────────── */
/* Employee multiselect — always-visible inline panel (GHL-style). */
.emp-multi { display: block; width: 100%; }
.emp-multi-panel {
  background: var(--card-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: .55rem;
  display: flex; flex-direction: column; gap: .5rem;
}
.emp-multi-head {
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
}
.emp-multi-summary {
  display: inline-flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  flex: 0 0 auto;
}
.emp-multi-search-wrap {
  position: relative;
  flex: 1 1 240px; min-width: 200px; order: 1;
}
.emp-multi-toggle {
  appearance: none; background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-sm); width: 36px; height: 36px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-3); cursor: pointer; flex: 0 0 auto; order: 2;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.emp-multi-toggle:hover { background: var(--bg-3); color: var(--text); border-color: var(--border-2); }
.emp-multi-toggle .lucide-ic {
  width: 1.05rem; height: 1.05rem;
  transition: transform .2s ease;
}
.emp-multi-panel.expanded .emp-multi-toggle {
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
}
.emp-multi-panel.expanded .emp-multi-toggle .lucide-ic { transform: rotate(180deg); }
.emp-multi-search-icon {
  position: absolute; top: 50%; inset-inline-start: .7rem;
  transform: translateY(-50%);
  color: var(--text-3); pointer-events: none;
  display: inline-flex; align-items: center;
}
.emp-multi-search-icon .lucide-ic { width: 1rem; height: 1rem; }
.emp-multi-search {
  font-size: .9rem; border-radius: var(--radius-sm); background: var(--card);
  padding-inline-start: 2.1rem;
}
.emp-multi-chip {
  display: inline-flex; align-items: center;
  background: var(--card); color: var(--text-2);
  border: 1px solid var(--border);
  border-radius: 999px; padding: .3rem .8rem;
  font-size: .82rem; font-weight: 600; line-height: 1.1;
  white-space: nowrap;
}
.emp-multi-chip.all {
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
}
.emp-multi-chip.selected-count {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
}
.emp-multi-clear {
  appearance: none; background: transparent; border: 0; padding: .2rem .4rem;
  color: var(--text-3); font: inherit; font-size: .82rem;
  cursor: pointer; text-decoration: underline;
}
.emp-multi-clear:hover { color: var(--text); }

.emp-multi-list {
  display: flex; flex-direction: column;
  max-height: 260px; overflow-y: auto;
  padding: .15rem;
  border-top: 1px solid var(--border);
  scrollbar-width: thin; scrollbar-color: var(--border-2) transparent;
}
.emp-multi-panel:not(.expanded) .emp-multi-list { display: none; }
.emp-multi-empty { text-align: center; padding: 1.2rem .5rem; font-size: .85rem; }
.emp-multi-list::-webkit-scrollbar { width: 6px; }
.emp-multi-list::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 3px; }
.emp-multi-row {
  display: flex; align-items: center; gap: .6rem;
  padding: .5rem .65rem; border-radius: var(--radius-sm); cursor: pointer;
  transition: background .1s ease;
  margin: 1px 0;
}
.emp-multi-row:hover { background: var(--bg-3); }
.emp-multi-row input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  width: 18px; height: 18px; border-radius: 5px;
  border: 1.5px solid var(--border-2); background: var(--card);
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; flex-shrink: 0; transition: background .15s ease, border-color .15s ease;
  position: relative;
}
.emp-multi-row input[type="checkbox"]:checked {
  background: var(--accent); border-color: var(--accent);
}
.emp-multi-row input[type="checkbox"]:checked::after {
  content: ""; position: absolute;
  width: 5px; height: 9px;
  border: solid #fff; border-width: 0 2px 2px 0;
  transform: rotate(45deg); top: 1px; left: 5px;
}
.emp-multi-row .avatar { width: 30px; height: 30px; font-size: .76rem; flex-shrink: 0; }
.emp-multi-row .emp-multi-name {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: .88rem; font-weight: 500; color: var(--text);
}
.emp-multi-row.all-row {
  border-bottom: 1px solid var(--border);
  padding-bottom: .6rem; margin-bottom: .2rem;
  font-weight: 600;
}
.emp-multi-row.all-row .emp-multi-name { font-weight: 600; }

/* Login "Keep me signed in" checkbox */
.login-remember {
  display: flex; align-items: center; gap: .55rem;
  cursor: pointer; user-select: none;
  font-size: .88rem; color: var(--text-2);
  padding: .3rem 0;
}
.login-remember input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  width: 18px; height: 18px; border-radius: 5px;
  border: 1.5px solid var(--border-2); background: var(--card);
  cursor: pointer; flex-shrink: 0; position: relative;
  transition: background .15s ease, border-color .15s ease;
}
.login-remember input[type="checkbox"]:checked {
  background: var(--accent); border-color: var(--accent);
}
.login-remember input[type="checkbox"]:checked::after {
  content: ""; position: absolute;
  width: 5px; height: 9px;
  border: solid #fff; border-width: 0 2px 2px 0;
  transform: rotate(45deg); top: 1px; left: 5px;
}
.login-remember:hover { color: var(--text); }

/* ── Staff table (GHL-inspired) ─────────────────────────────────── */
.staff-bar {
  display: grid;
  grid-template-columns: minmax(180px, 240px) 1fr auto;
  gap: .8rem;
  align-items: center;
}
.staff-role { font-weight: 600; }
.staff-search {
  display: flex; align-items: center; gap: .5rem;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 0 .8rem;
  transition: border-color .15s ease;
}
.staff-search:focus-within { border-color: var(--ring); box-shadow: 0 0 0 3px var(--ring-glow); }
.staff-search .lucide-ic { width: 1rem; height: 1rem; color: var(--icon-ink); flex-shrink: 0; }
.staff-search .input {
  background: transparent; border: 0; padding: .6rem 0;
  font-size: .92rem; flex: 1; outline: none; color: var(--text);
}
.staff-add { padding: .6rem 1.3rem; font-weight: 600; }

.staff-table thead th {
  padding: .9rem 1rem; font-size: .82rem; color: var(--text-3);
  text-transform: none; letter-spacing: 0;
  background: var(--card-2);
}
.staff-table tbody td { padding: .9rem 1rem; vertical-align: middle; }
.staff-table tbody tr + tr td { border-top: 1px solid var(--border); }
.staff-row-off { opacity: .55; }
.staff-name-cell { display: flex; align-items: center; gap: .7rem; min-width: 0; }
.staff-name { font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 220px; }
.staff-muted { color: var(--text-2); font-size: .9rem; }

.staff-role-tag {
  display: inline-block; padding: .28rem .7rem;
  background: var(--card-2); color: var(--text-2);
  border: 1px solid var(--border); border-radius: 999px;
  font-size: .72rem; font-weight: 600; letter-spacing: .04em;
}
.staff-role-tag.admin {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 38%, var(--border));
}

.staff-actions { display: inline-flex; gap: .35rem; }
.staff-icon-btn {
  appearance: none; background: transparent; border: 0;
  width: 34px; height: 34px; border-radius: var(--radius-sm);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-2); cursor: pointer;
  transition: background .12s ease, color .12s ease;
}
.staff-icon-btn:hover { background: var(--bg-3); color: var(--text); }
.staff-icon-btn.danger:hover { background: color-mix(in srgb, var(--danger) 16%, transparent); color: var(--danger); }
.staff-icon-btn .lucide-ic { width: 1.05rem; height: 1.05rem; }

.staff-foot {
  display: flex; align-items: center; justify-content: space-between;
  padding: .7rem 1rem; border-top: 1px solid var(--border);
  background: var(--card-2);
  gap: .8rem; flex-wrap: wrap;
}
.staff-pager { display: inline-flex; gap: .35rem; }
.staff-pager .btn-sm { min-width: 36px; }

@media (max-width: 780px) {
  .staff-bar { grid-template-columns: 1fr; }
  .staff-table tbody td { padding: .75rem .6rem; }
  .staff-name { max-width: 140px; }
}

/* ── Edit employee modal extras ─────────────────────────────────── */
.edit-grid {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: .8rem 1rem;
}
.edit-grid .label { margin-bottom: .25rem; }
.edit-grid > div { min-width: 0; }
@media (max-width: 680px) { .edit-grid { grid-template-columns: 1fr; } }

/* Bank / passport grouping inside the Payment tab */
.pay-bank-card {
  border: 1px solid var(--border);
  background: var(--card-2);
  border-radius: var(--radius);
  padding: .9rem 1rem 1.1rem;
}
.pay-bank-head {
  font-weight: 600; font-size: .95rem;
  color: var(--text); margin-bottom: .7rem;
  padding-bottom: .5rem; border-bottom: 1px dashed var(--border);
}

/* Permission groups as tabs inside the Permissions section */
.perm-group-tabs {
  display: flex; gap: .35rem; flex-wrap: wrap;
  padding: .3rem; margin-bottom: .7rem;
  background: var(--card-2); border: 1px solid var(--border); border-radius: var(--radius-field);
}
.perm-group-tab {
  appearance: none; background: transparent; border: 0;
  padding: .45rem .7rem; border-radius: var(--radius-sm); cursor: pointer;
  display: inline-flex; align-items: center; gap: .4rem;
  font: inherit; font-size: .82rem; font-weight: 600; color: var(--text-2);
  transition: background .12s ease, color .12s ease;
}
.perm-group-tab:hover { background: var(--bg-3); color: var(--text); }
.perm-group-tab.active {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
}
.perm-group-tab .lucide-ic { width: .9rem; height: .9rem; }
.perm-group-tab .chip { background: var(--bg-2); color: var(--text-3); font-size: .7rem; padding: .1rem .4rem; }
.perm-group-tab.active .chip { background: var(--bg-2); color: var(--accent); }

/* ── Settings shell (sectioned sidebar, GHL-inspired) ─────────────── */
.settings-shell {
  display: grid;
  /* 240px — mirrors the main sidebar's Asana width so the settings
     aside reads as the same chrome, not a second design. */
  grid-template-columns: 240px 1fr;
  gap: 1.2rem;
  align-items: start;
}
/* Sticky aside: stays pinned just below the topbar while the content
   column scrolls. `top` must clear the topbar (also sticky at top:0)
   otherwise the aside would slide *under* it. The +.5rem keeps a small
   visual breathing gap between topbar and aside. */
.settings-aside {
  position: sticky; top: calc(var(--topbar-h) + .5rem);
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-card);
  padding: .4rem;
  max-height: calc(100vh - var(--topbar-h) - 1.5rem);
  overflow-y: auto;
  scrollbar-width: thin; scrollbar-color: var(--border-2) transparent;
}
.settings-aside-head {
  padding: .7rem .9rem .5rem;
  display: flex; flex-direction: column; align-items: flex-start; gap: .45rem;
}
.settings-aside-head h3 { font-size: 1.1rem; font-weight: 600; }
/* Back button — flat row matching the nav items below (Asana-density
   pass 2026-07-07: tiles and accent bars retired; a quiet wash + bare
   glyph carry the interaction). Negative inline margin aligns it with
   the nav-item rows beneath the aside-head padding. */
.settings-back {
  appearance: none; background: transparent; border: 0;
  align-self: stretch;
  margin-inline: -.7rem;
  padding: .3rem .7rem; border-radius: var(--radius-sm);
  min-height: 32px;
  display: flex; align-items: center; gap: .55rem;
  color: var(--text-3); cursor: pointer; font: inherit; font-size: .85rem;
  position: relative;
  transition: background .15s ease, color .15s ease, transform .15s ease;
}
/* Slide-back instead of forward — the direction is inverted vs. the nav
   items below, so the motion itself hints "go back" (LTR: left, RTL:
   right). Semantic motion = free UX hint. */
.settings-back:hover {
  color: var(--text);
  background: color-mix(in srgb, var(--brand) 6%, transparent);
  transform: translateX(-2px);
}
html[dir="rtl"] .settings-back:hover { transform: translateX(2px); }
.settings-back .sb-arrow {
  width: 20px; height: 20px;
  color: var(--text-3);
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
  transition: color .12s ease;
}
.settings-back:hover .sb-arrow { color: var(--brand); }
.settings-back .lucide-ic { width: 1rem; height: 1rem; }
html[dir="rtl"] .settings-back .lucide-ic { transform: scaleX(-1); }

/* When the settings page is active we want the settings sidebar to
   replace the main app sidebar entirely. The page-level `.settings-focus`
   body class toggles the main sidebar off + reclaims the space. */
body.settings-focus .shell { grid-template-columns: 1fr; }
body.settings-focus .shell .sidebar { display: none; }

/* Employee settings page uses wrappers keyed by [data-section] so the
   sidebar can switch one at a time instead of showing all four forms at
   once. Scoped with [data-section] so it doesn't collide with the admin
   settings page, which also uses a `.settings-section` class for its
   section headers. */
.settings-main > .settings-section[data-section] { display: none; }
.settings-main > .settings-section[data-section].active { display: block; }
.settings-nav { display: flex; flex-direction: column; gap: 1px; padding: .2rem; }
/* Group label — same voice as the main sidebar's .nav-section. */
.settings-group-label {
  padding: .85rem .6rem .3rem;
  font-size: .66rem; font-weight: 600; letter-spacing: .12em;
  color: var(--text-3); text-transform: uppercase;
}
/* Nav rows — the exact treatment of the main sidebar (Asana-flat,
   2026-07-07): ~32px rows, 14px text, bare glyph, brand wash when
   active. One nav language across the whole app. */
.settings-nav-item {
  appearance: none; background: transparent; border: 0; border-radius: var(--radius-sm);
  padding: .3rem .55rem; cursor: pointer; text-align: start;
  min-height: 32px;
  display: flex; align-items: center; gap: .55rem;
  color: var(--text-2); font: inherit; font-weight: 500; font-size: .875rem;
  transition: background .12s ease, color .12s ease;
  position: relative;
}
.settings-nav-item:hover {
  background: color-mix(in srgb, var(--brand) 6%, transparent);
  color: var(--text);
}
/* Link items (permission-granted company sections) — same row look as
   the buttons, plus a trailing chevron hinting they navigate out. */
a.settings-nav-item { text-decoration: none; }
.settings-nav-ext { margin-inline-start: auto; display: inline-flex; color: var(--text-3); }
.settings-nav-ext .lucide-ic { width: .85rem; height: .85rem; }
.settings-nav-item.active {
  background: color-mix(in srgb, var(--brand) 9%, transparent);
  color: var(--text); font-weight: 600;
}
.settings-nav-icon {
  width: 20px; height: 20px;
  color: var(--text-2);
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
  transition: color .12s ease;
}
.settings-nav-icon .lucide-ic { width: 16px; height: 16px; }
.settings-nav-item:hover .settings-nav-icon { color: var(--text); }
.settings-nav-item.active .settings-nav-icon { color: var(--brand); }
.settings-nav-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Disclosure chevron is a phone-only affordance (nav rows open a sheet on
   phones); on desktop the row swaps an inline pane, so hide it there. */
.settings-nav-chevron { display: none; }

/* Settings content fills the column flush with the sidebar (GHL-style)
   instead of centering itself behind a 960px cap — small single-card
   sections were leaving large empty bands on wide screens. The aside
   (260px sticky) sits on the left, this column claims the rest.
   Applies to both the admin layout (.settings-content) and the employee
   layout (.settings-main). */
.settings-content,
.settings-main { min-width: 0; max-width: none; margin-inline: 0; width: 100%; }

.settings-section { animation: fade-in .2s ease; }
/* Drop per-card inline max-widths inside any settings section so each
   card stretches across the full content width — matches the GHL layout
   where every card claims its column instead of floating mid-gutter.
   Sections that want a multi-column tile wrap their cards in
   .settings-grid / .settings-cols (which have their own grid). */
.settings-section > .card { max-width: none !important; width: 100%; }

/* Payment section (employee /app/settings#payment): cards intentionally
   occupy the inline-start half of the content area, leaving the other
   half reserved for upcoming cards (international transfer presets,
   tax forms, etc.). Anchoring with margin-inline-end:auto keeps them
   flush against the settings aside in both RTL and LTR.
   Math: width = (100% - column gap) / 2  →  calc(50% - .6rem), where
   1.2rem is the planned gap between this column and the next. */
.settings-section[data-section="payment"] > .card,
.settings-section[data-section="payment"] > #pay-add-second-wrap {
  width: calc(50% - .6rem) !important;
  max-width: none !important;
  margin-inline-end: auto;
}
@media (max-width: 1024px) {
  /* Sub-1024px: a 50% column drops below ~400px, too cramped for a
     bank-form with side-by-side IBAN / SWIFT inputs. Restore full width. */
  .settings-section[data-section="payment"] > .card,
  .settings-section[data-section="payment"] > #pay-add-second-wrap {
    width: 100% !important;
  }
}

/* Two-column "masonry" layout for multi-card settings pages (Rewards,
   Schedule, etc.). Uses CSS multi-column instead of CSS grid so cards
   of differing heights pack vertically with no whitespace gaps — grid
   would force a uniform row height and leave a tail under shorter
   cards.
   Math: column-count fixes the layout at exactly 2 columns regardless
   of viewport (no auto-fit jumping to 3 cols on wide screens, which
   leaves the 4th card stranded in its own row). break-inside: avoid
   keeps each .card whole inside its column. The browser balances total
   column height automatically by reflowing cards between the two
   columns — matches GHL's settings-page rhythm. */
.settings-grid {
  column-count: 2;
  column-gap: 1.2rem;
}
.settings-grid > * {
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  page-break-inside: avoid;
  margin-bottom: 1.2rem;
  /* Force block context so margin-bottom + break-inside behave
     consistently across Chromium / WebKit / Firefox. */
  display: block;
}
.settings-grid .card { max-width: none !important; width: 100%; }
@media (max-width: 760px) {
  .settings-grid { column-count: 1; }
}

/* Explicit two-column layout: each column is its own vertical stack so
   cards inside align to the column's start (like GHL's split layouts).
   Use this when you want to *deliberately* group cards into left/right
   piles instead of letting auto-fit decide. */
.settings-cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.2rem;
  align-items: start;
}
.settings-col {
  display: flex; flex-direction: column; gap: 1.2rem; min-width: 0;
}
.settings-cols .card { max-width: none !important; }
@media (max-width: 760px) {
  .settings-cols { grid-template-columns: 1fr; }
}
.settings-section-head { margin-bottom: 1rem; }
.settings-section-head h2 { margin: 0 0 .25rem; font-size: 1.35rem; font-weight: 600; }
.settings-section-head p { margin: 0; color: var(--text-3); font-size: .88rem; line-height: 1.5; }

/* ── Settings rows (2026-07-07 overhaul) ──────────────────────────────
   Asana-style preference rows: a .settings-card holds hairline-separated
   rows, each row = title+description on the inline-start side and its
   control on the inline-end side. Sections adopt these instead of loose
   label/input stacks for anything that is a *preference* (theme,
   language, toggles) rather than a form. */
.settings-card { padding: 0; max-width: 680px; overflow: hidden; }
.settings-row {
  display: flex; align-items: center; gap: 1.2rem;
  padding: 1rem 1.2rem;
}
.settings-row + .settings-row { border-top: 1px solid var(--border); }
.settings-row-text { flex: 1; min-width: 0; }
.settings-row-title { font-weight: 600; font-size: .95rem; }
.settings-row-desc { color: var(--text-3); font-size: .8rem; margin-top: .15rem; line-height: 1.5; }
/* Two classes so this outranks `select.input { width:100% }` — the
   control must never out-grow the text column. */
.settings-row .settings-row-control { flex-shrink: 0; width: auto; min-width: 180px; max-width: 260px; }

/* Theme switch — the app's ONLY theme control (sidebar pill + topbar
   moon removed 2026-07-07). Segmented two-option switch on the sharp
   radius scale; the pressed segment lifts onto a card surface. Applies
   instantly via UI.setTheme — no Save, no reload. */
.appearance-theme-switch {
  display: inline-flex; gap: 2px; padding: 3px;
  background: var(--bg-3); border: 1px solid var(--border);
  border-radius: var(--radius); flex-shrink: 0;
}
.appearance-theme-switch button {
  appearance: none; border: 0; background: transparent;
  display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  padding: .4rem .9rem; border-radius: calc(var(--radius) - 3px);
  color: var(--text-2); font: inherit; font-size: .85rem; font-weight: 600;
  cursor: pointer;
  transition: background .18s ease, color .18s ease, box-shadow .18s ease;
}
.appearance-theme-switch button:hover { color: var(--text); }
.appearance-theme-switch button[aria-pressed="true"] {
  background: var(--card); color: var(--text);
  box-shadow: 0 1px 2px rgba(0,0,0,.16), 0 0 0 1px var(--border-2);
}
html.light .appearance-theme-switch button[aria-pressed="true"] {
  box-shadow: 0 1px 2px rgba(15,31,26,.08), 0 0 0 1px var(--border-2);
}
.appearance-theme-switch button .lucide-ic { width: 14px; height: 14px; }

/* ── Settings field density (Asana / Monday scale) ────────────────────
   Owner direction 2026-07-07: the default .input (.65rem/.95rem ≈ 44px)
   read as oversized on the settings forms — Asana/Monday sit their form
   fields at ~36px with 14px text. We slim every control INSIDE a
   settings pane to that density and unify the theme switch + segmented
   controls to the same rhythm, so the whole page reads as one system.

   Scoped to ≥781px (desktop web) ONLY — this is a web-perspective change;
   the ≤780px phone tier keeps its Clean-Minimal sizing untouched. Scoped
   to .settings-section so it never leaks into modals, tasks, or chat
   inputs that tune their own density. */
@media (min-width: 781px) {
  .settings-section .input {
    padding: .38rem .7rem;
    font-size: .875rem;
    line-height: 1.35;
    border-radius: var(--radius-field);
  }
  .settings-section input.input,
  .settings-section select.input { min-height: 36px; height: 36px; }
  .settings-section textarea.input { min-height: 72px; }
  .settings-section .label { font-size: .8rem; margin-bottom: .3rem; }
  /* Preference rows tighten to match the slimmer controls they hold. */
  .settings-row { padding: .8rem 1.1rem; }
  /* Theme switch + any segmented control in settings drop to the same
     14px / slim-pad rhythm as the fields beside them. */
  .settings-section .appearance-theme-switch button,
  .settings-section .segmented .seg-btn {
    padding: .34rem .75rem; font-size: .82rem;
  }
}

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

@media (max-width: 780px) {
  .settings-shell { grid-template-columns: 1fr; }
  .settings-aside { position: static; max-height: none; }
  .settings-nav { flex-direction: row; flex-wrap: wrap; gap: .3rem; }
  .settings-group-label { width: 100%; padding: .4rem .5rem .2rem; }
  .settings-nav-item { flex: 1 1 auto; }
  /* Preference rows stack on narrow screens so the control never
     squeezes the title column. */
  .settings-row { flex-direction: column; align-items: stretch; gap: .6rem; }
  .settings-row-control { width: 100%; min-width: 0; }
}

/* ── WhatsApp template editor (Settings → الردود الآلية والقوالب) ──────
   Two-pane modal: form fields (left) + a live WhatsApp-style bubble
   preview (right), mirroring lib/waTemplates.js renderTemplate. Royal
   Pixel tokens only — the bubble surface stays a neutral brand-tinted
   card, never a literal WhatsApp-green fill (two-gate color law). */
.modal.wa-tpl-modal { max-width: 920px; }
.wa-tpl-editor {
  display: grid;
  grid-template-columns: 1fr 300px;
  gap: 1.4rem;
  align-items: start;
}
@media (max-width: 760px) {
  .wa-tpl-editor { grid-template-columns: 1fr; }
}
.wa-tpl-field { margin-bottom: .9rem; }
.wa-tpl-field textarea.input { resize: vertical; min-height: 110px; }
.wa-tpl-toolbar { display: flex; align-items: center; gap: .3rem; flex-wrap: wrap; margin-bottom: .4rem; }
.wa-tpl-toolbar-sep { width: 1px; align-self: stretch; background: var(--border); margin: 0 .2rem; }
.wa-tpl-chip { cursor: pointer; font-size: .72rem; transition: border-color .12s ease, color .12s ease; }
.wa-tpl-chip:hover { border-color: var(--brand); color: var(--brand); }
.wa-tpl-optmode { display: flex; flex-direction: column; gap: .5rem; }
.wa-tpl-radio { display: flex; align-items: center; gap: .5rem; cursor: pointer; font-size: .88rem; }
.wa-tpl-btn-row .input { min-width: 0; }

/* ── Kind selector (text / buttons / list) — reuses the existing
   .segmented/.seg-btn pattern (attendance view toggle, workspace tabs)
   so it costs no new visual language, just a margin + icon sizing. */
.wa-tpl-kindsel { margin-bottom: 1rem; flex-wrap: wrap; }
.wa-tpl-kindsel .seg-btn { display: inline-flex; align-items: center; }
.wa-tpl-kindsel .seg-btn .lucide-ic { width: 15px; height: 15px; }

/* Buttons-kind media attach row + typed button rows. */
.wa-tpl-media-row { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; }
.wa-tpl-ibtn-row .input { min-width: 0; }

/* List-kind sections builder — one card per section holding its own
   title + rows, echoing the .card surface at a smaller scale so nested
   sections read as a sub-list, not a peer of the outer form card. */
.wa-tpl-section {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: .7rem .75rem;
  margin-bottom: .7rem;
  background: var(--bg-3);
}
.wa-tpl-section-head { display: flex; gap: .5rem; align-items: center; margin-bottom: .5rem; }
.wa-tpl-section-head .input { flex: 1; }
.wa-tpl-section-items { margin-bottom: .4rem; }
.wa-tpl-section-item .input { min-width: 0; }

/* Preview pane — a small phone-like surface holding the composed bubble
   plus (poll mode) option pills underneath, echoing the real WhatsApp
   layout for a poll-style reply menu. */
.wa-preview-phone {
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 1rem;
  min-height: 160px;
}
.wa-preview-bubble {
  background: var(--brand-soft);
  border: 1px solid color-mix(in srgb, var(--brand) 18%, var(--border));
  border-radius: var(--radius);
  padding: .7rem .85rem;
  font-size: .86rem;
  line-height: 1.55;
  color: var(--text);
  white-space: pre-wrap;
  word-break: break-word;
}
.wa-preview-bubble strong { font-weight: 700; }
.wa-preview-bubble em { font-style: italic; }
.wa-preview-pills { display: flex; flex-direction: column; gap: .4rem; margin-top: .6rem; }
.wa-preview-pill {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: .45rem .7rem;
  font-size: .8rem;
  color: var(--text-2);
  text-align: center;
}
.wa-poll-meter { margin-top: .6rem; font-size: .74rem; color: var(--text-3); font-variant-numeric: tabular-nums; }
.wa-poll-meter.is-over { color: var(--warn); font-weight: 600; }
.wa-poll-warning {
  display: flex; align-items: center; gap: .4rem;
  margin-top: .3rem;
  padding: .4rem .6rem;
  background: var(--warn-bg);
  color: var(--warn);
  border-radius: var(--radius-sm);
  font-size: .74rem;
}
.wa-poll-warning .lucide-ic { width: 14px; height: 14px; flex-shrink: 0; }

/* ── Native interactive preview (buttons/list kinds) ───────────────────
   Visual mock of the native WhatsApp message the customer sees — NOT a
   byte-parity target like the text-kind bubble above (native sends never
   degrade through this text path; see wa-replies-section.js's
   waPreviewRender "keep in sync" comment for the one that IS). Button
   rows use var(--brand) rather than WhatsApp's literal green, matching
   the bubble surface above — Royal Pixel two-gate color law: this is
   still product chrome, not a wire-accurate simulator. */
.wa-preview-native {
  background: var(--brand-soft);
  border: 1px solid color-mix(in srgb, var(--brand) 18%, var(--border));
  border-radius: var(--radius);
  padding: .7rem .85rem;
  font-size: .86rem;
  line-height: 1.55;
  color: var(--text);
}
.wa-preview-native-media {
  display: flex; align-items: center; justify-content: center; gap: .4rem;
  height: 80px; border-radius: var(--radius-sm);
  background: var(--card); border: 1px dashed var(--border);
  color: var(--text-3); font-size: .74rem; margin-bottom: .5rem;
}
.wa-preview-native-media .lucide-ic { width: 18px; height: 18px; }
.wa-preview-native-body { white-space: pre-wrap; word-break: break-word; }
.wa-preview-native-footer { color: var(--text-3); font-size: .76rem; margin-top: .4rem; }
.wa-preview-native-time { color: var(--text-3); font-size: .68rem; margin-top: .3rem; text-align: end; }
.wa-preview-native-btns {
  margin-top: .5rem; border-radius: var(--radius); overflow: hidden;
  border: 1px solid var(--border); background: var(--card);
}
.wa-preview-native-btn {
  display: flex; align-items: center; justify-content: center; gap: .45rem;
  padding: .6rem .5rem; font-size: .82rem; font-weight: 600;
  color: var(--brand); border-top: 1px solid var(--border);
}
.wa-preview-native-btn:first-child { border-top: none; }
.wa-preview-native-btn .lucide-ic { width: 15px; height: 15px; }
.wa-preview-list-sheet {
  margin-top: .7rem; background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); padding: .6rem .7rem;
}
.wa-preview-list-sheet-title { font-weight: 600; font-size: .82rem; margin-bottom: .4rem; }
.wa-preview-list-section { margin-bottom: .5rem; }
.wa-preview-list-section:last-child { margin-bottom: 0; }
.wa-preview-list-section-title { font-weight: 600; font-size: .74rem; color: var(--text-3); margin-bottom: .25rem; }
.wa-preview-list-row { padding: .35rem 0; border-top: 1px solid var(--border); font-size: .8rem; }
.wa-preview-list-row:first-child { border-top: none; }
.wa-preview-list-row-desc { color: var(--text-3); font-size: .72rem; }

/* ── Past-7-days strip (shift page) ── */
.p7-strip {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: .45rem;
}
.p7-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: .45rem .25rem .4rem;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  min-height: 96px;
}
.p7-dow { font-size: .7rem; color: var(--text-3); font-weight: 600; letter-spacing: .02em; }
.p7-day { font-size: .95rem; color: var(--text); font-weight: 600; margin-bottom: .25rem; }
.p7-bar {
  width: 8px;
  flex: 1;
  background: color-mix(in srgb, var(--border-2) 60%, transparent);
  border-radius: 4px;
  position: relative;
  overflow: hidden;
  margin: .15rem 0;
  min-height: 34px;
  display: flex;
  align-items: flex-end;
}
.p7-bar span {
  display: block;
  width: 100%;
  background: var(--success);
  border-radius: 4px 4px 2px 2px;
  transition: height .4s ease;
}
.p7-hrs { font-size: .68rem; color: var(--text-3); font-variant-numeric: tabular-nums; margin-top: .2rem; }
.p7-ok .p7-bar span { background: var(--success); }
.p7-miss .p7-bar span { background: var(--danger); }
.p7-off .p7-bar span { background: var(--text-3); opacity: .4; }
.p7-ok { border-color: color-mix(in srgb, var(--success) 35%, var(--border)); }
.p7-miss { border-color: color-mix(in srgb, var(--danger) 30%, var(--border)); }

/* ── Live earnings ticker (topbar chip, money + points) ──────────────── */
/* Sits between the Ask-AI pill and the comms icons. ONE quiet ghost chip
   — "$X.XX · ★N" — replaces the old green + gold pill pair: passive info
   must not out-shout the row's single CTA (Ask-AI). The star keeps a gold
   accent for glanceability; everything else stays neutral. Clicking the
   chip opens the breakdown popover, which also hosts the hide/show
   privacy toggle (.tk-pop-eye). The cents column uses tabular numerals
   so the digits don't dance as they tick. */
:root {
  /* Royal Pixel: the ticking balance is LIVE DATA — it speaks primary,
     not gold (spec §2: yellow never rides light surfaces as text/icon).
     Token names kept so the chip/popover plumbing is untouched. */
  --tk-gold:        var(--accent);
  --tk-gold-soft:   var(--accent-soft);
}
.tk-host { position: relative; display: inline-flex; align-items: center; }
.tk-chips {
  appearance: none; background: transparent; border: 0; padding: 0;
  display: inline-flex; cursor: pointer; font: inherit;
}
.tk-chip {
  /* Same neutral frame + vertical padding as .topbar-ai so the two pills
     read as one system (owner's unified-identity direction) and sit at
     matching height. */
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .4rem .7rem; border-radius: var(--radius-sm);
  background: var(--bg-2); border: 1px solid var(--border);
  font-size: .82rem; font-weight: 600; color: var(--text-2);
  font-variant-numeric: tabular-nums;
  transition: background .15s ease, border-color .15s ease, color .25s ease;
}
.tk-chip .tk-ico { width: 13px; height: 13px; display: inline-flex; flex-shrink: 0; color: var(--tk-gold); }
.tk-chip .tk-ico svg { width: 13px; height: 13px; }
.tk-chip .tk-dot { color: var(--text-3); }
/* Masked or empty: the gold star dims too — a $0 user shouldn't look
   like they have a balance, and hidden amounts shouldn't glitter. */
.tk-chip.tk-zero .tk-ico { color: var(--text-3); }
.tk-chip:hover {
  background: var(--bg-3);
  color: var(--text);
  border-color: color-mix(in srgb, var(--text-3) 30%, var(--border));
}
/* Off-clock: subtle opacity reduction only — a visual hint that the
   number isn't ticking right now. */
.tk-host.tk-frozen .tk-chip { opacity: .75; }

/* Privacy toggle — lives INSIDE the breakdown popover as a full-width
   row (the old standalone topbar eye read as mystery meat). Keeps the
   .tk-eye class: the popover's outside-click close handler uses it to
   stay open across a toggle. */
.tk-pop-eye {
  appearance: none; cursor: pointer;
  display: flex; align-items: center; gap: .5rem;
  width: 100%; margin-top: .6rem; padding: .5rem .65rem;
  background: transparent; border: 1px solid var(--border); border-radius: var(--radius-sm);
  color: var(--text-2); font: inherit; font-size: .82rem; font-weight: 600;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.tk-pop-eye svg { width: 1rem; height: 1rem; flex-shrink: 0; }
.tk-pop-eye:hover { background: var(--bg-3); color: var(--text); }
.tk-pop-eye:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.tk-pop-eye[aria-pressed="true"] {
  color: var(--tk-gold);
  border-color: color-mix(in srgb, var(--tk-gold) 45%, var(--border));
  background: color-mix(in srgb, var(--tk-gold-soft) 90%, var(--bg-2));
}

/* Masked state — show dots in place of digits without resizing the pill.
   Each number reserves a min-width so swapping "•••••" ↔ "$1,011.00"
   doesn't shift the chip's footprint (the eye click feels silent). */
.tk-money-num  { min-width: 5.2ch; text-align: center; display: inline-block; }
.tk-points-num { min-width: 3.8ch; text-align: center; display: inline-block; }

/* Breakdown popover — opens on click. Anchored under the chips so the
   layout stays predictable even at the topbar's narrowest. */
.tk-pop {
  position: absolute; top: calc(100% + .5rem); inset-inline-end: 0;
  width: 280px; padding: 1rem;
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-2);
  z-index: 60;
  animation: tk-pop-in .18s ease-out;
}
@keyframes tk-pop-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } }
.tk-pop-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: .55rem 0; border-bottom: 1px solid var(--border);
}
.tk-pop-row:last-of-type { border-bottom: 0; }
.tk-pop-label { color: var(--text-3); font-size: .82rem; }
.tk-pop-val { font-weight: 600; font-size: 1rem; font-variant-numeric: tabular-nums; }
.tk-pop-val .tk-pop-sub { color: var(--text-3); font-weight: 500; font-size: .78rem; margin-inline-start: .35rem; }
.tk-pop-rate { padding: .55rem 0; color: var(--text-3); font-size: .75rem; border-top: 1px solid var(--border); margin-top: .25rem; }
.tk-pop-link {
  display: block; margin-top: .65rem; padding: .55rem .75rem;
  text-align: center; font-weight: 600; font-size: .85rem;
  background: var(--accent); color: var(--accent-fg);
  border-radius: var(--radius-sm); text-decoration: none;
  transition: filter .15s ease;
}
.tk-pop-link:hover { filter: brightness(1.05); }

/* ── Spotlight celebration ──────────────────────────────────────────── */
/* Full-screen overlay with three layers (back to front):
     1. backdrop dim                                  → focus the eye
     2. radial spotlight beam pulsing from above      → premium, theatrical
     3. card with the bonus details + counter         → the message
   Confetti is generated dynamically in JS and animated by `sp-fall`. */
.sp-overlay {
  position: fixed; inset: 0; z-index: 1000;
  background: rgba(2, 6, 23, 0); /* fades in via class */
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; pointer-events: auto;
  transition: background .32s ease;
}
.sp-overlay.sp-show { background: rgba(2, 6, 23, .72); }
.sp-overlay.sp-hide { background: rgba(2, 6, 23, 0); }

/* Light-mode tweak: dim less aggressively so the page outlines stay
   visible behind the celebration. */
html.light .sp-overlay.sp-show { background: rgba(15, 23, 42, .58); }

.sp-beam {
  position: absolute; top: -10vh; left: 50%; transform: translateX(-50%);
  width: 120vw; height: 110vh;
  background: radial-gradient(closest-side, rgba(255, 220, 130, .22), rgba(255, 220, 130, .08) 35%, transparent 60%);
  pointer-events: none;
  animation: sp-beam-pulse 3.6s ease-in-out infinite;
  opacity: 0;
  transition: opacity .4s ease .1s;
}
.sp-overlay.sp-show .sp-beam { opacity: 1; }
@keyframes sp-beam-pulse {
  0%, 100% { transform: translateX(-50%) scale(1); }
  50%      { transform: translateX(-50%) scale(1.06); }
}

/* The card itself — rounded, glassy, with a subtle gold-tinged edge that
   says "this is special" without screaming. */
.sp-card {
  position: relative; z-index: 2;
  width: min(420px, 92vw); padding: 2.2rem 1.6rem 1.6rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-2);
  text-align: center;
  transform: translateY(40px) scale(.92); opacity: 0;
  transition: transform .42s cubic-bezier(.22, 1.18, .36, 1), opacity .32s ease;
}
.sp-overlay.sp-show .sp-card { transform: translateY(0) scale(1); opacity: 1; }

.sp-eyebrow {
  font-size: .72rem; font-weight: 600; letter-spacing: .14em;
  text-transform: uppercase;
  color: #fbbf24; /* gold — universally reads as celebration */
}
.sp-from { margin-top: .4rem; font-size: .9rem; color: var(--text-2); }
.sp-from b { color: var(--text); font-weight: 600; }
.sp-occasion { margin-top: .15rem; font-size: .82rem; color: var(--text-3); }

.sp-amount {
  margin: 1.1rem 0 .25rem;
  display: flex; align-items: baseline; justify-content: center; gap: .45rem;
}
.sp-points-num {
  font-size: 4rem; font-weight: 600; line-height: 1;
  color: #fbbf24;
  font-variant-numeric: tabular-nums;
}
.sp-points-lbl { font-size: 1rem; font-weight: 600; color: var(--text-2); }
.sp-usd { font-size: .92rem; color: var(--text-3); font-weight: 500; font-variant-numeric: tabular-nums; }
.sp-message {
  margin: 1.1rem auto 0; max-width: 320px;
  font-size: .95rem; color: var(--text); font-style: italic;
  line-height: 1.5;
  padding: .7rem 1rem;
  background: var(--bg-2); border-radius: var(--radius-field);
}
.sp-cta {
  margin-top: 1.4rem;
  appearance: none; border: 0; cursor: pointer;
  padding: .7rem 1.6rem; border-radius: 999px;
  background: var(--accent); color: var(--accent-fg);
  font: inherit; font-weight: 600; font-size: .95rem;
  transition: filter .15s ease;
}
.sp-cta:hover { filter: brightness(1.05); }
.sp-cta:active { filter: brightness(.96); }

.sp-close {
  position: absolute; top: 18px; inset-inline-end: 22px;
  width: 32px; height: 32px; border-radius: 50%;
  background: rgba(255, 255, 255, .08); color: rgba(255, 255, 255, .9);
  border: 0; cursor: pointer; font-size: 1.4rem; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .15s ease;
  z-index: 3;
}
.sp-close:hover { background: rgba(255, 255, 255, .16); }

/* Confetti — each piece falls with a slight rotation. We give them
   `--sp-rot` as a CSS variable from JS so the keyframe can read it. */
.sp-confetti {
  position: absolute; inset: 0; z-index: 1;
  pointer-events: none; overflow: hidden;
}
.sp-piece {
  position: absolute; top: -20px;
  border-radius: 2px;
  animation: sp-fall linear forwards;
  opacity: 0;
}
@keyframes sp-fall {
  0%   { transform: translateY(-20px) rotate(0deg);    opacity: 0; }
  10%  { opacity: 1; }
  100% { transform: translateY(110vh) rotate(var(--sp-rot, 360deg)); opacity: 1; }
}

/* Mobile: keep the card readable, drop the spotlight beam to a subtler
   gradient so the smaller screen doesn't look washed out. */
@media (max-width: 480px) {
  .sp-card { padding: 1.7rem 1.2rem 1.3rem; border-radius: var(--radius-card); }
  .sp-points-num { font-size: 3rem; }
  .sp-beam { background: radial-gradient(closest-side, rgba(255, 220, 130, .18), transparent 55%); }
}

/* ──────────────────────────────────────────────────────────────────────
   Chat — Slack/Gather flat layout overrides.
   Replaces the Telegram-style bubble look with a clean, flat list
   (avatar column + content column, name + timestamp header, grouped
   consecutive messages). Sidebar uses `# channel` glyph for channels
   and small avatars for DMs. Composer becomes a soft outlined bar.
   ────────────────────────────────────────────────────────────────────── */

.chat-shell { grid-template-columns: var(--chat-sb-w, 260px) 1fr; }

/* Sidebar polish */
.chat-sidebar { background: var(--bg-2); }
.chat-sidebar-search { padding: .65rem .7rem .35rem; }
.chat-sidebar-search > input { background: var(--card); border-color: var(--border); }
.chat-sidebar-title {
  padding: .35rem .9rem;
  font-size: .72rem;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-3);
  font-weight: 600;
}
.chat-sidebar-title:hover { background: transparent; color: var(--text-2); }
.chat-sidebar-section { border-bottom: 0; }
.chat-sidebar-body { padding: 0 .35rem .4rem; }
.chat-list-inner { padding: 0; }

/* Title row: collapsible title + small "+" action (Slack convention).
   The "+" stays subtle until you hover the section, matching established
   chat-app patterns and avoiding visual noise on the empty state. */
.chat-sidebar-title-row {
  display: flex;
  align-items: center;
  gap: .15rem;
  padding-inline-end: .45rem;
}
.chat-sidebar-title-row .chat-sidebar-title { flex: 1; min-width: 0; }
.chat-sidebar-add {
  width: 22px;
  height: 22px;
  border-radius: 5px;
  background: transparent;
  border: 0;
  color: var(--text-3);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: .7;
  transition: opacity .12s, background .12s, color .12s;
  flex-shrink: 0;
}
.chat-sidebar-section:hover .chat-sidebar-add,
.chat-sidebar-add:focus-visible { opacity: 1; }
.chat-sidebar-add:hover { background: var(--bg-3); color: var(--text); opacity: 1; }
.chat-sidebar-add .lucide-ic { width: 14px; height: 14px; }

/* Compact rounded chat rows (Slack-style) */
.chat-row,
.member-row {
  padding: .35rem .55rem;
  border-bottom: 0;
  border-radius: var(--radius-sm);
  margin: 1px 0;
  gap: .55rem;
  align-items: center;
  color: var(--text-2);
}
.chat-row:hover,
.member-row:hover { background: var(--bg-3); color: var(--text); }
.chat-row.active {
  background: var(--accent-soft);
  color: var(--text);
}
html:not(.light) .chat-row.active { background: rgba(255,255,255,.10); }
.chat-row.active .chat-row-name { font-weight: 600; }
.chat-row-info { gap: 0; }
.chat-row-top { gap: .3rem; }
.chat-row-name { font-weight: 500; font-size: .92rem; }
/* Last-message preview (GHL/WhatsApp style) — one quiet ellipsized line under
   the name. Was display:none ("Slack-style") until 2026-07-16; the owner chose
   the informative list: previews save a click per conversation. */
.chat-row-preview { display: block; font-size: .78rem; line-height: 1.35; margin-top: 1px; }
.chat-row.is-channel .chat-row-preview { display: none; }  /* channels keep the tight Slack row */

/* Channels: hash glyph instead of avatar */
.chat-row.is-channel { padding-inline-start: .35rem; }
.chat-row-hash {
  width: 22px;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  font-weight: 600;
  font-size: 1rem;
  color: var(--text-3);
  flex-shrink: 0;
}
.chat-row.is-channel.active .chat-row-hash { color: var(--text); }
/* Closed channel (قناة مغلقة): the hash yields to a lucide lock — fixed icon
   box inside the same 22px slot so rows never shift height. */
.chat-row-hash.is-closed .lucide-ic { width: 14px; height: 14px; }
/* Header lock sits inline just before the channel name (RTL-aware gap). */
.chat-name-lock { width: 14px; height: 14px; color: var(--text-3); vertical-align: -2px; margin-inline-end: .3rem; }

/* DM rows: small avatars (22px) */
.chat-row.is-dm .avatar,
.member-row .avatar { width: 22px; height: 22px; font-size: .65rem; border-radius: 5px; }

/* Header polish */
.chat-header { background: transparent; border-bottom: 1px solid var(--border); }

/* Messages area: flat, quiet canvas. Under Royal Pixel the card and bg are
   the same white, so the earlier card/bg mix collapsed to white-on-white and
   the composer's hairline frame melted (the owner read it as "the frame
   never shipped" — twice). surface-2 is the sanctioned tint that lets the
   white composer card + hairline actually read, which is exactly the GHL
   conversation anatomy the owner keeps referencing. */
.chat-messages, .msgs {
  background: var(--surface-2);
  padding: .25rem 0 .5rem;
  gap: 0;
}

/* Row layout: avatar column (40px) + content column */
.msg-row {
  display: grid !important;
  grid-template-columns: 44px 1fr;
  gap: 0;
  align-items: flex-start;
  padding: .35rem 1.1rem .25rem;
  position: relative;
  flex-direction: row !important;       /* override .msg-row.mine row-reverse */
}
.msg-row:hover { background: var(--bg-2); }
.msg-row.mine { /* no special alignment in flat mode */ }

.msg-avatar-col {
  display: flex;
  justify-content: center;
  padding-top: 2px;
}
.msg-avatar-col .avatar { width: 36px; height: 36px; font-size: .85rem; border-radius: 6px; }
.msg-avatar-btn,
.msg-avatar-static { display: inline-flex; }

.msg-content-col { min-width: 0; padding-inline-start: .15rem; }

.msg-head {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  margin: 0 0 .1rem;
  line-height: 1.2;
}
.msg-name {
  font-weight: 600;
  font-size: .95rem;
  color: var(--text);
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-family: inherit;
}
.msg-name:hover { text-decoration: underline; }
.msg-time { font-size: .72rem; color: var(--text-3); font-weight: 500; }

/* Grouped: hide head, hide avatar (show timestamp on hover) */
.msg-row.grouped { padding-top: .05rem; padding-bottom: .05rem; }
.msg-row.grouped .msg-avatar-col { padding-top: 4px; }
.msg-grouped-time {
  font-size: .65rem;
  color: var(--text-3);
  opacity: 0;
  transition: opacity .12s;
  font-variant-numeric: tabular-nums;
  /* Stay inside the avatar column so dates never visually collide with
     the message text in the content column. */
  display: inline-block;
  max-width: 44px;
  text-align: center;
  overflow: hidden;
  white-space: nowrap;
  line-height: 1;
}
.msg-row.grouped:hover .msg-grouped-time { opacity: 1; }

/* Flatten the message bubble */
.msg, .msg.mine {
  background: transparent !important;
  border: 0 !important;
  border-radius: 0 !important;
  padding: 0 !important;
  margin: 0 !important;
  max-width: 100% !important;
  color: var(--text);
  box-shadow: none;
}
.msg .msg-text {
  font-size: .95rem;
  line-height: 1.45;
  color: var(--text);
}
.msg.mine .msg-text { color: var(--text); }
.msg .sender-name { display: none; }   /* legacy header — replaced by .msg-head */

/* Inline meta (edited tag + read ticks) — flows on the same line as the
   last word of the message text (WhatsApp pattern), drops to next line if
   it doesn't fit. For voice/attachment-only messages, .msg-standalone-meta
   appears on its own thin row underneath. */
.msg-inline-meta {
  display: inline-flex;
  align-items: center;
  gap: .2rem;
  font-size: .7rem;
  color: var(--text-3);
  margin-inline-start: .4rem;
  vertical-align: baseline;
  white-space: nowrap;
}
.msg-inline-meta .ticks { display: inline-flex; }
.msg-inline-meta .ticks svg { width: 13px; height: 13px; color: var(--accent); stroke-width: 2.5; }
.msg-standalone-meta {
  font-size: .7rem;
  color: var(--text-3);
  margin-top: .25rem;
  display: flex;
  align-items: center;
  gap: .25rem;
}
.msg-standalone-meta .ticks svg { width: 13px; height: 13px; color: var(--accent); stroke-width: 2.5; }
/* WhatsApp channel badge on mirrored outbound bubbles. The green glyph is a
   deliberate channel affordance (info-carrying, per the emerald-mono exception);
   the tick reuses the app's sent/delivered/read metaphor, with the read state
   in WhatsApp's recognised blue so "read on WhatsApp" reads instantly. */
.wa-badge { display: inline-flex; align-items: center; gap: .12rem; }
.wa-badge .wa-glyph { display: inline-flex; color: var(--wa); line-height: 0; }
.wa-badge .wa-tick { width: 13px; height: 13px; color: var(--text-3); stroke-width: 2.5; }
.wa-badge .wa-tick svg { width: 13px; height: 13px; }
.wa-badge.is-delivered .wa-tick { color: var(--text-2); }
.wa-badge.is-read .wa-tick { color: #53BDEB; }
.wa-badge.is-failed .wa-tick { color: var(--danger, #dc2626); }
/* Delivery-failure marker (GHL-style): a warning triangle the sender sees when a
   message didn't reach the channel it was aimed at. The reason stays collapsed
   (the tip carries the `hidden` attribute) until the sender CLICKS the triangle
   — it is never shown inline. */
.msg-fail {
  position: relative;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  color: var(--danger, #dc2626);
  outline: none;
}
.msg-fail .msg-fail-ic { width: 14px; height: 14px; stroke-width: 2.5; }
.msg-fail .msg-fail-ic svg { width: 14px; height: 14px; }
.msg-fail-tip {
  position: absolute;
  bottom: calc(100% + 7px);
  /* Anchor the tip's inline-start edge near the marker so the box grows toward
     inline-end (leftward in RTL) — messages sit on the sender's side, so this
     keeps the tip inside the pane instead of spilling past the edge. */
  inset-inline-start: -4px;
  z-index: 50;
  display: flex;
  flex-direction: column;
  gap: .12rem;
  min-width: 180px;
  max-width: 260px;
  padding: .5rem .6rem;
  border-radius: 12px;
  background: var(--card);
  border: 1px solid var(--border);
  box-shadow: 0 10px 30px rgba(0,0,0,.18);
  text-align: start;
  white-space: normal;
  animation: failTipIn var(--dur-1, .15s) var(--ease-out, ease);
}
/* Explicit override — the base rule sets display:flex, which would otherwise beat
   the UA `[hidden]{display:none}` and leak the reason inline. Keep this. */
.msg-fail-tip[hidden] { display: none; }
@keyframes failTipIn { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: translateY(0); } }
.msg-fail-tip-title {
  font-size: .74rem;
  font-weight: 600;
  color: var(--danger, #dc2626);
  line-height: 1.3;
}
.msg-fail-tip-why {
  font-size: .72rem;
  font-weight: 400;
  color: var(--text-2);
  line-height: 1.4;
}
/* Legacy .meta — keep harmless in case anything else references it */
.msg .meta { display: inline-flex; font-size: .7rem; color: var(--text-3); }

/* Reactions as soft outlined pills */
.msg .reactions { gap: .3rem; margin-top: .3rem; }
.msg .reaction-chip,
.msg.mine .reaction-chip {
  background: var(--bg-3);
  border: 1px solid var(--border);
  color: var(--text-2);
  border-radius: var(--radius-field);
  padding: .12rem .5rem;
  font-size: .78rem;
  display: inline-flex;
  align-items: center;
  gap: .25rem;
}
.msg .reaction-chip:hover { border-color: var(--border-2); }
.msg .reaction-chip.mine,
.msg.mine .reaction-chip.mine {
  background: var(--accent-soft);
  border-color: var(--accent-soft-border);
  color: var(--text);
}

/* Reply quote: flatter look */
.msg .reply-quote,
.msg.mine .reply-quote {
  background: var(--bg-2);
  border-inline-start: 3px solid var(--border-2);
  border-radius: 0 6px 6px 0;
  margin-bottom: .25rem;
}

/* Attachment background tweaks for flat look */
.msg .att-file,
.msg.mine .att-file {
  background: var(--bg-3);
  color: var(--text);
  border: 1px solid var(--border);
}
.msg .att-file:hover { background: var(--card-2); }

/* Voice message: lighter on flat bg */
.voice-msg .voice-play,
.msg:not(.mine) .voice-msg .voice-play,
.msg.mine .voice-msg .voice-play { background: var(--accent); color: var(--accent-fg); }
.voice-msg .voice-wave .vb,
.msg:not(.mine) .voice-msg .voice-wave .vb,
.msg.mine .voice-msg .voice-wave .vb { background: color-mix(in srgb, var(--text-3) 60%, transparent); }

/* Hover dots: position to top-right of content column */
.msg-dots {
  position: absolute;
  top: -14px;
  inset-inline-end: 12px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 6px;
  width: 26px;
  height: 26px;
  font-size: .9rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  z-index: 4;
  box-shadow: 0 2px 6px rgba(0,0,0,.08);
}
.msg-row:hover .msg-dots { opacity: 1; }
.msg-dots:hover { background: var(--bg-3); opacity: 1 !important; }

/* Composer — soft outlined bar */
.composer.tg-composer {
  background: var(--card);
  /* A READABLE thin frame (GHL): --border (#E3E8E5) melted into the lightened
     chat canvas — the frame existed but was imperceptible, which the owner
     read as "the frame never shipped". One step darker + a whisper of lift
     keeps it رقيق while actually visible. Internal dividers stay on --border
     so the outer frame reads strongest (GHL's hierarchy). */
  border: 1px solid var(--border-2);
  border-radius: var(--radius-field);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .05);
  margin: 0;
  padding: 0;
  gap: 0;
  /* Stacked composer (GHL Conversations layout, 2026-07-17): a COLUMN of
     [channel bar (when capable)] → [roomy input] → [actions row]. The frame
     owns no padding — the channel bar bleeds edge-to-edge (tinted strip with
     its own border-bottom) and .comp-main pads the input/actions area. */
  flex-direction: column;
  align-items: stretch;
  transition: border-color .12s, box-shadow .12s;
}
/* Focus reads on the internal divider line (it tints green — see
   .comp-actions), NOT the outer frame: it keeps its resting look. */
.composer.tg-composer:focus-within {
  border-color: var(--border-2);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .05);
}
.tg-composer .comp-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  /* No inline padding — the rows own their insets so the divider LINE below
     the input runs the composer's full width (GHL: lines, not boxes). */
  padding: .1rem 0 .15rem;
}
.tg-composer .comp-actions {
  display: flex;
  align-items: center;
  gap: .2rem;
  /* The full-width hairline separating the open input area from the actions
     row (owner 2026-07-17: «مثل هذه — الخط»). Focus tints it green below. */
  border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
  padding: .2rem .5rem .05rem;
  transition: border-color var(--dur-1, .15s) var(--ease-out, ease);
}
.composer.tg-composer:focus-within .comp-actions {
  border-top-color: color-mix(in srgb, var(--ring) 40%, var(--border));
}
.tg-composer .comp-spring { flex: 1 1 auto; }
.composer.tg-composer textarea {
  font-size: .95rem;
  /* Open input area (GHL pattern): no box, no border — the composer's
     horizontal lines carry the structure. Doubled class beats the legacy
     `.composer textarea` border/ring on specificity, not source order. */
  padding: .55rem .6rem .45rem;
  min-height: 36px;
  width: 100%;
  border: 0;
  border-radius: 0;
  background: transparent;
  margin-block: 0;
}
.composer.tg-composer textarea:focus { outline: none; box-shadow: none; }
.tg-composer .rec-ui { padding-inline: .6rem; }
.composer.tg-composer textarea::placeholder { color: var(--text-3); }
.comp-btn { width: 32px; height: 32px; }
.comp-primary.is-send { background: var(--accent); color: var(--accent-fg); }
.comp-primary.is-busy { opacity: .55; cursor: progress; pointer-events: none; }
.comp-primary[disabled] { cursor: progress; }

/* Editing mode: accent border + soft tint so the user can't miss it */
.composer.tg-composer.is-editing {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 6%, var(--card));
}
.composer.tg-composer.is-editing:focus-within {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}

/* Recording: pulse the send button so the user knows the mic is hot.
   The pulse-rec keyframe was already defined earlier but never applied. */
.comp-primary.recording {
  background: var(--danger) !important;
  color: #fff !important;
  animation: pulse-rec 1.4s infinite;
}

/* Composer wrap groups the reply preview + composer with a single bottom
   margin so they slide together visually, and the reply preview tucks
   into the top edge of the composer instead of floating above it. */
/* GHL-style breathing room: the composer floats clear of the viewport's bottom
   edge instead of hugging it (owner found the near-flush look cramped). */
.composer-wrap { margin: .35rem 1.15rem 1.5rem; }
.composer-wrap > #reply-preview:not(:empty) {
  border: 1px solid var(--border);
  border-bottom: 0;
  border-radius: var(--radius-field) var(--radius-field) 0 0;
  background: var(--bg-2);
  margin-bottom: -1px;
}
/* When the reply preview docks above the composer, the channel bar's top
   corners square off with the frame's. */
.composer-wrap > #reply-preview:not(:empty) + .composer.tg-composer .comp-channel-bar {
  border-start-start-radius: 0;
  border-start-end-radius: 0;
}
.composer-wrap > #reply-preview:not(:empty) + .composer.tg-composer {
  border-radius: 0 0 var(--radius-field) var(--radius-field);
}

/* Empty state when no chat is selected */
.chat-empty-state {
  margin: auto;
  text-align: center;
  padding: 2rem 1rem;
  color: var(--text-3);
}
.chat-empty-state-title {
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--text-2);
  margin-bottom: .35rem;
}
.chat-empty-state-sub { font-size: .9rem; line-height: 1.5; }

/* Date separator — neutral line + chip */
.date-sep { margin: .9rem .25rem; position: relative; }
.date-sep::before {
  content: '';
  position: absolute;
  inset-inline: 1.1rem;
  top: 50%;
  height: 1px;
  background: var(--border);
}
.date-sep span {
  position: relative;
  background: var(--bg);
  color: var(--text-2);
  border: 1px solid var(--border);
  font-size: .72rem;
  font-weight: 600;
  padding: .15rem .65rem;
  border-radius: 999px;
  backdrop-filter: none;
}
html.light .date-sep span { background: var(--bg); color: var(--text-2); }

/* Unread waterline (WhatsApp-style) — same anatomy as .date-sep but tinted
   brand so "you stopped here" reads at a glance. Rendered once per open. */
.unread-sep { display: flex; justify-content: center; margin: .9rem .25rem; position: relative; }
.unread-sep::before {
  content: '';
  position: absolute;
  inset-inline: 1.1rem;
  top: 50%;
  height: 1px;
  background: color-mix(in srgb, var(--brand) 45%, var(--border));
}
.unread-sep span {
  position: relative;
  background: var(--accent-soft, color-mix(in srgb, var(--brand) 10%, var(--bg)));
  color: var(--brand);
  border: 1px solid color-mix(in srgb, var(--brand) 35%, var(--border));
  font-size: .72rem;
  font-weight: 600;
  padding: .15rem .65rem;
  border-radius: 999px;
}

/* Deep-link chip on a bubble (e.g. "view task" on a mirrored notification). */
.msg-link-chip {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  margin-top: .4rem;
  padding: .3rem .65rem;
  border: 1px solid color-mix(in srgb, var(--brand) 30%, var(--border));
  background: var(--accent-soft, color-mix(in srgb, var(--brand) 8%, var(--card)));
  color: var(--brand);
  font-size: .78rem;
  font-weight: 600;
  border-radius: var(--radius-field);
  cursor: pointer;
  transition: background var(--dur-1, .15s) var(--ease-out, ease), border-color var(--dur-1, .15s) var(--ease-out, ease);
}
.msg-link-chip:hover { background: color-mix(in srgb, var(--brand) 15%, var(--card)); border-color: var(--brand); }
.msg-link-chip .lucide-ic { width: 14px; height: 14px; }

/* Read-only DM (deleted peer): the composer is swapped for this quiet notice. */
.composer-locked {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .45rem;
  padding: .7rem .9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-field);
  background: var(--card-2);
  color: var(--text-3);
  font-size: .85rem;
}
.composer-locked .lucide-ic { width: 16px; height: 16px; flex-shrink: 0; }

/* Update nudge — quiet fixed pill when the server's deploy revision diverges
   from this long-lived SPA tab's (shell.js bindUpdateNudge). One click reloads. */
.update-pill {
  position: fixed;
  bottom: 18px;
  inset-inline-start: 18px;
  z-index: 240;
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  padding: .5rem .85rem;
  border: 1px solid color-mix(in srgb, var(--brand) 35%, var(--border));
  border-radius: 999px;
  background: var(--card);
  color: var(--text);
  font-size: .82rem;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 8px 24px rgba(0,0,0,.14);
  animation: failTipIn var(--dur-2, .25s) var(--ease-out, ease);
}
.update-pill:hover { border-color: var(--brand); background: var(--accent-soft, var(--card-2)); }
.update-pill .lucide-ic { width: 15px; height: 15px; color: var(--brand); }

/* Fresh-thread intro — the quiet threshold surface a brand-new conversation
   opens onto (avatar + who this is + one hint) instead of a blank pane. */
.thread-intro {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  padding: 3.5rem 1rem 2.5rem;
  text-align: center;
}
.thread-intro-avatar .avatar,
.thread-intro-avatar img.avatar {
  width: 56px;
  height: 56px;
  font-size: 1.15rem;
}
.thread-intro-title { font-weight: 600; font-size: 1.02rem; color: var(--text); margin-top: .35rem; }
.thread-intro-sub { font-size: .82rem; color: var(--text-3); }

/* Deleted-peer avatar: neutral glyph, no initials from raw tombstone ids. */
.avatar-deleted {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--card-2);
  border: 1px solid var(--border);
  color: var(--text-3);
  flex-shrink: 0;
}
.avatar-deleted.lg { width: 56px; height: 56px; }
.avatar-deleted .lucide-ic { width: 16px; height: 16px; }
.avatar-deleted.lg .lucide-ic { width: 24px; height: 24px; }

/* System message — match the new flat tone */
.msg-system span {
  background: transparent;
  border: 0;
  color: var(--text-3);
  font-size: .78rem;
}

/* Typing indicator — sits under the messages list */
.typing-indicator { padding: .25rem 1.1rem .5rem; color: var(--text-3); }

/* ─────────────────────────────────────────────────────────────────
   Help-tip — small ? icon + side drawer that pulls in a help article
   without leaving the current page. Flat style: no shadows, just a
   gentle border. Drawer slides in from the inset-end (right in LTR,
   left in RTL).
   ───────────────────────────────────────────────────────────────── */

.help-tip-icon {
  appearance: none; cursor: pointer;
  width: 18px; height: 18px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--bg-2); border: 1px solid var(--border);
  color: var(--text-3);
  font-size: .72rem; font-weight: 600; line-height: 1;
  padding: 0; margin-inline-start: .35rem;
  vertical-align: middle;
  transition: background .15s, color .15s, border-color .15s;
}
.help-tip-icon:hover {
  background: var(--accent-soft, color-mix(in srgb, var(--accent-2) 14%, transparent));
  color: var(--accent-2);
  border-color: var(--accent-2);
}

.help-drawer-backdrop {
  position: fixed; inset: 0;
  background: rgba(15, 23, 42, .35);
  z-index: 200;
  opacity: 0; pointer-events: none;
  transition: opacity .18s ease;
}
.help-drawer-backdrop.open { opacity: 1; pointer-events: auto; }

.help-drawer {
  position: absolute; top: 0; bottom: 0;
  inset-inline-end: 0;
  width: min(480px, calc(100vw - 2.5rem));
  background: var(--bg);
  border-inline-start: 1px solid var(--border);
  display: flex; flex-direction: column;
  transform: translateX(100%);
  transition: transform .22s ease;
}
html[dir="rtl"] .help-drawer { transform: translateX(-100%); }
.help-drawer-backdrop.open .help-drawer { transform: translateX(0); }

.help-drawer-head {
  display: flex; align-items: center; gap: .55rem;
  padding: .85rem 1rem;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.help-drawer-title {
  margin: 0; flex: 1;
  font-size: 1rem; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.help-drawer-full {
  font-size: .8rem; font-weight: 600;
  color: var(--accent-2); text-decoration: none;
  padding: .3rem .55rem; border-radius: 6px;
}
.help-drawer-full:hover { background: var(--bg-2); }
.help-drawer-close {
  appearance: none; background: transparent; border: 0; cursor: pointer;
  width: 30px; height: 30px; border-radius: var(--radius-sm);
  font-size: 1.4rem; line-height: 1; color: var(--text-2);
}
.help-drawer-close:hover { background: var(--bg-2); color: var(--text); }

.help-drawer-body {
  flex: 1; overflow-y: auto;
  padding: 1rem 1.2rem 2rem;
  font-size: .92rem; line-height: 1.65;
}
.help-drawer-article h1 { font-size: 1.4rem; margin: 0 0 .4rem; }
.help-drawer-article h2 {
  font-size: 1.1rem; margin: 1.6rem 0 .55rem;
  padding-top: .55rem; border-top: 1px solid var(--border);
}
.help-drawer-article h3 { font-size: 1rem; margin: 1.2rem 0 .4rem; }
.help-drawer-article p, .help-drawer-article li { color: var(--text); }
.help-drawer-article a { color: var(--accent-2); text-decoration: none; }
.help-drawer-article a:hover { text-decoration: underline; }
.help-drawer-article code {
  background: var(--bg-2); color: var(--accent-2);
  padding: .08rem .4rem; border-radius: 4px;
  font-size: .86em;
}
.help-drawer-article pre {
  background: var(--bg-2); color: var(--text);
  padding: .8rem; border-radius: var(--radius-sm);
  overflow-x: auto; font-size: .82rem; line-height: 1.55;
}
.help-drawer-article pre code { background: transparent; padding: 0; color: inherit; }
.help-drawer-article blockquote {
  background: var(--bg-2);
  border-inline-start: 3px solid var(--accent-2);
  padding: .65rem .9rem; border-radius: 6px;
  margin: .8rem 0;
}
.help-drawer-article blockquote p { margin: 0; }
.help-drawer-article table { width: 100%; border-collapse: collapse; font-size: .88rem; margin: .6rem 0 1rem; }
.help-drawer-article th, .help-drawer-article td {
  text-align: start; padding: .45rem .65rem;
  border-bottom: 1px solid var(--border); vertical-align: top;
}
.help-drawer-article th { background: var(--bg-2); font-weight: 600; }
.help-drawer-article ol, .help-drawer-article ul { padding-inline-start: 1.3rem; }
.help-drawer-article img { max-width: 100%; border-radius: 6px; margin: .5rem 0; }

/* ──────────────────────────────────────────────────────────────────────
   Chat — back-arrow + focus mode + mobile full-screen.
   Hybrid layout: side-by-side on desktop with an opt-in "focus mode"
   that hides the chat sidebar; full-screen swap between conversation
   list and conversation body on phones.
   ────────────────────────────────────────────────────────────────────── */

.chat-back-btn {
  background: transparent; border: 0; cursor: pointer;
  width: 36px; height: 36px; border-radius: 50%;
  color: var(--text); padding: 0;
  display: none; align-items: center; justify-content: center;
  flex-shrink: 0; margin-inline-end: .25rem;
  transition: background .15s ease;
}
.chat-back-btn:hover { background: var(--bg-3); }
.chat-back-btn:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.chat-back-btn .lucide-ic { width: 22px; height: 22px; }
/* The lucide arrow points left (LTR-natural). Flip in RTL so "back" reads
   as a right-pointing arrow, matching native iOS/Android Arabic UIs. */
html[dir="rtl"] .chat-back-btn .lucide-ic { transform: scaleX(-1); }

.chat-focus-btn.is-active { color: var(--accent); }

/* Desktop focus mode: collapse the chat sidebar so the conversation
   takes the full pane. Back arrow appears so the user can rejoin the
   list. We collapse the grid to a single 1fr track because hiding the
   sidebar with `display:none` removes it from the grid — leaving the
   body to auto-place into column 1 of the original two-column track. */
.chat-shell { transition: grid-template-columns .22s ease; }
.chat-shell.focus-mode { grid-template-columns: 1fr; }
.chat-shell.focus-mode .chat-sidebar { display: none; }
.chat-shell.focus-mode .chat-back-btn { display: inline-flex; }

/* Mobile (≤768px): WhatsApp-style swap. The chat list and the chat body
   live in the same single column; only one is visible at a time, gated
   on the `body.chat-open` flag set by Chat.open() / cleared by closeChat().
   Slide animation gives the screen change a native-feel transition. */
@media (max-width: 768px) {
  .chat-shell { grid-template-columns: 1fr !important; }
  .chat-shell .chat-sidebar { display: flex; width: 100%; border-inline-end: 0; }
  .chat-shell .chat-sidebar-resize { display: none; }
  .chat-shell .chat-body { display: none; }
  body.chat-open .chat-shell .chat-sidebar { display: none; }
  body.chat-open .chat-shell .chat-body { display: flex; animation: chat-slide-in .22s ease; }
  body.chat-open .chat-back-btn { display: inline-flex; }
  /* Focus toggle is meaningless on phones — the layout is already
     full-screen for the active conversation. */
  .chat-focus-btn { display: none; }
  /* Tighten header so the back arrow + name + actions fit on narrow widths. */
  .chat-header { padding: .55rem .6rem; }
  .chat-header-info { gap: .5rem; }
  /* Sidebar search input is redundant on mobile — the topbar ⌘K and the
     bottom-nav already provide global navigation. Removing it reclaims a
     vertical strip that otherwise sits below the topbar in the empty
     state and makes the list feel cramped. */
  .chat-sidebar-search { display: none; }
  /* When a chat is open on mobile, the chat-header already owns its own
     bottom border. Drop the topbar's border-bottom so the two don't
     stack into a thin double-line strip below the icons. */
  body.chat-open .topbar { border-bottom: 0; }
  /* Declutter the chat header — search-messages and call icons are
     surfaced inside the info panel (header tap) instead, so the header
     only shows the back arrow + avatar + name on phones. */
  #chat-search-btn,
  #chat-call-btn,
  #chat-meet-btn,
  #chat-wave-btn { display: none; }
  /* Inside an open conversation: hide the floating bottom-nav so it
     doesn't overlap the composer / emoji button. The chat header's
     back-arrow gives the user their way out, and the composer reclaims
     the full bottom of the screen with safe-area-inset padding so it
     never sits under the Android gesture bar. */
  body.chat-open .bottom-nav { display: none !important; }
  /* Virtual-office room dock is live → hide the floating bottom-nav so the
     room controls (mic / camera / leave, also bottom-pinned) never sit on
     top of the nav labels. The dock's own leave button returns the user to
     the floor, where hideDock() clears this flag and the nav reappears. */
  body.rm-dock-open .bottom-nav { display: none !important; }
  body.chat-open .composer-wrap {
    margin-bottom: max(.85rem, calc(env(safe-area-inset-bottom) + .35rem));
  }
  /* Chat-list view (no conversation open) — last few rows would tuck
     under the floating bottom-nav without breathing room. Pad the
     scrollable list so the final chat is fully reachable. */
  body:not(.chat-open) .chat-list-inner {
    padding-bottom: calc(110px + env(safe-area-inset-bottom));
  }
}

/* ──────────────────────────────────────────────────────────────────────
   Topbar — mobile-specific tweaks.
   Goals:
     • Keep all action icons in a single row (the topbar is flex with no
       wrap, so each item must be small enough to fit on a 360-375px
       viewport).
     • Drop affordances that don't make sense on a phone — focus mode is
       a desktop-only "shrink the chrome" feature; on mobile the layout
       is already full-screen.
   ────────────────────────────────────────────────────────────────────── */

/* ── Topbar user menu (mobile-only) ────────────────────────────────────
   Mirrors the sidebar user card. Visible at ≤720px so phone users can
   still reach Profile / Settings / Sign-out, since the sidebar that
   normally hosts those actions is hidden on phones. */
.topbar-user-slot { display: none; }
.topbar-user-btn { padding: 0; overflow: hidden; }
.topbar-user-btn .avatar { width: 30px; height: 30px; font-size: .72rem; }
.topbar-user-menu {
  display: none;
  position: absolute;
  top: calc(100% + 8px); inset-inline-end: 0;
  min-width: 220px;
  flex-direction: column;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-field);
  box-shadow: var(--shadow-2);
  padding: .35rem;
  z-index: 60;
}
.topbar-user-menu.open { display: flex; }
.topbar-user-menu > a, .topbar-user-menu > button {
  display: flex; align-items: center; gap: .55rem;
  padding: .55rem .6rem;
  border: 0; background: transparent;
  color: var(--text); text-align: start;
  border-radius: var(--radius-sm);
  font: inherit; cursor: pointer;
  text-decoration: none;
}
.topbar-user-menu > a:hover, .topbar-user-menu > button:hover { background: var(--bg-3); }
.topbar-user-menu > a .lucide-ic,
.topbar-user-menu > button .lucide-ic { width: 16px; height: 16px; color: var(--icon-ink); }
.topbar-user-sep { height: 1px; background: var(--border); margin: .25rem .15rem; }
.topbar-user-danger { color: var(--danger); }
.topbar-user-danger:hover { background: color-mix(in srgb, var(--danger) 10%, transparent) !important; }

@media (max-width: 720px) {
  /* ── Premium-mobile topbar (Phase A) ──────────────────────────────────
     Inspired by Linear / Things 3 / Slack mobile — the topbar holds at
     most 2-3 items: a clean left edge, the unread bell, and the avatar
     menu. Everything else (theme, contact, urgent, ticker chips, brand
     mark, ⌘K, focus) collapses behind either the avatar menu or in-page
     surfaces. The page title isn't crammed in the topbar either — it
     renders as a large iOS-style heading below (Phase C). */
  #topbar-focus,
  #topbar-cmdk,
  #topbar-contact,
  #topbar-urgent,
  .topbar-brand,
  .topbar-sep,
  #ticker-mount { display: none !important; }
  /* Avatar menu surfaces Profile / Settings / Sign-out on phones,
     where the sidebar that normally hosts those actions is hidden. */
  .topbar-user-slot { display: inline-flex; }
  .topbar {
    gap: .35rem;
    padding-inline-start: calc(.6rem + env(safe-area-inset-left));
    padding-inline-end: calc(.6rem + env(safe-area-inset-right));
    flex-wrap: nowrap; min-width: 0;
  }
  .topbar-actions { gap: .35rem; flex: 1 1 auto; min-width: 0; justify-content: flex-end; }
  .topbar-left { flex: 0 0 auto; }
  /* Smaller hit-area on phones — still ≥ 32px Apple HIG minimum. */
  .topbar-bell { width: 36px; height: 36px; }
}
html[dir="rtl"] body.chat-open .chat-shell .chat-body { animation-name: chat-slide-in-rtl; }
@keyframes chat-slide-in {
  from { transform: translateX(8%); opacity: .6; }
  to   { transform: translateX(0);   opacity: 1; }
}
@keyframes chat-slide-in-rtl {
  from { transform: translateX(-8%); opacity: .6; }
  to   { transform: translateX(0);   opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .chat-shell { transition: none; }
  body.chat-open .chat-shell .chat-body { animation: none; }
}

/* ──────────────────────────────────────────────────────────────────────
   Polls — chat استطلاع. The poll lives inside a message bubble; the
   default .msg padding already gives us the outer chrome. The bubble
   below replaces .msg-text with a Slack/Telegram-style stack: question
   header + clickable option rows with fill bars + tally meta + close
   button. Voted options get a brand-tinted fill + check mark; closed
   polls greyscale and become non-interactive.
   ────────────────────────────────────────────────────────────────────── */
.poll-bubble {
  display: flex;
  flex-direction: column;
  gap: .55rem;
  min-width: min(280px, 65vw);
  max-width: 420px;
}
.poll-q {
  display: flex; align-items: flex-start; gap: .5rem;
  font-weight: 600;
  font-size: .98rem;
  color: var(--text);
  line-height: 1.45;
}
.poll-q .lucide-ic {
  width: 18px; height: 18px;
  color: var(--brand);
  margin-top: .15rem;
  flex-shrink: 0;
}
.poll-q-text { flex: 1; word-break: break-word; }

.poll-options-list { display: flex; flex-direction: column; gap: .35rem; }
.poll-opt {
  position: relative;
  display: block;
  width: 100%;
  text-align: start;
  padding: .55rem .75rem;
  background: var(--card-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font: inherit;
  cursor: pointer;
  overflow: hidden;
  transition: border-color .15s, background .15s;
}
.poll-opt:hover:not(:disabled) {
  border-color: color-mix(in srgb, var(--brand) 35%, var(--border));
}
.poll-opt:disabled { cursor: default; opacity: .85; }
.poll-opt-fill {
  position: absolute; inset: 0;
  background: color-mix(in srgb, var(--brand) 10%, transparent);
  width: 0%;
  transition: width .35s cubic-bezier(.2, .9, .2, 1);
  pointer-events: none;
  z-index: 0;
}
.poll-opt.is-picked {
  border-color: color-mix(in srgb, var(--brand) 60%, var(--border));
  background: color-mix(in srgb, var(--brand) 6%, var(--card-2));
}
.poll-opt.is-picked .poll-opt-fill {
  background: color-mix(in srgb, var(--brand) 18%, transparent);
}
.poll-opt-row {
  position: relative; z-index: 1;
  display: flex; align-items: center; gap: .55rem;
  font-size: .92rem;
}
.poll-opt-mark {
  width: 18px; height: 18px;
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  border-radius: 50%;
  border: 1.5px solid color-mix(in srgb, var(--text-3) 50%, transparent);
  font-size: .75rem; font-weight: 600;
  color: var(--brand);
  background: var(--card);
}
.poll-opt.is-picked .poll-opt-mark {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--accent-fg);
}
.poll-opt-text { flex: 1; min-width: 0; word-break: break-word; }
.poll-opt-pct {
  flex-shrink: 0;
  font-size: .82rem;
  font-weight: 600;
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}
.poll-opt.is-picked .poll-opt-pct { color: var(--brand); }
.poll-opt-voters {
  position: relative; z-index: 1;
  display: block;
  font-size: .72rem;
  color: var(--text-3);
  margin-top: .25rem;
  margin-inline-start: 26px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.poll-meta {
  display: flex; align-items: center; justify-content: space-between;
  gap: .5rem;
  font-size: .78rem;
  color: var(--text-3);
  margin-top: .15rem;
}
.poll-tally { font-weight: 600; }
.poll-close-btn {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: .25rem .65rem;
  font-size: .75rem;
  font-weight: 600;
  color: var(--text-2);
  cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
}
.poll-close-btn:hover {
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  border-color: var(--danger);
  color: var(--danger);
}

.poll-bubble.is-closed .poll-q-text::after {
  content: ' · ' attr(data-closed-suffix);
  color: var(--text-3);
}

/* Create-poll modal */
.poll-create .poll-options { display: flex; flex-direction: column; gap: .4rem; max-height: 36vh; overflow-y: auto; }
.poll-create .poll-opt-row { display: flex; gap: .4rem; align-items: center; }
.poll-create .poll-opt-row .input { flex: 1 1 auto; min-width: 0; }
.poll-create .poll-opt-del {
  width: 36px; height: 36px;
  flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0;
}
.poll-create .poll-opt-del .lucide-ic { width: 16px; height: 16px; }
.poll-create .poll-add-opt { align-self: flex-start; }
.poll-create .poll-toggle {
  display: flex; align-items: center; gap: .55rem;
  cursor: pointer;
  font-size: .9rem;
  color: var(--text-2);
}
.poll-create .poll-toggle input[type="checkbox"] {
  width: 18px; height: 18px;
  accent-color: var(--brand);
  cursor: pointer;
}

/* ──────────────────────────────────────────────────────────────────────
   Scheduled messages + reminders — modals + popovers + list rows.
   Schedule modal builds on .ctt-modal grid; the "presets" row is a
   3-button quick selector (1h / 3h / tomorrow morning) above the
   manual date+time inputs. Remind-me popover is anchored at the
   click point; same preset list + a "custom" path that opens the
   datetime modal.
   ────────────────────────────────────────────────────────────────────── */
.sched-presets {
  display: flex; gap: .4rem; flex-wrap: wrap;
}
.sched-presets > button {
  flex: 1 1 auto;
  padding: .55rem .8rem;
  background: var(--card-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text);
  font: inherit;
  font-size: .82rem;
  font-weight: 600;
  cursor: pointer;
  transition: background .15s, border-color .15s;
}
.sched-presets > button:hover {
  background: color-mix(in srgb, var(--brand) 12%, var(--card-2));
  border-color: color-mix(in srgb, var(--brand) 35%, var(--border));
}
.sched-quote { background: var(--card-2); border-inline-start-color: color-mix(in srgb, var(--brand) 60%, transparent); }

.remind-pop {
  position: fixed;
  z-index: 70;
  min-width: 200px;
  max-width: 240px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-field);
  box-shadow: var(--shadow-2);
  padding: .35rem;
  display: flex;
  flex-direction: column;
  gap: 1px;
  animation: qr-pop-in .18s ease both;
}
.remind-pop-title {
  padding: .35rem .55rem .15rem;
  font-size: .72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-3);
}
.remind-pop > button {
  display: flex; align-items: center;
  width: 100%;
  padding: .55rem .65rem;
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
  color: var(--text);
  font: inherit;
  cursor: pointer;
  text-align: start;
}
.remind-pop > button:hover {
  background: color-mix(in srgb, var(--brand) 10%, var(--card-2));
}

/* Scheduled-list rows reuse .bm-row from bookmarks but add a tiny
   clock-style chip on the time so it reads as "future" not "past". */
.sched-row .bm-row-chat::before {
  content: '';
  display: inline-block;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--brand);
  margin-inline-end: .35rem;
  vertical-align: middle;
  animation: shift-strip-pulse 1.7s infinite ease-in-out;
}

/* Send button long-press hint — small clock badge that nudges users
   to discover the schedule action. Only shown when textarea has text. */
.comp-primary.is-send::after {
  content: '';
  position: absolute;
  inset-inline-end: -2px; bottom: -2px;
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--brand);
  border: 2px solid var(--card-2);
  opacity: 0;
  transition: opacity .2s;
}
.comp-primary.is-send { position: relative; }

/* Sidebar "scheduled" trigger — sits next to the bookmark trigger.
   Slightly offset inward so both remain visible without overlap. */
.chat-sidebar-sched-btn {
  inset-inline-end: 3.05rem !important;
}

/* ──────────────────────────────────────────────────────────────────────
   Slash commands — popover that opens above the composer when the user
   types "/" at the start of the textarea. Mirrors the @mention picker
   layout (icon + label + description) but each row is an action, not
   a value to insert. Picking a row consumes the textarea.
   ────────────────────────────────────────────────────────────────────── */
.slash-pop {
  position: absolute;
  bottom: calc(100% + 4px);
  inset-inline-start: 1rem;
  inset-inline-end: 1rem;
  max-width: 360px;
  max-height: 280px;
  overflow-y: auto;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-field);
  box-shadow: var(--shadow-2);
  padding: .25rem;
  z-index: 70;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.slash-pop-row {
  display: flex; align-items: center; gap: .65rem;
  padding: .55rem .65rem;
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
  color: var(--text);
  font: inherit;
  cursor: pointer;
  text-align: start;
}
.slash-pop-row:hover,
.slash-pop-row.active {
  background: color-mix(in srgb, var(--brand) 12%, var(--card-2));
}
.slash-pop-icon {
  width: 32px; height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--brand) 14%, transparent);
  color: var(--brand);
  flex-shrink: 0;
}
.slash-pop-icon .lucide-ic { width: 16px; height: 16px; }
.slash-pop-text {
  display: flex; flex-direction: column;
  flex: 1 1 auto; min-width: 0;
}
.slash-pop-cmd {
  font-weight: 600;
  font-size: .92rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--brand);
}
.slash-pop-desc {
  font-size: .78rem;
  color: var(--text-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ──────────────────────────────────────────────────────────────────────
   Quick replies — popover triggered by the lightning button in the
   composer. Hidden by default; opens above the button on tap, closes
   on click outside or after sending. The list is editable by the user
   via a small pencil button in the popover header.
   ────────────────────────────────────────────────────────────────────── */
.qr-pop {
  position: fixed;
  z-index: 70;
  min-width: 240px;
  max-width: min(320px, calc(100vw - 16px));
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-2);
  padding: .35rem .35rem .55rem;
  display: flex;
  flex-direction: column;
  gap: .35rem;
  animation: qr-pop-in .18s ease both;
}
@keyframes qr-pop-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.qr-pop-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: .35rem .55rem .15rem;
}
.qr-pop-title {
  font-size: .78rem; font-weight: 600;
  color: var(--text-3); text-transform: uppercase;
  letter-spacing: .04em;
}
.qr-pop-edit {
  width: 28px; height: 28px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; background: transparent; color: var(--text-3);
  border-radius: 6px; cursor: pointer;
}
.qr-pop-edit:hover { color: var(--brand); background: var(--bg-3); }
.qr-pop-edit .lucide-ic { width: 14px; height: 14px; }
.qr-pop-list {
  display: flex; flex-direction: column; gap: 2px;
  max-height: 280px;
  overflow-y: auto;
}
.qr-pop .quick-reply {
  display: block;
  width: 100%;
  text-align: start;
  padding: .55rem .7rem;
  border: 0;
  background: transparent;
  border-radius: var(--radius-sm);
  color: var(--text);
  font: inherit;
  font-size: .92rem;
  cursor: pointer;
  transition: background .12s ease;
}
.qr-pop .quick-reply:hover {
  background: color-mix(in srgb, var(--brand) 10%, var(--card-2));
}
.qr-pop-empty {
  padding: .85rem .7rem;
  text-align: center;
  font-size: .85rem;
  color: var(--text-3);
}

/* Editor modal — list of input rows + add/reset/save controls. */
.qr-edit { display: flex; flex-direction: column; gap: .8rem; }
.qr-edit-list { display: flex; flex-direction: column; gap: .4rem; max-height: 50vh; overflow-y: auto; }
.qr-edit-row { display: flex; gap: .4rem; align-items: center; }
.qr-edit-row .input { flex: 1 1 auto; min-width: 0; }
.qr-edit-del {
  width: 36px; height: 36px;
  flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0;
}
.qr-edit-del .lucide-ic { width: 16px; height: 16px; }
.qr-edit-add { align-self: flex-start; }

/* Sidebar "saved messages" trigger inside the chat search row. */
.chat-sidebar-saved-btn {
  position: absolute;
  inset-inline-end: 1.2rem;
  top: 50%; transform: translateY(-50%);
  width: 28px; height: 28px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent;
  border: 0;
  color: var(--text-3);
  cursor: pointer;
  border-radius: 6px;
}
.chat-sidebar-saved-btn:hover { color: var(--brand); background: var(--bg-3); }
.chat-sidebar-saved-btn .lucide-ic { width: 16px; height: 16px; }

/* Saved-messages side panel — list of bookmarked messages with chat
   context, time, and a single-tap unsave control. */
.bm-head { padding: 0 .25rem .85rem; border-bottom: 1px solid var(--border); margin-bottom: .55rem; }
.bm-head h3 { display: flex; align-items: center; margin: 0 0 .25rem; font-size: 1.1rem; font-weight: 600; }
.bm-sub { margin: 0; font-size: .85rem; color: var(--text-3); }
.bm-list { display: flex; flex-direction: column; gap: .35rem; }
.bm-row {
  position: relative;
  display: block;
  width: 100%;
  text-align: start;
  padding: .65rem .8rem;
  background: var(--card-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-field);
  font: inherit;
  cursor: pointer;
  color: var(--text);
}
.bm-row:hover { background: var(--card); border-color: color-mix(in srgb, var(--brand) 25%, var(--border)); }
.bm-row-top { display: flex; justify-content: space-between; gap: .5rem; margin-bottom: .25rem; font-size: .75rem; }
.bm-row-chat { font-weight: 600; color: var(--brand); }
.bm-row-time { color: var(--text-3); }
.bm-row-text { color: var(--text-2); font-size: .92rem; line-height: 1.5; word-break: break-word; }
.bm-row-actions { position: absolute; top: 8px; inset-inline-end: 8px; }
.bm-unsave {
  width: 28px; height: 28px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 0; cursor: pointer; border-radius: 6px;
  color: var(--text-3);
}
.bm-unsave:hover { color: var(--danger); background: var(--bg-3); }
.bm-empty { padding: 2rem 1rem; text-align: center; color: var(--text-3); font-size: .9rem; }

/* Tip modal — quick admin → employee bonus, amount chips + manual
   input + reason. Reuses .ctt-actions / .ctt-label from the
   convert-to-task modal so spacing stays consistent. */
.tip-modal { display: flex; flex-direction: column; gap: .8rem; }
.tip-head h3 { display: flex; align-items: center; margin: 0 0 .25rem; font-size: 1.1rem; font-weight: 600; }
.tip-sub { margin: 0; color: var(--text-3); font-size: .85rem; }
.tip-quote {
  padding: .55rem .8rem;
  background: var(--bg-3);
  border-inline-start: 3px solid var(--brand);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  color: var(--text-2);
  font-size: .88rem;
  line-height: 1.5;
}
html[dir="rtl"] .tip-quote { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.tip-amounts { display: flex; gap: .35rem; flex-wrap: wrap; }
.tip-amount {
  padding: .4rem .85rem;
  border-radius: 999px;
  background: var(--card-2);
  border: 1px solid var(--border);
  color: var(--text);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.tip-amount:hover { background: color-mix(in srgb, var(--brand) 12%, var(--card-2)); }
.tip-amount.active {
  background: var(--brand);
  color: var(--accent-fg);
  border-color: var(--brand);
}
.tip-field { display: flex; flex-direction: column; gap: .35rem; }

/* ──────────────────────────────────────────────────────────────────────
   Convert-to-task modal — opens from the chat message context menu and
   creates a task pre-filled with the message text. Tight stack of form
   fields with a quoted source preview at the top so the user always
   knows which message they're converting. Two-column row on desktop
   collapses to single column on phones.
   ────────────────────────────────────────────────────────────────────── */
.ctt-modal { display: flex; flex-direction: column; gap: .8rem; }
.ctt-head h3 { margin: 0 0 .25rem; font-size: 1.1rem; font-weight: 600; }
.ctt-sub { margin: 0; color: var(--text-3); font-size: .85rem; }
.ctt-quote {
  display: flex; flex-direction: column; gap: .2rem;
  padding: .65rem .85rem;
  background: var(--bg-3);
  border-inline-start: 3px solid var(--brand);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: .88rem;
}
html[dir="rtl"] .ctt-quote { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.ctt-quote-author { font-weight: 600; color: var(--brand); font-size: .8rem; }
.ctt-quote-text { color: var(--text-2); line-height: 1.5; word-break: break-word; }
.ctt-field { display: flex; flex-direction: column; gap: .35rem; }
.ctt-label { font-size: .82rem; font-weight: 600; color: var(--text-2); }
.ctt-optional { font-weight: 500; color: var(--text-3); font-size: .78rem; }
.ctt-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: .75rem;
}
.ctt-actions { display: flex; gap: .5rem; justify-content: flex-end; margin-top: .35rem; }
.ctt-actions .btn { min-width: 88px; }
@media (max-width: 720px) {
  .ctt-row { grid-template-columns: 1fr; }
  .ctt-actions { flex-direction: column-reverse; }
  .ctt-actions .btn { width: 100%; }
}

/* ──────────────────────────────────────────────────────────────────────
   Chat @mentions — popover that opens above the composer when the user
   types `@`, plus the inline pill that highlights resolved mentions in
   message bubbles. Works in both LTR and RTL. The picker anchors to
   #composer-wrap (position: relative) so it floats just above the input.
   ────────────────────────────────────────────────────────────────────── */
.mention-pick {
  position: absolute;
  bottom: calc(100% + 4px);
  inset-inline-start: 1rem;
  inset-inline-end: 1rem;
  max-width: 320px;
  max-height: 240px;
  overflow-y: auto;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-field);
  box-shadow: var(--shadow-2);
  padding: .25rem;
  z-index: 70;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.mention-pick-row {
  display: flex; align-items: center; gap: .55rem;
  padding: .45rem .55rem;
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
  color: var(--text);
  font: inherit;
  cursor: pointer;
  text-align: start;
}
.mention-pick-row:hover,
.mention-pick-row.active {
  background: color-mix(in srgb, var(--brand) 12%, var(--card-2));
}
.mention-pick-row .avatar { width: 28px; height: 28px; font-size: .72rem; flex-shrink: 0; }
.mention-pick-name {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 600; font-size: .9rem;
}
/* Position relative on composer-wrap so the picker anchors to it. */
#composer-wrap { position: relative; }

/* Mention pill — inline highlight inside message bubbles. Soft brand
   background + brand text for normal mentions; brighter when YOU are
   the one being mentioned (so a quick scroll spots a mention quickly). */
.msg-text .mention {
  display: inline-block;
  padding: .05rem .35rem;
  margin: 0 .05rem;
  border-radius: 6px;
  background: color-mix(in srgb, var(--brand) 14%, transparent);
  color: var(--brand);
  font-weight: 600;
  font-size: .92em;
  line-height: 1.2;
  vertical-align: baseline;
  white-space: nowrap;
}
.msg-text .mention.is-me {
  background: color-mix(in srgb, var(--brand) 28%, transparent);
  color: var(--accent-fg);
  background: var(--brand);
}

/* ──────────────────────────────────────────────────────────────────────
   Rewards hero card — balance + USD equivalent + redeem CTA. Desktop
   shows it in a single row; mobile stacks vertically with a full-width
   CTA so the button never wraps on top of the points number.
   ────────────────────────────────────────────────────────────────────── */
.rew-hero {
  padding: 1.4rem;
  background: color-mix(in srgb, var(--accent-2) 7%, var(--bg));
  border: 1px solid color-mix(in srgb, var(--accent-2) 22%, var(--border));
  display: flex;
  align-items: center;
  gap: 1rem;
}
.rew-hero-top {
  display: flex;
  align-items: center;
  gap: .85rem;
  flex: 1 1 auto;
  min-width: 0;
}
.rew-hero-icon {
  width: 48px; height: 48px;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--accent-2) 18%, transparent);
  color: var(--accent-2);
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.rew-hero-balance { flex: 1 1 auto; min-width: 0; }
.rew-hero-label {
  font-size: .72rem;
  color: var(--text-3);
  text-transform: uppercase;
  letter-spacing: .08em;
  font-weight: 600;
}
.rew-hero-numbers {
  display: flex; align-items: baseline;
  gap: .5rem;
  font-variant-numeric: tabular-nums;
  flex-wrap: wrap;
}
.rew-hero-points {
  font-size: 2.2rem; font-weight: 600;
  color: var(--accent-2);
  letter-spacing: -0.02em;
  line-height: 1;
}
.rew-hero-unit { font-weight: 600; color: var(--text-2); }
.rew-hero-usd { color: var(--text-3); margin-inline-start: .25rem; }
.rew-hero-cta { text-decoration: none; flex-shrink: 0; }
.rew-hero-cta:hover { text-decoration: none; }

@media (max-width: 720px) {
  .rew-hero {
    flex-direction: column;
    align-items: stretch;
    gap: 1rem;
    padding: 1.2rem;
  }
  .rew-hero-top { width: 100%; }
  .rew-hero-points { font-size: 1.95rem; }
  .rew-hero-cta {
    width: 100%;
    justify-content: center;
    padding-block: .7rem;
  }
}

/* ──────────────────────────────────────────────────────────────────────
   Mobile shift strip — live "you're on shift" pill INSIDE the topbar.
   Hidden by default (state="off"); appears inline in topbar-left when
   on-clock with a soft pulsing dot + live duration + live earnings.
   Tappable: opens the shift page. Lives in the chrome rather than as
   a floating overlay so it can't cover page content (channels list,
   rewards card, past-7-days tiles, etc.).
   ────────────────────────────────────────────────────────────────────── */
.mobile-shift-strip { display: none; }
@media (max-width: 720px) {
  .mobile-shift-strip[data-state="on"],
  .mobile-shift-strip[data-state="paused"] {
    display: inline-flex;
    align-items: center;
    gap: .42rem;
    padding: .32rem .7rem .32rem .55rem;
    border-radius: 999px;
    background: color-mix(in srgb, var(--brand) 10%, var(--card));
    border: 1px solid color-mix(in srgb, var(--brand) 32%, var(--border));
    color: var(--text);
    text-decoration: none;
    font-size: .78rem;
    font-weight: 600;
    min-width: 0;
    max-width: 100%;
    flex: 0 1 auto;
    /* Subtle entrance — fade + grow when state flips on. */
    animation: shift-strip-in .25s ease both;
  }
  .mobile-shift-strip-dot {
    width: 7px; height: 7px;
    border-radius: 50%;
    background: var(--brand);
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--brand) 60%, transparent);
    animation: shift-strip-pulse 1.7s infinite ease-in-out;
    flex-shrink: 0;
  }
  .mobile-shift-strip[data-state="paused"] .mobile-shift-strip-dot {
    background: var(--text-3);
    animation: none;
  }
  .mobile-shift-strip-label {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    color: var(--text-2);
  }
  .mobile-shift-strip-time {
    font-variant-numeric: tabular-nums;
    color: var(--text-2);
    font-weight: 600;
  }
  .mobile-shift-strip-sep { color: var(--text-3); font-weight: 400; }
  .mobile-shift-strip-money {
    color: var(--brand);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
  }
  /* Hide on the dashboard — the hero card there already shows
     earnings + points, no need to duplicate in the topbar. */
  body.page-overview .mobile-shift-strip { display: none !important; }
  /* Inside an open chat conversation: keep the strip visible so the
     employee always knows their shift state, but compact it (just the
     dot + duration, no money figure) so the chat header still has
     room for the back arrow + recipient name on a 360px viewport. */
  body.chat-open .mobile-shift-strip-money,
  body.chat-open .mobile-shift-strip-sep { display: none; }
  body.chat-open .mobile-shift-strip { padding-inline: .55rem .65rem; }
  /* Composer with 5 buttons (mic + attach + emoji + lightning + send)
     plus the textarea is tight on a 360px viewport. Tighten gaps and
     button sizes so the textarea keeps a usable typing area. */
  body.chat-open .tg-composer { gap: 0; padding: 0; margin: .4rem .65rem max(.4rem, env(safe-area-inset-bottom) + .1rem); }
  body.chat-open .tg-composer .comp-main { padding: .15rem 0 .2rem; }
  body.chat-open .comp-btn { width: 32px; height: 32px; }
  body.chat-open .comp-btn .lucide-ic { width: 18px; height: 18px; }
}
@keyframes shift-strip-in {
  from { opacity: 0; transform: scale(.92); }
  to   { opacity: 1; transform: scale(1); }
}
@keyframes shift-strip-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--brand) 60%, transparent); }
  50% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--brand) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .mobile-shift-strip-dot { animation: none; }
}

/* ──────────────────────────────────────────────────────────────────────
   Mobile-only large title (Phase C, iOS-style). Hidden on desktop and
   inside the chat shell (chat owns its own back-arrow header). The
   topbar title slot is hidden on mobile via the existing rule, so this
   becomes the primary "where am I" cue and reads as native-iOS premium.
   ────────────────────────────────────────────────────────────────────── */
.page-title-large { display: none; }
@media (max-width: 720px) {
  main:not(:has(.chat-shell)) > .page-title-large {
    display: block;
    /* iOS Large Title — confident, generous top breathing room. */
    padding: var(--sp-4) var(--sp-5) var(--sp-2);
    padding-inline-start: calc(var(--sp-5) + env(safe-area-inset-left));
    padding-inline-end: calc(var(--sp-5) + env(safe-area-inset-right));
    font: var(--t-large-title);
    letter-spacing: -0.02em;
    color: var(--text);
  }
  /* Empty title (page hasn't called setTitle yet) — collapse the slot
     so we don't leave a stray gap above the content. */
  main:not(:has(.chat-shell)) > .page-title-large:empty { display: none; }
}

/* ──────────────────────────────────────────────────────────────────────
   Mobile-only hero (Phase B) — premium summary card on the dashboard.
   Replaces the topbar earnings ticker on phones so the topbar can stay
   minimal. Two big numbers (this-month earnings + points), tappable to
   open the rewards page. Hidden entirely on desktop.
   ────────────────────────────────────────────────────────────────────── */
.mobile-hero, .mobile-hero-skeleton { display: none; }
@media (max-width: 720px) {
  /* Skeleton placeholder that mirrors the hero card's footprint while
     /api/rewards/ticker is in flight. */
  .mobile-hero-skeleton {
    display: block;
    height: 86px;
    border-radius: var(--radius-card);
    margin: 0 0 1rem;
  }
  .mobile-hero {
    display: block;
    text-decoration: none;
    color: var(--text);
    margin: 0 0 1rem;
    padding: 1rem 1.1rem;
    background: color-mix(in srgb, var(--brand) 8%, var(--card));
    border: 1px solid color-mix(in srgb, var(--brand) 25%, var(--border));
    border-radius: var(--radius-card);
    transition: transform .15s ease;
  }
  .mobile-hero:active { transform: scale(.99); }
  .mobile-hero-row { display: flex; align-items: stretch; gap: .8rem; }
  .mobile-hero-block { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: .25rem; }
  .mobile-hero-label {
    font-size: .72rem; font-weight: 600;
    color: var(--text-3); letter-spacing: .02em;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .mobile-hero-value {
    font-size: 1.6rem; font-weight: 600;
    color: var(--text); line-height: 1.1;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
  }
  /* Tiny caption below each big number — explains the value (cash
     equivalent for points, payout schedule for earnings). Keeps the
     hero from feeling like raw numbers without context. */
  .mobile-hero-sub {
    font-size: .68rem;
    color: var(--text-3);
    font-weight: 600;
    margin-top: .15rem;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .mobile-hero-sub-pts {
    color: color-mix(in srgb, var(--brand) 70%, var(--text-3));
    font-weight: 600;
    font-variant-numeric: tabular-nums;
  }
  .mobile-hero-sep {
    width: 1px;
    background: color-mix(in srgb, var(--brand) 18%, var(--border));
    align-self: stretch;
  }
}

/* ──────────────────────────────────────────────────────────────────────
   Voice message — compact pill with tinted card.
   Replaces the stretched full-width voice player with a fixed-width
   inline pill (WhatsApp/iMessage feel) that visually separates from
   plain text via a subtle accent-tinted background and rounded corners.
   ────────────────────────────────────────────────────────────────────── */
.msg .voice-msg {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  width: fit-content;
  min-width: 0;
  max-width: 320px;
  padding: .4rem .55rem .4rem .45rem;
  background: var(--accent-soft);
  border: 1px solid var(--accent-soft-border);
  border-radius: var(--radius);
  margin: .15rem 0;
}
.msg .voice-msg .voice-play {
  width: 34px; height: 34px;
  background: var(--accent);
  color: var(--accent-fg);
  box-shadow: 0 1px 3px color-mix(in srgb, var(--accent) 35%, transparent);
}
.msg .voice-msg .voice-play:hover { filter: brightness(.94); transition: filter .12s; }
.msg .voice-msg .voice-play svg { width: 16px; height: 16px; }
.msg .voice-msg .voice-body { gap: .25rem; min-width: 0; flex: 1; }
.msg .voice-msg .voice-wave {
  width: 200px;
  height: 28px;
  gap: 2px;
  display: flex;
  align-items: center;
  justify-content: space-between; /* spread bars edge-to-edge so the wave always
                                     fills its width, regardless of bar count */
  cursor: pointer;
  position: relative;
}
.msg .voice-msg .voice-wave .vb,
.msg:not(.mine) .voice-msg .voice-wave .vb,
.msg.mine .voice-msg .voice-wave .vb {
  width: 2.5px;
  min-height: 5px;
  background: color-mix(in srgb, var(--accent) 28%, transparent);
  border-radius: 999px;
  transition: background .12s, transform .12s;
  flex-shrink: 0;
}
/* Played bars: solid accent (WhatsApp). No overlay layer needed. */
.msg .voice-msg .voice-wave .vb.played,
.msg:not(.mine) .voice-msg .voice-wave .vb.played,
.msg.mine .voice-msg .voice-wave .vb.played {
  background: var(--accent);
}
/* Hide the legacy progress overlay element if it's still in the DOM */
.msg .voice-msg .voice-progress { display: none; }

.msg .voice-msg .voice-meta {
  font-size: .72rem;
  color: var(--text-2);
  display: flex;
  align-items: center;
  gap: .35rem;
}
.msg .voice-msg .voice-time {
  font-weight: 600;
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}
/* Playhead dot glides along the wave during playback (fades in only while
   playing — see paintProgress). Centered on the position line via a negative
   inline-start margin, which flips correctly under RTL. */
.msg .voice-msg .voice-head {
  position: absolute; top: 50%; inset-inline-start: 0;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--accent);
  transform: translateY(-50%); margin-inline-start: -4.5px;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);
  opacity: 0; pointer-events: none;
  transition: inset-inline-start .1s linear, opacity .15s ease;
}
/* Playback-speed pill — shown only on longer notes (revealed on decode). */
.msg .voice-msg .voice-speed {
  border: 1px solid var(--border); background: transparent;
  color: var(--text-2); font-size: .66rem; font-weight: 600;
  padding: .04rem .32rem; border-radius: 999px; cursor: pointer;
  line-height: 1.5; font-variant-numeric: tabular-nums;
  transition: background .12s, color .12s, border-color .12s;
}
.msg .voice-msg .voice-speed:hover { background: var(--card-2); color: var(--text); border-color: var(--text-3); }

/* Compact variant for very narrow screens */
@media (max-width: 480px) {
  .msg .voice-msg { max-width: 260px; }
  .msg .voice-msg .voice-wave { width: 140px; }
}

/* ──────────────────────────────────────────────────────────────────────
   Modals & floating menus — Duwam Minimal.
   The old "Liquid Glass" wave (translucent panes + blur 40px + sheens)
   was retired 2026-07-03: every floating surface is now a SOLID token
   surface + hairline border + the single --shadow-2 floating tier. The
   base component rules already carry that; only the ghost-button
   contrast fix from that era is worth keeping, re-expressed in tokens.
   ────────────────────────────────────────────────────────────────────── */
.modal .btn-ghost,
.modal-backdrop .btn-ghost {
  background: var(--bg-3);
  border-color: var(--border);
  color: var(--text-2);
}
.modal .btn-ghost:hover:not(:disabled),
.modal-backdrop .btn-ghost:hover:not(:disabled) {
  background: var(--card-2);
  border-color: var(--border-2);
  color: var(--text);
}

/* ──────────────────────────────────────────────────────────────────────
   Sidebar tooltip — only when sidebar is collapsed. Branded with the
   accent border so it reads as part of the system, not a generic OS
   tooltip. Uses data-tooltip attribute populated by Shell.mount().
   ────────────────────────────────────────────────────────────────────── */
.shell.sidebar-collapsed .nav a,
.shell.sidebar-collapsed .sidebar-shift,
.shell.sidebar-collapsed .sidebar-user-btn,
.shell.sidebar-collapsed .sidebar-toggle,
.shell.sidebar-collapsed .sidebar-head .brand {
  position: relative;
}
.shell.sidebar-collapsed [data-tooltip]:hover::after,
.shell.sidebar-collapsed [data-tooltip]:focus-visible::after {
  content: attr(data-tooltip);
  position: absolute;
  inset-inline-start: calc(100% + 12px);
  top: 50%;
  transform: translateY(-50%);
  /* Dark tooltip in dark mode (default). The light-mode variant below
     flips to a white pill so it doesn't fight the off-white canvas. */
  background: rgba(20, 24, 28, .96);
  color: #F3F4F6;
  padding: .32rem .72rem;
  border-radius: var(--radius-sm);
  font-size: .8rem;
  font-weight: 600;
  line-height: 1.4;
  white-space: nowrap;
  /* Very high z-index so the tooltip wins against any sibling stacking
     context (modals are 10000+, so we stay below them). */
  z-index: 9999;
  pointer-events: none;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .42);
  border: 1px solid rgba(255, 255, 255, .06);
  animation: sb-tooltip-in .14s ease both;
}
/* Light-mode tooltip — white pill with dark text, soft shadow tinted to
   match the off-white canvas. Reads as a quiet floating chip, not as a
   loud dark Material toast. */
html.light .shell.sidebar-collapsed [data-tooltip]:hover::after,
html.light .shell.sidebar-collapsed [data-tooltip]:focus-visible::after {
  background: #FFFFFF;
  color: var(--text);
  border-color: rgba(15, 31, 26, .08);
  box-shadow: 0 6px 18px rgba(15, 31, 26, .12);
}
@keyframes sb-tooltip-in {
  from { opacity: 0; transform: translateY(-50%) translateX(-4px); }
  to   { opacity: 1; transform: translateY(-50%) translateX(0); }
}

/* ── Attendance view toggle (Timeline / Table) ────────────────────── */
.att-view-toggle {
  display: inline-flex;
  gap: .15rem;
  padding: .25rem;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-field);
}
.att-view-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .45rem;
  min-width: 7.25rem;
  padding: .45rem 1rem;
  font: inherit;
  font-weight: 600;
  font-size: .85rem;
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
  color: var(--text-3);
  cursor: pointer;
  transition: background .12s, color .12s;
  line-height: 1;
}
.att-view-btn:hover { color: var(--text); background: var(--bg-3); }
.att-view-btn.is-on {
  background: var(--bg-3);
  color: var(--text);
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--accent) 35%, transparent);
}
.att-view-btn .lucide-ic { width: 1.05em; height: 1.05em; }
.att-timeline-host { width: 100%; }

/* Attendance sub-tab header: title+live on one side, view-toggle+date on
   the other. On phones it stacks into two tidy rows instead of the old
   four-way wrap. */
.att-head { display: flex; justify-content: space-between; align-items: center; gap: .6rem .8rem; flex-wrap: wrap; margin-bottom: 1rem; }
.att-head-title { display: flex; align-items: center; gap: .6rem; min-width: 0; }
.att-head-title h3 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.att-head-controls { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
@media (max-width: 640px) {
  .att-head { flex-direction: column; align-items: stretch; gap: .7rem; }
  .att-head-controls { justify-content: space-between; gap: .5rem; }
  .att-head-controls .att-view-toggle { flex: 1; }
  .att-head-controls .att-view-btn { flex: 1; min-width: 0; }
  /* daypicker fills the remaining row width so the row reads as one unit */
  .att-head-controls > #att-daypicker { flex: 1 1 100%; }
}

#att-daypicker.att-day-locked .dp-field {
  border-color: color-mix(in srgb, var(--warn) 55%, var(--border));
  color: var(--warn);
  background: color-mix(in srgb, var(--warn) 10%, transparent);
}
#att-daypicker.att-day-locked .dp-field .dp-icon { color: var(--warn); }
#att-daypicker.att-day-edit .dp-field {
  border-color: color-mix(in srgb, var(--danger) 55%, var(--border));
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 10%, transparent);
}
#att-daypicker.att-day-edit .dp-field .dp-icon { color: var(--danger); }

/* ── Attendance Timeline ──────────────────────────────────────────── */
.att-timeline {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.att-tl-toolbar {
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: .65rem .85rem;
  border-bottom: 1px solid var(--border);
  background: var(--bg);
  flex-wrap: wrap;
}
.att-tl-modeswitch {
  display: inline-flex;
  gap: .15rem;
  padding: .2rem;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.att-tl-mode-btn {
  padding: .35rem .8rem;
  font: inherit;
  font-weight: 600;
  font-size: .82rem;
  background: transparent;
  border: 0;
  border-radius: 7px;
  color: var(--text-3);
  cursor: pointer;
  transition: background .12s, color .12s;
}
.att-tl-mode-btn:hover { color: var(--text); background: var(--bg-3); }
.att-tl-mode-btn.is-on {
  background: var(--accent);
  color: var(--accent-contrast, #fff);
}
.att-tl-nav { display: inline-flex; align-items: center; gap: .35rem; }
.att-tl-date {
  min-width: 9rem;
  text-align: center;
  font-weight: 600;
  font-size: .9rem;
  color: var(--text);
}
.att-tl-spacer { flex: 1; }
.att-tl-legend {
  display: inline-flex;
  align-items: center;
  gap: .85rem;
  flex-wrap: wrap;
  font-size: .72rem;
  color: var(--text-3);
}
.att-tl-leg { display: inline-flex; align-items: center; gap: .35rem; }
.att-tl-sw {
  width: .9rem;
  height: .55rem;
  border-radius: 3px;
  display: inline-block;
}
.att-tl-sw.shift    { background: color-mix(in oklab, var(--accent) 18%, var(--bg-3)); border: 1px dashed color-mix(in oklab, var(--accent) 45%, transparent); }
.att-tl-sw.worked   { background: var(--accent); }
.att-tl-sw.overtime { background: var(--warn); }
.att-tl-sw.live     { background: var(--danger); width: .35rem; height: .9rem; border-radius: 999px; }

/* ── Day mode ─────────────────────────────────────────────────────── */
.att-tl-grid { padding: .5rem .85rem 1rem; }
.att-tl-axis,
.att-tl-row {
  display: grid;
  grid-template-columns: 14rem 1fr;
  align-items: stretch;
  gap: .75rem;
}
.att-tl-axis { padding: .35rem 0 .25rem; }
.att-tl-axis-track {
  position: relative;
  height: 1.25rem;
  border-bottom: 1px solid var(--border);
}
.att-tl-hour {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  font-size: .68rem;
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
}
html[dir="rtl"] .att-tl-hour { transform: translateX(50%); }
.att-tl-rows { display: flex; flex-direction: column; gap: .35rem; }
.att-tl-row { padding: .35rem 0; border-radius: var(--radius-sm); }
.att-tl-row:hover { background: color-mix(in oklab, var(--bg-3) 50%, transparent); }
.att-tl-name { display: flex; align-items: center; gap: .5rem; min-width: 0; }
.att-tl-name .status-dot {
  width: .55rem; height: .55rem; border-radius: 999px;
  background: var(--bg-3); flex: 0 0 auto;
}
.att-tl-name .status-dot.working { background: var(--success); box-shadow: 0 0 0 3px color-mix(in oklab, var(--success) 28%, transparent); }
.att-tl-name .status-dot.paused  { background: var(--warn); }
.att-tl-name .status-dot.ended   { background: var(--text-3); }
.att-tl-name-text { min-width: 0; flex: 1; }
.att-tl-name-main {
  font-weight: 600;
  font-size: .88rem;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.att-tl-name-sub { font-size: .72rem; color: var(--text-3); font-variant-numeric: tabular-nums; }
.att-tl-row-total { font-weight: 600; color: var(--text-2); }
.att-tl-ot-chip {
  display: inline-block;
  margin-inline-start: .25rem;
  padding: 0 .35rem;
  border-radius: 999px;
  background: color-mix(in oklab, var(--warn) 22%, transparent);
  color: var(--warn);
  font-size: .68rem;
}
.att-tl-track {
  position: relative;
  height: 2.4rem;
  background: var(--bg-3);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.att-tl-shift {
  position: absolute;
  top: 0; bottom: 0;
  background: color-mix(in oklab, var(--accent) 12%, transparent);
  border-inline-start: 1px dashed color-mix(in oklab, var(--accent) 45%, transparent);
  border-inline-end: 1px dashed color-mix(in oklab, var(--accent) 45%, transparent);
}
.att-tl-bar {
  position: absolute;
  top: .35rem; bottom: .35rem;
  border-radius: 5px;
  background: var(--accent);
  box-shadow: 0 1px 0 color-mix(in oklab, var(--accent) 60%, black);
  cursor: default;
  transition: filter .12s;
}
.att-tl-bar:hover { filter: brightness(1.1); }
.att-tl-bar.overtime { background: var(--warn); }
.att-tl-bar.is-open::after {
  content: "";
  position: absolute;
  inset-inline-end: 0;
  top: 0; bottom: 0;
  width: .35rem;
  border-radius: 0 5px 5px 0;
  background: color-mix(in oklab, var(--accent) 80%, white);
  animation: att-tl-pulse 1.4s ease-in-out infinite;
}
html[dir="rtl"] .att-tl-bar.is-open::after { border-radius: 5px 0 0 5px; }
@keyframes att-tl-pulse {
  0%, 100% { opacity: .55; }
  50%      { opacity: 1; }
}
.att-tl-now {
  position: absolute;
  top: -.15rem; bottom: -.15rem;
  width: 2px;
  background: var(--danger);
  pointer-events: none;
  z-index: 2;
}
.att-tl-now-dot {
  position: absolute;
  top: -.25rem;
  inset-inline-start: -.3rem;
  width: .65rem; height: .65rem;
  border-radius: 999px;
  background: var(--danger);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 28%, transparent);
}
.att-tl-loading {
  padding: 2.5rem 1rem;
  text-align: center;
  color: var(--text-3);
  font-size: .85rem;
}

/* ── Week mode ────────────────────────────────────────────────────── */
.att-tl-whead {
  display: grid;
  /* 3rd column mirrors .att-tl-wrow's weekly-summary cell. */
  grid-template-columns: 14rem 1fr 6.5rem;
  gap: .75rem;
  padding: .35rem 0 .5rem;
}
.att-tl-wsum-head {
  align-self: end;
  text-align: center;
  padding: .25rem 0;
  border-bottom: 1px solid var(--border);
  font-size: .7rem; color: var(--text-3); font-weight: 600;
}
.att-tl-whead-row {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: .35rem;
}
.att-tl-whead-col {
  text-align: center;
  padding: .25rem 0;
  border-bottom: 1px solid var(--border);
}
.att-tl-whead-col.today { border-bottom-color: var(--accent); }
.att-tl-whead-day  { font-size: .7rem;  color: var(--text-3); font-weight: 600; }
.att-tl-whead-date { font-size: .85rem; color: var(--text);   font-weight: 600; font-variant-numeric: tabular-nums; }
.att-tl-wrow {
  display: grid;
  /* 3rd column (2026-07-12): per-row weekly worked/required reconciliation. */
  grid-template-columns: 14rem 1fr 6.5rem;
  gap: .75rem;
  padding: .25rem 0;
  border-radius: var(--radius-sm);
}
.att-tl-wrow:hover { background: color-mix(in oklab, var(--bg-3) 50%, transparent); }
.att-tl-wtrack {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: .35rem;
  align-items: end;
}
.att-tl-wcell {
  position: relative;
  height: 3.4rem;
  background: var(--bg-3);
  border-radius: 6px;
  overflow: hidden;
  cursor: default;
  transition: filter .12s;
}
.att-tl-wcell:hover { filter: brightness(1.1); }
.att-tl-wcell.today  { box-shadow: inset 0 0 0 1px var(--accent); }
.att-tl-wcell.future { opacity: .35; pointer-events: none; }
.att-tl-wcell.absent { background: color-mix(in oklab, var(--danger) 14%, var(--bg-3)); }
.att-tl-wcell.off    { background: var(--bg-2); opacity: .6; }
.att-tl-wcell.live::after {
  content: "";
  position: absolute;
  top: .25rem;
  inset-inline-end: .25rem;
  width: .4rem; height: .4rem;
  border-radius: 999px;
  background: var(--danger);
  box-shadow: 0 0 0 2px color-mix(in oklab, var(--danger) 30%, transparent);
}
.att-tl-wbg {
  position: absolute;
  bottom: 0; left: 0; right: 0;
  background: color-mix(in oklab, var(--accent) 12%, transparent);
  border-top: 1px dashed color-mix(in oklab, var(--accent) 35%, transparent);
}
.att-tl-wfill { position: absolute; left: 0; right: 0; bottom: 0; }
.att-tl-wfill.regular  { background: var(--accent); }
.att-tl-wfill.overflow { background: color-mix(in oklab, var(--accent) 75%, white); }
.att-tl-wfill.overtime { background: var(--warn); }

/* ── Supervision layer (2026-07-12) ─────────────────────────────────────
 * Late chips, week-cell late marks, per-row weekly totals, clickable rows,
 * full legend swatches, KPI drill-down cards and the exception digest. */

/* Legend swatches for the states the week grid always painted but never
 * explained — the pink "absent" was the most alarming color on screen and
 * the only undocumented one. */
.att-tl-sw.absent { background: color-mix(in oklab, var(--danger) 14%, var(--bg-3)); }
.att-tl-sw.off    { background: var(--bg-2); box-shadow: inset 0 0 0 1px var(--border); opacity: .7; }
.att-tl-sw.future { background: var(--bg-3); opacity: .35; }

/* Late chip — amber, minutes in Latin digits, sits beside the name. */
.att-tl-late-chip {
  display: inline-block;
  margin-inline-start: .4rem;
  padding: .05rem .4rem;
  border-radius: 999px;
  font-size: .68rem; font-weight: 600;
  background: color-mix(in oklab, var(--warn) 16%, transparent);
  color: var(--warn, #b45309);
  border: 1px solid color-mix(in oklab, var(--warn) 35%, transparent);
  vertical-align: middle;
  font-variant-numeric: tabular-nums;
}

/* Week-cell late mark — small amber corner dot. */
.att-tl-wlate {
  position: absolute;
  top: .25rem; inset-inline-start: .25rem;
  width: .4rem; height: .4rem;
  border-radius: 999px;
  background: var(--warn);
}

/* Weekly reconciliation cell — worked / required + late/absent tallies. */
.att-tl-wsum { align-self: center; text-align: center; min-width: 0; }
.att-tl-wsum-hours { font-size: .82rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.att-tl-wsum-hours.is-short { color: var(--danger); }
.att-tl-wsum-req { color: var(--text-3); font-weight: 500; }
.att-tl-wsum-flags { font-size: .68rem; color: var(--warn, #b45309); margin-top: .1rem; }

/* Drillable rows — the affordance for row → employee report. */
.att-tl-row.is-clickable, .att-tl-wrow.is-clickable { cursor: pointer; }
.att-tl-row.is-clickable:hover { background: color-mix(in oklab, var(--bg-3) 55%, transparent); border-radius: var(--radius-sm); }

/* KPI drill-down cards + period caption. */
.att-kpi-click { cursor: pointer; }
.att-kpi-click:hover .stat { border-color: color-mix(in srgb, var(--brand) 40%, var(--border-2)); }
/* Keyboard parity: these are role="button" tabindex="0" cards, so focus needs
   its own visible state — it previously had only the --danger one removed above. */
.att-kpi-click:focus-visible .stat { border-color: color-mix(in srgb, var(--brand) 60%, var(--border-2)); box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 18%, transparent); }
.att-kpi-click .stat { transition: border-color .12s ease, box-shadow .12s ease; }
.att-kpi-period {
  font-size: .64rem; font-weight: 600; color: var(--text-3);
  background: var(--bg-3); border-radius: 999px; padding: .05rem .35rem;
  margin-inline-start: .3rem; vertical-align: middle;
}

/* Exception digest — names are actions, severity carries color. */
.att-digest { display: flex; flex-wrap: wrap; align-items: center; gap: .25rem .45rem; }
.att-digest-sep { color: var(--text-3); }
.att-digest-name {
  appearance: none; border: 0; padding: 0; background: transparent;
  font: inherit; font-weight: 600; color: var(--brand); cursor: pointer;
}
.att-digest-name:hover { text-decoration: underline; }
.att-digest-bad  { color: var(--danger); }
.att-digest-warn { color: var(--warn, #b45309); }

/* KPI list modal rows. */
.att-list-row {
  display: flex; align-items: center; gap: .6rem;
  width: 100%; padding: .5rem .6rem;
  appearance: none; border: 0; background: transparent;
  border-radius: 10px; cursor: pointer; font: inherit; color: var(--text);
  text-align: start;
}
.att-list-row:hover { background: var(--bg-3); }
.att-list-name { flex: 1; min-width: 0; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.att-list-meta { color: var(--text-3); font-size: .8rem; font-variant-numeric: tabular-nums; }
.att-list-meta.is-warn { color: var(--warn, #b45309); }

@media (max-width: 720px) {
  .att-tl-axis,
  .att-tl-row {
    grid-template-columns: 8rem 1fr;
    gap: .5rem;
  }
  .att-tl-whead,
  .att-tl-wrow {
    grid-template-columns: 8rem 1fr 4.4rem;
    gap: .5rem;
  }
  .att-tl-name-main { font-size: .8rem; }
  .att-tl-hour:nth-child(odd) { display: none; }
  .att-tl-legend { display: none; }
}

/* ── Custom fields Phase B — drawer controls + list-view extras ── */

.cf-multi-wrap { display: flex; flex-wrap: wrap; gap: .35rem; }
.cf-multi-chip {
  display: inline-flex; align-items: center;
  padding: .25rem .65rem;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: .82rem;
  color: var(--text-2);
  cursor: pointer;
  transition: background .12s, color .12s, border-color .12s;
  font-family: inherit;
}
.cf-multi-chip:hover { background: var(--bg-2); color: var(--text); }
.cf-multi-chip.is-selected {
  background: color-mix(in srgb, var(--accent) 22%, var(--bg-3));
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
}
.cf-multi-chip:disabled { opacity: .55; cursor: not-allowed; }

.cf-check {
  display: inline-flex; align-items: center; gap: .4rem;
  cursor: pointer; user-select: none;
}
.cf-check input { width: 18px; height: 18px; cursor: pointer; }

.cf-list-tools {
  display: flex; align-items: center; gap: .5rem;
  padding: .35rem 0 .5rem;
  flex-wrap: wrap;
}
.cf-list-tool {
  display: inline-flex; align-items: center; gap: .4rem;
  font-size: .85rem;
  color: var(--text-2);
}
.cf-list-tool .input { padding: .35rem .5rem; }

.table th.sortable {
  cursor: pointer; user-select: none; white-space: nowrap;
}
.table th.sortable:hover { color: var(--text); background: var(--bg-3); }
.table th.cf-header { color: var(--accent-2, var(--text-2)); }
.table td.cf-cell { font-size: .85rem; }
.table tr.cf-group-head td {
  background: var(--bg-2);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  padding: .45rem .8rem;
  font-size: .82rem;
  color: var(--text-2);
}

/* ── Login aside bottom: stats column + waitlist CTA ───────────────────
   Wraps both blocks so the parent's justify-content:space-between
   anchors them at the bottom of the panel instead of distributing the
   items vertically. flex-wrap kicks in on narrow desktop widths so the
   stats column drops below the form instead of clipping. */
.auth-bottom {
  display: flex;
  flex-direction: row;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
}
/* Stats stacked vertically with Lucide-style icons (building / gift /
   smartphone) so the column reads as a meaningful trust-signal list
   instead of an abstract bulleted line. The inline auth-stats-dot
   separators are dropped — they made sense as a horizontal divider,
   but visually clutter a column. */
.auth-bottom .auth-stats {
  flex-direction: column;
  align-items: flex-start;
  gap: 0.5rem;
  font-size: 0.82rem;
  opacity: 0.92;
}
.auth-bottom .auth-stats-dot {
  display: none;
}
.auth-bottom .auth-stats > span:not(.auth-stats-dot) {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-weight: 500;
}
.auth-stat-icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  color: var(--primary-300);
}
/* Royal Pixel signature under the login form — one of the 4 sanctioned
   mountain spots (spec §5). */
.auth-mountains { margin-top: 2rem; text-align: center; opacity: .9; }
.auth-mountains .px-mountains { margin-inline: auto; }

/* Pre-launch "notify me" form — lives on the leading edge of
   .auth-bottom (where the previous trio of "Coming soon" download
   badges used to sit). Compact frosted pill so it doesn't compete
   with the brand mark or the quote. */
.auth-waitlist {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  max-width: 320px;
  align-items: stretch;
}
.auth-waitlist-head {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.92rem;
  font-weight: 600;
  color: #fff;
}
.auth-waitlist-head svg { width: 18px; height: 18px; color: var(--primary-300); flex-shrink: 0; }
.auth-waitlist-sub {
  font-size: 0.74rem;
  opacity: 0.78;
  line-height: 1.6;
  margin: 0;
  color: var(--primary-100);
}
.auth-waitlist-form {
  display: flex;
  gap: 6px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.18);
  border-radius: 999px;
  padding: 4px;
  transition: border-color .15s ease, background .15s ease;
}
.auth-waitlist-form:focus-within {
  border-color: color-mix(in srgb, var(--primary-300) 55%, transparent);
  background: rgba(255,255,255,0.14);
}
.auth-waitlist-form input[type="email"] {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: 0;
  outline: 0;
  color: #fff;
  font: inherit;
  font-size: 0.86rem;
  padding: 7px 12px;
  font-family: inherit;
}
.auth-waitlist-form input[type="email"]::placeholder {
  color: rgba(255,255,255,0.55);
}
.auth-waitlist-form button {
  background: var(--primary-500);
  color: #fff;
  border: 0;
  border-radius: 999px;
  padding: 7px 16px;
  font-weight: 500;
  font-size: 0.84rem;
  cursor: pointer;
  white-space: nowrap;
  transition: transform .15s ease, box-shadow .2s ease, opacity .2s ease;
  font-family: inherit;
}
.auth-waitlist-form button:hover:not(:disabled) {
  filter: brightness(1.06);
}
.auth-waitlist-form button:disabled {
  opacity: 0.6;
  cursor: wait;
}
/* App-live CTA — replaces the retired waitlist form. Full-width royal-blue
   pill matching the panel; links to /download. Mirrors the old native
   button so the aside keeps one visual language. */
.auth-waitlist-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: var(--primary-500);
  color: #fff;
  border: 0;
  border-radius: 999px;
  padding: 11px 18px;
  font-weight: 600;
  font-size: 0.9rem;
  text-decoration: none;
  cursor: pointer;
  box-shadow: 0 10px 24px -12px rgba(29, 95, 214, 0.7);
  transition: transform .15s ease, box-shadow .2s ease, filter .2s ease;
  font-family: inherit;
}
.auth-waitlist-cta:hover {
  transform: translateY(-1px);
  filter: brightness(1.06);
  box-shadow: 0 16px 30px -12px rgba(29, 95, 214, 0.75);
}
.auth-waitlist-cta svg { width: 18px; height: 18px; flex-shrink: 0; }
/* Inline result tile — replaces the form on success or appears
   beneath it on error. The .ok / .err modifiers tint the chrome. */
.auth-waitlist-msg {
  font-size: 0.78rem;
  padding: 7px 12px;
  border-radius: var(--radius-sm);
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.16);
  color: rgba(255, 255, 255, 0.92);
  display: flex;
  align-items: center;
  gap: 7px;
}
.auth-waitlist-msg svg { width: 14px; height: 14px; flex-shrink: 0; }
.auth-waitlist-msg.ok { background: color-mix(in srgb, var(--success) 20%, transparent); border-color: color-mix(in srgb, var(--success) 45%, transparent); }
.auth-waitlist-msg.err { background: rgba(248, 113, 113, 0.18); border-color: rgba(248, 113, 113, 0.40); }
.auth-waitlist-msg[hidden] { display: none; }

/* Standalone /download page — same form scaled up for a hero card. */
.dl-page {
  min-height: 100vh;
  background: var(--primary-700);
  color: #fff;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 48px 22px;
  font-family: var(--font-body);
}
.dl-page-card {
  max-width: 640px;
  width: 100%;
  text-align: center;
}
.dl-page-logo {
  width: 96px;
  height: 96px;
  background: rgba(255,255,255,0.14);
  border: 1px solid rgba(255,255,255,0.25);
  border-radius: 24px;
  display: grid;
  place-items: center;
  margin: 0 auto 24px;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
.dl-page-logo svg { width: 60px; height: 60px; }
.dl-page-title {
  font-size: 2.2rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0 0 0.6rem;
}
.dl-page-sub {
  font-size: 1.05rem;
  opacity: 0.92;
  margin: 0 0 2rem;
  line-height: 1.7;
}
.dl-page .auth-waitlist {
  max-width: 460px;
  margin: 0 auto 2.5rem;
  align-items: center;
  text-align: center;
}
.dl-page .auth-waitlist-head { font-size: 1.1rem; }
.dl-page .auth-waitlist-head svg { width: 22px; height: 22px; }
.dl-page .auth-waitlist-sub { font-size: 0.92rem; }
.dl-page .auth-waitlist-form { width: 100%; padding: 5px; }
.dl-page .auth-waitlist-form input[type="email"] { font-size: 0.95rem; padding: 9px 14px; }
.dl-page .auth-waitlist-form button { font-size: 0.92rem; padding: 9px 20px; }
.dl-page-features {
  list-style: none;
  padding: 0;
  margin: 0 0 2rem;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
  text-align: start;
}
@media (max-width: 540px) {
  .dl-page-features { grid-template-columns: 1fr; }
  .dl-page-title { font-size: 1.7rem; }
}
.dl-page-features li {
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: var(--radius-field);
  padding: 14px 16px;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 0.95rem;
  line-height: 1.6;
}
.dl-page-features li svg {
  flex-shrink: 0;
  margin-top: 2px;
  color: var(--primary-100);
}
.dl-page-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: rgba(255,255,255,0.85);
  text-decoration: none;
  font-size: 0.9rem;
  padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid rgba(255,255,255,0.22);
  transition: background .15s ease;
}
.dl-page-back:hover { background: rgba(255,255,255,0.08); }

/* Mobile: the auth aside is hidden on small screens, so the desktop
   bottom block is hidden too — keep .auth-mobile-brand uncluttered. */
@media (max-width: 720px) {
  .auth-bottom { display: none; }
}

/* ══════════════════════════════════════════════════════════════════════
   MOBILE DESIGN SYSTEM v2  (2026-07-05)
   A cohesive premium mobile language over the locked emerald brand:
   generous rhythm, confident metrics, unified cards, grouped sections,
   and theme character — airy in light, immersive-emerald in dark.
   Scoped to ≤640px so desktop is untouched; placed last so it refines
   the earlier primitives on the cascade. Reference: the calm-fintech /
   health-app spacing + big-number hierarchy the owner shared.
   ════════════════════════════════════════════════════════════════════ */
:root {
  --space-1: 4px;  --space-2: 8px;   --space-3: 12px;  --space-4: 16px;
  --space-5: 20px; --space-6: 24px;  --space-7: 32px;  --space-8: 44px;
}

/* Section label — small-caps muted header that groups content, like the
   reference apps' "ACTIVE GROUPS" / "CATEGORY BREAKDOWN". Arabic ignores
   text-transform but the weight + tracking + muted tone still read as a
   grouping label. */
.ds-label {
  display: block;
  font: 600 .72rem/1 var(--font-body);
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--text-3);
  margin: 0 0 .7rem .1rem;
}

@media (max-width: 640px) {
  /* — Rhythm — roomier, consistent gaps between metric groups. */
  .kpi-strip { gap: .65rem; margin-bottom: 1.5rem; }
  .grid.grid-3 { gap: .65rem; }

  /* — Metrics — confident numbers, calmer labels, unified 16px radius.
     The big tabular figure is the hero of every data card in the refs. */
  .kpi { padding: .9rem 1rem 1rem; border-radius: var(--radius-card); }
  .kpi-num { font-size: 1.7rem; }
  .kpi-label { font-size: .7rem; letter-spacing: .07em; }
  .stat { border-radius: var(--radius-card); padding: 1rem 1.05rem; }
  .stat .label { letter-spacing: .06em; font-weight: 600; }
  .stat .value {
    font-family: var(--font-numeric); font-variant-numeric: tabular-nums;
    font-size: 1.7rem; letter-spacing: -.02em; margin-top: .3rem;
  }

  /* — Cards — one radius, one resting shadow, generous inner padding so
     content breathes instead of hugging the edges. */
  .card { border-radius: var(--radius-card); }

  /* — Light theme: airy. Rely on the soft border + faint shadow-1; no
     heavy separators. */
  :root.light .card,
  :root.light .kpi,
  :root.light .stat { box-shadow: var(--shadow-1); }

  /* — Dark theme: immersive. Primary actions, the active nav tile, and
     the send-shift CTA earn a soft emerald glow — the one flourish that
     gives the dark mode the "WildClock" depth without gradients.
     (Dark is the DEFAULT theme — only a `light` class is ever toggled on
     <html>, so dark rules must use :root:not(.light), never :root.dark.) */
  /* (Clean Minimal: the active nav is now an emerald-soft WASH pill, not a
     filled glowing square — the old glow override is retired.) */
  :root:not(.light) .btn-primary {
    box-shadow: 0 6px 22px -8px color-mix(in srgb, var(--brand) 60%, transparent);
  }

  /* Section labels sit a touch tighter to their group on phones. */
  .ds-label { margin-bottom: .55rem; }

  /* Channel # chip: the base desktop rule (width:22px, no height) sits
     LATER in the file than the 48px mobile rule and used to squash the
     chip into a 22×48 ellipse. Re-assert the circle here (end of file
     wins the cascade). */
  .chat-row-hash {
    width: 48px; height: 48px;
    border-radius: 50%;
    background: color-mix(in srgb, var(--brand) 14%, var(--card-2));
    color: var(--brand);
    font-size: 1.4rem; font-weight: 600;
  }

  /* ── Corner-anchored topbar ─────────────────────────────────────────
     Desktop keeps its 6-column grid; on phones the bar reads as two
     corners: the account avatar at the inline-start, AI + bell at the
     inline-end. display:contents flattens .topbar-actions so its
     children join the flex row and can be ordered. All logical props —
     the whole bar mirrors itself when the language flips direction. */
  .topbar { display: flex; align-items: center; }
  .topbar-actions { display: contents; }
  .topbar-user-slot { order: -1; }
  #topbar-ai { order: 5; margin-inline-start: auto; }
  /* Bell → trailing corner. Targeted by a PLAIN class, not :has(), which
     silently no-ops on older Android WebViews (Chrome <105 / Safari
     <15.4) — there the bell kept its default order and drifted to the
     center/right instead of the inline-end. The avatar's .topbar-user-slot
     already uses this class pattern; the bell now matches it. */
  .topbar-bell-slot { order: 6; }
  /* The account menu now opens from the START corner — anchor it there
     so the 220px panel drops inward instead of off-screen. */
  .topbar-user-slot .topbar-user-menu { inset-inline-end: auto; inset-inline-start: 0; }

  /* Bell dropdown → fixed sheet on phones. Anchored absolute positioning
     clipped part of the panel at screen edges, and a long list ran past
     the fold with overflow:hidden eating the tail. Fixed + internal
     scroll can't clip anywhere. */
  .bell-panel {
    position: fixed;
    top: calc(64px + env(safe-area-inset-top));
    inset-inline: 10px;
    width: auto;
    max-height: calc(100dvh - 64px - 120px - env(safe-area-inset-bottom));
    display: none;
    flex-direction: column;
  }
  .bell-panel.open { display: flex; }
  .bell-panel .bell-panel-list {
    flex: 1 1 auto;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }

  /* ── Employees: member cards (contact-list pattern) ─────────────────
     The primary cell IS the card; the label/value cells that as-cards
     used to stack are hidden here — the profile sheet owns the detail.
     Quick-glance mail/phone icons light up when the member has them. */
  .staff-table.as-cards > tbody > tr > td:not(.td-primary) { display: none; }
  .staff-table.as-cards > tbody > tr {
    padding: .2rem .9rem;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .staff-table.as-cards > tbody > tr:active { background: var(--bg-3); }
  .staff-table.as-cards .staff-name-cell { display: flex; align-items: center; gap: .75rem; }
  .staff-table.as-cards .staff-name-cell .avatar { width: 46px; height: 46px; font-size: .95rem; flex-shrink: 0; }
  .staff-table.as-cards .staff-name-cell .kbd { white-space: nowrap; font-size: .72rem; }
  .emp-quick { display: inline-flex; align-items: center; gap: .45rem; flex-shrink: 0; }
  .emp-quick-ic {
    width: 30px; height: 30px; border-radius: var(--radius-sm);
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--bg-3); color: var(--icon-ink); opacity: .38;
  }
  .emp-quick-ic.on { opacity: 1; color: var(--brand); background: var(--brand-soft); }
  .emp-quick-ic .lucide-ic { width: 15px; height: 15px; }
  .emp-quick-chevron { color: var(--text-3); display: inline-flex; }
  .emp-quick-chevron .lucide-ic { width: 17px; height: 17px; }
  .emp-role-inline {
    font-size: .68rem; font-weight: 600; color: var(--text-3);
    background: var(--bg-3); padding: .1rem .45rem; border-radius: 999px;
  }
  .emp-off-tag {
    font-size: .64rem; font-weight: 600; color: var(--danger);
    background: color-mix(in srgb, var(--danger) 10%, transparent);
    padding: .08rem .4rem; border-radius: 999px; margin-inline-start: .35rem;
    vertical-align: middle;
  }

  /* ══ Tasks screen: mobile decluttering ═══════════════════════════════
     The desktop toolbar (view tabs · scope · project · search · filters
     · group · add) used to wrap into 3-4 loose rows and, with the chips,
     ate ~40% of the viewport before the first task. Phones get exactly
     TWO tight control rows + ONE swipeable chip line. */
  .tasks-controls {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: .45rem; row-gap: .5rem;
  }
  .tasks-controls-divider, .tasks-controls-spacer { display: none; }
  /* Row 1 — the two segmented switches share one line. */
  .tasks-controls .ws-tabs { order: 1; flex: 1 1 auto; margin-bottom: 0; }
  .tasks-controls .scope-switch { order: 2; flex: 0 0 auto; }
  /* Row 2 — project + search grow; filter / group / add are square.
     Bases kept small so all five controls share ONE line at 390px. */
  .tasks-controls #tf-project { order: 3; flex: 1 1 24%; min-width: 90px; height: 40px; padding-block: 0; }
  .tasks-controls .task-filter-search { order: 4; flex: 1 1 22%; min-width: 90px; height: 40px; }
  .tasks-controls .task-filter-dropdown { order: 5; flex: 0 0 auto; }
  .tasks-controls .task-filter-btn { height: 40px; padding-inline: .55rem; }
  .tasks-controls #tf-add { order: 6; height: 40px; width: 42px; flex: 0 0 42px; padding: 0; justify-content: center; }
  .tasks-controls #tf-add .tf-add-text { display: none; }
  .tasks-controls .tfb-text { display: none; }
  /* Chip strip — ONE line, finger-swipeable, never wraps. */
  .tasks-chips {
    flex-wrap: nowrap; overflow-x: auto;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
    gap: .4rem; padding-block: .1rem .25rem;
  }
  .tasks-chips::-webkit-scrollbar { display: none; }
  .tasks-chips-spacer { display: none; }
  .tasks-chips .quick-filter-group,
  .tasks-chips .active-filter-group { display: inline-flex; flex-wrap: nowrap; gap: .4rem; flex-shrink: 0; }
  .tasks-chips .quick-filter, .tasks-chips .chip,
  .tasks-chips .task-chip-count, .tasks-chips .task-chip-clear { flex-shrink: 0; white-space: nowrap; }

  /* ── List view → glanceable task cards ──────────────────────────────
     Row anatomy: [bulk][type][title][project][assignee][status][prio]
     [due][cf…]. Phones show what drives the tap decision — bold title
     line, then one meta line: status · priority · due · project, with
     the assignee avatars pulled to the end. Bulk-select, type icon and
     custom-field columns are desktop workflows — the task panel has
     them all (progressive disclosure). */
  .tasks-viewport table.table { min-width: 0 !important; }
  .tasks-viewport table.table thead { display: none; }
  .tasks-viewport table.table,
  .tasks-viewport table.table > tbody { display: block; width: 100%; }
  .tasks-viewport table.table > tbody > tr {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: .3rem .6rem;
    background: var(--card); border: 1px solid var(--border);
    border-radius: var(--radius-card);
    padding: .75rem .9rem;
    margin-bottom: .55rem;
  }
  :root:not(.light) .tasks-viewport table.table > tbody > tr {
    background: color-mix(in srgb, var(--card) 72%, transparent);
    border-color: rgba(255, 255, 255, .10);
  }
  .tasks-viewport table.table > tbody > tr:hover td { background: transparent; }
  .tasks-viewport table.table > tbody > tr > td { display: block; border: 0; padding: 0; }
  .tasks-viewport table.table td.bulk-select-cell,
  .tasks-viewport table.table td.task-type-cell,
  .tasks-viewport table.table td.cf-cell { display: none; }
  .tasks-viewport table.table > tbody > tr > td:nth-child(3) {
    order: 1; flex: 1 1 100%;
    font-size: .95rem; line-height: 1.45;
  }
  .tasks-viewport table.table > tbody > tr > td[data-edit-status]   { order: 2; }
  .tasks-viewport table.table > tbody > tr > td[data-edit-priority] { order: 3; }
  .tasks-viewport table.table > tbody > tr > td[data-edit-due]      { order: 4; }
  .tasks-viewport table.table > tbody > tr > td:nth-child(4) {
    order: 5; font-size: .74rem; min-width: 0; max-width: 38%;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .tasks-viewport table.table > tbody > tr > td:nth-child(5) { order: 6; margin-inline-start: auto; }
  /* Unassigned "—" placeholder is noise on a card — the بدون مكلَّف chip
     already carries that signal. */
  .tasks-viewport table.table .row-av-empty { display: none; }
  /* Group headers (تجميع حسب…) become slim section labels between cards. */
  .tasks-viewport table.table tr.list-group-head {
    background: transparent; border: 0; border-radius: 0;
    padding: .35rem .2rem .15rem; margin-bottom: .2rem;
  }
  .tasks-viewport table.table tr.list-group-head:hover td { background: transparent; }

  /* ── Kanban: touch cleanup ──────────────────────────────────────────
     Hover-reveal icons don't exist on touch — after a tap they'd stick
     visible on one random card (the inconsistency in the screenshots).
     Tap opens the task panel where every action lives. */
  .kanban-task .t-actions { display: none !important; }

  /* ── Check-in is THE core action — make it unmissable on phones. ────
     Full-width, thumb-height (54px), bold label, emerald glow. Covers
     the attendance section hero and the employee shift page ring CTA. */
  .att-self-actions { width: 100%; display: flex; flex-direction: column; gap: .55rem; margin-top: .8rem; }
  .att-self-actions .btn {
    width: 100%; min-height: 54px; justify-content: center;
    font-size: 1.02rem; font-weight: 500; border-radius: var(--radius-card);
  }
  .att-self-actions .btn-primary,
  .att-card .btn-primary {
    box-shadow: 0 12px 28px -10px color-mix(in srgb, var(--brand) 55%, transparent);
  }
  .att-card .btn-primary { min-height: 54px; padding-inline: 2.4rem; font-size: 1.05rem; font-weight: 500; border-radius: var(--radius-card); }

  /* ══ Companion-app chrome (2026-07-06) ═══════════════════════════════
     ── Raised central check-in CTA in the bottom nav. The one action the
     whole product exists for sits under the thumb on every screen. */
  .bottom-nav-cta { position: relative; flex-direction: column !important; gap: 2px; background: transparent !important; padding: 0 !important; }
  .bottom-nav-cta .bottom-nav-cta-circle {
    width: 56px; height: 56px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--brand); color: var(--accent-fg);
    margin-top: -16px;
    /* Flat: the 4px page-colored ring notches it into the nav so it still
       reads as raised, WITHOUT the emerald glow (owner: "بدون التوهج"). */
    border: 4px solid var(--cta-ring);
    box-shadow: none;
    transition: transform .15s ease;
  }
  .bottom-nav-cta:active .bottom-nav-cta-circle { transform: scale(.94); }
  .bottom-nav-cta .bottom-nav-cta-circle .lucide-ic { width: 24px; height: 24px; stroke-width: 2.1; }
  /* CTA label stays visible below the circle (it is the product's core action). */
  .bottom-nav-cta .bottom-nav-label { max-width: 72px; opacity: 1; margin: 0; color: var(--brand); font: var(--t-caption); font-weight: 600; }
  .bottom-nav-cta.active .bottom-nav-cta-circle { background: var(--brand-hover); }

  /* ── Unified topbar action buttons (2026-07-06, owner) ───────────────
     The assistant, quick-action, bell and avatar all read as ONE flat
     40px icon-button family — no tinted squares, matched sizes, even
     edge spacing. "أزرار متناسقة … فلات". */
  .topbar {
    padding-inline-start: max(12px, env(safe-area-inset-left));
    padding-inline-end: max(12px, env(safe-area-inset-right));
  }
  .topbar-actions { gap: .2rem; }
  .topbar-ai,
  .topbar-quick-btn,
  .topbar-bell,
  .topbar-user-btn {
    width: 40px !important; height: 40px !important;
    border-radius: var(--radius-sm) !important;
    padding: 0 !important;
    background: transparent !important;
    border: 1px solid transparent !important;
    box-shadow: none !important;
  }
  .topbar-ai:hover,
  .topbar-quick-btn:hover,
  .topbar-bell:hover,
  .topbar-user-btn:hover {
    background: color-mix(in srgb, var(--text) 7%, transparent) !important;
    border-color: transparent !important;
  }
  /* Bell: a clearer, slightly bolder glyph — same Lucide identity. */
  .topbar-bell .lucide-ic { width: 22px; height: 22px; stroke-width: 2.1; }
  .topbar-bell { color: var(--text); }
  /* Quick-action + assistant: flat, icon-only. The assistant keeps a
     brand-tinted sparkle as its sole (flat) identity cue. */
  .topbar-quick-btn .lucide-ic { width: 21px; height: 21px; stroke-width: 2; }
  .topbar-quick-btn { color: var(--text-2); }
  .topbar-ai-ic .lucide-ic, .topbar-ai-ic svg, .topbar-ai-ic i { width: 22px !important; height: 22px !important; }
  .topbar-ai-ic { color: var(--brand); }
  /* Avatar: prominent but within a matched 40px hit-area. */
  .topbar-user-btn .avatar { width: 34px !important; height: 34px !important; font-size: .8rem; }

  /* ── Bell / contact / urgent panels: flat when open (owner). Kill the
     heavy floating shadow — a hairline border defines the sheet. */
  .bell-panel { box-shadow: 0 6px 18px -14px rgba(0,0,0,.4); }

  /* ── Breathing room: nothing touches the screen edges. One shared
     gutter for the content column + safe-area awareness. Full-bleed
     surfaces (chat shell) get inner padding instead. */
  .shell > main > .main {
    padding-inline-start: max(18px, env(safe-area-inset-left));
    padding-inline-end: max(18px, env(safe-area-inset-right));
  }
  .chat-shell .chat-sidebar { padding-inline: 10px; }

  /* ── Modal / bottom-sheet action rows: full-width, thumb-height,
     centered — no more buttons clustered in a bottom corner. Catches the
     common inline `justify-content:flex-end` footer AND the .modal-actions
     convention across every modal (task create, confirms, forms). */
  .modal [style*="flex-end"],
  .modal-actions {
    justify-content: stretch !important;
    gap: .6rem !important;
    flex-wrap: nowrap;
  }
  .modal [style*="flex-end"] > .btn,
  .modal-actions > .btn {
    flex: 1 1 0; min-height: 48px; justify-content: center; font-weight: 500;
  }

  /* ── Settings: clean full-width list rows instead of the cramped
     wrapping chip grid. Each destination is a card with a tinted icon,
     label, and a disclosure chevron — the standard mobile settings row. */
  .settings-nav { flex-direction: column !important; flex-wrap: nowrap !important; gap: .4rem !important; padding: 0; }
  .settings-group-label { padding: .6rem .2rem .2rem; font-weight: 600; letter-spacing: .04em; }
  .settings-nav-item {
    width: 100%; flex: 0 0 auto !important;
    display: flex; align-items: center; gap: .8rem;
    padding: .8rem 1rem; min-height: 56px;
    background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-card);
  }
  :root:not(.light) .settings-nav-item { background: color-mix(in srgb, var(--card) 72%, transparent); border-color: rgba(255,255,255,.10); }
  .settings-nav-item:hover { transform: none; }
  .settings-nav-item::before { display: none; } /* kill the desktop accent rail */
  .settings-nav-item.active { background: var(--brand-soft); border-color: var(--accent-soft-border); }
  .settings-nav-icon { width: 38px; height: 38px; border-radius: var(--radius-sm); }
  .settings-nav-icon .lucide-ic { width: 18px; height: 18px; }
  .settings-nav-label { font-size: .95rem; font-weight: 600; }
  /* Disclosure chevron — a real Lucide chevron-left. In RTL Arabic it
     already points forward (toward the reading edge); LTR flips it. This
     replaces two rival CSS ::after pseudo-borders that merged into an
     ambiguous 3-sided shape (the owner's "unclear arrow"). */
  .settings-nav-chevron {
    margin-inline-start: auto; flex-shrink: 0;
    display: inline-flex; align-items: center; justify-content: center;
    color: var(--text-3);
  }
  .settings-nav-chevron .lucide-ic { width: 18px; height: 18px; stroke-width: 2.25; }
  html[dir="ltr"] .settings-nav-chevron .lucide-ic { transform: scaleX(-1); }

  /* ── Task-type picker (create wizard): stacked full-width cards, each a
     comfortable tap target — not a cramped 3-across row. */
  .mode-pick { display: flex; flex-direction: column; gap: .6rem; }
  .mode-card { width: 100%; text-align: start; padding: 1rem 1.1rem; }

  /* ── Tasks: the mobile view switcher is now 3 tabs (Gantt hidden in JS);
     let them share the width evenly instead of hugging the inline edge. */
  .tasks-controls .ws-tabs { justify-content: stretch; }

  /* ── mhome Digest ─────────────────────────────────────────────────── */
  /* ── Home Digest (Clean Minimal) ─────────────────────────────────────
     Uses shared .m-list/.m-row/.m-icon-tile/.m-section-label; hero + pulse
     + date are the home-specific pieces. */
  .mh-screen { gap: var(--sp-5); }
  .mh-date { font: var(--t-subhead); color: var(--text-3); margin: calc(-1 * var(--sp-2)) 0 calc(-1 * var(--sp-3)); }
  .mh-hero {
    display: block; padding: var(--sp-4); text-decoration: none; color: inherit;
    background: var(--card); border: 1px solid var(--hairline); border-radius: var(--radius-lg);
  }
  .mh-hero:active { background: var(--bg-2); }
  .mh-hero-top { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
  .mh-hero-label { font: var(--t-section-label); letter-spacing: .06em; text-transform: uppercase; color: var(--text-3); }
  .mh-hero-status { display: inline-flex; align-items: center; gap: var(--sp-2); font: var(--t-subhead); color: var(--text-2); }
  .mh-hero-time {
    font-size: 2.15rem; font-weight: 600; letter-spacing: -.02em;
    line-height: 1.1; margin: var(--sp-2) 0; text-align: start; color: var(--text); font-family: var(--font-numeric);
  }
  .mh-hero-meta { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); color: var(--text-3); font: var(--t-callout); }
  .mh-hero-meta b { color: var(--text); }
  .mh-hero-open { display: inline-flex; align-items: center; gap: var(--sp-2); color: var(--brand); font-weight: 600; white-space: nowrap; }
  .mh-hero-open .m-chev { border-color: var(--brand); }
  .mh-pulse {
    display: flex; align-items: center; gap: var(--sp-4);
    padding: var(--sp-4) var(--sp-5); text-decoration: none; color: inherit;
    background: var(--card); border: 1px solid var(--hairline); border-radius: var(--radius-card);
  }
  .mh-pulse:active { background: var(--bg-2); }
  .mh-pulse-item { font: var(--t-callout); color: var(--text-2); }
  .mh-pulse-item b { color: var(--text); font-weight: 600; margin-inline-end: var(--sp-1); }
  .mh-pulse-sep { width: 1px; height: 16px; background: var(--hairline-strong); }
  .mh-pulse-go { margin-inline-start: auto; }
  .mh-block-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--sp-3); }
  .mh-see-all { font: var(--t-caption); font-weight: 600; color: var(--brand); text-decoration: none; }
  /* Unread notification row: emerald wash + emphasized title (dot carries the signal). */
  .m-row.is-unread { background: color-mix(in srgb, var(--brand) 5%, var(--card)); }
  .m-row.is-unread .m-row-title { font-weight: 600; }
  .m-row-sub.is-overdue { color: var(--danger); }

  /* First-session activation checklist (phone, employee). Reuses the shared
     .mh-block/.m-list/.m-row; only the done-state + dismiss button are new. */
  .mh-act-row.is-done .m-row-title { color: var(--text-3); text-decoration: line-through; }
  .mh-act-dismiss { background: none; border: 0; cursor: pointer; padding: 0; font: inherit; }

  /* ── Notifications destination screen (Clean Minimal) — uses shared
     .m-list/.m-row/.m-icon-tile/.m-segmented; only the sub-header row and
     a 2-line body allowance are screen-specific. */
  .ntf-subhead { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); margin: calc(-1 * var(--sp-2)) 0 var(--sp-1); }
  .ntf-row { align-items: flex-start; }
  .ntf-row .m-icon-tile { margin-top: 1px; }
  .ntf-row .m-row-sub { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
  .ntf-row .m-row-side { flex-direction: column; align-items: flex-end; gap: var(--sp-2); }
}
/* Chevrons drawn as "back/forward" arrows must mirror in LTR. */
[dir="ltr"] .dir-chevron .lucide-ic { transform: rotate(180deg); }
/* Desktop hides the phone-only quick strip; the columns carry the data. */
@media (min-width: 641px) {
  .emp-quick { display: none; }
  .emp-role-inline { display: none; }
}

/* ── Employee profile sheet (all widths; bottom-sheet ≤640px) ────────── */
.emp-sheet { max-width: 460px; }
.emp-sheet-head { display: flex; align-items: center; gap: .9rem; margin-bottom: .9rem; }
.emp-sheet-head .avatar { width: 56px; height: 56px; font-size: 1.1rem; flex-shrink: 0; }
.emp-sheet-name { font-size: 1.15rem; font-weight: 600; }
.emp-sheet-sub { color: var(--text-3); font-size: .82rem; display: flex; align-items: center; gap: .35rem; flex-wrap: wrap; margin-top: .2rem; }
.emp-sheet-banner {
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  color: var(--danger); font-weight: 600; font-size: .85rem;
  border-radius: var(--radius-sm); padding: .5rem .8rem; margin-bottom: .7rem;
}
.emp-sheet-rows { display: flex; flex-direction: column; margin-bottom: 1rem; }
.emp-sheet-row {
  display: flex; align-items: center; gap: .75rem;
  padding: .65rem .2rem;
  border-bottom: 1px solid var(--border);
  color: inherit; text-decoration: none;
}
.emp-sheet-row:last-child { border-bottom: 0; }
a.emp-sheet-row:hover { text-decoration: none; background: var(--bg-3); border-radius: var(--radius-sm); }
.emp-sheet-ic {
  width: 36px; height: 36px; border-radius: var(--radius-sm); flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--brand-soft); color: var(--brand);
}
.emp-sheet-ic .lucide-ic { width: 17px; height: 17px; }
.emp-sheet-meta { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: .05rem; }
.emp-sheet-label { font-size: .7rem; font-weight: 600; color: var(--text-3); letter-spacing: .04em; }
.emp-sheet-value { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.emp-sheet-go { color: var(--text-3); display: inline-flex; flex-shrink: 0; }
.emp-sheet-go .lucide-ic { width: 16px; height: 16px; }
.emp-sheet-actions { display: flex; gap: .6rem; }
.emp-sheet-actions .btn { flex: 1; justify-content: center; min-height: 44px; }
.emp-sheet-danger { color: var(--danger); }

/* Roster: account created (or password-reset) but the invite link hasn't
   been used yet — the employee CANNOT log in until they open it. Amber so
   admins spot stuck onboarding at a glance instead of hearing about it
   from the employee's failed login. */
.emp-pending-chip {
  font-size: .64rem; font-weight: 600; color: var(--warn);
  background: color-mix(in srgb, var(--warn) 12%, transparent);
  padding: .08rem .45rem; border-radius: 999px;
  white-space: nowrap; vertical-align: middle;
}

/* Dashboard widget board — MOBILE-ONLY tidy override. This block lost its
   `@media (max-width: 640px)` wrapper in an earlier edit and was leaking to
   desktop, where `grid-column: span 2/1 !important` overrode every widget's
   own `--w` span — silently disabling per-widget resize (the S/M/L/XL button
   AND the corner grip) on desktop and pinning the whole board to 2 columns.
   Re-scoped to phones (2026-07-07): on a narrow screen KPIs go a clean 2-up
   and every panel spans full width; desktop keeps its 4-column, resizable
   layout from admin-dashboard.css. */
@media (max-width: 640px) {
  .dash-grid { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; gap: .65rem !important; }
  .dash-widget { grid-column: span 2 !important; }
  .dash-widget.is-kpi { grid-column: span 1 !important; }
  .dash-widget.is-kpi:last-child:nth-child(odd) { grid-column: span 2 !important; }
  /* The shell's big page-title already reads "لوحة التحكم"; drop the
     widget-board toolbar's duplicate title but keep the Customize action. */
  .dash-toolbar { margin-top: -.2rem; }
  .dash-tb-title { display: none; }
  .dash-tb-actions { margin-inline-start: auto; }
}

/* ══════════════════════════════════════════════════════════════════════
   MOBILE AURORA GLASS  (2026-07-05 — owner-directed)
   Mobile-only theme character on top of DS v2: an emerald-first aurora
   backdrop with a violet/sky whisper at the edges, glass chrome on the
   FIXED layers only (topbar, bottom nav, sticky sub-tabs, sheets), and
   translucent no-blur surfaces for scrolling content so mid-range
   Android WebViews keep a fluid 60fps. Desktop (>720px) is untouched —
   it stays on the Duwam Minimal language.
   Dark = :root:not(.light)  ·  Light = :root.light
   ════════════════════════════════════════════════════════════════════ */
@media (max-width: 720px) {
  /* ── Aurora canvas ─────────────────────────────────────────────────
     body is the non-scrolling 100vh root (the shell's <main> scrolls),
     so these gradients are effectively fixed — zero scroll repaints. */
  /* Royal Pixel (2026-07-17): the aurora color layer is retired — quiet
     flat canvas on phones too (no gradients, no violet, no emerald). The
     structural layer below (translucent cards, fixed glass chrome) stays;
     over a flat canvas it resolves near-solid and keeps layout stable. */
  :root:not(.light) body { background: var(--bg); }
  :root.light body { background: var(--bg); }

  /* ── Content surfaces — translucent, luminous edge, NO backdrop blur.
     The aurora glows through faintly; an inset top highlight gives the
     "lit glass edge" read without costing a blur pass per card. */
  :root:not(.light) .card,
  :root:not(.light) .kpi,
  :root:not(.light) .stat {
    background: color-mix(in srgb, var(--card) 72%, transparent);
    border-color: rgba(255, 255, 255, .10);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05), var(--shadow-1);
  }
  :root.light .card,
  :root.light .kpi,
  :root.light .stat {
    background: rgba(255, 255, 255, .74);
    border-color: rgba(15, 31, 26, .08);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .85), var(--shadow-1);
  }

  /* ── Glass chrome — the fixed/sticky layers get the real blur. ──── */
  .topbar {
    background: color-mix(in srgb, var(--bg) 62%, transparent);
    -webkit-backdrop-filter: blur(16px) saturate(1.5);
    backdrop-filter: blur(16px) saturate(1.5);
  }
  .bottom-nav {
    background: color-mix(in srgb, var(--card) 66%, transparent);
    -webkit-backdrop-filter: blur(18px) saturate(1.6);
    backdrop-filter: blur(18px) saturate(1.6);
    border-color: rgba(255, 255, 255, .14);
  }
  :root.light .bottom-nav { border-color: rgba(15, 31, 26, .10); background: rgba(255, 255, 255, .66); }
  /* Sticky sub-tab pill: swap the solid backdrop strip for true glass. */
  .ws-tabs:not(.tasks-controls .ws-tabs) {
    box-shadow: none;
    clip-path: none;
    background: color-mix(in srgb, var(--card-2) 55%, transparent);
    -webkit-backdrop-filter: blur(14px) saturate(1.4);
    backdrop-filter: blur(14px) saturate(1.4);
  }
  /* Bottom sheets float on glass too. */
  .modal {
    background: color-mix(in srgb, var(--bg-2) 78%, transparent);
    -webkit-backdrop-filter: blur(22px) saturate(1.5);
    backdrop-filter: blur(22px) saturate(1.5);
    border-color: rgba(255, 255, 255, .12);
  }
  :root.light .modal { border-color: rgba(15, 31, 26, .10); background: rgba(250, 251, 250, .80); }

  /* No backdrop-filter support (older WebViews): fall back to almost-
     opaque surfaces so text never floats on raw aurora. */
  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .topbar { background: var(--bg); }
    .bottom-nav { background: var(--card); }
    .ws-tabs:not(.tasks-controls .ws-tabs) { background: var(--card-2); }
    .modal { background: var(--bg-2); }
  }
}

/* ══════════════════════════════════════════════════════════════════════
   CLEAN MINIMAL — shared mobile component library (2026-07-06)
   iOS-native companion language for phones (≤720px). All rules live in
   this media block so desktop/web is untouched. Dark = :root:not(.light).
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width: 720px) {
  /* Scroll container: one gutter + clearance for the floating tab bar. */
  .m-screen {
    display: flex; flex-direction: column; gap: var(--sp-6);
    padding: var(--sp-2) 0 calc(var(--sp-16) + var(--sp-6) + env(safe-area-inset-bottom));
  }

  /* In-body Large Title (title + optional subtitle + optional text action). */
  .m-large-title {
    display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-3);
  }
  .m-large-title h1, .m-lt-title {
    margin: 0; font: var(--t-large-title); letter-spacing: -0.02em; color: var(--text);
  }
  .m-lt-sub { margin: var(--sp-1) 0 0; font: var(--t-subhead); color: var(--text-3); }
  .m-lt-sub .num, .m-tnum { font-family: var(--font-numeric); font-variant-numeric: tabular-nums; }
  .m-textbtn {
    flex-shrink: 0; align-self: center; background: transparent; border: 0;
    color: var(--brand); font: var(--t-callout); font-weight: 600; padding: var(--sp-2) var(--sp-1); cursor: pointer;
  }
  .m-textbtn:disabled { color: var(--text-3); opacity: .5; }

  /* Segmented control (iOS). */
  .m-segmented {
    display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
    gap: 2px; padding: 3px; height: 40px;
    background: var(--bg-3); border-radius: var(--radius-field);
  }
  .m-seg-btn {
    border: 0; background: transparent; cursor: pointer;
    font: var(--t-subhead); color: var(--text-2);
    border-radius: var(--radius-sm); display: grid; place-items: center;
    transition: color .15s ease, background .18s ease;
  }
  .m-seg-btn.is-active { background: var(--card); color: var(--text); box-shadow: var(--shadow-1); font-weight: 600; }
  :root:not(.light) .m-seg-btn.is-active { box-shadow: none; border: 1px solid var(--hairline); }

  /* Inset grouped list card + rows with text-inset hairline dividers. */
  .m-list {
    background: var(--card); border: 1px solid var(--hairline);
    border-radius: var(--radius-card); overflow: hidden;
  }
  .m-row {
    position: relative; display: flex; align-items: center; gap: var(--sp-3);
    width: 100%; text-align: start; min-height: 60px; padding: var(--sp-3) var(--sp-4);
    background: transparent; border: 0; color: inherit; font: inherit; cursor: pointer;
    text-decoration: none;
  }
  .m-row:active { background: var(--bg-2); }
  /* Divider inset past the leading icon (52px = 40 tile + 12 gap). */
  .m-row + .m-row::before {
    content: ""; position: absolute; top: 0; inset-inline-start: 52px; inset-inline-end: 0;
    height: 1px; background: var(--hairline);
  }
  .m-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
  .m-row-title { font: var(--t-callout); font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .m-row-sub { font: var(--t-caption); color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: flex; align-items: center; gap: var(--sp-2); }
  .m-row-side { flex-shrink: 0; display: flex; align-items: center; gap: var(--sp-2); color: var(--text-3); }
  .m-row-time { font: var(--t-caption); color: var(--text-3); font-family: var(--font-numeric); font-variant-numeric: tabular-nums; white-space: nowrap; }

  /* Rounded tinted leading icon tile (per-kind tint via data-kind). */
  .m-icon-tile {
    width: 40px; height: 40px; flex-shrink: 0; border-radius: var(--radius-field);
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--bg-3); color: var(--text-2);
  }
  .m-icon-tile .lucide-ic, .m-icon-tile svg { width: 18px; height: 18px; stroke-width: 1.9; }
  .m-icon-tile[data-kind="attendance.started"], .m-icon-tile[data-kind="attendance.ended"],
  .m-icon-tile[data-kind="task.completed"], .m-icon-tile[data-kind="brand"] { background: color-mix(in srgb, var(--brand) 12%, transparent); color: var(--brand); }
  .m-icon-tile[data-kind="admin.urgent"], .m-icon-tile[data-kind="danger"] { background: color-mix(in srgb, var(--danger) 12%, transparent); color: var(--danger); }
  .m-icon-tile[data-kind="task.assigned"], .m-icon-tile[data-kind="chat.message"], .m-icon-tile[data-kind="info"] { background: color-mix(in srgb, var(--info) 14%, transparent); color: var(--info); }
  .m-icon-tile[data-kind="rewards.updated"], .m-icon-tile[data-kind="warn"] { background: color-mix(in srgb, var(--warn) 14%, transparent); color: var(--warn); }

  /* Disclosure chevron — points forward in the reading direction. */
  .m-chev { width: 8px; height: 8px; flex-shrink: 0; border: solid var(--text-3); border-width: 2px 2px 0 0; transform: rotate(-135deg); opacity: .55; }
  html[dir="ltr"] .m-chev { transform: rotate(45deg); }

  /* Quiet uppercase section label. */
  .m-section-label { font: var(--t-section-label); letter-spacing: .06em; text-transform: uppercase; color: var(--text-3); padding: 0 var(--sp-1) var(--sp-1); }

  /* Unread dot. */
  .m-unread-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--brand); flex-shrink: 0; }

  /* Status pill (tonal, information-carrying). */
  .m-pill { display: inline-flex; align-items: center; gap: var(--sp-1); border-radius: var(--radius-pill); padding: 3px var(--sp-2); font: var(--t-caption); font-weight: 600; }
  .m-pill.is-ontime, .m-pill.is-early { background: color-mix(in srgb, var(--brand) 12%, transparent); color: var(--brand); }
  .m-pill.is-late { background: color-mix(in srgb, var(--warn) 15%, transparent); color: var(--warn); }
  .m-pill.is-absent { background: color-mix(in srgb, var(--danger) 12%, transparent); color: var(--danger); }
  .m-pill.is-off { background: var(--bg-3); color: var(--text-3); }

  /* Empty state. */
  .m-empty { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); text-align: center; background: var(--card); border: 1px solid var(--hairline); border-radius: var(--radius-card); padding: var(--sp-12) var(--sp-6); }
  .m-empty-ic { width: 64px; height: 64px; border-radius: var(--radius-lg); background: var(--brand-soft); color: var(--brand); display: grid; place-items: center; margin-bottom: var(--sp-1); }
  .m-empty-ic .lucide-ic, .m-empty-ic svg { width: 26px; height: 26px; }
  .m-empty-title { font: var(--t-headline); color: var(--text); }
  .m-empty-hint { font: var(--t-callout); color: var(--text-3); max-width: 240px; }

  /* ── Tasks (Clean Minimal) ───────────────────────────────────────────
     Date + status Segmented Control head; the old toolbar demotes to a
     slim icon strip; the list becomes calm grouped rows. */
  .tasks-board.is-phone { display: flex; flex-direction: column; gap: var(--sp-4); height: auto; min-height: 0; }
  /* The viewport must be a full-width BLOCK that flows in the page scroll —
     the desktop `display:flex` made the block list size to content and hug
     the start edge (the empty gap the owner flagged). */
  .tasks-board.is-phone .tasks-viewport { display: block; overflow: visible; height: auto; flex: 0 0 auto; }
  .tasks-board.is-phone .tasks-viewport > * { width: 100%; }
  .tm-head { display: flex; flex-direction: column; gap: var(--sp-3); }
  .tm-date { font: var(--t-subhead); color: var(--text-3); margin-top: calc(-1 * var(--sp-2)); }
  /* Demote the desktop toolbar on phones: the segmented control owns status;
     keep only search + filter + view/scope as a slim secondary strip, and
     move Add to the FAB. */
  .tasks-board.is-phone .tasks-toolbar { margin: 0; }
  /* ONE height system for every control (add / view-pills / search / filter)
     so nothing mismatches; wrap into two clean rows — Row 1 [+][view pills],
     Row 2 [search][filter] — the Asana/Monday search-under-tabs pattern. */
  .tasks-board.is-phone { --m-ctl-h: 44px; }
  .tasks-board.is-phone .tasks-controls {
    display: flex; flex-wrap: wrap; gap: var(--sp-2); row-gap: var(--sp-2); align-items: center;
  }
  /* Add-task leads Row 1 (owner: bottom FAB was wrong) — a compact emerald
     square on the same 44px baseline as the view-switcher pills beside it. */
  .tasks-board.is-phone #tf-add {
    order: -1; flex: 0 0 var(--m-ctl-h); width: var(--m-ctl-h); height: var(--m-ctl-h); padding: 0;
    border-radius: var(--radius-field); justify-content: center;
  }
  .tasks-board.is-phone #tf-add .tf-add-text { display: none; }
  /* Status is owned by the segmented control; project/scope/group live in
     the filter sheet — hide their heavy toolbar chrome to calm the screen.
     Keep the view-switch (list/board/today), search, and filter button. */
  .tasks-board.is-phone .scope-switch,
  .tasks-board.is-phone .tasks-controls-divider,
  .tasks-board.is-phone .tasks-controls-spacer,
  .tasks-board.is-phone #tf-project,
  .tasks-board.is-phone #tf-group { display: none !important; }
  /* Row 1: view-switcher pills fill the rest of the line next to [+], on the
     same 44px baseline. Their flex-grow eats the leftover width so the 60%
     search field cannot fit and deterministically wraps to Row 2. */
  .tasks-board.is-phone .ws-tabs {
    order: 0; flex: 1 1 auto; height: var(--m-ctl-h); margin-bottom: 0;
    padding: 3px; align-items: center; overflow-x: auto;
  }
  .tasks-board.is-phone .ws-tab { padding: 0 var(--sp-3); height: 100%; }
  /* Row 2: search grows, filter squares off — both 44px, matching radius. */
  .tasks-board.is-phone .task-filter-search {
    order: 1; flex: 1 1 60%; min-width: 60%; height: var(--m-ctl-h);
  }
  .tasks-board.is-phone .task-filter-search > input { height: var(--m-ctl-h); border-radius: var(--radius-field); }
  .tasks-board.is-phone .task-filter-dropdown { order: 2; flex: 0 0 auto; }
  .tasks-board.is-phone .task-filter-btn {
    height: var(--m-ctl-h); width: var(--m-ctl-h); padding: 0;
    justify-content: center; border-radius: var(--radius-field);
  }
  .tasks-board.is-phone .task-filter-btn .tfb-text { display: none; }
  /* Chips: reserve trailing scroll room so the last chip never clips. */
  .tasks-board.is-phone .tasks-chips {
    margin-top: var(--sp-2); padding-inline: var(--sp-1);
    scroll-padding-inline: var(--sp-4); scroll-snap-type: x proximity;
  }
  .tasks-board.is-phone .tasks-chips > * { scroll-snap-align: start; }
  .tasks-board.is-phone .tasks-chips::after { content: ""; flex: 0 0 var(--sp-4); }

  .m-task-row {
    position: relative; display: flex; flex-direction: column; cursor: pointer;
    transition: background .15s ease;
  }
  .m-task-line { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-3) var(--sp-4); min-height: 60px; }
  .m-task-row:active { background: var(--bg-2); }
  .m-task-row.is-selected { background: color-mix(in srgb, var(--brand) 6%, var(--card)); }
  .m-task-row + .m-task-row::before {
    content: ""; position: absolute; top: 0; inset-inline-start: 52px; inset-inline-end: 0;
    height: 1px; background: var(--hairline);
  }
  .m-task-check {
    width: 24px; height: 24px; flex-shrink: 0; border-radius: 50%;
    border: 1.5px solid var(--text-3); background: transparent; cursor: pointer; padding: 0;
    display: inline-flex; align-items: center; justify-content: center; color: transparent;
    transition: background .15s ease, border-color .15s ease;
  }
  .m-task-check.is-done, .m-task-check.is-selected { background: var(--brand); border-color: var(--brand); color: var(--accent-fg); }
  .m-task-row.is-locked .m-task-check { border-style: dashed; border-color: var(--border-2); cursor: default; opacity: .55; }
  /* Contextual action footer, revealed when a task is selected. */
  .m-task-actionbar { display: flex; align-items: center; gap: var(--sp-2); padding: 0 var(--sp-4) var(--sp-3); padding-inline-start: 52px; }
  .m-task-actbtn { flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-1); min-height: 40px; border-radius: var(--radius); border: 0; font: var(--t-subhead); font-weight: 600; cursor: pointer; }
  .m-task-actbtn .lucide-ic, .m-task-actbtn svg { width: 16px; height: 16px; }
  .m-task-actbtn.is-primary { background: var(--brand); color: var(--accent-fg); }
  .m-task-actbtn.is-ghost { background: var(--bg-3); color: var(--text); }
  .m-task-actmuted { flex: 1; text-align: center; font: var(--t-subhead); color: var(--text-3); padding: var(--sp-2); }
  .m-task-actopen { flex: 0 0 auto; min-height: 40px; padding: 0 var(--sp-4); border-radius: var(--radius); border: 1px solid var(--hairline); background: transparent; color: var(--text-2); font: var(--t-subhead); font-weight: 600; cursor: pointer; }
  .m-task-check .lucide-ic, .m-task-check svg { width: 14px; height: 14px; stroke-width: 2.6; }
  .m-task-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
  .m-task-title { font: var(--t-callout); font-weight: 600; color: var(--text); line-height: 1.35; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
  .m-task-meta { display: flex; align-items: center; gap: var(--sp-2); font: var(--t-caption); color: var(--text-3); min-width: 0; }
  .m-task-meta .tm-due.is-overdue { color: var(--danger); }
  .m-task-meta .tm-proj { display: inline-flex; align-items: center; gap: var(--sp-1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .m-task-meta .tm-proj-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
  .m-task-meta .tm-dot-sep { color: var(--text-3); opacity: .6; }
  .m-task-meta .rd-icon { display: none; }
  .m-task-trailing { flex-shrink: 0; display: flex; align-items: center; gap: var(--sp-2); }
  .m-task-trailing .tm-prio-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
  /* Asana-style trailing: assignee avatar + real Lucide disclosure chevron
     fill the row's end so the width is used instead of a dead void. */
  .m-task-avatar { width: 28px; height: 28px; flex-shrink: 0; display: inline-flex; }
  .m-task-avatar .avatar { width: 28px; height: 28px; font-size: 11px; }
  .m-task-avatar.is-empty { border-radius: 50%; border: 1.5px dashed var(--border-2); }
  .m-task-av-more { font: var(--t-caption); color: var(--text-3); font-weight: 600; }
  .m-chev { display: inline-flex; align-items: center; color: var(--text-3); opacity: .5; }
  .m-chev .lucide-ic, .m-chev svg { width: 16px; height: 16px; }
  html[dir="ltr"] .m-chev { transform: scaleX(-1); }
  /* Agenda time-chip — emerald, tabular; renders only for timed tasks. */
  .m-task-meta .tm-time { font: var(--t-caption); font-variant-numeric: tabular-nums; font-weight: 600; color: var(--brand); }
  .m-task-row.is-task-completed .m-task-title { text-decoration: line-through; color: var(--text-3); }
  .m-task-row.is-task-completed { opacity: .72; }
  .m-task-row.is-task-completed .m-task-check { opacity: 1; }
  .tm-group-count { color: var(--text-3); font-weight: 600; }

  /* Emerald add FAB (the single allowed emerald glow). */
  .m-fab {
    position: fixed; z-index: 45;
    inset-inline-end: max(var(--sp-4), env(safe-area-inset-right));
    bottom: calc(var(--sp-16) + var(--sp-6) + env(safe-area-inset-bottom));
    width: 56px; height: 56px; border-radius: var(--radius-lg); border: 0;
    background: var(--brand); color: var(--accent-fg);
    display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
    box-shadow: 0 12px 28px -10px color-mix(in srgb, var(--brand) 55%, transparent);
    transition: transform .12s ease;
  }
  .m-fab:active { transform: scale(.94); }
  .m-fab .lucide-ic, .m-fab svg { width: 26px; height: 26px; }
}

/* ══ CLEAN MINIMAL — Chat (mobile) ══════════════════════════════════════ */
@media (max-width: 720px) {
  /* In-list Large Title (chat is excluded from the shell page-title). */
  .chat-list-header { padding: var(--sp-4) var(--sp-4) var(--sp-2);
    padding-inline-start: calc(var(--sp-4) + env(safe-area-inset-left));
    padding-inline-end: calc(var(--sp-4) + env(safe-area-inset-right)); }
  .chat-large-title { margin: 0; font: var(--t-large-title); letter-spacing: -0.02em; color: var(--text); }
  body.chat-open .chat-list-header { display: none; }

  /* List sits on the canvas; kill the card-2 section bars + wallpaper. */
  .chat-sidebar-title-row { background: transparent !important; border: 0 !important; padding: var(--sp-4) var(--sp-4) var(--sp-1) !important; }
  .chat-sidebar-title { font: var(--t-section-label) !important; letter-spacing: .04em; text-transform: uppercase; color: var(--text-3) !important; background: transparent !important; }
  .chat-sidebar-title .chat-sidebar-chev { width: 16px; height: 16px; }
  .chat-sidebar-add { color: var(--text-3); opacity: 1; }
  .chat-messages, .msgs { background-image: none !important; background: var(--bg) !important; }

  /* Conversation rows — airy, text-inset dividers, no full-bleed borders. */
  .chat-row { min-height: 72px; padding: var(--sp-3) var(--sp-4); gap: var(--sp-3); border-bottom: 0 !important; }
  .chat-row:active { background: var(--card-2); }
  .chat-list-inner .chat-row:not(:last-child) { box-shadow: inset 0 -1px 0 var(--hairline); }
  .chat-list-inner .chat-row:not(:last-child)::after { content: none; }
  .chat-row-hash { width: 48px; height: 48px; background: var(--brand-soft); color: var(--brand); font-size: 1.35rem; font-weight: 600; }
  .chat-row-hash.is-closed .lucide-ic { width: 18px; height: 18px; } /* scale the closed-channel lock with the 48px tile */
  .chat-row-name { font: var(--t-callout); font-weight: 600; }
  .chat-row-time { font: var(--t-caption); color: var(--text-3); font-family: var(--font-numeric); font-variant-numeric: tabular-nums; }
  .chat-row-preview { font: var(--t-caption); color: var(--text-3); }
  .chat-preview-ic { display: inline-flex; vertical-align: middle; margin-inline-end: 4px; color: var(--icon-ink); }
  .chat-preview-ic .lucide-ic, .chat-preview-ic svg { width: 14px; height: 14px; }
  .chat-row-unread { min-width: 20px; height: 20px; padding: 0 6px; border-radius: var(--radius-pill); background: var(--brand); color: var(--accent-fg); font: var(--t-caption); font-weight: 600; font-family: var(--font-numeric); display: inline-flex; align-items: center; justify-content: center; }
  .member-row { min-height: 64px; padding: var(--sp-3) var(--sp-4); gap: var(--sp-3); border-bottom: 0 !important; }
  .member-row:not(:last-child) { box-shadow: inset 0 -1px 0 var(--hairline); }

  /* Calm in-conversation header. */
  .chat-header { background: var(--bg) !important; border-bottom: 1px solid var(--hairline) !important; }
}

/* Chat list full-bleed on the canvas (phones) — drop the card frame. */
@media (max-width: 720px) {
  .chat-shell.card { border: 0 !important; border-radius: 0 !important; background: var(--bg) !important; box-shadow: none !important; }
}

/* ══ CLEAN MINIMAL — Attendance / shift (mobile) ════════════════════════ */
@media (max-width: 720px) {
  /* Shell already draws the "الحضور" Large Title — drop the card's own h2. */
  .att-card > h2[data-i18n="attendance"] { display: none; }
  /* Flat hero card: hairline, no gradient tint, calm radius. */
  .att-card {
    background: var(--card) !important; border: 1px solid var(--hairline) !important;
    border-radius: var(--radius-lg) !important; box-shadow: none !important;
    padding: var(--sp-5) !important;
  }
  .att-card::before { display: none !important; }
  /* Ring: flat, clean track + thin emerald core border, no heavy shadows. */
  .att-ring { box-shadow: none !important; }
  .att-ring-core { box-shadow: none !important; background: var(--bg-2) !important; border-color: var(--hairline) !important; }
  .att-elapsed-value { font-family: var(--font-numeric); font-variant-numeric: tabular-nums; }
  .att-elapsed-label { font: var(--t-section-label); letter-spacing: .06em; text-transform: uppercase; color: var(--text-3); }
  /* Quiet stat chips instead of loud outlined pills. */
  .att-card .chip { background: var(--bg-2) !important; border: 1px solid var(--hairline) !important; border-radius: var(--radius-field) !important; color: var(--text-2); font-weight: 600; }
  /* Primary button: flat emerald, thumb height (glow already dropped). */
  .att-card .btn-primary { box-shadow: none !important; border-radius: var(--radius) !important; }
  /* Past-7 tiles: calm — drop the pink/red tint frames. */
  .p7-tile { background: var(--card) !important; border: 1px solid var(--hairline) !important; border-radius: var(--radius-field) !important; }
  .p7-dow { color: var(--text-3) !important; }
  /* Flexible-hours ribbon: quiet pill. */
  .shift-pill { background: var(--card) !important; border: 1px solid var(--hairline) !important; box-shadow: none !important; }
}

/* ══ CLEAN MINIMAL — audit fixes (mobile sections) 2026-07-07 ══════════ */
/* Upgrade/plan-gate modal icon (all sizes): Lucide lock, not an emoji. */
.um-icon { color: var(--brand); }
.um-icon .lucide-ic, .um-icon svg { width: 34px; height: 34px; }

@media (max-width: 720px) {
  /* Settings: the shell already draws the "الإعدادات" Large Title — drop
     the duplicate inner header + flatten the aside so it's not a card-in-
     card; the nav rows are the clean full-width list. */
  .settings-aside { background: transparent !important; border: 0 !important; box-shadow: none !important; padding: 0 !important; }
  .settings-aside-head h3 { display: none; }
  .settings-aside-head { margin-bottom: var(--sp-2); }

  /* Rewards hero: flatten the emerald tint to a calm hairline card. */
  .rew-hero { background: var(--card) !important; border: 1px solid var(--hairline) !important; border-radius: var(--radius-lg) !important; box-shadow: none !important; }
  .rew-hero-cta { box-shadow: none !important; }
}

/* Wiki: shell draws "المعرفة" Large Title — hide the sidebar's duplicate. */
@media (max-width: 720px) {
  .wk-side-title { display: none !important; }
}

/* ══ CLEAN MINIMAL — employee cards distinction (mobile) ════════════════ */
@media (max-width: 640px) {
  /* Per-person colored ring around the avatar so cards are distinguishable
     at a glance (owner: "بطاقاتهم متشابهة"). Ring hue = name hash. */
  .staff-av { display: inline-flex; flex-shrink: 0; border-radius: 50%; padding: 2px; background: var(--av-ring); }
  .staff-av .avatar { width: 46px; height: 46px; font-size: 1rem; box-shadow: 0 0 0 2px var(--card); }
  .staff-table.as-cards .staff-name-cell { gap: .8rem; }
  .staff-table.as-cards .staff-name { font: var(--t-callout); font-weight: 600; }
}

/* ══ CLEAN MINIMAL — Board vertical-stacked on mobile ═══════════════════ */
@media (max-width: 640px) {
  /* The horizontal side-scroll board is desktop-first; on phones stack the
     columns vertically as full-width sections (Trello-mobile pattern). */
  .tasks-board.is-phone .kanban {
    display: flex; flex-direction: column; gap: var(--sp-5);
    grid-auto-flow: unset; grid-auto-columns: unset;
    overflow: visible; height: auto;
  }
  .tasks-board.is-phone .kanban-col { width: 100%; max-height: none; }
  .tasks-board.is-phone .kanban-col h4 {
    background: transparent; border: 0; padding: 0 var(--sp-1) var(--sp-2);
    font: var(--t-section-label); letter-spacing: .06em; text-transform: uppercase; color: var(--text-3);
  }
  .tasks-board.is-phone .kanban-col .col-body {
    overflow: visible; max-height: none; gap: var(--sp-2);
    background: var(--card); border: 1px solid var(--hairline); border-radius: var(--radius-card); padding: var(--sp-2);
  }
  .tasks-board.is-phone .kanban-task {
    background: transparent; border: 0; border-radius: var(--radius-sm);
    box-shadow: none; padding: var(--sp-3);
  }
  .tasks-board.is-phone .kanban-task + .kanban-task { border-top: 1px solid var(--hairline); border-radius: 0; }
  .tasks-board.is-phone .kanban-col .col-empty { padding: var(--sp-4); }
}

/* ══ CLEAN MINIMAL — Popover→bottom-sheet on phones ════════════════════ */
@media (max-width: 640px) {
  .m-sheet-backdrop {
    position: fixed; inset: 0; z-index: 1200;
    background: rgba(0,0,0,.34);
    animation: m-sheet-fade .2s ease;
  }
  .task-filter-menu.m-sheet, .task-group-menu.m-sheet {
    position: fixed; inset-inline: 0; bottom: 0; top: auto;
    width: 100%; min-width: 0; max-width: none;
    max-height: 82vh; overflow-y: auto;
    border: 0; border-top-left-radius: var(--radius-card); border-top-right-radius: var(--radius-card);
    border-bottom-left-radius: 0; border-bottom-right-radius: 0;
    box-shadow: 0 -12px 40px rgba(0,0,0,.28);
    padding: var(--sp-3) var(--sp-4) calc(var(--sp-5) + env(safe-area-inset-bottom));
    z-index: 1201; animation: m-sheet-up .26s cubic-bezier(.22,1,.36,1);
  }
  /* Grabber handle so it reads as a draggable sheet. */
  .task-filter-menu.m-sheet::before, .task-group-menu.m-sheet::before {
    content: ""; display: block; width: 38px; height: 4px; margin: 2px auto var(--sp-3);
    background: var(--text-3); opacity: .32; border-radius: 999px;
  }
  .task-filter-menu.m-sheet .chip-row > button { padding: .5rem .9rem; font-size: .9rem; }
  .task-filter-menu.m-sheet select,
  .task-filter-menu.m-sheet input[type="date"] { padding: .7rem .6rem; font-size: 1rem; }
  .task-filter-menu.m-sheet .menu-foot { position: sticky; bottom: 0; background: var(--card); padding-top: var(--sp-2); }
  .task-filter-menu.m-sheet .menu-foot .btn { flex: 1; padding: .7rem; }
}
@keyframes m-sheet-up { from { transform: translateY(100%); } to { transform: translateY(0); } }
@keyframes m-sheet-fade { from { opacity: 0; } to { opacity: 1; } }

/* ══ CLEAN MINIMAL — Settings detail as bottom-sheet on phones ══════════ */
@media (max-width: 640px) {
  /* Nav list is the landing view; the inline detail pane is unused. */
  .settings-shell .settings-content { display: none; }
  .settings-shell { display: block; }
  .settings-shell .settings-aside { width: 100%; max-width: none; border: 0; }
  /* Row reads as a navigable link — the .settings-nav-chevron span (real
     Lucide icon) provides the affordance; padding reserves its space. */
  .settings-nav-item { padding-inline-end: var(--sp-5); }

  .settings-sheet-backdrop {
    position: fixed; inset: 0; z-index: 1200; background: rgba(0,0,0,.34);
    animation: m-sheet-fade .2s ease;
  }
  .settings-sheet {
    position: fixed; inset-inline: 0; bottom: 0; top: 8vh; z-index: 1201;
    background: var(--bg); display: flex; flex-direction: column;
    border-top-left-radius: var(--radius-card); border-top-right-radius: var(--radius-card);
    box-shadow: 0 -12px 40px rgba(0,0,0,.3); overflow: hidden;
    animation: m-sheet-up .28s cubic-bezier(.22,1,.36,1);
  }
  .settings-sheet-head {
    display: flex; align-items: center; gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--hairline);
    flex: 0 0 auto; background: var(--card);
  }
  .settings-sheet-head::before {
    content: ""; position: absolute; inset-inline: 0; top: 6px;
    width: 38px; height: 4px; margin: 0 auto;
    background: var(--text-3); opacity: .3; border-radius: 999px;
  }
  .settings-sheet-head { position: relative; padding-top: calc(var(--sp-3) + 8px); }
  .settings-sheet-close {
    display: inline-flex; align-items: center; justify-content: center;
    width: 34px; height: 34px; border-radius: 50%;
    background: var(--bg-2); border: 0; color: var(--text); cursor: pointer;
  }
  .settings-sheet-close svg, .settings-sheet-close .lucide-ic { width: 18px; height: 18px; }
  .settings-sheet-title { flex: 1; text-align: center; font: var(--t-title); font-weight: 600; }
  .settings-sheet-spacer { width: 34px; flex: 0 0 34px; }
  .settings-sheet-body {
    flex: 1 1 auto; overflow-y: auto; padding: var(--sp-4);
    padding-bottom: calc(var(--sp-6) + env(safe-area-inset-bottom));
    -webkit-overflow-scrolling: touch;
  }
  /* Section content inside the sheet fills width; kill the two-col split. */
  .settings-sheet-body .settings-cols { display: block; }
  .settings-sheet-body .card { max-width: none !important; }
}

/* ══ CLEAN MINIMAL — Mobile Agenda (day-grouped schedule) ══════════════ */
@media (max-width: 640px) {
  .m-agenda { --agenda-strip-h: 68px; display: flex; flex-direction: column; gap: var(--sp-4); }
  /* Pinned rolling week strip — day pills, today emerald, count dot. */
  .m-agenda-strip {
    position: sticky; top: 0; z-index: 5;
    display: flex; gap: var(--sp-2); overflow-x: auto;
    scroll-snap-type: x proximity; scroll-padding-inline: var(--sp-3);
    padding: var(--sp-2) var(--sp-3); margin-inline: calc(var(--sp-3) * -1);
    background: var(--bg); border-bottom: 1px solid var(--hairline);
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
  }
  .m-agenda-strip::-webkit-scrollbar { display: none; }
  .m-agenda-pill {
    flex: 0 0 auto; scroll-snap-align: start; width: 44px; min-height: 52px;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
    border: 1px solid var(--hairline); border-radius: var(--radius-card);
    background: var(--bg-2); color: var(--text); font: var(--t-caption); cursor: pointer;
  }
  .m-agenda-pill .mp-wd { color: var(--text-3); font-weight: 600; }
  .m-agenda-pill .mp-dn { font-weight: 600; font-size: 15px; }
  .m-agenda-pill .mp-dot { width: 5px; height: 5px; border-radius: 999px; background: transparent; }
  .m-agenda-pill.has-tasks .mp-dot { background: var(--brand); }
  .m-agenda-pill.is-empty { color: var(--text-3); background: transparent; }
  .m-agenda-pill.is-today { background: var(--brand); border-color: var(--brand); }
  .m-agenda-pill.is-today .mp-wd, .m-agenda-pill.is-today .mp-dn { color: #fff; }
  .m-agenda-pill.is-today .mp-dot { background: #fff; }
  .m-agenda-pill.is-active:not(.is-today) { border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand) inset; }
  .m-agenda-day { display: flex; flex-direction: column; gap: var(--sp-2); }
  .m-agenda-dayhead {
    position: sticky; top: var(--agenda-strip-h); z-index: 3;
    display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-1);
    background: var(--bg); /* opaque so rows don't bleed under the sticky header */
  }
  .m-agenda-daylabel { font: var(--t-title); font-weight: 600; color: var(--text); }
  .m-agenda-daycount {
    min-width: 22px; height: 22px; padding: 0 6px; border-radius: 999px;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--bg-2); color: var(--text-3); font: var(--t-caption); font-weight: 600;
  }
  .m-agenda-day.is-overdue .m-agenda-daylabel { color: #DC2626; }
  .m-agenda-day.is-overdue .m-agenda-daycount { background: rgba(220,38,38,.12); color: #DC2626; }
  .m-agenda-day.is-undated .m-agenda-daylabel { color: var(--text-3); }
}

/* ══ CLEAN MINIMAL — Task detail as a bottom-sheet on phones ════════════ */
@media (max-width: 640px) {
  /* The desktop side drawer (.side-panel) becomes a bottom-anchored sheet
     on phones, reusing the m-sheet vocabulary (grabber + slide-up + dim).
     Scoped to the .side-panel--sheet marker (added in JS on phone) so the
     desktop drawer is never matched. */
  .side-panel-backdrop { z-index: 1000; background: rgba(0,0,0,.34); animation: m-sheet-fade .2s ease; }
  .side-panel--sheet {
    top: 8vh; bottom: 0; inset-inline: 0; width: 100%;
    border-inline-start: 0;
    border-top-left-radius: var(--radius-card); border-top-right-radius: var(--radius-card);
    border-bottom-left-radius: 0; border-bottom-right-radius: 0;
    box-shadow: 0 -12px 40px rgba(0,0,0,.3);
    animation: m-sheet-up .28s cubic-bezier(.22,1,.36,1);
  }
  html[dir="rtl"] .side-panel--sheet { --slide-from: 0; }
  /* Grabber pill on the header + a comfortable swipe zone. */
  .side-panel--sheet .side-panel-head { position: relative; padding-top: calc(.75rem + 8px); touch-action: none; }
  .side-panel--sheet .side-panel-head::before {
    content: ""; position: absolute; inset-inline: 0; top: 6px;
    width: 38px; height: 4px; margin: 0 auto;
    background: var(--text-3); opacity: .3; border-radius: 999px;
  }
}

/* ══ CLEAN MINIMAL — Attendance daily cards as stat-tiles on phones ═════ */
@media (max-width: 640px) {
  /* Kill the spreadsheet reflow for the attendance table: each row becomes
     a card — avatar + name/id header, a dividerless 2×2 grid of stat tiles
     (Required / Overtime / Present / Earnings), one calm report action.
     Scoped to .att-cards so no other as-cards table is touched; the extra
     .table class bumps specificity above the generic .table.as-cards rules. */
  .table.att-cards.as-cards > tbody > tr {
    display: grid; grid-template-columns: 1fr 1fr;
    grid-template-areas: 'head head' 'a b' 'c d' 'act act';
    gap: var(--sp-2); align-items: stretch;
    border: 1px solid var(--hairline); border-radius: 16px;
    background: var(--card); padding: var(--sp-4); box-shadow: var(--shadow-1);
    margin-bottom: var(--sp-3);
  }
  .att-cards.as-cards--nopay > tbody > tr,
  .table.att-cards--nopay.as-cards > tbody > tr { grid-template-areas: 'head head' 'a b' 'c c' 'act act'; }
  /* No dividers, no ledger padding. */
  .table.att-cards.as-cards > tbody > tr > td {
    border-top: 0 !important; padding: 0; min-height: 0; display: flex;
  }
  /* Header: avatar hero + name + id. */
  .table.att-cards.as-cards > tbody > tr > td.td-primary {
    grid-area: head; display: flex; align-items: center; gap: var(--sp-3); padding: 0 0 var(--sp-1);
  }
  .table.att-cards.as-cards .att-av { display: inline-flex; flex-shrink: 0; }
  .table.att-cards.as-cards .att-av .avatar { width: 42px; height: 42px; font-size: 15px; }
  .table.att-cards.as-cards .att-id-row { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; min-width: 0; }
  .table.att-cards.as-cards .att-name { font-weight: 600; font-size: 1rem; color: var(--text); }
  /* Tiles: quiet caption over a big value; the caption comes from ::before
     (data-label) which survives paintRow's innerHTML swaps. */
  .table.att-cards.as-cards > tbody > tr > td.t-req { grid-area: a; }
  .table.att-cards.as-cards > tbody > tr > td.t-ot  { grid-area: b; }
  .table.att-cards.as-cards > tbody > tr > td.t-pres{ grid-area: c; }
  .table.att-cards.as-cards > tbody > tr > td.t-earn{ grid-area: d; }
  .table.att-cards.as-cards > tbody > tr > td.t-req,
  .table.att-cards.as-cards > tbody > tr > td.t-ot,
  .table.att-cards.as-cards > tbody > tr > td.t-pres,
  .table.att-cards.as-cards > tbody > tr > td.t-earn {
    flex-direction: column; align-items: flex-start; justify-content: center; gap: 3px;
    background: var(--bg-2); border-radius: 12px; padding: var(--sp-3);
  }
  .table.att-cards.as-cards > tbody > tr > td.t-req::before,
  .table.att-cards.as-cards > tbody > tr > td.t-ot::before,
  .table.att-cards.as-cards > tbody > tr > td.t-pres::before,
  .table.att-cards.as-cards > tbody > tr > td.t-earn::before {
    order: 2; content: attr(data-label); font-size: .7rem; font-weight: 600; color: var(--text-3);
    text-align: start;
  }
  /* The value (bdi / chip / live timer) sits above the caption, big + bold. */
  .table.att-cards.as-cards > tbody > tr > td.t-req > bdi,
  .table.att-cards.as-cards > tbody > tr > td.t-ot,
  .table.att-cards.as-cards > tbody > tr > td.t-pres,
  .table.att-cards.as-cards > tbody > tr > td.t-earn { font-size: 1.15rem; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
  .table.att-cards.as-cards > tbody > tr > td .text-subtle { font-size: 1.15rem; }
  /* Action row: report is the calm full-width primary; mic demoted to a
     compact square beside it (resolves the "mic clutter" complaint). */
  .table.att-cards.as-cards > tbody > tr > td.td-actions {
    grid-area: act; display: flex; gap: var(--sp-2); padding-top: var(--sp-1); justify-content: stretch;
  }
  .table.att-cards.as-cards .td-actions [data-action="report"] {
    flex: 1; justify-content: center; min-height: 44px; border-radius: var(--radius-field);
    background: color-mix(in srgb, var(--brand) 10%, transparent); color: var(--brand); border: 0; font-weight: 600;
  }
  .table.att-cards.as-cards .td-actions [data-action="report"] .lucide-ic { width: 16px; height: 16px; }
  .table.att-cards.as-cards .td-actions .mic-btn {
    flex: 0 0 44px; width: 44px; height: 44px; padding: 0; justify-content: center;
    color: var(--text-3); border-radius: var(--radius-field);
  }
}
/* Avatar hero is a phone-only affordance — keep the desktop identity cell
   (inline dot + name) exactly as it was. */
.att-av { display: none; }

/* ══ CLEAN MINIMAL — iOS-native forms inside the task-detail sheet ══════ */
@media (max-width: 640px) {
  /* Inside the phone task-detail bottom-sheet the quick-add rows and the
     comment composer follow the iOS pattern: full-width fields + a full-
     width, centered primary action with a comfortable 46–48px tap target.
     Scoped to .side-panel--sheet so desktop + other panels are untouched. */

  /* Checklist / subtask add: field on top, full-width primary below. */
  .side-panel--sheet .sp-checklist-add {
    flex-direction: column; align-items: stretch; gap: var(--sp-2); margin-top: var(--sp-2);
  }
  .side-panel--sheet .sp-checklist-add > input {
    width: 100%; min-height: 46px; border-radius: var(--radius-field); font-size: 1rem;
  }
  .side-panel--sheet .sp-checklist-add > .btn {
    width: 100%; min-height: 46px; border-radius: var(--radius-field);
    justify-content: center; font-size: .95rem; font-weight: 500;
  }

  /* Reminders: presets as comfortable wrapped chips; the custom row stacks
     into full-width fields + a full-width "ذكّرني" primary. */
  .side-panel--sheet .sp-rem-presets { gap: var(--sp-2); }
  .side-panel--sheet .sp-rem-presets > .btn { min-height: 40px; border-radius: var(--radius-field); }
  .side-panel--sheet .sp-rem-custom {
    flex-direction: column; align-items: stretch; gap: var(--sp-2);
  }
  .side-panel--sheet .sp-rem-custom .input,
  .side-panel--sheet .sp-rem-custom > input {
    width: 100%; min-width: 0; min-height: 46px; border-radius: var(--radius-field); font-size: 1rem;
  }
  .side-panel--sheet .sp-rem-custom > .btn {
    width: 100%; min-height: 46px; border-radius: var(--radius-field);
    justify-content: center; font-size: .95rem; font-weight: 500;
  }

  /* Comment composer: drop the composer avatar for a full-width roomy
     textarea, and turn the small end-aligned Send into a full-width,
     centered primary (the "إرسال عريض ومتوسط" the owner asked for). */
  .side-panel--sheet .sp-comment-add { gap: var(--sp-2); margin-top: var(--sp-3); }
  .side-panel--sheet .sp-comment-add > .avatar { display: none; }
  .side-panel--sheet .sp-comment-add > textarea {
    width: 100%; min-height: 92px; border-radius: var(--radius-field); font-size: 1rem;
  }
  .side-panel--sheet .sp-comment-add + div > .btn {
    width: 100%; min-height: 48px; border-radius: var(--radius-field);
    justify-content: center; font-size: 1rem; font-weight: 500;
  }
}

/* ── CRM App Switcher ─────────────────────────────────────────────────── */
.ws-switch { position: relative; display: inline-flex; }
.ws-switch-btn { position: static; display: inline-flex; align-items: center; justify-content: center; }
/* Sized to match its siblings — .sidebar-quick-btn (32px/--radius-sm) and
   .topbar-quick-btn (36px/--radius-sm) — so it reads as one row. */
.sidebar-switch .ws-switch-btn { width: 32px; height: 32px; border-radius: var(--radius-sm); }
.topbar-switch .ws-switch-btn { width: 36px; height: 36px; border-radius: var(--radius-sm); }
.ws-switch-menu {
  position: absolute; inset: auto; top: calc(100% + 6px); inset-inline-end: 0;
  z-index: 60; min-width: 200px; padding: 6px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: 12px; box-shadow: var(--shadow-2);
  transform: translateY(-4px); opacity: 0; visibility: hidden;
  transition: opacity var(--dur-1, .12s) var(--ease-out, ease), transform var(--dur-1, .12s) var(--ease-out, ease), visibility var(--dur-1, .12s);
}
.ws-switch.open .ws-switch-menu { opacity: 1; visibility: visible; transform: translateY(0); }
/* On desktop the switcher is a wide, select-like trigger — its menu matches
   the trigger's width and aligns exactly under it (both edges), so it never
   hugs one side or overflow the title. (Collapsed sidebar + mobile topbar keep
   the compact right-aligned popover.) */
.shell:not(.sidebar-collapsed) .sidebar-switch .ws-switch-menu { inset-inline: 0; min-width: 0; }
.ws-switch-head { padding: 6px 8px 8px; font-size: 11px; font-weight: 600; color: var(--text-3); }
.switch-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 10px; border: 0; border-radius: var(--radius-sm); background: transparent;
  color: var(--text); font: inherit; text-align: start; cursor: pointer;
}
.switch-item:hover { background: var(--bg-3); }
.switch-item.is-current { background: var(--primary-50); color: var(--primary-700); }
.switch-item-ic { display: inline-flex; }
.switch-item-label { flex: 1; }
.switch-item-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--primary-600); }

/* ── CRM Contacts — bulk-selection bar (Task 2, 2026-07-27
   crm-contacts-page-enrich) ──────────────────────────────────────────────
   Sits INSIDE the section flow (sticky, not the floating fixed pill
   admin/tasks-page.js uses) — quiet, one-line, Royal Pixel: a neutral
   card surface with a brand-tinted count, never a loud banner. Shown only
   when ≥1 row is selected (crm-contacts.js's bulkBarSection()). The
   checkbox column itself reuses the existing `.bulk-select-cell input`
   accent-color rule (admin/tasks-page.js's bulk selection, elsewhere in
   this file), so no new checkbox styling is needed here. */
.crm-bulk-bar {
  position: sticky; top: 0; z-index: 5;
  display: flex; align-items: center; justify-content: space-between;
  gap: .75rem; flex-wrap: wrap;
  padding: .55rem .9rem;
  margin-bottom: .6rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-1, 0 1px 2px rgba(0,0,0,.04));
}
.crm-bulk-bar-count {
  font-size: .85rem; font-weight: 600; color: var(--brand);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.crm-bulk-bar-actions { display: inline-flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.crm-bulk-bar-clear {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 50%;
  background: transparent; border: 1px solid transparent;
  color: var(--text-3); cursor: pointer;
}
.crm-bulk-bar-clear:hover { background: var(--bg-3); color: var(--text); }
/* Bulk selection is a desktop-table affordance — the checkbox column adds
   noise to the phone card layout without a table to select IN, so it's
   dropped there (same call as tasks-page.js keeping selection out of its
   own mobile view). */
@media (max-width: 640px) {
  .table.as-cards td.bulk-select-cell, .table.as-cards th.bulk-select-cell { display: none; }
}

/* ── CRM Contacts — list toolbar, paging footer, sortable headers (Wave A
   Task 6, 2026-07-30 crm-contacts-wave-a plan) ──────────────────────────
   Search box (+ #crm-ct-filters-mount, Task 7's filter builder) above the
   bulk bar; a quiet paging footer below the table, same card-surface
   language as .crm-bulk-bar above (never a loud banner). Every container
   resets position/inset/transform/z-index per this repo's CSS convention —
   a same-named global rule elsewhere in this file could otherwise leak a
   structural property in. */
.crm-ct-toolbar {
  position: static; inset: auto; transform: none; z-index: auto;
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  margin-bottom: .6rem;
}
.crm-ct-search { position: relative; flex: 1 1 260px; min-width: 200px; }
.crm-ct-search .lucide-ic {
  position: absolute; top: 50%; inset-inline-start: .7rem;
  transform: translateY(-50%);
  width: 1rem; height: 1rem;
  color: var(--icon-ink); pointer-events: none;
}
.crm-ct-search input { width: 100%; padding-inline-start: 2.1rem; }
.crm-ct-paging {
  position: static; inset: auto; transform: none; z-index: auto;
  display: flex; align-items: center; justify-content: space-between;
  gap: .75rem; flex-wrap: wrap;
  margin-top: .6rem;
  padding: .5rem .1rem;
}
.crm-ct-paging-label {
  font-size: .82rem; color: var(--text-3);
  font-variant-numeric: tabular-nums;
}
.crm-ct-paging-actions { display: inline-flex; align-items: center; gap: .4rem; }
/* The "select all N matching" escape hatch — a quiet inline banner between
   the bulk bar and the table, shown only once something's selected on a
   page smaller than the total match count. */
.crm-ct-selectall-hint {
  position: static; inset: auto; transform: none; z-index: auto;
  display: flex; align-items: center; justify-content: space-between;
  gap: .6rem; flex-wrap: wrap;
  padding: .5rem .9rem;
  margin-bottom: .6rem;
  background: var(--accent-soft);
  border: 1px solid var(--border-2);
  border-radius: var(--radius-card);
  font-size: .82rem; color: var(--text-2);
}
/* The failed-fetch state (whole-branch review IMPORTANT fix — refresh()
   used to bail with no render() at all on a failed primary fetch, leaving
   the mount permanently blank). Reuses .table-empty's own layout (a single
   spanning <td>) and just adds a message + retry stacked underneath. */
.crm-ct-load-error-msg { color: var(--danger); margin-bottom: .5rem; }
/* Empty list body states (Task 2, 2026-08-14 contacts-polish plan) — the
   spanning <td>'s designed content, two variants selected by crm-contacts.js
   own emptyStateHtml(hasActiveQuery). A quiet center stack: no illustration
   (that's the page-level .empty-state's job elsewhere), just a small icon
   circle + copy + actions sized for sitting inside a table row. */
.crm-ct-empty {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: .5rem;
  padding-block: 3rem;
}
.crm-ct-empty-icon {
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  color: var(--icon-ink);
  margin-bottom: .3rem;
}
.crm-ct-empty-title { font: 600 .95rem/1.45 var(--font-display); color: var(--text); }
.crm-ct-empty-hint { font-size: .82rem; line-height: 1.45; color: var(--text-2); max-width: 32ch; }
.crm-ct-empty-actions { display: flex; align-items: center; gap: .6rem; margin-top: .4rem; flex-wrap: wrap; justify-content: center; }
/* Loading skeleton — crm-contacts.js's skeletonRowsHtml(), shown by
   refresh() ONLY on the very first load. Reuses the SAME pt-shimmer
   keyframes/gradient idiom as .topbar-title:empty above, scoped to its own
   block so it can be a staggered per-cell width instead of one fixed bar. */
.crm-ct-skel-row td { padding-block: .55rem; }
.crm-ct-skel-block {
  height: .9rem;
  border-radius: var(--radius-sm);
  background: linear-gradient(90deg, var(--bg-3) 25%, var(--bg-2) 45%, var(--bg-3) 65%);
  background-size: 300% 100%;
  animation: pt-shimmer 1.4s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .crm-ct-skel-block { animation: none; background: var(--bg-3); }
}
/* Sortable column header — a plain <th> (role="button" tabindex="0" +
   Enter/Space wired in crm-contacts.js), so cursor + a subtle active tint
   are the only visual affordances beyond the direction chevron. Task 4
   (2026-08-14 contacts-polish plan) adds an AT-REST affordance: an inactive
   column now always renders a chevrons-up-down glyph (crm-contacts.js), and
   it sits dimmed (opacity .4) until hover/focus so the header row stays
   quiet — brightening to a full hint only on interaction, same idiom as
   every other icon-affordance-on-hover pattern in this file. */
.crm-ct-sortable { cursor: pointer; user-select: none; white-space: nowrap; }
.crm-ct-sortable span, .crm-ct-sortable .lucide-ic { display: inline-flex; vertical-align: middle; }
.crm-ct-sortable .lucide-ic { width: .85rem; height: .85rem; margin-inline-start: 2px; color: var(--icon-ink); opacity: .4; }
/* Active column: existing brand tint, PLUS opacity: 1 — without this the
   base rule's opacity: .4 above would still apply (it's a different
   property than `color`, so the more specific active selector winning on
   `color` alone does not also win on `opacity`) and dim the current sort
   direction's own chevron, which defeats the point of a brand-tinted
   indicator. */
.crm-ct-sortable.is-active, .crm-ct-sortable.is-active .lucide-ic { color: var(--brand); opacity: 1; }
.crm-ct-sortable:hover { color: var(--text); }
.crm-ct-sortable:hover .lucide-ic, .crm-ct-sortable:focus-visible .lucide-ic { opacity: 1; }
.crm-ct-sortable:focus-visible { outline: 2px solid var(--primary-600); outline-offset: -2px; }

/* ── Contacts condition builder (Wave A Task 7, 2026-07-30
   crm-contacts-wave-a plan) — the HighLevel-style filter popover, mounted
   into #crm-ct-filters-mount next to the search box above. Same portaled-
   to-<body>/fixed-positioned/outside-click+Escape-close mechanics as
   crm-chat.js's openTagPicker/openSaveViewPopover (see
   crm-contact-filters.js) — REQUIRED, not just stylistic: the mount point
   sits a few rows above a `.table-scroll` ancestor, and a non-portaled
   popover would get clipped by that overflow container. Every non-fixed
   container below resets position/inset/transform/z-index per this repo's
   CSS convention (a same-named global rule elsewhere in this file could
   otherwise leak a structural property in). */
.crm-filters-btn {
  position: static; inset: auto; transform: none; z-index: auto;
  appearance: none; display: inline-flex; align-items: center; gap: 5px;
  border: 1px dashed var(--border-2); background: transparent; cursor: pointer;
  padding: 5px 12px; border-radius: var(--radius-pill);
  font: inherit; font-size: 12.5px; font-weight: 600; color: var(--text-2); white-space: nowrap;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.crm-filters-btn:hover { color: var(--primary-700); border-color: var(--primary-300); background: var(--accent-soft); }
.crm-filters-btn.is-active { border-style: solid; border-color: var(--primary-300); background: var(--accent-soft); color: var(--primary-700); }
.crm-filters-btn .lucide-ic { width: 13px; height: 13px; }

.crm-filters-popover {
  position: fixed; z-index: 60; min-width: 360px; max-width: 420px;
  max-height: min(72vh, 560px); overflow-y: auto;
  padding: 10px; background: var(--bg-2); border: 1px solid var(--border-2);
  border-radius: var(--radius-field, 12px); box-shadow: var(--shadow-2);
  animation: fade-in .14s ease;
}
.crm-filters-popover-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.crm-filters-popover-title { font-size: 12px; font-weight: 700; color: var(--text-2); }
.crm-filters-popover-close {
  appearance: none; border: 0; background: transparent; cursor: pointer; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 50%; color: var(--text-3);
  transition: color .15s ease, background .15s ease;
}
.crm-filters-popover-close:hover { color: var(--text); background: var(--bg-3); }
.crm-filters-popover-close .lucide-ic { width: 13px; height: 13px; }

.crm-filters-dropped-hint {
  position: static; inset: auto; transform: none; z-index: auto;
  display: flex; align-items: center; gap: 6px;
  padding: 6px 8px; margin-bottom: 8px;
  background: var(--warn-bg); border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent);
  border-radius: var(--radius-sm);
  font-size: 11px; color: var(--warn);
}
.crm-filters-dropped-hint .lucide-ic { width: 13px; height: 13px; flex: none; }

.crm-filters-match { margin-bottom: 8px; }
.crm-filters-match .input { height: 32px; font-size: 12.5px; padding-block: 0; }

.crm-filters-rows {
  position: static; inset: auto; transform: none; z-index: auto;
  display: flex; flex-direction: column; gap: 8px; margin-bottom: 8px;
}
.crm-filters-row {
  position: static; inset: auto; transform: none; z-index: auto;
  padding: 8px; background: var(--card-2); border: 1px solid var(--border-2);
  border-radius: var(--radius-sm);
}
.crm-filters-row-top { display: grid; grid-template-columns: 1fr 1fr auto; gap: 6px; align-items: center; }
.crm-filters-row-top .input { height: 30px; font-size: 12px; padding-block: 0; }
.crm-filters-row-del {
  appearance: none; cursor: pointer; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: 50%;
  background: transparent; border: 1px solid var(--border-2); color: var(--text-3);
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.crm-filters-row-del:hover { color: var(--danger); border-color: var(--danger); background: var(--danger-bg); }
.crm-filters-row-del .lucide-ic { width: 12px; height: 12px; }
.crm-filters-row-value { margin-top: 6px; }
.crm-filters-row-value .input { width: 100%; height: 30px; font-size: 12px; }

.crm-filters-daterange { display: flex; align-items: center; gap: 6px; }
.crm-filters-daterange .input { flex: 1 1 0; min-width: 0; }
.crm-filters-daterange-sep { color: var(--text-3); flex: none; }

/* The multi control's own client-side filter (review finding, 2026-07-30 —
   only shown past MULTI_SEARCH_THRESHOLD options) — same compact-search
   visual language as .crm-tag-picker-search, sized down one notch since it
   sits inside an already-narrow row rather than a full-width picker. */
.crm-filters-multi-search { width: 100%; height: 28px; margin-bottom: 4px; padding: 4px 8px; font-size: 11.5px; }
.crm-filters-multi-list {
  max-height: 160px; overflow-y: auto;
  display: flex; flex-direction: column; gap: 2px;
  padding: 4px; border: 1px solid var(--border-2); border-radius: var(--radius-sm);
}
.crm-filters-multi-row {
  display: flex; align-items: center; gap: 6px;
  padding: 4px 6px; border-radius: var(--radius-sm); cursor: pointer;
  font-size: 12px; color: var(--text-2);
}
.crm-filters-multi-row:hover { background: var(--accent-soft); }
/* Review-fix (Wave A Task 8 round 2) — matches the repo convention
   (.bulk-select-cell input[type="checkbox"], duwam.css:4385); the checked
   fill reads brand-blue instead of the browser default. The UNCHECKED state,
   which this note used to record as an accepted app-wide defect, is now
   handled once at the root by `color-scheme` (see the :root block). */
.crm-filters-value-multi { accent-color: var(--brand); cursor: pointer; }
.crm-filters-multi-empty { padding: 8px; text-align: center; color: var(--text-3); font-size: 11.5px; }
/* Honest-truncation hint (review finding, 2026-07-30) — the `accounts`
   source is server-paginated (lib/pagination.js's MAX_LIMIT); this is the
   crm_filter_dropped hint's same quiet-disclosure visual language reused
   for "the list you're picking from isn't everything" rather than "a
   condition was dropped" — same warn tokens, smaller/lighter since it's
   informational, not a data-loss warning. */
.crm-filters-multi-truncated {
  margin-top: 4px; padding: 4px 6px;
  font-size: 10.5px; color: var(--text-3); line-height: 1.4;
}

.crm-filters-add {
  position: static; inset: auto; transform: none; z-index: auto;
  display: inline-flex; align-items: center; gap: 5px; width: 100%;
  appearance: none; border: 1px dashed var(--border-2); background: transparent; cursor: pointer;
  padding: 7px 8px; border-radius: var(--radius-sm); justify-content: center;
  font: inherit; font-size: 12px; font-weight: 600; color: var(--primary-700);
  margin-bottom: 8px;
}
.crm-filters-add:hover { background: var(--accent-soft); }
.crm-filters-add .lucide-ic { width: 12px; height: 12px; }

.crm-filters-actions {
  position: static; inset: auto; transform: none; z-index: auto;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
}

/* ── Contacts column chooser (Wave B Task 3, 2026-07-30
   crm-contacts-wave-b plan) — the «الأعمدة (N/M)» trigger sits beside the
   filters trigger above, so its resting state matches it exactly
   (.crm-ct-columns-btn mirrors .crm-filters-btn); the popover reuses
   .crm-filters-popover's own portaled/fixed/clamped mechanics (see
   crm-contact-columns.js's positionPopover, copied from crm-contact-
   filters.js's). Every non-fixed container resets position/inset/
   transform/z-index per this repo's CSS convention (a same-named global
   rule elsewhere in this file could otherwise leak a structural property
   in). */
.crm-ct-columns-btn {
  position: static; inset: auto; transform: none; z-index: auto;
  appearance: none; display: inline-flex; align-items: center; gap: 5px;
  border: 1px dashed var(--border-2); background: transparent; cursor: pointer;
  padding: 5px 12px; border-radius: var(--radius-pill);
  font: inherit; font-size: 12.5px; font-weight: 600; color: var(--text-2); white-space: nowrap;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.crm-ct-columns-btn:hover { color: var(--primary-700); border-color: var(--primary-300); background: var(--accent-soft); }
.crm-ct-columns-btn[aria-expanded="true"] { border-style: solid; border-color: var(--primary-300); background: var(--accent-soft); color: var(--primary-700); }
.crm-ct-columns-btn .lucide-ic { width: 13px; height: 13px; }

.crm-columns-popover {
  position: fixed; z-index: 60; min-width: 260px; max-width: 340px;
  max-height: min(72vh, 480px); overflow-y: auto;
  padding: 10px; background: var(--bg-2); border: 1px solid var(--border-2);
  border-radius: var(--radius-field, 12px); box-shadow: var(--shadow-2);
  animation: fade-in .14s ease;
}
.crm-columns-popover-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.crm-columns-popover-title { font-size: 12px; font-weight: 700; color: var(--text-2); }
.crm-columns-popover-close {
  appearance: none; border: 0; background: transparent; cursor: pointer; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 50%; color: var(--text-3);
  transition: color .15s ease, background .15s ease;
}
.crm-columns-popover-close:hover { color: var(--text); background: var(--bg-3); }
.crm-columns-popover-close .lucide-ic { width: 13px; height: 13px; }

.crm-columns-rows {
  position: static; inset: auto; transform: none; z-index: auto;
  display: flex; flex-direction: column; gap: 2px; margin-bottom: 8px;
}
.crm-columns-row {
  position: static; inset: auto; transform: none; z-index: auto;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 4px 6px; border-radius: var(--radius-sm);
}
.crm-columns-row:hover { background: var(--accent-soft); }
.crm-columns-row-label {
  display: flex; align-items: center; gap: 7px; min-width: 0;
  font-size: 12.5px; color: var(--text-2); cursor: pointer;
}
.crm-columns-row-label span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Matches the repo convention (.bulk-select-cell input[type="checkbox"],
   duwam.css:4385) — the checked fill reads brand-blue instead of the
   browser default, including the locked `name` row's disabled-but-checked
   state. */
.crm-columns-check { accent-color: var(--brand); cursor: pointer; flex: none; }
.crm-columns-check:disabled { cursor: not-allowed; }
.crm-columns-reorder { display: inline-flex; align-items: center; gap: 2px; flex: none; }
.crm-columns-move {
  appearance: none; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 50%;
  background: transparent; border: 0; color: var(--text-3);
  transition: background .15s ease, color .15s ease;
}
.crm-columns-move:hover:not(:disabled) { color: var(--text); background: var(--bg-3); }
.crm-columns-move:disabled { opacity: .35; cursor: not-allowed; }
.crm-columns-move .lucide-ic { width: 12px; height: 12px; }

.crm-columns-actions {
  position: static; inset: auto; transform: none; z-index: auto;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
}

/* ── Contact custom fields — settings admin panel (Wave C Task 6,
   2026-07-30 crm-contacts-wave-c plan) ───────────────────────────────────
   Mirrors .tag-cat-list/.tag-cat-row's bordered-list shape (settings-page.js
   renderCrmTags) rather than .canned-list's card shape — a row here carries
   its own reorder cluster, so the flatter tag-catalog layout reads cleaner
   with one more interactive element per row. */
.crm-cf-list {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  overflow: hidden; background: var(--card);
}
.crm-cf-row {
  position: static; inset: auto; transform: none; z-index: auto;
  display: flex; align-items: center; gap: .7rem;
  padding: .6rem .8rem; border-bottom: 1px solid var(--border);
}
.crm-cf-row:last-child { border-bottom: none; }
.crm-cf-row:hover { background: var(--bg-2); }
.crm-cf-row.is-archived { opacity: .68; }
.crm-cf-row-reorder { display: inline-flex; align-items: center; gap: 2px; flex: none; }
.crm-cf-move {
  appearance: none; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; border-radius: 50%;
  background: transparent; border: 0; color: var(--text-3);
  transition: background .15s ease, color .15s ease;
}
.crm-cf-move:hover:not(:disabled) { color: var(--text); background: var(--bg-3); }
.crm-cf-move:disabled { opacity: .35; cursor: not-allowed; }
.crm-cf-move .lucide-ic { width: 12px; height: 12px; }
.crm-cf-row-main { flex: 1 1 auto; min-width: 0; }
.crm-cf-row-label {
  display: flex; align-items: center; gap: .5rem;
  font-weight: 600; font-size: .9rem; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.crm-cf-archived-chip { font-size: .68rem; }
.crm-cf-row-type { font-size: .76rem; color: var(--text-3); margin-top: .1rem; }
.crm-cf-row-actions { display: flex; align-items: center; gap: .25rem; flex: none; }
.crm-cf-empty { padding: 2rem; text-align: center; color: var(--text-3); }

/* Options editor (UI.modal) — a compact bordered list, add row below it. */
.crm-cf-opt-list {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  max-height: 220px; overflow-y: auto; margin-bottom: .3rem;
}
.crm-cf-opt-row {
  display: flex; align-items: center; justify-content: space-between; gap: .5rem;
  padding: .4rem .6rem; border-bottom: 1px solid var(--border);
  font-size: .85rem; color: var(--text);
}
.crm-cf-opt-row:last-child { border-bottom: none; }

/* ── Contacts CSV import wizard + export button (Wave C Task 6,
   2026-07-30 crm-contacts-wave-c plan) ───────────────────────────────────
   Toolbar cluster sits beside the filters/columns triggers (same row,
   crm-contacts.js's .crm-ct-toolbar); the wizard itself is a UI.modal whose
   `.modal` innerHTML is swapped per step (crm-contact-import.js's own
   paint()), so only the parts specific to THIS wizard need rules here —
   h3/.modal-actions/.table already come from the shared modal styles. */
.crm-ct-import-mount { display: flex; align-items: center; gap: 8px; }
.crm-ct-import-btn, .crm-ct-export-btn {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12.5px; padding: 5px 12px;
}
.crm-ct-import-btn .lucide-ic, .crm-ct-export-btn .lucide-ic { width: 13px; height: 13px; }

.crm-import-choose {
  position: relative;
  display: inline-flex; align-items: center; gap: 6px;
  cursor: pointer; margin-bottom: .3rem;
}
.crm-import-file-err { color: var(--danger); min-height: 1.2em; margin: .3rem 0 .6rem; }
.crm-import-map-rows {
  display: flex; flex-direction: column; gap: .5rem;
  max-height: 320px; overflow-y: auto; margin-bottom: .3rem;
}
.crm-import-map-row {
  display: grid; grid-template-columns: 1fr 1fr; gap: .6rem; align-items: center;
}
.crm-import-map-label { font-size: .85rem; color: var(--text-2); }
.crm-import-summary { font-size: .92rem; font-weight: 600; color: var(--text); margin: .4rem 0 .8rem; }
.crm-import-capped {
  padding: .5rem .7rem; margin-bottom: .7rem;
  background: var(--warn-bg); border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent);
  border-radius: var(--radius-sm); font-size: .8rem; color: var(--warn);
}
.crm-import-skipped { margin-top: .5rem; }
.crm-import-skipped-title { font-size: .78rem; font-weight: 600; color: var(--text-3); margin-bottom: .3rem; }
.crm-import-skipped-list {
  max-height: 180px; overflow-y: auto; margin: 0; padding-inline-start: 1.1rem;
  font-size: .8rem; color: var(--text-2); line-height: 1.6;
}

/* ── Nameless-contact rows + inline rename (Wave A Task 8, 2026-07-30
   crm-contacts-wave-a plan) — the owner's screenshot-of-phone-numbers
   complaint, closed: a contact WhatsApp never named renders as a plain
   muted number (never dressed up as a name), with a quiet «أضف اسماً»
   affordance that only earns full contrast on row hover/focus — same
   reveal-on-hover contract as .row-actions above, but a plain inline text
   button (not an absolutely-positioned icon cluster) since it sits inline
   after the number rather than pinned to a table edge, so no RTL transform
   flip is needed. `dir="ltr"` on the number itself (crm-contacts.js's
   contactNameCellHtml) is what keeps the digits reading left-to-right
   inside an otherwise RTL row — this block is purely visual. */
.crm-ct-nameless {
  display: inline-block;
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
}
.crm-ct-rename {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  margin-inline-start: .5rem; padding: 0;
  font: inherit; font-size: .78rem; font-weight: 600;
  color: var(--text-3);
  opacity: 0; pointer-events: none;
  transition: opacity 150ms ease, color 150ms ease;
}
tr:hover .crm-ct-rename, tr:focus-within .crm-ct-rename {
  opacity: 1; pointer-events: auto;
}
.crm-ct-rename:hover, .crm-ct-rename:focus-visible { color: var(--primary-600); }
.crm-ct-rename:focus-visible {
  opacity: 1; pointer-events: auto;
  outline: 2px solid var(--primary-600); outline-offset: 2px;
}
/* Touch devices never trigger :hover — keep the affordance visible at low
   contrast rather than requiring a hover that can never happen (same
   fallback .row-actions uses above). */
@media (hover: none) {
  .crm-ct-rename { opacity: .7; pointer-events: auto; }
}

/* ── Bulk message composer (Task 3, contacts-page-enrich) ────────────────
   Opened via UI.modal() from the bulk bar's «إرسال رسالة» — a plain
   .modal, so only the parts specific to this composer (textarea sizing,
   the {{name}} insert chip, the preview box, the result line) need their
   own rules; h3/.field/.modal-actions all reuse the shared modal styles
   the create-contact form (openCreate) already relies on. */
.crm-bulk-msg-textarea { width: 100%; min-height: 130px; resize: vertical; }
.crm-bulk-msg-insert { margin-top: .6rem; }
.crm-bulk-msg-preview {
  margin-top: .9rem;
  padding: .6rem .8rem;
  background: var(--card-2);
  border: 1px solid var(--border-2);
  border-radius: var(--radius-sm);
}
.crm-bulk-msg-preview-label {
  font-size: .72rem; font-weight: 600; color: var(--text-3);
  text-transform: uppercase; letter-spacing: .02em;
  margin-bottom: .3rem;
}
.crm-bulk-msg-preview-text { font-size: .88rem; color: var(--text); white-space: pre-wrap; word-break: break-word; }
/* Hidden until a send actually resolves (empty textContent) — no stray
   box taking up space while the composer is still being written. */
.crm-bulk-msg-result { margin-top: .7rem; font-size: .85rem; font-weight: 600; color: var(--brand); }
.crm-bulk-msg-result:empty { display: none; }

/* ── CRM Activity Timeline ────────────────────────────────────────────── */
/* Shared by the per-record widget (crm-timeline.js, mounted in the contact/
   account/deal detail drawer) and the "My activities" section
   (crm-activities.js) — the list stands alone in the drawer and sits inside
   a plain .card in the section, so it carries no outer border/background
   of its own. */
.crm-tl-compose {
  position: static;
  display: flex; align-items: flex-end; gap: .6rem;
  margin: 0 0 .9rem;
}
.crm-tl-input { flex: 1 1 auto; resize: vertical; min-height: 40px; }
.crm-tl-add { flex: 0 0 auto; }

.crm-tl-list { list-style: none; margin: 0; padding: 0; }
.crm-tl-item {
  position: static;
  display: flex; align-items: flex-start; gap: .65rem;
  padding: .6rem 0;
  border-bottom: 1px solid var(--border);
}
.crm-tl-item:last-child { border-bottom: 0; }
.crm-tl-ic {
  flex: 0 0 auto; margin-top: 2px;
  width: 28px; height: 28px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent-soft); color: var(--brand);
  border-radius: var(--radius-sm);
}
.crm-tl-ic .lucide-ic { width: 15px; height: 15px; stroke-width: 2; }
.crm-tl-body { flex: 1 1 auto; min-width: 0; }
.crm-tl-text {
  display: flex; align-items: center; gap: .5rem;
  font-size: .88rem; color: var(--text); line-height: 1.5;
  word-break: break-word;
}
.crm-tl-meta { font-size: .76rem; color: var(--text-3); margin-top: 2px; }
.crm-tl-empty {
  list-style: none;
  padding: 1.1rem .5rem; text-align: center;
  font-size: .85rem; color: var(--text-3);
}

/* Done task: dim + strike the body text, keep the checkbox + meta legible. */
.is-done .crm-tl-text { color: var(--text-3); text-decoration: line-through; }
.crm-tl-done { flex: 0 0 auto; }

/* ── CRM Reports (Wave 3.3a Task 3) ──────────────────────────────────────
   From/to range control (plain native date inputs — date-i18n.js routes
   them through the standard calendar overlay automatically) + a CSS-only
   per-day trend chart. No chart library: two thin bars per day (inbound
   volume, neutral; answered, accent) scaled to the range's own max. */
.crm-rep-range { display: flex; align-items: flex-end; gap: .6rem; flex-wrap: wrap; }
.crm-rep-range-field { display: flex; flex-direction: column; gap: .3rem; font-size: .75rem; color: var(--text-2); }
.crm-rep-range-field input { min-width: 148px; }
.crm-rep-trend { display: flex; align-items: flex-end; gap: .5rem; min-height: 120px; overflow-x: auto; padding: 0 .1rem; }
.crm-rep-trend-col { display: flex; flex-direction: column; align-items: center; gap: .35rem; min-width: 26px; flex: 1 0 auto; }
.crm-rep-trend-bars { display: flex; align-items: flex-end; justify-content: center; gap: 3px; height: 92px; width: 100%; }
.crm-rep-trend-bar { width: 8px; min-height: 2px; border-radius: 2px 2px 0 0; transition: height var(--dur-2) var(--ease-out); }
.crm-rep-trend-bar.is-inbound { background: var(--border-2); }
.crm-rep-trend-bar.is-answered { background: var(--accent-grad); }
.crm-rep-trend-day { font-size: .65rem; color: var(--text-3); font-family: ui-monospace, monospace; white-space: nowrap; }
.crm-rep-trend-legend { display: flex; align-items: center; gap: .3rem; font-size: .75rem; color: var(--text-2); }
.crm-rep-legend-dot { width: 8px; height: 8px; border-radius: 2px; display: inline-block; margin-inline-start: .5rem; }
.crm-rep-legend-dot:first-child { margin-inline-start: 0; }
.crm-rep-legend-dot.is-inbound { background: var(--border-2); }
.crm-rep-legend-dot.is-answered { background: var(--accent-grad); }

/* ── CRM Reports — source-comparison panel (lead-capture chapter, Task 12)
   "which source brings buyers?". Bars are share-of-total width, same idiom
   as the trend chart above. The unattributed row (.is-unattributed) is
   deliberately warn-toned rather than accent-toned so it reads as "we
   genuinely don't know", never as a strong source — see crm-reports.js's
   sourceReportHtml for why that row is always rendered, never omitted. */
.crm-src-note { font-size: .8rem; line-height: 1.5; color: var(--text-2); margin: .2rem 0 0; max-width: 62ch; }
.crm-src-list { display: flex; flex-direction: column; gap: .6rem; margin-top: .9rem; }
.crm-src-row { display: grid; grid-template-columns: minmax(110px, 1fr) minmax(70px, 2fr) auto; align-items: center; gap: .7rem; }
.crm-src-label { font-size: .85rem; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crm-src-bar-track { height: 8px; border-radius: 4px; background: var(--bg-3); overflow: hidden; }
.crm-src-bar { height: 100%; border-radius: 4px; background: var(--accent-grad); transition: width var(--dur-2) var(--ease-out); }
.crm-src-count { font-size: .8rem; color: var(--text-2); white-space: nowrap; font-family: ui-monospace, monospace; }
.crm-src-pct { margin-inline-start: .3rem; color: var(--text-3); }
.crm-src-row.is-unattributed .crm-src-label { color: var(--warn); font-weight: 600; }
.crm-src-row.is-unattributed .crm-src-bar { background: var(--warn); }

/* ── Switcher (extended) / quick-action (icon-only) ─────────────────────── */
.quick-btn-label { font-size: 12px; font-weight: 500; white-space: nowrap; color: var(--text-2); }
.ws-switch-caret { display: inline-flex; color: var(--text-3); opacity: .7; }
.ws-switch-caret .lucide-ic { width: 16px; height: 16px; }
.shell.sidebar-collapsed .quick-btn-label,
.shell.sidebar-collapsed .ws-switch-caret { display: none; }
.topbar-quick .quick-btn-label, .topbar-switch .quick-btn-label,
.topbar-quick .ws-switch-caret, .topbar-switch .ws-switch-caret { display: none; }

/* ── CRM service-desk inbox (three-pane: list | thread | context) ───────── */
/* Height: the SAME token formula .tasks-board already uses elsewhere in
   this file (--topbar-h + the — now locally-shrunk, see main:has(.crm-chat)
   below — page head + .main's own 2.4rem block padding), not a
   disconnected magic number. `100vh - 220px` used to guess a taller chrome
   stack than this screen actually has, leaving the panel roughly two rows
   shorter than the room really available (chat-density task, 2026-08-10). */
/* The substrate. The three panes below float on it and the 8px gutter is
   what separates them — so no pane draws a divider of its own (guarded by
   test/workspaceSurface.guard.test.js). The outer hairline is gone with the
   dividers: in light the grey block defines its own edge, and in dark the
   substrate IS the page canvas, so a frame would draw a box around nothing.
   Padding and gap come out of the fixed height via the global
   `box-sizing: border-box`, so the panes lose 16px of height, not the
   viewport. */
.crm-chat { display: grid; grid-template-columns: 260px 1fr 340px; gap: 8px; padding: 8px;
  height: calc(100vh - var(--topbar-h) - var(--page-head-h, 0px) - 2.4rem); min-height: 420px;
  border-radius: var(--radius-lg); overflow: hidden; background: var(--workspace-canvas); }
/* One pane geometry for all three, stated once. --radius-card (12px) nests
   inside the substrate's --radius-lg (16px): an inner corner must be
   tighter than the outer one it sits in, or the two curves visibly fight. */
.crm-chat-list, .crm-chat-pane, .crm-chat-context {
  background: var(--workspace-pane); border: 1px solid var(--border); border-radius: var(--radius-card);
}
/* Right context pane collapsed to a narrow icon rail — same "shrink, never
   fully vanish" idiom as .shell.sidebar-collapsed, so there's always a
   visible affordance to re-expand it. */
/* 340px, not 300 (Phase 2): the panel now carries grouped custom fields whose
   labels are tenant-authored sentences, and a stacked label+control needs the
   width to avoid wrapping every one of them. */
.crm-chat.is-context-collapsed { grid-template-columns: 260px 1fr 44px; }
/* `min-width: 0` overrides the grid item's default `auto` (min-content)
   sizing — without it a single-column narrow layout (see the max-width:640
   query below) can "blow out" wider than the viewport, since a grid track's
   automatic minimum is based on its widest item's min-content width. */
.crm-chat-list { overflow-y: auto; min-width: 0; }
/* List head — a compact toolbar: the «محادثة جديدة» proactive entry point
   on top, then the /inbox `view` select, then the waiting/recent sort
   toggle. Sticky so it stays visible while the row list scrolls. */
.crm-chat-list-head {
  padding: 10px 12px; position: sticky; top: 0; background: var(--bg-2); z-index: 1;
  display: flex; flex-direction: column; gap: 8px; border-bottom: 1px solid var(--border);
}
/* «محادثة جديدة» — full-width so it reads as the list's primary action. */
.crm-chat-new-conv-btn { width: 100%; justify-content: center; }
.crm-chat-new-conv-btn .lucide-ic { width: 14px; height: 14px; }
/* The contact picker it opens is portaled to <body> (see crm-chat.js
   openContactPicker) rather than nested here, so an 8-second poll's
   paintList() repaint of this sticky head can never yank it out from under
   an in-progress search — same reasoning as .crm-chat-canned-picker below,
   just floated instead of locally absolute since its anchor (this button)
   gets torn down and rebuilt on every list repaint. Fixed positioning
   (viewport-relative) avoids scrollY math entirely. */
.crm-chat-contact-picker {
  position: fixed; z-index: 60; display: flex; flex-direction: column;
  max-height: 320px; overflow: hidden;
  background: var(--bg-2); border: 1px solid var(--border-2);
  border-radius: var(--radius-field, 12px); box-shadow: var(--shadow-2);
  animation: fade-in .14s ease;
}
/* Compact override, same idiom as .crm-chat-view-select/.crm-chat-assign-
   select — the popover search box is a dense affordance, not a form field,
   so it drops .input's default field height/padding. */
.crm-chat-contact-picker-search { width: auto; margin: 8px 8px 4px; }
input.crm-chat-contact-picker-search.input {
  height: 32px; padding: 4px 10px; font-size: 12px; border-radius: var(--radius-sm);
}
/* `min-height: 0` overrides the flex item's default `auto` (min-content)
   floor — without it this column can't shrink below its content height, so
   `overflow-y: auto` never actually kicks in and the popover just grows
   past its own `max-height` instead of scrolling internally. */
.crm-chat-contact-picker-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 6px; }
.crm-chat-contact-picker-empty { padding: 14px; text-align: center; color: var(--text-3); font-size: 12px; }
.crm-chat-contact-picker-row {
  display: flex; flex-direction: column; gap: 2px; width: 100%;
  appearance: none; border: 0; background: transparent; cursor: pointer;
  padding: 7px 10px; border-radius: var(--radius-sm); text-align: start; font: inherit;
  color: var(--text);
}
.crm-chat-contact-picker-row:hover, .crm-chat-contact-picker-row.is-active { background: var(--accent-soft); }
.crm-chat-contact-picker-name { font-size: 12.5px; font-weight: 600; }
.crm-chat-contact-picker-phone { font-size: 11px; color: var(--text-3); font-variant-numeric: tabular-nums; }
.crm-chat-view-select { width: 100%; }
select.crm-chat-view-select.input,
.crm-chat-view-select .uisel-trigger.input {
  height: 32px; padding: 4px 10px; font-size: 12px; border-radius: var(--radius-sm);
}
.crm-chat-sort-toggle { display: flex; padding: 3px; gap: 2px; border-radius: var(--radius-pill); background: var(--card); border: 1px solid var(--border); }
.crm-chat-sort-seg {
  flex: 1; appearance: none; border: 0; background: transparent; cursor: pointer;
  padding: 5px 8px; border-radius: var(--radius-pill);
  font: inherit; font-size: 11px; font-weight: 600; color: var(--text-2); white-space: nowrap;
  transition: background .15s ease, color .15s ease;
}
.crm-chat-sort-seg:hover:not(.is-active) { color: var(--text); }
.crm-chat-sort-seg.is-active { background: var(--accent-soft); color: var(--accent); }
.crm-chat-contact { display: flex; flex-direction: column; gap: 2px; width: 100%; padding: 10px 14px; border: 0; border-inline-start: 3px solid transparent;
  border-bottom: 1px solid var(--border);
  background: transparent; text-align: start; cursor: pointer; color: var(--text); }
.crm-chat-contact:last-child { border-bottom: 0; }
.crm-chat-contact:hover { background: var(--bg-3); }
.crm-chat-contact.is-active { background: var(--accent-soft); border-inline-start-color: var(--primary-600); }
.crm-chat-contact-top { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.crm-chat-contact-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: 500; }
.crm-chat-contact-snippet { font-size: 11px; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Conversation tag chips on an inbox row (Wave 3.1 Task 3) — informational
   only (no remove affordance here; editing lives in the thread header). */
.crm-chat-contact-tags { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 2px; }
.crm-chat-contact-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 2px; }
/* Assignee-initial badge on a contact row (Task 4) — small, muted, never
   competes with the identity dot / status idioms used elsewhere (Royal
   Pixel: identity = dot/initial, not color-coding). */
.crm-chat-badge {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 50%; box-sizing: border-box;
  background: var(--bg-3); color: var(--text-2); border: 1px solid var(--border);
  font-size: 10px; font-weight: 700; line-height: 1;
}
/* Assignee PHOTO badge (Task 6, inbox quick-wins) — same 18px circle as the
   initial fallback above, filled with the assignee's avatar via an inline
   background-image (set by contactRow) instead of a text initial. The
   card-colored ring keeps the small circle legible against a busy photo in
   both themes, same idea as .crm-chat-channel-badge's own border. */
.crm-chat-badge.has-photo {
  background-size: cover; background-position: center; color: transparent;
  border: 1px solid var(--card);
}
/* Absolute last-message time on inbox rows (Task 2) — HH:MM for same-day
   messages, day+month for older. Sits at the trailing edge via margin-inline-
   start:auto; appears after the assignee badge and before the next row element
   (the snippet). Latin digits in both locales (dir="ltr"). */
.crm-chat-contact-time {
  margin-inline-start: auto; font-size: 0.72rem; color: var(--text-3);
  font-family: var(--font-numeric); font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}
/* WhatsApp channel badge on a conversation avatar (Task 3, inbox quick-wins)
   — the inbox row and thread header ONLY (contactAvatarHtml, the shared
   circle the context side panel's avatar-edit button also calls, stays
   badge-free). `.crm-chat-avatar-wrap` is a plain relative inline-flex box
   around the existing `.avatar`; it must never disturb that avatar's own
   flex order ahead of the name/badge/time siblings in `.crm-chat-contact-top`
   (Task 2). Logical inset properties (inset-block-end/inset-inline-start)
   so the badge lands bottom-right in RTL and bottom-left in LTR without a
   dir() branch. `var(--wa)` is the WhatsApp brand color — a permitted
   channel-logo exception to the two-gate color law, not decoration. */
.crm-chat-avatar-wrap { position: relative; display: inline-flex; flex-shrink: 0; }
.crm-chat-channel-badge {
  position: absolute; inset-block-end: -2px; inset-inline-start: -2px;
  width: 14px; height: 14px; border-radius: var(--radius-pill);
  background: var(--wa); border: 1.5px solid var(--card);
  display: flex; align-items: center; justify-content: center;
}
.crm-chat-channel-badge svg { width: 9px; height: 9px; fill: #fff; }
/* Contact avatar (Task 4, 2026-07-28 crm-contact-avatars plan) — reuses the
   shared `.avatar` circle (image-fill path) but stays flex:none so it never
   competes with the name's `flex:1 1 auto` in the row/header it sits in.
   `.sm` is a dedicated small size for the inbox row / contacts table cell —
   no other `.avatar` caller emits an `sm` size class today (UI.avatar's own
   'sm' arg is a no-op there), so scoping the size to `.crm-avatar` can't
   resize any existing avatar elsewhere in the app. `.crm-avatar-empty` is
   the generic-icon variant (a Lucide `user` icon, never initials) — a
   neutral circle via existing tokens, matching the `.avatar-deleted`
   precedent above. Doubled-up class selectors beat `.avatar:not([style*=
   "background"])`'s own specificity so the neutral bg/color reliably win
   over the shared avatar's default accent fill. */
.crm-avatar { flex: none; }
.crm-avatar.sm { width: 28px; height: 28px; }
.crm-avatar.crm-avatar-empty {
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text-3);
}
.crm-avatar-empty .lucide-ic { width: 60%; height: 60%; }

/* Contacts table identity cell (Task 1, 2026-08-14 contacts-polish plan) —
   the avatar + name + optional subline that replaced the standalone
   `td.crm-ct-avatar-cell` (crm-contacts.js's contactNameCellHtml /
   identitySubHtml build the markup this styles). `.crm-ct-identity` is a
   row (avatar, then the stacked text column); `min-width: 0` on both the
   row and the text column is what lets the name/subline ellipsize instead
   of forcing the table wider — a flex item defaults to a content-based
   min-width otherwise, which silently defeats `text-overflow: ellipsis`.
   `.crm-ct-identity-name`/`.crm-ct-identity-sub` are blockified for free by
   being flex children of `.crm-ct-identity-text` (CSS Display's flex-item
   blockification), so `overflow: hidden` on a `<span>` here isn't a no-op
   the way it would be on an ordinary inline element. On mobile (≤640px
   as-cards, duwam.css ~3147) this cell IS `.td-primary` — no extra rule
   needed there, `.crm-ct-identity`'s own `display: flex` already reads as
   the card header row (avatar beside the stacked text), an improvement
   over the OLD standalone avatar `<td>`, which — having neither
   `.td-primary` nor a `data-label` — fell into the generic as-cards `td`
   rule and rendered as its own near-empty flex row floating above the
   name row. */
.crm-ct-identity { display: flex; align-items: center; gap: .6rem; min-width: 0; }
.crm-ct-identity-text { min-width: 0; display: flex; flex-direction: column; }
.crm-ct-identity-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crm-ct-identity-sub { font-size: .75rem; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── Table discipline: sticky header + ellipsis (Task 3, 2026-08-14
   contacts-polish plan) — `.crm-ct` is a NEW hook class added in
   crm-contacts.js's render() on the table's `.card` wrapper; no scoping
   class existed there before (it was a bare `<div class="card">`), and
   this file has no other `.crm-ct` (bare) selector — every other
   `crm-ct-*` class is a distinct, hyphen-suffixed name, so this scope is
   new and cannot collide. The bulk bar (`.crm-bulk-bar`) already sticks
   WITHIN the page; the `<thead>` used to scroll away with the rows
   instead — this closes that inconsistency. `.crm-ct-paging` renders as
   a SIBLING of `.card` (i.e. already OUTSIDE `.card` entirely, not just
   outside `.table-scroll` — verified in crm-contacts.js's render()), so
   it never enters this scroll region; no DOM relocation was needed.

   Desktop-only: `.table.as-cards` (≤640px, duwam.css ~3147) hides
   `<thead>` entirely and stacks rows into cards — a capped-height inner
   scroll region there would just clip cards mid-list for no benefit.
   `min-width: 641px` is the exact complement of that breakpoint, so
   phones are untouched.

   Height budget: `calc(100vh - var(--topbar-h) - 230px)` — topbar
   (`--topbar-h`) plus ~230px covers the page title row, the search/
   filter/columns toolbar, the bulk bar when present, and the paging
   footer BELOW this scroll region — so a long contact list scrolls
   inside its own card instead of pushing paging off-screen. Mirrors the
   existing `.import-sample-wrap` + `.import-sample th` pattern
   (duwam.css ~5905) at table scale instead of a small preview grid.

   Ellipsis: the identity `<td class="td-primary">` gets ONLY a wider
   `max-width` here — no `overflow`/`text-overflow`/`white-space` — cap
   the box and let Task 1's own inner chain (`.crm-ct-identity` →
   `.crm-ct-identity-text`, both `min-width: 0`) do the ellipsis, exactly
   like it already does at any other width; repeating those three
   declarations on the td would double-wrap a already-clipped flex
   layout for no visual effect (the td holds one flex child, no direct
   text of its own for `text-overflow` to act on). Every OTHER data cell
   is plain escaped text with no such inner chain, so the default rule
   below carries the full four-declaration idiom itself. Structural
   `:not()` handles the checkbox (`.bulk-select-cell`) and actions
   (`.cell-actions`) cells; the tag cell (CELL_RENDERERS.tag) renders a
   bare `<td>` with no class of its own, so it is excluded the same way
   `.table-scroll:has(> .table.as-cards)` already reads structure
   elsewhere in this file (line ~3205) — an override keyed on the tag
   chips' own wrapper (`.crm-chat-contact-tags`) rather than a JS class
   this task does not otherwise need. An empty tag cell ("—", no tags)
   never grows the wrapper, so it stays under the plain default rule —
   harmless, a one-character em dash never overflows 28ch. */
@media (min-width: 641px) {
  .crm-ct .table-scroll {
    max-height: calc(100vh - var(--topbar-h) - 230px);
    overflow-y: auto;
  }
  .crm-ct thead th {
    position: sticky;
    top: 0;
    background: var(--card);
    /* Above .crm-bulk-bar (z-index 5, page-sticky outside this table): when
       selection chrome grows past the scroll region's static height budget
       the page itself scrolls and the two sticky bands meet. Losing that
       contest would hide the column headers under the bulk toolbar. */
    z-index: 6;
    box-shadow: 0 1px 0 var(--border);
  }
  /* Ellipsis discipline applies to DATA cells only. A spanning cell (the
     designed empty state, its load-error twin) holds block content whose
     hint line must wrap — and `white-space: nowrap` inherits into it, so
     leaving .table-empty in scope clips that hint mid-sentence. Skeleton
     rows are released just below for the same reason. */
  .crm-ct td:not(.bulk-select-cell):not(.cell-actions):not(.td-primary):not(.table-empty) {
    max-width: 28ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .crm-ct .crm-ct-skel-row td {
    max-width: none;
    overflow: visible;
    white-space: normal;
  }
  .crm-ct td.td-primary { max-width: 34ch; }
  .crm-ct td:has(.crm-chat-contact-tags) {
    max-width: none;
    overflow: visible;
    text-overflow: clip;
    white-space: normal;
  }
}

/* Conversation status chip (Task 3) — one colour token per status, dot-
   prefixed like the task .status-badge system, but its own class since the
   value set (open/pending/snoozed/closed) is a distinct vocabulary. open =
   live/needs-attention (info/primary), pending = waiting on an external
   actor (warn), snoozed = deliberately parked (dashed neutral), closed =
   dormant (neutral). */
.crm-chat-status-chip {
  display: inline-flex; align-items: center; gap: 5px; flex: none;
  padding: 3px 8px; border-radius: var(--radius-pill);
  font-size: 11px; font-weight: 600; line-height: 1.3; white-space: nowrap;
  border: 1px solid transparent;
}
.crm-chat-status-chip::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
.crm-chat-status-chip.st-open    { background: var(--accent-soft); color: var(--info); border-color: color-mix(in srgb, var(--info) 28%, transparent); }
.crm-chat-status-chip.st-pending { background: var(--warn-bg); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 28%, transparent); }
.crm-chat-status-chip.st-snoozed { background: color-mix(in srgb, var(--text-3) 14%, transparent); color: var(--text-2); border-color: color-mix(in srgb, var(--text-3) 30%, transparent); border-style: dashed; }
.crm-chat-status-chip.st-closed  { background: color-mix(in srgb, var(--text-3) 12%, transparent); color: var(--text-3); border-color: color-mix(in srgb, var(--text-3) 25%, transparent); }
/* SLA waiting chip (Task 3) — only rendered while a reply is actively
   timed; the colour ramp IS the signal (ok=neutral, warn=amber,
   breach=red+pulse, reusing the existing .status-badge.is-urgent pulse
   keyframe rather than inventing a second one). `data-waiting-since` feeds
   the client-side tick loop (crm-chat.js tickWaitingChips) — text/colour
   both recompute every second without a refetch. */
.crm-chat-wait-chip {
  display: inline-flex; align-items: center; gap: 4px; flex: none;
  padding: 3px 8px; border-radius: var(--radius-pill);
  font-size: 11px; font-weight: 600; line-height: 1.3; white-space: nowrap;
  border: 1px solid transparent; font-variant-numeric: tabular-nums;
}
.crm-chat-wait-chip .lucide-ic { width: 11px; height: 11px; flex: none; }
.crm-chat-wait-chip.lvl-ok     { background: var(--bg-3); color: var(--text-2); border-color: var(--border-2); }
.crm-chat-wait-chip.lvl-warn   { background: var(--warn-bg); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 30%, transparent); }
.crm-chat-wait-chip.lvl-breach { background: var(--danger-bg); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 34%, transparent); animation: status-pulse 1.8s ease-in-out infinite; }
/* Frozen (Task 5, 2026-07-28 crm-chat-karzoun-polish plan) — the desk is
   outside business hours, so a leftover breach pulse read as false urgency;
   the level colour still carries meaning (a chip that breached before
   closing stays visibly red), it just stops animating while paused. Same
   token family, no new hue — the moon glyph inherits `currentColor` from
   the chip's own level colour like the clock it replaced. */
.crm-chat-wait-chip.is-frozen { animation: none; }
/* Neutral last-message age chip (chat-density task, 2026-08) — the OTHER
   half of the one-slot-per-row contract: rendered only while there is NO
   active wait (crm-chat.js's ageChipHtml already guards on
   `!item.waitingSince`), so this never appears beside .crm-chat-wait-chip
   on the same row. Deliberately quieter than even .lvl-ok above — no fill,
   no border, tighter padding, --text-3 not --text-2 — so a glance still
   lands on an amber/red waiting row first; this is read on a second pass,
   the same "identity, not color-coding" idea .crm-chat-badge's own comment
   states for the assignee initial. `data-last-message-at` feeds the same
   single tick loop .crm-chat-wait-chip's `data-waiting-since` does
   (crm-chat.js tickWaitingChips, extended, not a second interval). */
.crm-chat-age-chip {
  display: inline-flex; align-items: center; flex: none;
  padding: 3px 4px; font-size: 11px; font-weight: 500; line-height: 1.3;
  color: var(--text-3); white-space: nowrap; font-variant-numeric: tabular-nums;
}
.crm-chat-pane { display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; }
.crm-chat-placeholder, .crm-chat-empty { margin: auto; padding: 24px; color: var(--text-3); font-size: 13px; text-align: center; }
/* Header — contact name leads, then the status control, then the assign
   control. `flex-wrap` + `min-width:0` on the name let long names ellipsis
   /the row wrap onto a second line instead of pushing controls off the
   edge of the pane on narrow widths. */
.crm-chat-head {
  padding: 12px 16px; border-bottom: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap;
}
.crm-chat-head-name {
  flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 600; color: var(--text);
}
/* Status control (Task 4, 2026-07-28 crm-chat-karzoun-polish plan) — the
   waiting chip + ONE `<select>` (مفتوحة/بانتظار العميل/مؤجلة/مغلقة — POST
   /contacts/:id/status; "snoozed" opens UI.datePicker for snoozedUntil
   instead of committing straight away). Replaces Task 3's 3-way segmented
   buttons + a separate «تأجيل» button — see crm-chat.js's statusControlHtml
   for why the old text chip is gone too (the select's own selected option
   already says it). `.crm-chat-status-actions`/`.crm-chat-status-seg`/
   `.crm-chat-snooze-btn` retired with them (`.crm-chat-status-chip` itself
   is KEPT — still rendered on inbox list rows, just not in this header). */
.crm-chat-status { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; flex: 1 1 auto; min-width: 0; }
/* A cheap at-a-glance colour cue beside the select — same st-<status> token
   family as .crm-chat-status-chip above (info/warn/neutral), no new hues. */
.crm-chat-status-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: currentColor; }
.crm-chat-status-dot.st-open    { color: var(--info); }
.crm-chat-status-dot.st-pending { color: var(--warn); }
.crm-chat-status-dot.st-snoozed,
.crm-chat-status-dot.st-closed  { color: var(--text-3); }
/* Compact select override, same idiom as .crm-chat-assign-select below —
   a dense header affordance, not a form field. */
.crm-chat-status-select { min-width: 120px; max-width: 160px; }
select.crm-chat-status-select.input,
.crm-chat-status-select .uisel-trigger.input {
  height: 30px; padding: 4px 10px; font-size: 12px; border-radius: var(--radius-sm);
}
/* `.crm-chat-assign`/`.crm-chat-assign-label` (the thread-header wrapper +
   label) retired by Task 3 (2026-07-28 crm-chat-karzoun-polish plan) — the
   control itself (`.crm-chat-assign-select`, still below) moved into the
   contact panel; see `.crm-chat-context-assign` near .crm-chat-context-tags
   further down this file. Compact select override — a dense affordance, not
   a form field, so it overrides `.input`'s default field height/size.
   Targets both the plain native <select> (pre-enhancement / opted-out
   contexts) and the enhanced popover's trigger button (`.uisel-trigger`
   lives inside the wrapper, which inherits the `crm-chat-assign-select`
   class per UI.enhanceSelect's class-carry rule). */
.crm-chat-assign-select { min-width: 132px; max-width: 220px; }
select.crm-chat-assign-select.input,
.crm-chat-assign-select .uisel-trigger.input {
  height: 30px; padding: 4px 10px; font-size: 12px; border-radius: var(--radius-sm);
}
/* ── Tag chips + picker (Wave 3.1 Task 3, 2026-07-26 tags-saved-views
   plan) ──────────────────────────────────────────────────────────────────
   `.crm-tag` is the ONE chip primitive every surface renders through
   (crm-chat.js's tagChipHtml): inbox rows, thread header, contact sidebar,
   the picker's own rows, and the inbox tag-filter bar all share it. Colour
   comes from a `.tag-<name>` modifier reading the --tag-<name>-bg/-fg
   tokens (light+dark, see the :root/:root.light token blocks) — quiet
   fills, never the semantic danger/warn/success set. */
.crm-tag {
  display: inline-flex; align-items: center; gap: 4px; flex: none;
  padding: 2px 8px; border-radius: var(--radius-pill);
  font-size: 10.5px; font-weight: 600; line-height: 1.4; white-space: nowrap;
}
.crm-tag-x {
  appearance: none; border: 0; background: transparent; cursor: pointer; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 13px; height: 13px; margin-inline-start: 1px; padding: 0;
  color: inherit; opacity: .65; border-radius: 50%;
}
.crm-tag-x:hover { opacity: 1; background: color-mix(in srgb, currentColor 20%, transparent); }
.crm-tag-x .lucide-ic { width: 9px; height: 9px; }
.crm-tag.tag-slate  { background: var(--tag-slate-bg);  color: var(--tag-slate-fg); }
.crm-tag.tag-blue   { background: var(--tag-blue-bg);   color: var(--tag-blue-fg); }
.crm-tag.tag-green  { background: var(--tag-green-bg);  color: var(--tag-green-fg); }
.crm-tag.tag-amber  { background: var(--tag-amber-bg);  color: var(--tag-amber-fg); }
.crm-tag.tag-red    { background: var(--tag-red-bg);    color: var(--tag-red-fg); }
.crm-tag.tag-violet { background: var(--tag-violet-bg); color: var(--tag-violet-fg); }
.crm-tag.tag-teal   { background: var(--tag-teal-bg);   color: var(--tag-teal-fg); }
.crm-tag.tag-pink   { background: var(--tag-pink-bg);   color: var(--tag-pink-fg); }
/* Inbox-row overflow chip (voice-tags-polish fix B) — deliberately NOT one
   of the 8 --tag-<name> swatches (it isn't a real tag, just a "+N more"
   count), so it takes the neutral --bg-3/--text-3 pair instead — quiet by
   design, never competing with the actual tag colors it's summarizing. */
.crm-tag.crm-tag-more { background: var(--bg-3); color: var(--text-3); cursor: default; }
/* `.crm-chat-tags` (the thread-header tags row wrapper) retired by Task 4
   (2026-07-28 crm-chat-karzoun-polish plan) — tags now live ONLY in the
   contact panel (`.crm-chat-context-tags` further down this file); the
   header block/call site are gone from crm-chat.js's openThread. Kept:
   `.crm-chat-tags-empty` (shared with the panel's own empty-state text) and
   `.crm-chat-tag-add-btn` (crm-chat.js's tagsControlHtml — unreachable from
   the header now, but deliberately left in place, same as the deprecated
   saveConvTags path it belongs to). */
.crm-chat-tags-empty { font-size: 11px; color: var(--text-3); }
.crm-chat-tag-add-btn {
  appearance: none; display: inline-flex; align-items: center; gap: 5px;
  border: 1px dashed var(--border-2); background: transparent; cursor: pointer;
  padding: 4px 9px; border-radius: var(--radius-pill);
  font: inherit; font-size: 11px; font-weight: 600; color: var(--text-2); white-space: nowrap;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.crm-chat-tag-add-btn:hover { color: var(--primary-700); border-color: var(--primary-300); background: var(--accent-soft); }
.crm-chat-tag-add-btn .lucide-ic { width: 12px; height: 12px; }
/* Inbox tag-filter bar — toggle chips reusing `.crm-tag`'s own colour class
   for their resting state, dimmed until active so the row doesn't compete
   with the status/wait chips on the rows just below it. */
.crm-chat-tag-filter-bar { display: flex; flex-wrap: wrap; gap: 5px; padding: 2px 0 4px; }
.crm-chat-tag-filter-chip {
  appearance: none; border: 1px solid transparent; cursor: pointer; font: inherit;
  opacity: .55; transition: opacity .15s ease, box-shadow .15s ease;
}
.crm-chat-tag-filter-chip:hover { opacity: .85; }
.crm-chat-tag-filter-chip.is-active { opacity: 1; box-shadow: 0 0 0 1.5px currentColor inset; }
/* Saved views row (Wave 3.1 Task 5, 2026-07-26 tags-saved-views plan) — a
   second "view row" beside the built-in select: the loaded personal, then
   shared (badged «مشترك»), saved filter bundles as small pill tabs, plus a
   trailing «＋ احفظ العرض» action reusing .crm-chat-tag-add-btn's own
   dashed-border idiom. The divider is only rendered (in crm-chat.js) once
   at least one saved view exists — an empty company/user just sees the
   bare save button, no dead chrome. */
.crm-chat-views-row { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; padding: 2px 0 4px; }
.crm-chat-views-divider { width: 1px; align-self: stretch; min-height: 18px; background: var(--border); flex: none; }
.crm-view-tab {
  display: inline-flex; align-items: stretch; flex: none;
  border: 1px solid var(--border); border-radius: var(--radius-pill); overflow: hidden;
  background: var(--card); transition: border-color .15s ease, background .15s ease;
}
.crm-view-tab:hover { border-color: var(--border-2); }
.crm-view-tab.is-active { border-color: var(--primary-600); background: var(--accent-soft); }
.crm-view-tab-btn {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 10px; font: inherit; font-size: 11px; font-weight: 600; color: var(--text-2); white-space: nowrap;
}
.crm-view-tab:hover .crm-view-tab-btn { color: var(--text); }
.crm-view-tab.is-active .crm-view-tab-btn { color: var(--primary-700); }
/* Small inline marker on a shared view's own tab — quiet/neutral, never the
   semantic danger/warn/success set (Royal Pixel: identity, not alarm). */
.crm-view-shared-badge {
  display: inline-flex; align-items: center; flex: none;
  padding: 1px 6px; border-radius: var(--radius-pill);
  font-size: 9.5px; font-weight: 700; letter-spacing: .2px;
  background: color-mix(in srgb, var(--text-3) 16%, transparent); color: var(--text-2);
}
.crm-view-tab-actions { display: inline-flex; align-items: center; border-inline-start: 1px solid var(--border); }
.crm-view-tab-edit, .crm-view-tab-delete {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; color: var(--text-3); transition: color .15s ease, background .15s ease;
}
.crm-view-tab-edit:hover { color: var(--primary-700); background: var(--accent-soft); }
.crm-view-tab-delete:hover { color: var(--danger); background: var(--danger-bg); }
.crm-view-tab-edit .lucide-ic, .crm-view-tab-delete .lucide-ic { width: 11px; height: 11px; }
.crm-view-save-btn {
  appearance: none; display: inline-flex; align-items: center; gap: 5px;
  border: 1px dashed var(--border-2); background: transparent; cursor: pointer;
  padding: 5px 10px; border-radius: var(--radius-pill);
  font: inherit; font-size: 11px; font-weight: 600; color: var(--text-2); white-space: nowrap;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.crm-view-save-btn:hover { color: var(--primary-700); border-color: var(--primary-300); background: var(--accent-soft); }
.crm-view-save-btn .lucide-ic { width: 12px; height: 12px; }
/* Save-view popover (T5 review fix #1) — replaces an earlier prompt()+
   confirm() flow whose OK/Cancel binary silently mapped "Cancel" to "save
   as personal" instead of "abort". Same portaled-to-<body>, fixed-
   positioned, bg-2/border-2/shadow-2/fade-in language as .crm-tag-picker
   below; the two scope buttons are deliberately equal visual weight (no
   "primary" bias toward personal-vs-shared — neither is the default). */
.crm-view-save-popover {
  position: fixed; z-index: 60; min-width: 260px; max-width: 300px;
  padding: 10px; background: var(--bg-2); border: 1px solid var(--border-2);
  border-radius: var(--radius-field, 12px); box-shadow: var(--shadow-2);
  animation: fade-in .14s ease;
}
.crm-view-save-popover-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.crm-view-save-popover-title { font-size: 12px; font-weight: 700; color: var(--text-2); }
.crm-view-save-popover-close {
  appearance: none; border: 0; background: transparent; cursor: pointer; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 50%; color: var(--text-3);
  transition: color .15s ease, background .15s ease;
}
.crm-view-save-popover-close:hover { color: var(--text); background: var(--bg-3); }
.crm-view-save-popover-close .lucide-ic { width: 13px; height: 13px; }
.crm-view-save-name { width: 100%; margin-bottom: 8px; height: 32px; padding: 4px 10px; font-size: 12.5px; border-radius: var(--radius-sm); }
.crm-view-save-popover-hint { margin: 0 0 8px; font-size: 11px; color: var(--text-3); line-height: 1.4; }
.crm-view-save-popover-actions { display: flex; gap: 6px; }
.crm-view-save-scope-btn {
  flex: 1 1 50%; appearance: none; cursor: pointer;
  border: 1px solid var(--border-2); background: var(--card); color: var(--text-2);
  padding: 7px 8px; border-radius: var(--radius-sm);
  font: inherit; font-size: 11.5px; font-weight: 600; text-align: center; white-space: nowrap;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.crm-view-save-scope-btn:hover { color: var(--primary-700); border-color: var(--primary-300); background: var(--accent-soft); }

/* Contacts-page segment tabs (Wave B Task 4, 2026-07-30 crm-contacts-wave-b
   plan) — "smart lists". Same visual language/tokens as .crm-view-tab/
   .crm-view-save-btn/.crm-view-save-popover above (the shipped inbox's own
   saved-views row), so an operator who learns one recognises the other.
   Structured as a single flat <button> per tab (data-seg-id + .is-active on
   the SAME element, unlike the inbox's outer-wrapper-plus-inner-button
   shape) — this row has only one action button per tab (delete), not two
   (edit+delete), so the extra wrapper the inbox needs isn't earned here. */
.crm-seg-tabs-row { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; padding: 2px 0 4px; }
.crm-seg-row { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; margin-bottom: 6px; }
.crm-seg-tab-wrap { display: inline-flex; align-items: center; gap: 2px; }
.crm-seg-tab {
  appearance: none; cursor: pointer; flex: none;
  display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  background: var(--card); padding: 5px 10px;
  font: inherit; font-size: 11px; font-weight: 600; color: var(--text-2); white-space: nowrap;
  transition: border-color .15s ease, background .15s ease, color .15s ease;
}
.crm-seg-tab:hover { border-color: var(--border-2); color: var(--text); }
.crm-seg-tab.is-active { border-color: var(--primary-600); background: var(--accent-soft); color: var(--primary-700); }
/* Small inline marker on a shared segment's own tab — quiet/neutral, never
   the semantic danger/warn/success set (Royal Pixel: identity, not alarm) —
   mirrors .crm-view-shared-badge's own declarations exactly. */
.crm-seg-shared-badge {
  display: inline-flex; align-items: center; flex: none;
  padding: 1px 6px; border-radius: var(--radius-pill);
  font-size: 9.5px; font-weight: 700; letter-spacing: .2px;
  background: color-mix(in srgb, var(--text-3) 16%, transparent); color: var(--text-2);
}
.crm-seg-tab-delete {
  appearance: none; border: 1px solid var(--border); background: var(--card); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; border-radius: 50%; color: var(--text-3);
  transition: color .15s ease, background .15s ease, border-color .15s ease;
}
.crm-seg-tab-delete:hover { color: var(--danger); background: var(--danger-bg); border-color: var(--danger-bg); }
.crm-seg-tab-delete .lucide-ic { width: 11px; height: 11px; }
.crm-seg-save-btn {
  appearance: none; display: inline-flex; align-items: center; gap: 5px;
  border: 1px dashed var(--border-2); background: transparent; cursor: pointer;
  padding: 5px 10px; border-radius: var(--radius-pill);
  font: inherit; font-size: 11px; font-weight: 600; color: var(--text-2); white-space: nowrap;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.crm-seg-save-btn:hover { color: var(--primary-700); border-color: var(--primary-300); background: var(--accent-soft); }
.crm-seg-save-btn .lucide-ic { width: 12px; height: 12px; }
.crm-seg-save-popover {
  position: fixed; z-index: 60; min-width: 260px; max-width: 300px;
  padding: 10px; background: var(--bg-2); border: 1px solid var(--border-2);
  border-radius: var(--radius-field, 12px); box-shadow: var(--shadow-2);
  animation: fade-in .14s ease;
}
.crm-seg-save-popover-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.crm-seg-save-popover-title { font-size: 12px; font-weight: 700; color: var(--text-2); }
.crm-seg-save-popover-close {
  appearance: none; border: 0; background: transparent; cursor: pointer; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 50%; color: var(--text-3);
  transition: color .15s ease, background .15s ease;
}
.crm-seg-save-popover-close:hover { color: var(--text); background: var(--bg-3); }
.crm-seg-save-popover-close .lucide-ic { width: 13px; height: 13px; }
.crm-seg-save-name { width: 100%; margin-bottom: 8px; height: 32px; padding: 4px 10px; font-size: 12.5px; border-radius: var(--radius-sm); }
.crm-seg-save-popover-actions { display: flex; gap: 6px; }
.crm-seg-save-scope-btn {
  flex: 1 1 50%; appearance: none; cursor: pointer;
  border: 1px solid var(--border-2); background: var(--card); color: var(--text-2);
  padding: 7px 8px; border-radius: var(--radius-sm);
  font: inherit; font-size: 11.5px; font-weight: 600; text-align: center; white-space: nowrap;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.crm-seg-save-scope-btn:hover { color: var(--primary-700); border-color: var(--primary-300); background: var(--accent-soft); }

/* Popover — same visual language as .crm-chat-canned-picker/.crm-chat-
   mention-picker (bg-2/border-2/shadow-2/fade-in) but portaled to <body>
   and fixed-positioned under its trigger button (contact-picker mechanics),
   since it opens from a header/sidebar button rather than the composer. */
.crm-tag-picker {
  position: fixed; z-index: 60; min-width: 220px; max-width: 280px;
  padding: 8px; background: var(--bg-2); border: 1px solid var(--border-2);
  border-radius: var(--radius-field, 12px); box-shadow: var(--shadow-2);
  animation: fade-in .14s ease;
}
.crm-tag-picker-search { width: 100%; margin-bottom: 6px; height: 30px; padding: 4px 10px; font-size: 12px; border-radius: var(--radius-sm); }
.crm-tag-picker-list { max-height: 220px; overflow-y: auto; display: flex; flex-direction: column; gap: 1px; }
.crm-tag-picker-row {
  display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%;
  appearance: none; border: 0; background: transparent; cursor: pointer;
  padding: 6px 8px; border-radius: var(--radius-sm); text-align: start;
}
.crm-tag-picker-row:hover { background: var(--accent-soft); }
.crm-tag-picker-row .lucide-ic { width: 13px; height: 13px; color: var(--primary-600); flex: none; }
.crm-tag-picker-empty { padding: 12px; text-align: center; color: var(--text-3); font-size: 12px; }
.crm-tag-picker-create {
  display: flex; align-items: center; gap: 6px; width: 100%;
  appearance: none; border: 1px dashed var(--border-2); background: transparent; cursor: pointer;
  padding: 6px 8px; border-radius: var(--radius-sm); text-align: start; margin-top: 2px;
  font: inherit; font-size: 12px; font-weight: 600; color: var(--primary-700);
}
.crm-tag-picker-create:hover { background: var(--accent-soft); }
.crm-tag-picker-create .lucide-ic { width: 12px; height: 12px; }

/* ── Bulk-tag picker (Task 4, 2026-07-27 crm-contacts-page-enrich) ───────
   A plain .modal (opened via UI.modal from the contacts bulk bar's «وسم»),
   NOT the anchored .crm-tag-picker popover above — the two share the same
   search/list/quick-create visual language (.crm-tag-picker-search/-list/
   -create/-empty are reused as-is) but this one needs modal width + an
   explicit Apply action, since staging ADD/REMOVE across many contacts
   isn't a save-per-click toggle. Each row shows the tag chip plus two
   small round toggles: a SUCCESS-tinted "+" (stage for ADD) and a
   DANGER-tinted "×" (stage for REMOVE) — the one semantic-color use this
   picker earns, since add/remove is a real state change, not decoration
   (color doctrine's semantic gate). */
.crm-bulk-tag-hint { margin: -4px 0 10px; font-size: 11.5px; font-weight: 600; color: var(--text-3); }
.crm-bulk-tag-legend { display: flex; align-items: center; gap: 16px; margin: 0 0 8px; font-size: 11px; font-weight: 600; color: var(--text-3); }
.crm-bulk-tag-legend span { display: inline-flex; align-items: center; gap: 4px; }
.crm-bulk-tag-legend .lucide-ic { width: 12px; height: 12px; }
.crm-bulk-tag-list { max-height: 260px; }
.crm-bulk-tag-row {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 6px 8px; border-radius: var(--radius-sm);
}
.crm-bulk-tag-row:hover { background: var(--accent-soft); }
.crm-bulk-tag-row-actions { display: inline-flex; align-items: center; gap: 6px; flex: none; }
.crm-bulk-tag-btn {
  appearance: none; cursor: pointer; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; border-radius: 50%;
  background: transparent; border: 1px solid var(--border-2); color: var(--text-3);
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.crm-bulk-tag-btn:hover { color: var(--text); background: var(--bg-3); }
.crm-bulk-tag-btn .lucide-ic { width: 12px; height: 12px; }
.crm-bulk-tag-btn-add.is-active { background: var(--success-bg); border-color: var(--success); color: var(--success); }
.crm-bulk-tag-btn-remove.is-active { background: var(--danger-bg); border-color: var(--danger); color: var(--danger); }

/* Wraps #crm-chat-messages so the jump-to-latest button below can float over
   its bottom-end corner without sitting under the compose box: the wrapper
   (not .crm-chat-messages) now takes the flex:1/min-height:0 slot inside
   .crm-chat-pane's column, and is the `position: relative` the button's own
   `position: absolute` resolves against. */
.crm-chat-messages-wrap { position: relative; flex: 1; min-height: 0; display: flex; flex-direction: column; }
.crm-chat-messages {
  flex: 1; min-height: 0; overflow-y: auto; padding: 14px 16px; display: flex; flex-direction: column; gap: 6px;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) { .crm-chat-messages { scroll-behavior: auto; } }
/* Calendar-day separator chip (inbox quick-wins Task 1) — a quiet pill
   centered between day groups, matching fmtTime's own «10:38 ص» Latin-digit
   locale so a bubble's clock and its day chip never disagree. */
.crm-chat-day-sep {
  display: flex; align-items: center; justify-content: center; gap: .35rem;
  margin: .6rem auto .4rem; padding: .2rem .7rem; width: max-content;
  font-size: .74rem; color: var(--text-3); background: var(--bg-2);
  border: 1px solid var(--border); border-radius: var(--radius-pill);
}
.crm-chat-day-sep .lucide-ic { width: 12px; height: 12px; }
/* Jump-to-latest — shown only once the agent has scrolled far enough up
   that a new inbound (the 8s poll) would otherwise land off-screen; see
   crm-chat.js's paintJumpLatest. `.btn.btn-icon` gives the pill/circle
   surface a floating control needs (unlike the `-ghost` toolbar icons
   beside it, which read as part of the surface, not floating over it). The
   explicit [hidden] override exists because `.btn`'s own `display:
   inline-flex` (an AUTHOR rule) otherwise beats the UA `[hidden]{display:
   none}` rule — the same gotcha every other JS-toggled control in this file
   already works around (see .crm-chat-attach-btn[hidden] above). */
.crm-chat-jump-latest {
  position: absolute; inset-block-end: 16px; inset-inline-end: 20px;
  box-shadow: var(--shadow-2); z-index: 2;
}
.crm-chat-jump-latest[hidden] { display: none; }
.crm-chat-bubble { max-width: 72%; padding: 8px 12px; border-radius: 14px; font-size: 13px; line-height: 1.5; word-break: break-word; display: flex; flex-direction: column; gap: 2px; }
.crm-chat-bubble-meta { font-size: 10px; opacity: .6; align-self: flex-end; font-variant-numeric: tabular-nums; }
.crm-chat-bubble-meta.is-failed { color: var(--danger); opacity: .9; }
/* Auto-reply tag — a bot welcome is a real outbound bubble but wasn't typed by
   the agent; a quiet pill inside the meta line marks it without shouting. */
.crm-chat-bubble-auto { display: inline-block; margin-inline-end: 5px; padding: 0 5px; border-radius: 999px; border: 1px solid currentColor; font-weight: 600; }
.crm-chat-bubble.out { margin-inline-start: auto; background: var(--accent-soft); color: var(--primary-700); border-end-end-radius: 4px; }
.crm-chat-bubble.in { margin-inline-end: auto; background: var(--bg-2); color: var(--text); border: 1px solid var(--border); border-end-start-radius: 4px; }
/* Internal note bubble — deliberately NOT bubble-shaped like .in/.out: a
   wide amber banner, centered, so it reads as "system record" rather than
   "someone said this". Reuses the existing --warn/--warn-bg semantic pair
   (already tuned for both themes) instead of inventing a new hue. */
.crm-chat-bubble.note {
  align-self: center; width: 92%; max-width: 92%; margin-inline: 0;
  background: var(--warn-bg); border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent);
  border-radius: var(--radius-sm);
}
.crm-chat-bubble-note-head { display: flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 700; color: var(--warn); }
.crm-chat-bubble-note-head .lucide-ic { width: 13px; height: 13px; flex: none; }
.crm-chat-bubble.note .crm-chat-bubble-text { color: var(--text); }
.crm-chat-bubble.note .crm-chat-bubble-meta { color: var(--warn); }
/* Attachment bubbles (Task 5, 2026-07-26 mentions-attachments plan) —
   renders inside a normal .in/.out bubble, above an optional caption
   (.crm-chat-bubble-caption reuses .crm-chat-bubble-text's own styling via
   the shared class, the bubble's flex-column `gap: 2px` already separates
   the two). Works for either direction: an inbound row the customer sent
   media on, or an outbound row the agent attached a file to. */
.crm-att-image-link { display: block; align-self: flex-start; }
.crm-att-image {
  display: block; max-width: 220px; max-height: 220px; width: auto; height: auto;
  border-radius: var(--radius-sm); object-fit: cover; cursor: pointer;
}
.crm-att-file {
  display: inline-flex; align-items: center; gap: 6px; align-self: flex-start;
  padding: 6px 10px; border-radius: var(--radius-sm);
  background: color-mix(in srgb, currentColor 10%, transparent);
  color: inherit; text-decoration: none; font-size: 12px; font-weight: 600;
  max-width: 100%;
}
.crm-att-file:hover { background: color-mix(in srgb, currentColor 18%, transparent); }
.crm-att-file .lucide-ic { width: 14px; height: 14px; flex: none; }
.crm-att-file span:first-of-type { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crm-att-file span:last-child { opacity: .75; flex: none; font-variant-numeric: tabular-nums; }
/* Inline voice/audio player (voice-tags-polish fix A) — a plain native
   <audio controls>, not chat.js's fancier waveform voice-message widget
   (YAGNI here — a native control reads fine inside a compact inbox bubble).
   `width: 260px` is a FIXED px preferred size, not a percentage:
   `.crm-chat-bubble` is a flex item sized by its own auto inline-margin
   (.in/.out), i.e. shrink-to-fit around its content, not a determinate
   width — a percentage-width `width` on this child is a circular layout
   dependency that real Chrome resolves by collapsing the native audio
   control down to its bare "more options" glyph (verified visually, see
   voice-tags-shots). Mirrors chat.js's own real voice player, which
   sidesteps the exact same pitfall with a fixed `min-width: 240px` on
   `.voice-msg` rather than a percentage `width`.
   `max-width: 100%` is safe to add on top of the fixed width (does NOT
   reintroduce the collapse above — verified at 1000px desktop AND
   320/375/390/414px mobile widths): it clamps the control down to the
   bubble's own content box once `.crm-chat-bubble`'s `max-width: 72%` bites
   on a narrow/mobile viewport (`@media (max-width: 640px)` collapses
   `.crm-chat` to a single column, so the bubble can end up narrower than
   260px there) — without it the player bled ~13-17px past the bubble edge.
   accent-color nudges the native play/volume affordances toward the brand
   instead of leaving the OS default, in both light and dark (the browser
   itself adapts the rest of the control chrome to prefers-color-scheme). */
.crm-att-audio { display: block; width: 260px; max-width: 100%; height: 32px; accent-color: var(--primary-600); }
/* Inbound-media-download-failure placeholder (crmChatInbound.js's
   attachmentFailed marker) — a muted inline note standing in for the
   attachment that never made it into storage, never a broken image/link. */
.crm-chat-attach-failed { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-2); font-style: italic; }
.crm-chat-attach-failed .lucide-ic { width: 13px; height: 13px; flex: none; color: var(--warn); }
.crm-chat-compose { position: relative; display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; border-top: 1px solid var(--border); }
.crm-chat-compose-row { display: flex; gap: 8px; }
.crm-chat-input { flex: 1; resize: none; }
.crm-chat-send { white-space: nowrap; }
/* Recording bar — replaces the compose row while the mic is live, rather than
   sitting beside it. Two rows would let an agent type into a box whose text
   the recording cannot carry (WhatsApp's audio stanza has no caption field),
   so the composer offers exactly one thing at a time. */
/* The player inside a bubble. Native controls, sized to the bubble rather
   than to the browser's default 300px so a recording does not widen the
   thread. */
/* An ABSOLUTE width, matching .crm-att-audio below — which has always been
   right, two rules away, while this one collapsed to a 29px grey stub.
   `width: 100%` asks the bubble how wide it is, and the bubble is shrink-to-fit,
   waiting on this child to answer. The first attempt at a fix used
   `min-width: min(240px, 100%)` and changed nothing, because a percentage
   measured against a shrink-to-fit box is cyclic and CSS resolves it to auto —
   zero. Measured in Chrome against the real DOM: 29px before, 260px after. */
.crm-chat-voice { display: block; width: 260px; max-width: 100%; margin: 2px 0; }
/* The 72% cap on .crm-chat-bubble exists so PROSE wraps before it reaches the
   far edge of the thread. A player has no prose, and on a 320px phone the cap
   left it 181px — under the width at which Chrome drops the transport
   controls. Measured for the inbound attachment player too, which collapsed
   there for the same reason and had never been noticed. */
.crm-chat-bubble.has-audio { max-width: 100%; }
.crm-chat-rec-bar { display: flex; align-items: center; gap: 8px; }
.crm-chat-rec-bar[hidden] { display: none; }
.crm-chat-rec-time {
  font-variant-numeric: tabular-nums; /* the clock must not shift as it ticks */
  color: var(--text-2); min-width: 3.5ch;
}
.crm-chat-rec-cancel { margin-inline-start: auto; }
.crm-chat-rec-dot {
  width: 10px; height: 10px; border-radius: 50%; flex: 0 0 10px;
  background: var(--danger);
  animation: crm-rec-pulse 1.4s ease-in-out infinite;
}
@keyframes crm-rec-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .35 } }
@media (prefers-reduced-motion: reduce) { .crm-chat-rec-dot { animation: none; } }
/* Attach button (Task 5) — same .btn/.btn-ghost/.btn-icon system as the AI
   sparkles trigger, WhatsApp-channel only. Hidden AND disabled in note mode
   (crm-chat.js's applyComposeChannel) — the explicit [hidden] override is
   required because `.btn`'s own `display: inline-flex` beats the UA
   `[hidden]{display:none}` rule in the cascade otherwise (same gotcha as
   `.comp-btn[hidden]` elsewhere in this file). */
.crm-chat-attach-btn { color: var(--text-2); }
.crm-chat-attach-btn:hover:not(:disabled) { color: var(--primary-700); }
.crm-chat-attach-btn[hidden] { display: none; }
.crm-chat-attach-input { display: none; }
/* Composer hint (Task 5) — discoverability hint that shows when textarea is
   empty. Decorative (aria-hidden) — screen readers hear the placeholder text
   instead. Hidden when content is present; text swaps based on channel. */
.crm-chat-compose-hint { font-size: .72rem; color: var(--text-3); padding-inline: .4rem; }
.crm-chat-compose-hint[hidden] { display: none; }
/* Pending-upload chip — sits above the compose row once POST
   /api/crm/upload succeeds; filename + size + a remove affordance. Cleared
   (chip hidden) on send, on explicit remove, on a thread switch, and on
   switching the composer to note mode. */
.crm-chat-pending-attach { display: flex; }
.crm-chat-pending-attach[hidden] { display: none; }
.crm-chat-pending-attach-chip {
  display: inline-flex; align-items: center; gap: 6px; align-self: flex-start;
  max-width: 100%; padding: 5px 6px 5px 10px; border-radius: var(--radius-pill);
  background: var(--accent-soft); color: var(--primary-700);
  font-size: 12px; font-weight: 600;
}
.crm-chat-pending-attach-chip .lucide-ic { width: 13px; height: 13px; flex: none; }
.crm-chat-pending-attach-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 240px; }
.crm-chat-pending-attach-size { color: var(--text-3); font-weight: 500; flex: none; }
.crm-chat-pending-attach-remove {
  appearance: none; border: 0; background: transparent; cursor: pointer; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 50%; color: inherit; opacity: .75;
}
.crm-chat-pending-attach-remove:hover { opacity: 1; background: var(--card); }
.crm-chat-pending-attach-remove .lucide-ic { width: 12px; height: 12px; }
/* `/` canned-reply picker (Task 5) — a popover anchored to the whole
   compose block (not just the textarea), so it clears the channel toggle
   too and always opens flush above the composer regardless of which row
   triggered it. `.crm-chat` (the 3-pane grid) clips at its own outer
   edge, not at inner siblings, so this can safely overlay the scrollable
   message list above it without being cut off — same idiom as
   .date-picker (bg-2/border-2/shadow-2), scoped locally instead of
   portaled since the composer never scrolls out from under it. */
.crm-chat-canned-picker {
  position: absolute; z-index: 20; left: 0; right: 0; bottom: 100%; margin-bottom: 8px;
  max-height: 240px; overflow-y: auto; padding: 6px;
  background: var(--bg-2); border: 1px solid var(--border-2);
  border-radius: var(--radius-field, 12px); box-shadow: var(--shadow-2);
  animation: fade-in .14s ease;
}
.crm-chat-canned-empty { padding: 14px; text-align: center; color: var(--text-3); font-size: 12px; }
.crm-chat-canned-row {
  display: flex; flex-direction: column; gap: 2px; width: 100%;
  appearance: none; border: 0; background: transparent; cursor: pointer;
  padding: 7px 10px; border-radius: var(--radius-sm); text-align: start; font: inherit;
  color: var(--text);
}
.crm-chat-canned-row:hover, .crm-chat-canned-row.is-active { background: var(--accent-soft); }
.crm-chat-canned-row-top { display: flex; align-items: center; gap: 8px; min-width: 0; }
.crm-chat-canned-shortcut {
  flex: none; font-size: 11px; font-weight: 700; color: var(--primary-700);
  background: var(--accent-soft); border-radius: var(--radius-pill); padding: 1px 7px;
}
.crm-chat-canned-row.is-active .crm-chat-canned-shortcut { background: var(--card); }
.crm-chat-canned-title { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12.5px; font-weight: 600; }
.crm-chat-canned-body { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; color: var(--text-3); }
/* `@` mention picker (Task 2, 2026-07-26 mentions-attachments plan) — same
   popover language as .crm-chat-canned-picker (bg-2/border-2/shadow-2,
   flush above the whole compose block), reusing that exact CSS shape since
   the two never show at once (mutual exclusion in crm-chat.js). Narrower
   row (just a name, no shortcut/preview lines) than a canned-reply row. */
.crm-chat-mention-picker {
  position: absolute; z-index: 20; left: 0; right: 0; bottom: 100%; margin-bottom: 8px;
  max-height: 240px; overflow-y: auto; padding: 6px;
  background: var(--bg-2); border: 1px solid var(--border-2);
  border-radius: var(--radius-field, 12px); box-shadow: var(--shadow-2);
  animation: fade-in .14s ease;
}
.crm-chat-mention-empty { padding: 14px; text-align: center; color: var(--text-3); font-size: 12px; }
.crm-chat-mention-row {
  display: flex; align-items: center; width: 100%;
  appearance: none; border: 0; background: transparent; cursor: pointer;
  padding: 7px 10px; border-radius: var(--radius-sm); text-align: start; font: inherit;
  color: var(--text); font-size: 12.5px; font-weight: 600;
}
.crm-chat-mention-row:hover, .crm-chat-mention-row.is-active { background: var(--accent-soft); }
/* Inline @-token chip inside a note bubble (nice-to-have #4) — a subtle
   pill, not a full accent block, so a mention reads as a highlighted word
   within the sentence rather than competing with the bubble's own amber
   "internal note" identity. */
.crm-chat-mention-chip {
  display: inline; background: var(--accent-soft); color: var(--primary-700);
  border-radius: var(--radius-pill); padding: 0 6px; font-weight: 600;
}
/* Composer channel toggle — compact segmented pill, same metaphor as
   .segmented/.seg-btn elsewhere but pill-radius per the Royal Pixel scale
   (buttons are always full-pill). WhatsApp segment takes the primary
   treatment when active; the note segment takes the amber one, so the
   toggle itself previews what mode you're about to send in. */
.crm-chat-channel { display: inline-flex; align-self: flex-start; padding: 3px; gap: 2px; border-radius: var(--radius-pill); background: var(--bg-3); border: 1px solid var(--border); }
.crm-chat-channel-seg {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 6px 12px; border-radius: var(--radius-pill);
  font: inherit; font-size: 12px; font-weight: 600; color: var(--text-2);
  transition: background .15s ease, color .15s ease;
}
.crm-chat-channel-seg .lucide-ic { width: 13px; height: 13px; }
.crm-chat-channel-seg:hover:not(.is-active) { color: var(--text); }
.crm-chat-channel-seg[data-channel="whatsapp"].is-active { background: var(--accent-soft); color: var(--primary-700); }
.crm-chat-channel-seg[data-channel="note"].is-active { background: var(--warn-bg); color: var(--warn); }
/* Note mode — amber accent on the textarea + send button so it's obvious
   the next message stays internal, before it's even sent. */
.crm-chat-compose.is-note .crm-chat-input { border-color: var(--warn); }
.crm-chat-compose.is-note .crm-chat-input:focus { border-color: var(--warn); box-shadow: 0 0 0 3px color-mix(in srgb, var(--warn) 25%, transparent); }
.crm-chat-compose.is-note .crm-chat-send { background: var(--warn); border-color: var(--warn); color: #fff; }
.crm-chat-compose.is-note .crm-chat-send:hover:not(:disabled) { filter: brightness(.92); }
/* ── AI assist (Task 2) ──────────────────────────────────────────────────
   Sparkles trigger + action-menu popover in the composer row, gated
   client-side on GET /api/agent/config's `enabled` flag (crm-chat.js only
   emits this markup at all when aiEnabled — no CSS-only hiding needed).
   The trigger reuses the shared .btn/.btn-ghost/.btn-icon system, and its
   loading state reuses .btn-busy/.btn-spinner (same in-flight idiom as
   UI.bindBusy) rather than a bespoke spinner. */
.crm-chat-ai-btn { color: var(--text-2); }
.crm-chat-ai-btn:hover:not(:disabled) { color: var(--primary-700); }
.crm-chat-ai-btn[aria-expanded="true"] { background: var(--accent-soft); color: var(--primary-700); }
/* Popover — same visual language as .crm-chat-canned-picker (bg-2/border-2/
   shadow-2/fade-in), anchored to the inline-end edge of the composer
   (where the AI + send buttons sit) instead of spanning full width, since
   this is a short vertical action list rather than a searchable list. */
.crm-chat-ai-menu {
  position: absolute; z-index: 21; inset-inline-end: 0; bottom: 100%; margin-bottom: 8px;
  min-width: 190px; padding: 6px;
  background: var(--bg-2); border: 1px solid var(--border-2);
  border-radius: var(--radius-field, 12px); box-shadow: var(--shadow-2);
  animation: fade-in .14s ease;
  display: flex; flex-direction: column; gap: 1px;
}
.crm-chat-ai-menu-item {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  width: 100%; text-align: start; padding: 8px 10px; border-radius: var(--radius-sm);
  font: inherit; font-size: 12.5px; font-weight: 600; color: var(--text);
}
.crm-chat-ai-menu-item:hover:not(:disabled) { background: var(--accent-soft); color: var(--primary-700); }
.crm-chat-ai-menu-item:disabled { color: var(--text-3); cursor: not-allowed; opacity: .6; }
/* «بمساعدة الذكاء» hint — shown right under the compose row once a draft
   lands in the textarea; dismissed the instant the agent edits it
   (crm-chat.js hides it on the textarea's own `input` event), so there's
   no close button here by design. */
.crm-chat-ai-hint { display: flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 600; color: var(--primary-700); }
.crm-chat-ai-hint .lucide-ic { width: 12px; height: 12px; flex: none; }
/* Summary panel — dismissible, read-only, sits between the message list
   and .crm-chat-compose. Distinct from the internal-note bubble (amber,
   .crm-chat-bubble.note): this is an AI surface, so it takes the brand
   accent-soft/primary-700 pairing already used for "active" states
   throughout this file instead of a new hue. */
.crm-chat-ai-summary {
  margin: 0 12px 10px; padding: 10px 12px;
  background: var(--accent-soft); border: 1px solid color-mix(in srgb, var(--primary-600) 28%, transparent);
  border-radius: var(--radius-sm);
  animation: fade-in .14s ease;
}
.crm-chat-ai-summary-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 6px; }
.crm-chat-ai-summary-title { display: flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 700; color: var(--primary-700); text-transform: uppercase; letter-spacing: .02em; }
.crm-chat-ai-summary-title .lucide-ic { width: 13px; height: 13px; }
.crm-chat-ai-summary-dismiss {
  appearance: none; border: 0; background: transparent; cursor: pointer; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: var(--radius-sm); color: var(--text-2);
}
.crm-chat-ai-summary-dismiss:hover { background: var(--card); color: var(--text); }
.crm-chat-ai-summary-dismiss .lucide-ic { width: 13px; height: 13px; }
.crm-chat-ai-summary-body { font-size: 12.5px; line-height: 1.6; color: var(--text); white-space: pre-wrap; word-break: break-word; }
/* Context pane (Task 3 — SLOT only; Task 6 fills the body). Logical
   `border-inline-start` mirrors correctly under RTL, matching the list
   pane's `border-inline-end` on the opposite edge. */
/* Row, not column (Phase 3): the panel is now a main column plus a rail.
   The rail is the LAST flex child, so it mirrors to the outer edge in both
   directions with no RTL special case. */
.crm-chat-context { display: flex; flex-direction: row; min-width: 0; overflow: hidden; }
.crm-chat-context-main { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; overflow: hidden; }
/* Collapsed IS the rail. The old rules hid the head text and the body while
   leaving a head bar behind; hiding the whole main column instead is what
   makes the existing 44px collapsed grid track mean exactly the rail's width,
   and it is why the collapse toggle had to move INTO the rail — left in the
   head it would have vanished with everything else. */
.crm-chat.is-context-collapsed .crm-chat-context-main { display: none; }
.crm-chat-context-rail {
  flex: none; width: 44px; display: flex; flex-direction: column; align-items: center;
  gap: 4px; padding: 8px 0; background: var(--bg-2); border-inline-start: 1px solid var(--border);
}
.crm-chat-context-rail-btn {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0; border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--icon-ink); cursor: pointer;
  transition: background .15s ease, color .15s ease;
}
.crm-chat-context-rail-btn:hover { background: var(--bg-3); color: var(--text); }
.crm-chat-context-rail-btn.is-active { background: var(--accent-soft); color: var(--primary-600); }
.crm-chat-context-rail-btn .lucide-ic { width: 15px; height: 15px; }
.crm-chat-context-rail-spacer { flex: 1 1 auto; }
/* Slot containers carry no display of their own, so the UA stylesheet's
   `[hidden] { display: none }` is enough for them — see the sweep in
   test/crm.chat.contextPanel.client.test.js for why that matters. */
/* Only the NON-default slots: the contact slot's own .crm-chat-context-profile
   already carries its padding, and adding more here would double it. No
   `display` is set, so the UA stylesheet's bare `[hidden]` still hides a slot
   with no companion rule needed. */
.crm-chat-context-slot:not([data-slot="contact"]) { padding: 12px; }
.crm-ctx-files { display: flex; flex-direction: column; gap: 2px; }
.crm-ctx-file {
  display: flex; align-items: center; gap: 8px; padding: 8px 10px;
  border-radius: var(--radius-sm); color: var(--text); text-decoration: none; line-height: 1.45;
}
.crm-ctx-file:hover { background: var(--bg-2); }
.crm-ctx-file-ic { flex: none; color: var(--icon-ink); display: inline-flex; }
.crm-ctx-file-ic .lucide-ic { width: 14px; height: 14px; }
.crm-ctx-file-main { min-width: 0; display: flex; flex-direction: column; }
.crm-ctx-file-name { font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crm-ctx-file-meta { font-size: 10.5px; color: var(--text-3); font-variant-numeric: tabular-nums; }
.crm-ctx-file-failed {
  margin-bottom: 8px; padding: 8px 10px; border-radius: var(--radius-sm);
  background: var(--warn-bg, var(--bg-2)); color: var(--text-2); font-size: 11.5px; line-height: 1.45;
}
.crm-ctx-marketing { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; padding: 4px 2px; }
.crm-ctx-consent {
  display: inline-flex; align-items: center; padding: 3px 10px; border-radius: var(--radius-pill);
  font-size: 11px; font-weight: 600; line-height: 1.45;
}
.crm-ctx-consent.is-granted { background: var(--success-bg); color: var(--success); }
.crm-ctx-consent.is-revoked { background: var(--danger-bg); color: var(--danger); }
.crm-ctx-consent.is-none { background: var(--bg-3); color: var(--text-3); }
.crm-ctx-consent-body { margin: 0; font-size: 12px; color: var(--text-2); line-height: 1.45; }
.crm-ctx-consent-scope { margin: 0; font-size: 11px; color: var(--text-3); line-height: 1.45; }
.crm-chat-context-head {
  padding: 12px 14px; border-bottom: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  font-size: 12px; font-weight: 600; color: var(--text-2); white-space: nowrap;
}
.crm-chat-context-toggle {
  appearance: none; border: 0; background: transparent; cursor: pointer; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; border-radius: var(--radius-sm); color: var(--text-2);
  transition: background .15s ease, color .15s ease;
}
.crm-chat-context-toggle:hover { background: var(--bg-3); color: var(--text); }
/* Chevron rotates 180° between the open/collapsed state — a simple flip is
   legible in both RTL/LTR without needing a direction-specific glyph. */
.crm-chat-context-toggle .lucide-ic { transition: transform .15s ease; transform: rotate(-90deg); }
.crm-chat.is-context-collapsed .crm-chat-context-toggle .lucide-ic { transform: rotate(90deg); }
.crm-chat-context-body { flex: 1; overflow-y: auto; }
.crm-chat-context-placeholder { margin: auto; padding: 20px 14px; color: var(--text-3); font-size: 12px; text-align: center; }
/* Profile block (Task 6; made inline-editable in Wave 2, 2026-07-27
   crm-contact-inline-edit plan) — name leads (now two live text inputs),
   then editable tag chips (Wave 3.1 Task 3 —
   contactTagsControlHtml/paintContactTags, PATCH /contacts/:id), then a
   label/live-input row stack (phone/email/job title/an account picker),
   then a save-on-dirty bar. */
.crm-chat-context-profile { padding: 10px; }
/* Identity card (Phase 2) — who this is, who owns them, how they're
   labelled, in one bounded block on the panel's own tint. Everything below it
   is the RECORD, in collapsible groups; the card is the only part that never
   collapses. */
.crm-chat-context-card {
  padding: 12px; border: 1px solid var(--border); border-radius: var(--radius-card);
  background: var(--bg-2); margin-bottom: 10px;
}
/* Avatar, name and the open-record action share the top line: the name reads
   beside the face rather than under it, which is what makes the block scan as
   one identity instead of three stacked controls. */
.crm-chat-context-card-top { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
/* The name is a HEADING here, not a field — the record below edits it. */
.crm-chat-context-identity { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.crm-chat-context-name {
  font-size: 13.5px; font-weight: 600; color: var(--text); line-height: 1.45;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.crm-chat-context-source { font-size: 10.5px; color: var(--text-3); line-height: 1.45; }
/* Was a full-width primary button in its own row — the heaviest element on a
   pane whose job is reading a record. Now a quiet icon affordance in the
   card, same weight as the panel's other chrome. */
.crm-chat-context-open {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0; border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--card); color: var(--icon-ink);
  cursor: pointer; transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.crm-chat-context-open:hover { background: var(--bg-3); color: var(--text); border-color: var(--border-2); }
.crm-chat-context-open .lucide-ic { width: 14px; height: 14px; }
/* Field search — only rendered once the field count crosses the threshold,
   so this styles a control most tenants never see. */
.crm-chat-context-search {
  display: flex; align-items: center; gap: 6px; margin-bottom: 8px;
  padding: 0 10px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--card);
}
.crm-chat-context-search .lucide-ic { flex: none; width: 13px; height: 13px; color: var(--icon-ink); }
.crm-chat-context-search-input {
  flex: 1 1 auto; min-width: 0; border: 0; outline: none; background: transparent;
  padding: 7px 0; font: inherit; font-size: 12px; color: var(--text); line-height: 1.45;
}
.crm-chat-context-search-input::placeholder { color: var(--text-3); }
.crm-chat-context-search:focus-within { border-color: var(--ring); box-shadow: 0 0 0 3px var(--ring-glow); }
.crm-chat-context-search-empty { padding: 10px 2px; color: var(--text-3); font-size: 12px; line-height: 1.45; }
/* The filter sets `hidden` on rows and groups. The UA stylesheet's
   `[hidden] { display: none }` is a plain attribute selector, so ANY class
   rule that sets `display` beats it — and `.crm-chat-context-row` sets
   `display: flex`. Without these two rules the filter looks completely
   broken while being completely correct: the property is set, the element
   still paints. Class+attribute outranks the class alone, so no !important. */
.crm-chat-context-row[hidden] { display: none; }
.crm-chat-context-group[hidden] { display: none; }
/* Collapsible field groups — native <details>, so open/close and keyboard
   operation cost no JS. The head is the grey strip the benchmark uses to
   separate one folder of fields from the next. */
.crm-chat-context-group { border: 1px solid var(--border); border-radius: var(--radius-card); margin-bottom: 8px; overflow: hidden; }
.crm-chat-context-group-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 9px 12px; background: var(--bg-2); cursor: pointer;
  font-size: 11.5px; font-weight: 700; color: var(--text-2); line-height: 1.45;
  list-style: none; user-select: none;
}
/* Both halves are needed: `display: none` kills WebKit's marker, `list-style:
   none` kills Firefox's. Either alone leaves a stray triangle in one engine. */
.crm-chat-context-group-head::-webkit-details-marker { display: none; }
.crm-chat-context-group-head:hover { background: var(--bg-3); }
.crm-chat-context-group-head .lucide-ic { width: 13px; height: 13px; color: var(--icon-ink); transition: transform .15s ease; transform: rotate(-90deg); }
.crm-chat-context-group[open] > .crm-chat-context-group-head .lucide-ic { transform: rotate(0deg); }
.crm-chat-context-group-body { padding: 10px 12px 12px; background: var(--card); }
.crm-chat-context-group-body .crm-chat-context-rows { margin-top: 0; }
/* Avatar upload/remove (Task 5, 2026-07-28 crm-contact-avatars plan) — the
   `lg` contactAvatarHtml circle wrapped in a real <button> (mirrors
   .info-avatar-btn's group-avatar pattern, chat.js) with a small, ALWAYS-
   visible camera badge (not hover-only — this is a primary, frequent edit
   for a CRM contact, not a rare group-chat action, and hover has no touch
   equivalent) plus a separate small remove (X) badge, shown only when a
   photo already exists. crm-contacts.js's drawer reuses this exact block
   (`.crm-avatar-edit`), same shared-class idiom as `.crm-chat-context-*`
   itself (see the shared-block comment near .detail-panel-body above). */
.crm-avatar-edit { position: relative; display: inline-flex; margin-bottom: 10px; }
.crm-avatar.lg { width: 64px; height: 64px; font-size: 1.3rem; }
.crm-avatar-edit-trigger { position: relative; padding: 0; border: 0; background: transparent; cursor: pointer; border-radius: var(--radius-pill); line-height: 0; }
.crm-avatar-edit-trigger:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.crm-avatar-edit-btn {
  position: absolute; inset-inline-end: -2px; inset-block-end: -2px;
  width: 22px; height: 22px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  background: var(--primary-600); color: #fff; border: 2px solid var(--card);
}
.crm-avatar-edit-btn .lucide-ic { width: 12px; height: 12px; }
.crm-avatar-edit-remove {
  position: absolute; inset-inline-start: -2px; inset-block-end: -2px; padding: 0; cursor: pointer;
  width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  background: var(--danger); color: #fff; border: 2px solid var(--card);
}
.crm-avatar-edit-remove .lucide-ic { width: 11px; height: 11px; }
.crm-avatar-edit-remove:hover { filter: brightness(1.08); }
/* Assignee row (Task 3, 2026-07-28 crm-chat-karzoun-polish plan) — the
   thread-header's «إسناد» control relocated here, Karzoun-style («الوكيل
   المكلَّف»), right after the avatar block and before the name row. Label +
   select on one row, same idiom as `.crm-chat-context-row-account` below;
   the select itself is the SAME `.crm-chat-assign-select` node the header
   used to hold (id/class untouched, only its mount point moved) — this just
   lets it fill the row instead of the header's fixed 132–220px band.
   `.crm-chat-context-assign-slot` is a dedicated repaint target SIBLING of
   the label (whole-branch review fix) — paintAssignControl's innerHTML swap
   targets only this slot, so the label next to it survives every repaint
   instead of being wiped out by a whole-row overwrite. It has to carry its
   own `flex: 1 1 auto` (not just the select inside it) since a plain <div>
   is the actual flex ITEM here — the select's own flex declaration below
   only matters once ITS parent is a flex container too, which the slot
   becomes via `display: flex` right below. */
.crm-chat-context-assign { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.crm-chat-context-assign-label { flex: none; font-size: 11px; font-weight: 600; color: var(--text-3); white-space: nowrap; }
.crm-chat-context-assign-slot { display: flex; flex: 1 1 auto; min-width: 0; }
.crm-chat-context-assign-slot .crm-chat-assign-select { flex: 1 1 auto; min-width: 0; max-width: none; }
/* CSAT rating chip (Wave 3.3b Task 6) — reuses the app-wide `.chip` look
   (duwam.css, existing token-only pill) as-is; this rule only adds the
   spacing to sit between the assign row and the name row. Renders only
   when csatRatingChipHtml has a rating to show (see crm-chat.js). */
.crm-chat-context-csat { display: inline-flex; margin-bottom: 8px; }
.crm-chat-context-tags { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; margin-top: 8px; }
.crm-chat-context-tag-add {
  appearance: none; display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 50%; flex: none;
  border: 1px dashed var(--border-2); background: transparent; cursor: pointer; color: var(--text-3);
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.crm-chat-context-tag-add:hover { color: var(--primary-700); border-color: var(--primary-300); background: var(--accent-soft); }
.crm-chat-context-tag-add .lucide-ic { width: 11px; height: 11px; }
/* Name row — firstName/lastName as two "quiet" inline inputs replacing the
   old read-only heading; same Trello-inspired idiom as the task panel's
   .sp-title (transparent at rest, a soft fill on hover, the ring on
   focus) so the sidebar still reads as plain text until touched. */
.crm-chat-context-name-row { display: flex; gap: 6px; margin: 0 -6px; }
.crm-chat-context-name-input {
  flex: 1 1 50%; min-width: 0; font-size: 14px; font-weight: 600; color: var(--text);
  background: transparent; border: 1px solid transparent; border-radius: var(--radius-sm);
  padding: 3px 6px; font-family: inherit; outline: none;
  transition: background .15s ease, border-color .15s ease;
}
.crm-chat-context-name-input:hover { background: var(--bg-2); }
.crm-chat-context-name-input:focus { background: var(--bg-2); border-color: var(--ring); box-shadow: 0 0 0 3px var(--ring-glow); }
.crm-chat-context-name-input.is-invalid { border-color: var(--danger); }
.crm-chat-context-name-input::placeholder { color: var(--text-3); font-weight: 500; }
.crm-chat-context-rows { display: flex; flex-direction: column; gap: 7px; margin-top: 10px; }
.crm-chat-context-row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 12px; }
.crm-chat-context-row-edit { align-items: center; }
.crm-chat-context-row-label { flex: none; color: var(--text-3); }
/* Custom-field rows stack the label above the control — a tenant's label can
   be a full sentence, and the built-ins' side-by-side row would squeeze the
   control down to nothing.
   MUST stay below `.crm-chat-context-row` above: that rule sets
   `align-items: baseline`, this one needs `stretch`, and at equal specificity
   the later declaration wins. Declared earlier, every control here sizes to
   its own content instead of the group width — measured at 62px for an
   enhanced select against 167px for a textarea in the same panel.
   `.crm-chat-context-cf-control` is also given an explicit width because the
   controls are not always plain inputs: UI.enhanceSelect swaps a <select> for
   a `.uisel` div and the date field renders a `.dp-shell` span, neither of
   which a rule targeting `.input` can reach. */
.crm-chat-context-row-cf { display: flex; flex-direction: column; align-items: stretch; gap: 4px; }
.crm-chat-context-row-cf .crm-chat-context-row-label { color: var(--text-3); font-size: 11px; }
.crm-chat-context-cf-control { width: 100%; }
.crm-chat-context-cf-control > * { width: 100%; max-width: none; }
/* A custom field is a field, and it reads like the built-ins beside it: the
   generic `.input` chrome (a real bordered form control) is unset so the quiet
   idiom above — transparent at rest, a fill on hover, the ring on focus — is
   what shows. Without this the panel spoke two vocabularies: text for the six
   built-ins, boxed controls for everything the tenant defined, and the lower
   half was the half that looked like a form.
   `text-align: start`, not the built-ins' `end`: a stacked label+control row
   has the label above, so trailing the value would leave it floating away
   from the name it belongs to. */
.crm-chat-context-row-cf .crm-chat-context-input {
  width: 100%; font-size: 12px; text-align: start;
  background: transparent; border: 1px solid transparent; box-shadow: none;
}
.crm-chat-context-row-cf .crm-chat-context-input:hover { background: var(--bg-2); }
.crm-chat-context-row-cf .crm-chat-context-input:focus {
  background: var(--bg-2); border-color: var(--ring); box-shadow: 0 0 0 3px var(--ring-glow);
}
.crm-chat-context-row-cf .crm-chat-context-input::placeholder { color: var(--text-3); }
/* The em-dash placeholder must not shout — it marks an empty row, it is not a
   value. Same tone as a label. */
.crm-chat-context-input::placeholder { color: var(--text-3); }
.crm-chat-context-cf-archived {
  margin-inline-start: 5px; padding: 1px 5px; border-radius: var(--radius-pill);
  background: var(--bg-3); color: var(--text-3); font-size: 9.5px; font-weight: 600;
}
/* Phone/email/job-title — same quiet-until-focused input as the name row,
   right-aligned at rest to preserve the old label-leads/value-trails row
   layout, switching to the input's natural (start) alignment once focused
   so typing doesn't fight the caret's own direction. */
.crm-chat-context-input {
  flex: 1 1 auto; min-width: 0; text-align: end; font-size: 12px; color: var(--text);
  background: transparent; border: 1px solid transparent; border-radius: var(--radius-sm);
  padding: 3px 6px; font-family: inherit; outline: none;
  transition: background .15s ease, border-color .15s ease;
}
.crm-chat-context-input:hover { background: var(--bg-2); }
.crm-chat-context-input:focus { background: var(--bg-2); border-color: var(--ring); box-shadow: 0 0 0 3px var(--ring-glow); text-align: start; }
.crm-chat-context-input.is-invalid { border-color: var(--danger); }
/* Account row — an enhanced <select> (UI.enhanceSelect; same compact-
   override idiom as .crm-chat-assign-select below) sourced from
   GET /api/crm/accounts, plus a quick-create circle mirroring
   .crm-chat-context-tag-add's own dashed-circle shape. */
.crm-chat-context-row-account { align-items: center; }
.crm-chat-context-account-picker { display: flex; align-items: center; gap: 6px; flex: 1 1 auto; min-width: 0; }
.crm-chat-context-account-select { flex: 1 1 auto; min-width: 0; }
select.crm-chat-context-account-select.input,
.crm-chat-context-account-select .uisel-trigger.input {
  height: 28px; padding: 3px 8px; font-size: 12px; border-radius: var(--radius-sm);
}
.crm-chat-context-account-new {
  appearance: none; display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 50%; flex: none;
  border: 1px dashed var(--border-2); background: transparent; cursor: pointer; color: var(--text-3);
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.crm-chat-context-account-new:hover { color: var(--primary-700); border-color: var(--primary-300); background: var(--accent-soft); }
.crm-chat-context-account-new .lucide-ic { width: 12px; height: 12px; }
/* Save-on-dirty bar — hidden while pristine ([hidden], overridden below
   per the [hidden]+display gotcha every other toggled block in this file
   already works around — see .crm-chat-pending-attach[hidden] etc.); any
   field diff reveals it (syncContactEditBar). */
.crm-chat-context-savebar {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--border-2);
}
.crm-chat-context-savebar[hidden] { display: none; }
.crm-chat-context-savebar-err { flex: 1 1 auto; min-width: 0; font-size: 11px; color: var(--danger); }
.crm-chat-context-savebar-actions { display: flex; gap: 6px; flex: none; }
/* Quick action — navigates to the CRM contact record; full-width so the
   longer Arabic label never truncates at the sidebar's 300px width. */
/* (.crm-chat-context-actions retired in Phase 2 — its single «فتح ملف
   العميل» button moved into the identity card as .crm-chat-context-open.) */
.crm-chat-context-section { padding: 12px 14px; border-bottom: 1px solid var(--border); }
.crm-chat-context-section:last-child { border-bottom: 0; }
.crm-chat-context-section-head { font-size: 11px; font-weight: 700; color: var(--text-3); text-transform: uppercase; letter-spacing: .02em; margin-bottom: 8px; }
.crm-chat-context-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.crm-chat-context-empty { color: var(--text-3); font-size: 12px; padding: 4px 0; }
/* Deal row — name leads, stage + amount follow on a second line as small
   chips/tabular text (mirrors the kanban card's name-then-meta layout in
   crm-deals.js without pulling in its full card styling). */
.crm-chat-context-deal { display: flex; flex-direction: column; gap: 4px; }
.crm-chat-context-deal-name { font-size: 12.5px; font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crm-chat-context-deal-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.crm-chat-context-deal-stage {
  font-size: 10.5px; font-weight: 600; color: var(--primary-700);
  background: var(--accent-soft); border-radius: var(--radius-pill); padding: 1px 7px;
}
.crm-chat-context-deal-amount { font-size: 11px; color: var(--text-2); font-variant-numeric: tabular-nums; }
/* Activity row — a small type icon + one-line-clamped text, deliberately
   terser than the full crm-timeline.js widget (this is a glance, not the
   record's own activity tab). */
.crm-chat-context-activity { display: flex; align-items: flex-start; gap: 8px; }
.crm-chat-context-activity-ic { flex: none; color: var(--icon-ink); margin-top: 1px; }
.crm-chat-context-activity-ic .lucide-ic { width: 13px; height: 13px; }
.crm-chat-context-activity-text { font-size: 12px; color: var(--text-2); line-height: 1.45; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
/* (The three collapsed-state rules that hid the head text and body were
   retired in Phase 3 — `.crm-chat-context-main { display: none }` above hides
   the whole column, leaving exactly the rail.) */
@media (max-width: 640px) {
  .crm-chat { grid-template-columns: 1fr; height: calc(100vh - 180px); }
  /* `40vh` rather than a `%` — a percentage `max-height` on a grid item
     whose row track is auto-sized (implicit rows, as `.crm-chat` has no
     `grid-template-rows`) resolves against an indefinite reference and
     several engines collapse it to a near-zero used value; `vh` is always
     relative to the viewport, so it stays well-defined here regardless of
     row-track sizing (same ~38%-of-viewport intent, pinned to a definite
     basis). Pre-existing quirk on this exact rule, tightened while already
     touching this media query for the Task 3 header/status reflow. */
  .crm-chat-list { max-height: 40vh; }
  .crm-chat-context { display: none; }
  /* The thread header packs the name + waiting chip + status select in one
     row — stack everything as full-width blocks instead of trusting
     wrap-computed reflow. (Assign AND tags moved out of the header entirely
     — Tasks 3/4, 2026-07-28 crm-chat-karzoun-polish plan — so neither is
     part of this reflow; the contact panel they live in now is already
     hidden at this width via .crm-chat-context above. The 3-way segmented
     status control + standalone snooze button Task 4 replaced with a single
     select are gone too — one dense select never needed its own stacked
     reflow rule the way that wider nowrap group did.) */
  .crm-chat-head { flex-direction: column; align-items: stretch; }
  .crm-chat-status { width: 100%; }
  .crm-chat-status-select { max-width: none; flex: 1 1 auto; }
  /* `white-space: nowrap` on the sort/status segmented labels (kept for a
     crisp single-line pill at normal widths) is exactly what can force a
     narrow single-column layout to blow out wider than the viewport — a
     flex item's automatic min-width is its min-content, and forcing nowrap
     makes that min-content the WHOLE phrase instead of its widest word.
     Relax both here so the controls can wrap/shrink instead. */
  .crm-chat-sort-toggle { flex-wrap: wrap; }
  .crm-chat-sort-seg { white-space: normal; }
}

/* Contact page (Wave D Task 2, 2026-07-30 crm-contacts-wave-d plan) — the
   routable record page replacing the crm-contacts.js slide-in drawer.
   Task 2 ships the shell (head + .ws-tabs strip, already styled app-wide)
   and this overview PLACEHOLDER; Tasks 3/4 build the real edit surface and
   the remaining tabs, reusing these same classes. Logical properties only
   (margin-inline-start etc.) so the RTL-first layout never needs a mirrored
   rule. */
.crm-contact-head-row { display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap; margin-block-end: 1rem; }
.crm-contact-name { margin: 0; font-size: 1.35rem; font-weight: 700; color: var(--text); }
.crm-contact-account { color: var(--text-2); font-size: .9rem; }
.crm-contact-overview-grid {
  display: grid; grid-template-columns: max-content 1fr; gap: .5rem 1.25rem;
  margin: 0;
}
.crm-contact-overview-grid dt { color: var(--text-2); font-size: .85rem; font-weight: 600; }
.crm-contact-overview-grid dd { margin: 0; color: var(--text); font-size: .9rem; }
.crm-contact-overview .text-subtle { margin-block-start: 1rem; font-size: .82rem; }
.crm-contact-empty { display: flex; flex-direction: column; align-items: center; gap: 1rem; }

/* Task 3 — the real editable overview surface, reusing crm-chat.js's own
   .crm-chat-context-* profile/tags/rows/savebar classes (the drawer already
   does this exact reuse for its own edit block — see crm-contacts.js's
   contactDrawerBodyHtml comment) at page scale rather than sidebar scale.
   `.card` carries no padding of its own app-wide, so every card this page
   stacks (the stat strip / this editor / the custom-fields section) opts
   into it here, once. */
.crm-contact-overview { padding: 1.1rem 1.25rem; }
/* «أضف اسماً» — the rename affordance next to a phone-placeholder name.
   Deliberately NOT crm-ct-rename (that class is opacity:0 until a
   surrounding `tr:hover`, built for the contacts TABLE row — this page has
   no such row, so that affordance would just stay invisible here). Always
   visible, ghost-button styling. */
.crm-contact-rename {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  margin-inline-start: .5rem; padding: 0;
  font: inherit; font-size: .85rem; font-weight: 600;
  color: var(--primary-600);
}
.crm-contact-rename:hover, .crm-contact-rename:focus-visible { text-decoration: underline; }
.crm-contact-overview-actions { display: flex; gap: .6rem; margin-block-start: 1rem; padding-block-start: 1rem; border-block-start: 1px solid var(--border); }
/* Permanent erasure — reachable, never the thing a hand lands on by momentum.
   It sits beside «حذف», which is a filled red button, and two filled red
   buttons side by side is a coin toss with no undo on one face. So this one is
   TEXT: same danger hue, no fill, pushed to the far end away from the primary
   destructive control. Underlined on hover only, so it still reads as a
   deliberate control rather than a disabled label. */
.crm-contact-erase {
  margin-inline-start: auto;
  background: none; border: 0; padding: .4rem .2rem;
  font: inherit; font-size: .82rem; font-weight: 600;
  color: var(--danger); cursor: pointer; border-radius: var(--r-sm);
}
.crm-contact-erase:hover { text-decoration: underline; }
.crm-contact-erase:focus-visible { outline: 2px solid var(--danger); outline-offset: 2px; }

/* The phrase the operator must retype. dir="ltr" is set on the element itself
   because a phone number — the fallback for an unnamed lead — renders its digit
   groups reversed in an Arabic page, and nobody can retype what they are shown
   backwards. */
.crm-erase-phrase {
  /* INLINE and borderless, deliberately.
     The first cut was a full-width bordered box, which put two identical
     rounded rectangles one above the other: the sample to copy and the input
     to type into. On a dialog whose button destroys data with no undo, "which
     box do I type in" is not a question the design gets to leave open. Inline,
     unbordered and tinted, it reads as quoted text; the input below it is then
     the only thing on the dialog shaped like a field. */
  display: inline-block; margin-block: .35rem .55rem;
  padding: .2rem .5rem; border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  color: var(--text);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .95rem; font-weight: 600; letter-spacing: .01em;
  user-select: all;
}
.crm-erase-list { margin: .3rem 0 .8rem; padding-inline-start: 1.1rem; line-height: 1.6; }
.crm-erase-list li { margin-block: .15rem; }
.crm-erase-warn { color: var(--danger); font-weight: 600; line-height: 1.6; }
.crm-erase-receipt { margin: .6rem 0; padding-inline-start: 1.1rem; line-height: 1.7; }
.crm-erase-receipt b { font-variant-numeric: tabular-nums; }

/* Task 4 — conversation (read-only)/deals/activity tabs. Logical
   properties only, same RTL-first rule as the block above. */
.crm-contact-conv { padding: 1.1rem 1.25rem; display: flex; flex-direction: column; gap: 1rem; }
.crm-contact-conv-notice {
  display: flex; align-items: center; gap: .5rem; padding: .6rem .85rem;
  border-radius: var(--radius-sm); background: var(--surface-2); color: var(--text-2);
  font-size: .85rem;
}
.crm-contact-conv-notice .lucide-ic, .crm-contact-conv-notice svg { width: 16px; height: 16px; flex-shrink: 0; }
.crm-contact-conv-thread { display: flex; flex-direction: column; gap: .6rem; max-height: 26rem; overflow-y: auto; }
.crm-contact-conv-empty { color: var(--text-2); font-size: .9rem; text-align: center; padding: 1rem 0; }
.crm-contact-msg {
  max-width: 80%; padding: .55rem .75rem; border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--text); font-size: .9rem; line-height: 1.5;
}
.crm-contact-msg.in { align-self: flex-start; }
.crm-contact-msg.out { align-self: flex-end; background: var(--primary-50); }
.crm-contact-msg.note { align-self: stretch; max-width: 100%; background: color-mix(in srgb, var(--spark) 15%, var(--surface-2)); }
.crm-contact-msg-head { display: flex; align-items: center; gap: .4rem; font-size: .78rem; font-weight: 600; color: var(--text-2); margin-block-end: .25rem; }
.crm-contact-msg-head .lucide-ic, .crm-contact-msg-head svg { width: 14px; height: 14px; }
.crm-contact-msg-body { white-space: pre-wrap; word-break: break-word; }
.crm-contact-msg-att-img { max-width: 100%; max-height: 14rem; border-radius: var(--radius-sm); display: block; }
.crm-contact-msg-att-file { display: inline-flex; align-items: center; gap: .4rem; color: var(--primary-600); }
.crm-contact-msg-att-failed { display: inline-flex; align-items: center; gap: .4rem; color: var(--text-2); font-size: .85rem; }
.crm-contact-conv-open { align-self: flex-start; }

.crm-contact-deals { padding: 0; }
.crm-contact-deal-list { list-style: none; margin: 0; padding: 0; }
.crm-contact-deal-row {
  display: flex; align-items: center; gap: 1rem; padding: .8rem 1.25rem;
  border-block-end: 1px solid var(--border);
}
.crm-contact-deal-row:last-child { border-block-end: 0; }
.crm-contact-deal-name { flex: 1; font-weight: 600; color: var(--text); }
.crm-contact-deal-stage { color: var(--text-2); font-size: .85rem; }
.crm-contact-deal-amount { color: var(--text); font-weight: 600; font-size: .9rem; white-space: nowrap; }

/* Task 8 (2026-07-31 crm-campaigns-wave-a) — the campaigns section: list,
   builder, results. Logical properties only, same RTL-first rule as the
   CRM contact blocks above. `.callout`/`.callout-warn` is a small generic
   inline-notice component (the ban-risk notice is its first caller); reuse
   it for the next warn-toned banner rather than inventing another class. */
.callout {
  display: flex; align-items: flex-start; gap: .6rem;
  padding: .75rem 1rem; border-radius: var(--radius-sm); font-size: .85rem; line-height: 1.6;
}
.callout .lucide-ic, .callout svg { width: 18px; height: 18px; flex-shrink: 0; margin-block-start: .1rem; }
.callout-warn {
  background: var(--warn-bg); color: var(--warn);
  border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent);
}
/* Neutral/informational tone — Task 6 (2026-08-03 crm-lead-capture)'s
   source-attribution honesty notice is the first caller; distinct from
   -warn (amber/risk) since "this is best-effort, not a mistake" reads
   wrong in a risk color. */
.callout-info {
  background: var(--accent-soft); color: var(--info);
  border: 1px solid color-mix(in srgb, var(--info) 28%, transparent);
}

.crm-camp-list-head, .crm-camp-builder-head {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  margin-block-end: 1rem;
}
.crm-camp-builder-head h2 { flex: 1; margin: 0; font-size: 1.1rem; }
.crm-camp-builder, .crm-camp-results { display: flex; flex-direction: column; gap: 1.1rem; max-width: 42rem; }
.crm-camp-section { display: flex; flex-direction: column; gap: .6rem; }
.crm-camp-section h3 { margin: 0; font-size: .95rem; color: var(--text-2); }
.crm-camp-builder-actions { justify-content: flex-end; }

.crm-camp-open-link {
  background: none; border: 0; padding: 0; color: var(--text); font-weight: 600;
  cursor: pointer; text-align: start;
}
.crm-camp-open-link:hover { color: var(--primary-600); }

.crm-camp-step {
  display: flex; flex-direction: column; gap: .5rem; padding: .8rem;
  border: 1px solid var(--border); border-radius: var(--radius-field); background: var(--surface-2);
}
.crm-camp-step + .crm-camp-step { margin-block-start: .6rem; }
.crm-camp-step-head { display: flex; align-items: center; justify-content: space-between; }
.crm-camp-step-num {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.6rem; height: 1.6rem; border-radius: 999px; background: var(--primary-50);
  color: var(--primary-600); font-weight: 600; font-size: .8rem;
}
.crm-camp-step-wait { max-width: 16rem; }

.crm-camp-audience-summary { display: flex; flex-direction: column; gap: .6rem; }
.crm-camp-kpis, .crm-camp-audience-kpis { display: flex; gap: 1.2rem; flex-wrap: wrap; }
.crm-camp-kpi { display: flex; flex-direction: column; gap: .1rem; }
.crm-camp-kpi b { font-size: 1.3rem; font-variant-numeric: tabular-nums; }
.crm-camp-kpi span { font-size: .78rem; color: var(--text-2); }
.crm-camp-kpi.is-muted b { color: var(--text-2); }

.crm-camp-reason-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .3rem; }
.crm-camp-reason-row {
  display: flex; align-items: center; justify-content: space-between; gap: .6rem;
  padding: .35rem .1rem; font-size: .85rem; color: var(--text-2);
  border-block-end: 1px solid var(--border);
}
.crm-camp-reason-row:last-child { border-block-end: 0; }
.crm-camp-reason-row b { color: var(--text); font-variant-numeric: tabular-nums; }

.crm-camp-empty-hint { color: var(--text-2); font-size: .85rem; margin: 0; }
.crm-camp-stats { display: flex; flex-direction: column; gap: .8rem; }

/* ── Entry points (Task 6, 2026-08-03 crm-lead-capture) ───────────────────
   Same layout primitives as .crm-camp-* above, namespaced separately per
   this codebase's own duplication idiom (see crm-campaigns.js's header) —
   each feature file owns its classes even when nearly identical, rather
   than reaching across into a sibling feature's namespace. Logical
   properties only, same RTL-first rule as the rest of this file. */
.crm-ep-list-head, .crm-ep-builder-head {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  margin-block-end: 1rem;
}
.crm-ep-builder-head h2 { flex: 1; margin: 0; font-size: 1.1rem; }
.crm-ep-builder { display: flex; flex-direction: column; gap: 1.1rem; max-width: 34rem; }
.crm-ep-section { display: flex; flex-direction: column; gap: .6rem; }
.crm-ep-builder-actions { justify-content: flex-end; }
.crm-ep-empty-hint { color: var(--text-2); font-size: .85rem; margin: 0; line-height: 1.5; }

.crm-ep-open-link {
  background: none; border: 0; padding: 0; color: var(--text); font-weight: 600;
  cursor: pointer; text-align: start;
}
.crm-ep-open-link:hover { color: var(--primary-600); }

/* Deliverables panel — the URL/copy row, the QR image, and the embed
   snippet. The QR PNG itself has a plain white background/black modules
   (qrcode.toDataURL's default), which is unreadable dropped straight onto
   a dark canvas — .crm-ep-qr-img is boxed in a fixed white card so it
   stays scannable in both themes, never inheriting the page's own
   background. */
.crm-ep-deliverables { border-block-start: 1px solid var(--border); padding-block-start: 1.1rem; }
.crm-ep-deliverables h3 { margin: 0 0 .6rem; font-size: .95rem; color: var(--text-2); }
.crm-ep-copy-row { display: flex; gap: .5rem; align-items: center; }
.crm-ep-copy-row .input { flex: 1; font-size: .82rem; }
.crm-ep-qr-wrap {
  display: flex; flex-direction: column; align-items: center; gap: .6rem;
  padding: 1.1rem; margin-block-start: .8rem;
}
.crm-ep-qr-img {
  background: #fff; padding: 10px; border-radius: var(--radius-field);
  border: 1px solid var(--border); width: 180px; height: 180px;
}
.crm-ep-embed { display: flex; flex-direction: column; gap: .5rem; margin-block-start: 1rem; }
.crm-ep-embed-label { font-size: .82rem; color: var(--text-2); font-weight: 600; }
.crm-ep-embed-code {
  margin: 0; padding: .8rem; background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-field); font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  font-size: .78rem; line-height: 1.55; white-space: pre-wrap; word-break: break-all;
}

/* ── Entry points · form-kind builder (Task 11, 2026-08-03 crm-lead-capture)
   Field list + bilingual consent/success editors, same layout primitives as
   the block above. `.crm-ep-field-row` is a card per field, grid-laid so
   key/type/labels/required/options line up without a table for something
   this small. Logical properties only, same RTL-first rule as the rest of
   this file. */
.crm-ep-fields-list { display: flex; flex-direction: column; gap: .8rem; }
.crm-ep-field-row {
  border: 1px solid var(--border); border-radius: var(--radius-field);
  padding: .8rem; display: flex; flex-direction: column; gap: .6rem;
  background: var(--surface-2);
}
.crm-ep-field-row-top { display: flex; align-items: center; gap: .3rem; }
.crm-ep-field-num { font-size: .78rem; font-weight: 700; color: var(--text-2); min-width: 1.4rem; }
.crm-ep-field-up { margin-inline-start: auto; }
.crm-ep-field-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .6rem .8rem; }
.crm-ep-field-grid .crm-ep-field-required,
.crm-ep-field-grid .crm-ep-field-options { grid-column: 1 / -1; }
.crm-ep-field-required { display: flex; flex-direction: row; align-items: center; gap: .5rem; }
.crm-ep-field-required input[type="checkbox"] { width: 1rem; height: 1rem; accent-color: var(--accent); }
@media (max-width: 560px) {
  .crm-ep-field-grid { grid-template-columns: 1fr; }
}
/* Reserves a line even when empty so a key error appearing/disappearing
   never shifts the row beneath it. */
.crm-ep-field-err { display: block; min-height: 1.1em; font-size: .76rem; color: var(--danger); line-height: 1.5; }
.crm-ep-input-error { border-color: var(--danger); }
.crm-ep-fields-actions { display: flex; align-items: center; gap: .8rem; flex-wrap: wrap; }
.crm-ep-fields-max-hint { font-size: .8rem; color: var(--text-2); }

/* ── WhatsApp Status composer ─────────────────────────────────────────────
   The recipient count is the largest thing on the card, on purpose. It is the
   number of real human beings who will receive an unrecallable broadcast from
   the company's own number, and it has to be readable in the same glance as
   the publish button — not in a tooltip, and not after the fact. */
.wast-audience {
  margin-block-start: 1rem; padding: .8rem 1rem;
  border-radius: var(--radius); background: var(--surface);
  border: 1px solid var(--border);
  display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap;
}
.wast-audience strong { font-size: 1.9rem; line-height: 1; font-variant-numeric: tabular-nums; }
.wast-over { color: var(--danger); font-weight: 600; }
.wast-post { padding: .7rem 0; border-block-end: 1px solid var(--border); }
.wast-post:last-child { border-block-end: 0; }
.wast-post-text { line-height: 1.7; white-space: pre-wrap; }

/* Tag chips in the status composer. `.chip.active` did not exist, so selected
   and unselected tags rendered IDENTICALLY — on the one screen where the
   selection decides who receives an unrecallable broadcast. Keyed off
   aria-pressed so the visual state and the announced state are the same fact. */
.wast-tags { display: flex; flex-wrap: wrap; gap: .4rem; }
.wast-tags .chip { cursor: pointer; }
.wast-tags .chip[aria-pressed="true"] {
  background: var(--accent); color: var(--accent-fg);
  border-color: var(--accent); font-weight: 600;
}
