:root {
  /* Brand palette — sage-tinted neutral surfaces, 3-step depth (bg < panel < card) */
  --bg: #eef2ee; --panel: #f6f9f6; --card: #ffffff; --line: #dbe3db;
  --ink: #2f2c27; --text: #46433d; --muted: #6a6257;
  --brand: #14452f; --orange: #cc7a52; --teal: #3f8a8c; --gold: #d0a23f; --green: #6d8f5e; --whatsapp: #1fa855;
  --accent: #14452f; --accent2: #3f8a8c;
  --danger: #c0504d;
  /* Accessible text-on-tint for status pills (≥4.5:1 on their pale fills; borders keep the brighter brand hue) */
  --teal-ink: #2f6668; --orange-ink: #8f4d2c; --gold-ink: #7c5e10; --danger-ink: #a83f3a; --green-ink: #567046;
  --field: #ffffff;
  /* Chart palette (Team dashboard). NOT the brand hues verbatim: --teal reads gray as a data fill
     (OKLCH chroma .074, under the .10 floor), so the data teal is a chroma-corrected sibling. Validated
     as a pair on the white card surface — CVD ΔE 10.1, normal-vision 22.1, both ≥3:1 contrast. Two series
     is the hard cap here; a third hue fails the separation checks against these two. */
  --viz-1: #00939a; --viz-2: #cc7a52;
  --viz-dim: #c4c9c4;   /* de-emphasis: context marks + sparkline history */
  /* Pipeline stages new→negotiating: ORDINAL, so one hue light→dark (passes monotone-L + 2:1 light end).
     FIVE steps, not four: BOOK_STAGES (server.js:2342) is new|no_answer|contacted|viewing|negotiating,
     and StageBars paints segment i with --viz-st{i+1} — so the 5th resolved to an undefined property
     and the closest-to-closing segment rendered with no fill at all. st5 continues the same ramp. */
  --viz-st1: #86b7b9; --viz-st2: #62a2a4; --viz-st3: #3f8a8c; --viz-st4: #2f6668; --viz-st5: #22494b;

  /* ── Scales ─────────────────────────────────────────────────────────────────────────────────
     Added as the single source of truth for the redesign. Every value below is one that ALREADY
     appears in this file — the scales describe where the stylesheet already clusters, they don't
     move anything. Declarations get pointed at them as each area is reworked, so a rule that's
     about to be deleted doesn't get tokenized first. */

  /* Spacing. gap is already the most disciplined dimension here (4/6/8 cover 80% of uses). */
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px;

  /* Radius. 8/12/999/50% already cover 76 of 105 declarations; 16 is the mobile sheet's top corners. */
  --r1: 8px; --r2: 12px; --r3: 16px; --rp: 999px;

  /* Elevation. The 17 one-off shadows in this file are really these five tiers, verbatim — so
     swapping a declaration to a token is a no-op. Prune tiers once surfaces are consolidated. */
  --sh1: 0 1px 2px rgba(87,83,76,.05);      /* resting: chat bubble, stat surface */
  --sh2: 0 2px 8px rgba(0,0,0,.08);         /* nudge: pull-to-refresh badge */
  --sh3: 0 6px 18px rgba(64,60,53,.12);     /* lifted: card hover, tooltip */
  --sh4: 0 12px 30px rgba(0,0,0,.18);       /* menu: profile + assignee dropdowns */
  --sh5: 0 12px 40px rgba(60,52,42,.18);    /* overlay: modal sheet */

  /* Type. A contiguous ramp, not a sample: every step below is a size this file already used,
     and t4/t6 exist because 14px and 16px were the two most-used sizes in the stylesheet with no
     token to name them — which is why the old 7-step scale was adopted by 6 declarations out of 130.
     The half-steps it replaced (10/11.5/12.5/13.5/14.5/16.5) each snapped to the nearest step.
     Two display one-offs stay literal on purpose (.fc-phone 30px, .fc-done-check 34px): one use
     each, and a step nothing else reaches is a list entry, not a scale. */
  --t1: 11px; --t2: 12px; --t3: 13px;  /* micro · meta · control */
  --t4: 14px; --t5: 15px; --t6: 16px;  /* card title · body · section */
  --t7: 17px; --t8: 18px;              /* heading · figure */
  --t9: 20px; --t10: 22px; --t11: 24px; --t12: 26px;  /* display */

  /* Motion. --ease is the curve already used at .card hover and .msgrow entry; it becomes the only
     one. Six of the ten transitions in this file named no easing at all and silently inherited the
     browser default, which is a different curve. */
  --ease: cubic-bezier(.22,1,.36,1);
  --d1: 120ms;  /* hover, press, opacity */
  --d2: 160ms;  /* rows entering, card lift */
  --d3: 220ms;  /* sheets, drawers, bars filling */

  /* Layering. This contract used to live in a comment at the .bottomnav rule. */
  --z-sticky: 1; --z-topbar: 10; --z-nav: 15; --z-modal: 20; --z-pop: 30; --z-toast: 40;

  /* ── Office shell ────────────────────────────────────────────────────────────────────────────
     --nav-w is the live sidebar width and is what the shell grid reads; the rail breakpoint just
     reassigns it. --top-h doubles as the sidebar's sticky offset, so the two can never drift.
     --chrome-h is the vertical chrome the WhatsApp panes budget around: it replaces three
     hand-tuned magic numbers (150/190/220) that were tuned by eye against the old topbar and would
     have broken silently the moment the shell height changed. Values are the originals, so this
     is a no-op today and a safety net tomorrow. */
  --nav-w: 256px; --nav-rail: 56px; --top-h: 52px; --chrome-h: 150px;
}
/* One switch for every tokenized transition in the file — zeroing the durations opts the whole app out
   at once, so a new motion site can never forget to honour the OS setting. The two hand-written blocks
   further down stay: they kill a hover TRANSFORM and two looping keyframes, which a duration can't. */
@media (prefers-reduced-motion: reduce) { :root { --d1: 0ms; --d2: 0ms; --d3: 0ms; } }
* { box-sizing: border-box; }
/* Phone feel: no grey flash on tap, no 350ms double-tap-zoom delay on controls. */
html { -webkit-tap-highlight-color: transparent; }
button, a, input, select, textarea, label { touch-action: manipulation; }
body {
  margin: 0; background: var(--bg); color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: var(--t5);
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3 { font-weight: 700; letter-spacing: -.01em; }
button { font: inherit; cursor: pointer; }
input:not([type="file"]), textarea, select {
  font: inherit; width: 100%; padding: 10px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--field); color: var(--text);
}
input[type="file"] { font: inherit; padding: 6px 0; color: var(--muted); }
input:focus, textarea:focus, select:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 2px rgba(20,69,47,.15); }
input[type="checkbox"] { accent-color: var(--brand); }
/* Keyboard focus ring for all interactive controls (inputs use their own ring above). */
button:focus-visible, a:focus-visible, summary:focus-visible, [tabindex]:focus-visible, label.btn:focus-within { outline: 2px solid var(--brand); outline-offset: 2px; }
textarea { resize: vertical; min-height: 60px; }
/* Inline line-icons (Lucide) — inherit text color/size, align to the baseline of adjacent text. */
.ic { display: inline-block; vertical-align: -0.14em; flex-shrink: 0; }
label { display: block; margin: 10px 0 4px; color: var(--muted); font-size: var(--t3); }

.topbar {
  display: flex; align-items: center; gap: 10px; padding: 12px 16px;
  position: sticky; top: 0; background: var(--brand); z-index: var(--z-topbar);
  box-shadow: 0 1px 0 rgba(0,0,0,.08);
}
.topbar h1 { font-size: var(--t6); margin: 0; color: #fff; display: flex; align-items: center; gap: 10px; }
.topbar h1.brand-link { cursor: pointer; }
.topbar h1.brand-link:hover { opacity: .85; }
.brand-mark { width: 28px; height: auto; display: block; flex-shrink: 0; }
.topbar .who { color: rgba(255,255,255,.72); }
.topbar .linklike { color: rgba(255,255,255,.85); }
.topbar .linklike:hover { color: #fff; background: rgba(255,255,255,.14); }
.topbar .linklike.active { color: #fff; background: rgba(255,255,255,.14); }
/* The bar is filled with --brand, so the global brand focus ring (l.77) paints brand-on-brand — an
   invisible indicator on every topbar control, in both the office and field shells. Named per class
   rather than `.topbar :focus-visible`: .profile-menu and PasswordModal are DOM descendants of the
   topbar but render on light surfaces, and would trade one invisible ring for two. */
.topbar .linklike:focus-visible, .topbar .divtab:focus-visible, .topbar .profile-pill:focus-visible { outline-color: #fff; }
/* Topbar profile pill + dropdown */
.profile-wrap { position: relative; }
.profile-pill { display: flex; align-items: center; gap: 8px; background: rgba(255,255,255,.14); border: none; border-radius: 999px; padding: 4px 12px 4px 4px; color: #fff; font-size: var(--t3); font-weight: 600; cursor: pointer; }
.profile-pill:hover { background: rgba(255,255,255,.22); }
.profile-avatar { border-radius: 50%; object-fit: cover; display: block; flex-shrink: 0; }
.profile-avatar-fallback { border-radius: 50%; background: var(--teal); color: #fff; display: inline-flex; align-items: center; justify-content: center; font-weight: 700; flex-shrink: 0; }
.profile-menu { position: absolute; top: calc(100% + 8px); inset-inline-end: 0; background: var(--card); border: 1px solid var(--line); border-radius: var(--r2); box-shadow: var(--sh4); padding: 12px; width: 240px; z-index: var(--z-pop); }
.profile-menu-head { display: flex; align-items: center; gap: 10px; padding-bottom: 10px; margin-bottom: 8px; border-bottom: 1px solid var(--line); }
.profile-menu-name { font-weight: 700; color: var(--ink); font-size: var(--t4); }
.profile-menu-role { color: var(--muted); font-size: var(--t2); margin-top: 2px; }
.profile-menu-actions { display: flex; flex-direction: column; padding-bottom: 8px; margin-bottom: 8px; border-bottom: 1px solid var(--line); }
.profile-menu .linklike { justify-content: flex-start; display: flex; align-items: center; gap: 8px; width: 100%; text-align: start; color: var(--muted); }
.profile-menu .linklike:hover { color: var(--brand); background: var(--panel); }
/* Google Calendar row. The two actions are plain .linklike like every other row in this menu; these two
   rules only cover the bits that are text rather than a control — the connected address (which must
   ellipsis, not widen the 240px menu, because a Google address can be long) and the OAuth outcome. */
.profile-google-acct { display: flex; align-items: center; gap: 8px; padding: 8px 10px; color: var(--muted); font-size: var(--t2); }
.profile-google-acct span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.profile-google-msg { color: var(--brand); font-size: var(--t2); padding: 0 10px 8px; }
.tab { padding: 8px 14px; border-radius: 999px; border: 1px solid var(--line); background: transparent; color: var(--text); }
.tab.active { background: var(--brand); color: #fff; border-color: var(--brand); font-weight: 600; }
.linklike { background: none; border: none; color: var(--muted); padding: 8px 10px; border-radius: 8px; }
.linklike:hover { color: var(--brand); background: var(--panel); }
/* .linklike is disabled in real flows (profile-menu photo actions during upload, app.js:730-731; the
   call-list skip button, app.js:3748) but only .btn had a :disabled rule — so a disabled link looked
   and hovered exactly like a live one. :active is what makes it acknowledge a tap on a phone. */
.linklike:disabled { opacity: .55; cursor: default; }
.linklike:disabled:hover { color: var(--muted); background: none; }
.linklike:not(:disabled):active { background: var(--panel); }

/* Division switcher + sub-tabs */
.divswitch { display: flex; gap: 6px; background: var(--panel); border: 1px solid var(--line); border-radius: 999px; padding: 3px; }
.topbar .divswitch { background: rgba(255,255,255,.14); border-color: transparent; }
.divtab { padding: 7px 14px; border-radius: 999px; border: none; background: transparent; color: var(--text); font-weight: 600; }
.topbar .divtab { color: rgba(255,255,255,.8); }
.divtab.active { background: var(--teal); color: #fff; }
.who { color: var(--muted); font-size: var(--t3); font-weight: 600; }
.linklike.active { color: var(--brand); font-weight: 600; }
.editedby { font-size: var(--t1); font-style: italic; margin-top: 6px; }
.editedby .ago { cursor: help; opacity: 0.8; }

/* ══ Office shell: top bar + left sidebar + content column ══════════════════════════════════════
   Every class below is NEW. The field shell (FieldApp) renders .topbar, .linklike, .wrap, .tab,
   .divswitch and .divtab, and ~12 in-content controls reuse the same six — .divswitch alone has 11
   call sites, 8 of them in the field view. So none of them are restyled here; the office grows its
   own vocabulary instead and the Arabic RTL shell is left exactly as it was.
   This shell is LTR-only by construction (FieldApp is the RTL one and never renders .sidenav), but
   the rules still use logical properties so nothing has to be revisited if that ever changes. */
.shell { display: grid; grid-template-columns: var(--nav-w) minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); min-height: 100dvh; }

/* Light bar, not the old brand-filled one: the sidebar is now what carries "place", and a dark bar
   above a tinted rail is two competing chrome surfaces. Keeps .linklike's default ink readable and
   lets the global brand focus ring land visibly, so it needs no white-ring override of its own. */
.shell-top { grid-column: 1 / -1; position: sticky; top: 0; z-index: var(--z-topbar);
  display: flex; align-items: center; gap: var(--s3); min-height: var(--top-h);
  padding: var(--s2) var(--s4); padding-top: calc(var(--s2) + env(safe-area-inset-top));
  background: var(--card); border-bottom: 1px solid var(--line); }
.shell-brand { display: flex; align-items: center; gap: var(--s2); margin: 0;
  font-size: var(--t7); color: var(--ink); cursor: pointer; }
.shell-brand:hover { opacity: .85; }
.shell-burger { display: none; align-items: center; justify-content: center; width: 44px; height: 44px;
  border: 1px solid var(--line); border-radius: var(--r1); background: var(--card); color: var(--text); cursor: pointer; }

/* Sticky under the top bar, scrolling independently so a long admin group never pushes Home away. */
/* height, not max-height + align-self:start: with the latter the rail is only as tall as its items,
   so its --panel surface stopped mid-page and the page background showed through beneath it. */
.sidenav { grid-column: 1; position: sticky; top: var(--top-h); align-self: start;
  height: calc(100dvh - var(--top-h)); overflow-y: auto;
  display: flex; flex-direction: column; gap: 2px;
  padding: var(--s3) var(--s2) var(--s5);
  background: var(--panel); border-inline-end: 1px solid var(--line); }
.sidenav-item { position: relative; display: flex; align-items: center; gap: var(--s3); width: 100%;
  min-height: 40px; padding: var(--s2) var(--s3); border: none; border-radius: var(--r1);
  background: none; color: var(--text); font: inherit; font-size: var(--t5); font-weight: 500;
  text-align: start; cursor: pointer;
  transition: background-color var(--d1) var(--ease), color var(--d1) var(--ease); }
.sidenav-item:hover { background: var(--card); color: var(--ink); }
/* Active is ONE mechanism plus a weight bump: a surface tint lifting the row off --panel onto
   --card. No side stripe, no coloured label, no filled brand block — the loud version of this is
   what makes a sidebar feel like a toolbar. */
.sidenav-item.active { background: var(--card); color: var(--ink); font-weight: 600; box-shadow: inset 0 0 0 1px var(--line); }
.sidenav-ic { display: inline-flex; flex: none; }
.sidenav-txt { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The label anchors its group instead of floating above it: a hairline runs from the word to the
   edge of the rail, and the padding is asymmetric (24 above, 8 below) so the heading belongs to the
   rows beneath it rather than sitting equidistant between two groups. */
.sidenav-label { display: flex; align-items: center; gap: var(--s2);
  padding: var(--s5) var(--s3) var(--s2); color: var(--muted);
  font-size: var(--t1); font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
.sidenav-label::after { content: ""; flex: 1 1 auto; height: 1px; background: var(--line); }
.sidenav-scrim { display: none; }

.shell-main { grid-column: 2; min-width: 0; }
/* Same max-width and centring as .wrap (l.195) so the strip lines up with the content beneath it.
   The pair is still two literals; they are commented on both sides rather than tokenised, because
   .wrap is shared with the field shell and must not gain a dependency the field view also inherits. */
.subnav { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2);
  width: 100%; max-width: 1100px; margin: 0 auto; padding: var(--s3) var(--s4) 0; }
.subnav-item { min-height: 36px; padding: var(--s2) var(--s3); border: none; border-radius: var(--r1);
  background: none; color: var(--muted); font: inherit; font-size: var(--t5); font-weight: 500; cursor: pointer;
  transition: background-color var(--d1) var(--ease), color var(--d1) var(--ease); }
.subnav-item:hover { background: var(--panel); color: var(--ink); }
.subnav-item.active { background: var(--panel); color: var(--ink); font-weight: 600; }

/* WCAG 2.4.1 Bypass Blocks. First focusable element on the page; a sidebar is exactly the kind of
   repeated block this criterion exists for. */
.skiplink { position: absolute; inset-inline-start: -9999px; top: 0; z-index: var(--z-toast);
  padding: var(--s2) var(--s4); background: var(--brand); color: #fff; font-weight: 600;
  border-end-end-radius: var(--r1); }
.skiplink:focus { inset-inline-start: 0; }

/* Tablet: collapse to an icon rail. Scoped to a RANGE so the drawer block below never has to undo
   it. Labels are clipped rather than removed, and each row keeps its `title` so hovering still
   names it — NN/g's data is that hidden navigation costs ~39% on desktop task time, so this is a
   deliberate tradeoff at a width where 256px of chrome costs more than it returns, and never the
   default at full width. */
@media (min-width: 1025px) and (max-width: 1280px) {
  :root { --nav-w: var(--nav-rail); }
  .sidenav { padding-inline: var(--s1); }
  .sidenav-item { justify-content: center; gap: 0; padding-inline: 0; min-height: 44px; }
  .sidenav-txt { display: none; }
  /* The group headings can't fit the rail, but simply hiding them runs all eleven icons together as
     one undifferentiated column. Collapse each to a hairline so the Divisions/Desk split survives;
     font-size:0 hides the words without removing them from the accessibility tree. */
  .sidenav-label { display: block; height: 1px; width: 24px; margin: var(--s2) auto; padding: 0;
    background: var(--line); font-size: 0; overflow: hidden; }
  /* The group rule IS the hairline at this width — the ::after would double it. */
  .sidenav-label::after { display: none; }
  /* A count is unreadable at rail width, so the badge degrades to a dot: same "something is
     waiting" signal, no false precision. */
  .sidenav-item .review-badge { position: absolute; top: 3px; inset-inline-end: 3px;
    width: 8px; height: 8px; min-width: 0; padding: 0; font-size: 0; line-height: 0; }
}

/* Below 1024 the boards are already stacking, which is where a permanent sidebar stops paying for
   itself: it becomes an off-canvas drawer over the content. */
@media (max-width: 1024px) {
  .shell { grid-template-columns: minmax(0, 1fr); }
  .shell-main { grid-column: 1; }
  .shell-burger { display: inline-flex; }
  .sidenav { position: fixed; inset-block: 0; inset-inline-start: 0; width: 280px; max-height: none;
    z-index: var(--z-nav); transform: translateX(-100%);
    transition: transform var(--d3) var(--ease); box-shadow: var(--sh4);
    padding-top: calc(var(--s3) + env(safe-area-inset-top));
    padding-inline-start: calc(var(--s2) + env(safe-area-inset-left)); }
  .nav-open .sidenav { transform: translateX(0); }
  /* Between the top bar and the drawer, so the scrim dims the WHOLE page — at --z-sticky it sat
     under .shell-top and left an undimmed strip that still looked interactive. Not a new token:
     the drawer owns --z-nav here and the office shell never co-renders with the field bottom nav. */
  .nav-open .sidenav-scrim { display: block; position: fixed; inset: 0;
    z-index: calc(var(--z-nav) - 1); background: rgba(60,52,42,.45); }
  .sidenav-item { min-height: 44px; }
}
/* The rail and drawer both animate position; honour the OS setting. --d3 is already zeroed by the
   :root reduced-motion block, but the transform itself still needs cancelling. */
@media (prefers-reduced-motion: reduce) { .sidenav { transition: none; } }

/* Photos */
/* aspect-ratio, not max-height: with only a max the box is 0px tall until the image decodes and then
   jumps to 120px, so every Owners/Buildings kanban card reflows as photos land. The explicit 64x64 on
   the next line still wins for .list/.area-card (width+height beat aspect-ratio). */
.photo-thumb { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; border-radius: 8px; margin-bottom: 8px; border: 1px solid var(--line); }
.list .photo-thumb, .area-card .photo-thumb { width: 64px; height: 64px; margin: 0; }
.photo-edit { max-width: 100%; max-height: 220px; border-radius: 10px; border: 1px solid var(--line); }
/* Gallery cells: fixed-size thumbnails inside a cell, each with a corner remove button. */
.photo-cell { position: relative; display: inline-block; }
.photo-cell .photo-edit { width: 96px; height: 96px; object-fit: cover; display: block; }
.photo-x { position: absolute; top: -6px; inset-inline-end: -6px; width: 20px; height: 20px; border-radius: 50%; border: none; background: var(--danger); color: #fff; cursor: pointer; line-height: 20px; padding: 0; }
.photo-x::before { content: ''; position: absolute; top: 50%; left: 50%; width: 44px; height: 44px; transform: translate(-50%, -50%); }
.review-video { width: 100%; max-height: 360px; border-radius: 10px; border: 1px solid var(--line); background: #000; margin-top: 4px; }

/* Maps — Airbnb shape/elevation language on the hub palette (single soft shadow tier). */
:root { --map-shadow: rgba(0,0,0,.02) 0 0 0 1px, rgba(0,0,0,.04) 0 2px 6px, rgba(0,0,0,.1) 0 4px 8px; }
.mapview { width: 100%; height: 70vh; min-height: 380px; border-radius: 14px; border: 1px solid var(--line); box-shadow: var(--map-shadow); overflow: hidden; }
.pinmap { width: 100%; height: 220px; border-radius: 14px; border: 1px solid var(--line); box-shadow: var(--map-shadow); overflow: hidden; margin-top: 4px; }

/* Plain pin markers (Google's PinElement, brand-colored) — .wasl-pin is our own hook class
   added alongside PinElement's internal markup, used only for the hover highlight below. */
.wasl-pin { cursor: pointer; transition: filter var(--d1) var(--ease), transform var(--d1) var(--ease); }
.wasl-pin:hover, .wasl-pin.is-hover { filter: brightness(0.85); transform: scale(1.12); }

/* Building pins: a small photo + name chip, centered on the coordinate (no pointer tail). */
.wasl-chip { display: flex; align-items: center; gap: 6px; transform: translate(-50%, -50%); background: #fff; border-radius: 999px; box-shadow: var(--map-shadow); padding: 4px 10px 4px 4px; cursor: pointer; transition: transform var(--d1) var(--ease), box-shadow var(--d1) var(--ease); white-space: nowrap; max-width: 220px; }
.wasl-chip:hover { transform: translate(-50%, -50%) scale(1.05); }
.wasl-chip-photo { width: 24px; height: 24px; border-radius: 50%; object-fit: cover; flex-shrink: 0; display: block; }
.wasl-chip-photo-placeholder { display: flex; align-items: center; justify-content: center; background: var(--panel); color: var(--muted); }
.wasl-chip-name { font-size: var(--t2); font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; }

/* Airbnb-style zoom control: a single white pill, +/- stacked, replacing Google's default. */
.wasl-zoomctl { display: flex; flex-direction: column; width: 34px; border-radius: 8px; background: #fff; box-shadow: var(--map-shadow); overflow: hidden; margin: 0 10px 10px 0; }
.wasl-zoomctl-btn { width: 34px; height: 34px; border: none; background: #fff; color: var(--ink); font-size: var(--t8); line-height: 1; cursor: pointer; display: flex; align-items: center; justify-content: center; }
.wasl-zoomctl-btn:hover { background: var(--panel); }
.wasl-zoomctl-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.wasl-zoomctl-btn + .wasl-zoomctl-btn { border-top: 1px solid var(--line); }

/* Popup card — Airbnb listing-card: rounded photo, ink title, full-width action. */
.gm-style-iw-c { border-radius: 14px !important; box-shadow: var(--map-shadow) !important; padding: 12px !important; }
.gm-style-iw-d { overflow: hidden !important; }
.map-popup { width: 200px; text-align: left; }
.map-popup-img { width: 100%; height: 120px; object-fit: cover; border-radius: 12px; border: none; display: block; margin-bottom: 8px; }
.map-popup-name { font-weight: 600; font-size: var(--t5); color: var(--ink); letter-spacing: -.01em; margin-bottom: 8px; }
/* Tap-a-pin-on-a-phone path; was ~35px. .gm-style-iw-c autosizes to the taller content. */
.map-popup .btn { width: 100%; padding: 9px 14px; font-size: var(--t4); border-radius: 8px; min-height: 44px; display: flex; align-items: center; justify-content: center; }
.map-popup-add { margin-top: 6px; }

/* Cluster bubble (grouped pins when zoomed out). */
.wasl-cluster {
  display: flex; align-items: center; justify-content: center; transform: translate(-50%, -50%);
  width: 34px; height: 34px; border-radius: 50%; background: var(--brand); color: #fff;
  font-weight: 700; font-size: var(--t3); box-shadow: var(--map-shadow); cursor: pointer;
}

.wrap { padding: 16px; max-width: 1100px; margin: 0 auto; }
.wrap h2 { color: var(--ink); font-size: var(--t10); margin: 8px 0 14px; }
.btn { padding: 10px 16px; border-radius: 8px; border: none; background: var(--brand); color: #fff; font-weight: 600;
  transition: filter var(--d1) var(--ease), transform var(--d1) var(--ease); }
.btn:hover { filter: brightness(.96); }
/* :hover does nothing on a touchscreen and l.60 removes the native tap flash, so without this the
   primary button — 140 call sites, including the field call-outcome buttons — acknowledged a tap with
   nothing at all. Every sibling control (.sendbtn, .fc-bigbtn, .status-chip) already had a press state.
   The --d1 token is zeroed by the reduced-motion switch at l.57, so this opts out for free. */
.btn:active:not(:disabled) { transform: scale(.98); filter: brightness(.92); }
.btn.secondary { background: var(--panel); color: var(--text); border: 1px solid var(--line); }
.btn.danger { background: transparent; color: var(--danger); border: 1px solid var(--danger); }
.row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.spacer { flex: 1; }

.board { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; align-items: start; }
.project-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 14px; }
.col { background: var(--panel); border: 1px solid var(--line); border-radius: 14px; padding: 6px 8px 10px; position: relative; overflow: hidden; transition: background-color var(--d1) var(--ease), border-color var(--d1) var(--ease); }
/* Stage-colored top accent so the pipeline reads left → right (Spotted → Listed) */
.board .col::before { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 3px; background: var(--st, var(--line)); }
.board .col:nth-child(1) { --st: var(--gold); }
.board .col:nth-child(2) { --st: var(--orange); }
.board .col:nth-child(3) { --st: var(--teal); }
.board .col:nth-child(4) { --st: var(--green); }
/* 5-stage variant (Outreach): To contact → Contacted → Brochure sent → In conversation → Done */
.board.cols5 { grid-template-columns: repeat(5, 1fr); }
.board.cols5 .col:nth-child(1) { --st: var(--muted); }
.board.cols5 .col:nth-child(2) { --st: var(--gold); }
.board.cols5 .col:nth-child(3) { --st: var(--orange); }
.board.cols5 .col:nth-child(4) { --st: var(--teal); }
.board.cols5 .col:nth-child(5) { --st: var(--green); }
.col h3 { margin: 0 0 8px; padding: 10px 4px 9px; font-size: var(--t7); font-weight: 800; color: var(--ink); text-transform: none; letter-spacing: -.01em; border-bottom: 1px solid var(--line); }
.card { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 12px; margin-bottom: 9px; transition: transform var(--d2) var(--ease), border-color var(--d2) var(--ease), box-shadow var(--d2) var(--ease), opacity var(--d2) var(--ease); cursor: pointer; }
.card:hover { transform: translateY(-2px); border-color: var(--brand); box-shadow: var(--sh3); }
.card[draggable="true"] { cursor: grab; }
.card.dragging { opacity: .45; cursor: grabbing; transform: scale(.98); }
.col.drag-over { background: color-mix(in srgb, var(--st, var(--brand)) 10%, var(--panel)); border-color: var(--st, var(--brand)); border-style: dashed; }
/* .area-card shares this markup (name + meta rows) but matched neither rule, so every tenant row and
   review-queue item rendered its name at plain body weight — no hierarchy at all. Selector extension,
   declarations unchanged. overflow-wrap: .col is overflow:hidden for the stage accent, so a long
   unbroken token (a pasted URL, a spaceless Arabic project name) was sliced off mid-word. */
.card .name, .area-card .name { font-weight: 700; color: var(--ink); font-size: var(--t4); letter-spacing: -.01em; overflow-wrap: anywhere; }
.card .meta, .area-card .meta { color: var(--muted); font-size: var(--t2); margin-top: 4px; }
.card .price { margin: 10px 0 2px; display: flex; align-items: baseline; gap: 5px; }
.card .price b { font-size: var(--t8); font-weight: 800; color: var(--ink); letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.card .price span { font-size: var(--t1); color: var(--muted); font-weight: 600; }
.assignee { display: flex; align-items: center; gap: 6px; font-size: var(--t2); color: var(--text); font-weight: 600; }
.card .assignee { margin-top: 9px; }
.client-card .avatar { width: 34px; height: 34px; font-size: var(--t5); font-weight: 700; }
.avatar.consumer { background: linear-gradient(135deg, var(--teal), var(--teal-ink)); box-shadow: 0 2px 6px rgba(63,138,140,.30); }
/* Commercial/office tenants: gold, so a company reads as neither the brand-green investor nor the teal resident. */
.avatar.commercial { background: linear-gradient(135deg, var(--gold), var(--gold-ink)); box-shadow: 0 2px 6px rgba(208,162,63,.30); }
.client-card .name { margin-bottom: 3px; }
.pill.investor { background: rgba(20,69,47,.13); color: var(--brand); border-color: rgba(20,69,47,.4); }
.pill.consumer { background: rgba(63,138,140,.13); color: var(--teal-ink); border-color: rgba(63,138,140,.4); }
.pill.commercial { background: rgba(208,162,63,.13); color: var(--gold-ink); border-color: rgba(208,162,63,.4); }
.client-phone-row { margin-top: 9px; }
.call-link { display: inline-flex; align-items: center; gap: 5px; padding: 3px 8px; border-radius: var(--rp); transition: background var(--d1) var(--ease), color var(--d1) var(--ease); }
.call-link:hover { background: var(--teal); color: #fff; text-decoration: none; }
/* Outreach: Discarded lives in a tucked-away side strip — collapsed to a slim tab, still a drop target. */
.board.cols5.has-discard { grid-template-columns: repeat(5, 1fr) 44px; }
.board.cols5.has-discard.discard-open { grid-template-columns: repeat(5, 1fr) 1fr; }
.board.cols5.has-discard .col:nth-child(6) { --st: var(--danger); }
.col.discard.collapsed { display: flex; align-items: flex-start; justify-content: center; min-height: 180px; padding: 12px 2px; }
.discard-tab { background: none; border: none; cursor: pointer; color: var(--muted); font: inherit; font-size: var(--t2); font-weight: 700; display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 0; }
.discard-tab span { writing-mode: vertical-rl; letter-spacing: .02em; }
.discard-tab:hover { color: var(--ink); }
/* Dated note log (outreach modal) + latest-note preview on cards */
.note-log { margin-top: 8px; max-height: 180px; overflow-y: auto; display: grid; gap: 6px; }
.note-entry { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 6px 8px; font-size: var(--t3); }
.note-entry .meta { font-size: var(--t2); margin-bottom: 2px; }
.card .note-preview { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mini { width: 21px; height: 21px; border-radius: 50%; background: var(--teal); color: #fff; font-size: var(--t1); font-weight: 700; display: grid; place-items: center; object-fit: cover; flex-shrink: 0; }
.assignee-stack { display: inline-flex; }
.assignee-stack .stack-item { display: block; margin-inline-start: -8px; }
.assignee-stack .stack-item:first-child { margin-inline-start: 0; }
.assignee-stack .mini { border: 2px solid var(--card); }
.assignee-picker { position: relative; }
.assignee-picker-btn { display: flex; align-items: center; gap: 8px; width: 100%; background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 7px 10px; font-size: var(--t3); font-weight: 600; color: var(--text); cursor: pointer; text-align: start; }
.assignee-picker-btn:hover { border-color: var(--brand); }
.assignee-picker-btn > span:nth-child(2) { flex: 1; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.assignee-menu { position: absolute; top: calc(100% + 6px); left: 0; right: 0; background: var(--card); border: 1px solid var(--line); border-radius: var(--r2); box-shadow: var(--sh4); padding: 6px; max-height: 260px; overflow-y: auto; z-index: var(--z-pop); }
.assignee-row { display: flex; align-items: center; gap: 8px; padding: 7px 8px; border-radius: 8px; cursor: pointer; font-size: var(--t3); color: var(--text); }
.assignee-row:hover { background: var(--panel); }
.assignee-row input { width: auto; flex-shrink: 0; margin: 0; padding: 0; }
/* People-filter variant: single-select assignee picker sitting inline in board toolbars */
.assignee-filter .assignee-picker-btn { width: auto; max-width: 240px; }
.assignee-filter .assignee-menu { right: auto; min-width: 210px; }
/* Filter rows are real buttons (keyboard-focusable) — strip button chrome so they match the label rows */
button.assignee-row { width: 100%; background: none; border: 0; font: inherit; text-align: start; }
.assignee-row.selected { background: var(--panel); font-weight: 700; }
/* The :root duration override already zeroes .card's transition; this only has to cancel the lift. */
@media (prefers-reduced-motion: reduce) { .card:hover { transform: none; } }
.pill { display: inline-block; padding: 3px 9px; border-radius: 999px; font-size: var(--t1); font-weight: 600; border: 1px solid var(--line); }
/* Red count on the Review nav button — draws the data master's eye when captures are waiting. */
.review-badge { display: inline-block; min-width: 18px; padding: 1px 6px; margin-inline-start: 6px; border-radius: 999px; background: var(--danger); color: #fff; font-size: var(--t1); font-weight: 700; line-height: 16px; text-align: center; vertical-align: middle; }
.pill.signed { background: rgba(63,138,140,.13); color: var(--teal-ink); border-color: rgba(63,138,140,.4); }
.pill.per_deal { background: rgba(204,122,82,.14); color: var(--orange-ink); border-color: rgba(204,122,82,.42); }
.pill.ready { background: rgba(109,143,94,.15); color: var(--green-ink); border-color: rgba(109,143,94,.4); }
.pill.build { background: rgba(208,162,63,.15); color: var(--gold-ink); border-color: rgba(208,162,63,.45); }
.pill.partner { background: rgba(20,69,47,.10); color: var(--brand); border-color: rgba(20,69,47,.35); }
/* available ≡ rented and reserved ≡ vacant — they were byte-identical rules, now one each. */
.pill.unit-available, .pill.unit-rented { background: rgba(63,138,140,.14); color: var(--teal-ink); border-color: var(--teal); }
.pill.unit-reserved, .pill.unit-vacant { background: rgba(204,122,82,.16); color: var(--orange-ink); border-color: var(--orange); }
.pill.unit-sold { background: rgba(192,80,77,.14); color: var(--danger-ink); border-color: var(--danger); }

/* supply↔demand matches: an investor's budget band + district → the listed properties that fit */
.pill.match-pill { background: rgba(109,143,94,.15); color: var(--green-ink); border-color: rgba(109,143,94,.45); margin-inline-end: 6px; }
.match-wrap { margin-top: 14px; }
.match-hint { padding: 10px 0; }
.matchlist { border: 1px solid var(--line); border-radius: 12px; overflow: hidden; background: var(--card); }
.match-head { padding: 9px 12px; font-size: var(--t2); font-weight: 700; color: var(--green-ink); background: rgba(109,143,94,.1); border-bottom: 1px solid var(--line); }
.match-row { display: flex; align-items: center; gap: 10px; padding: 9px 12px; border-top: 1px solid var(--line); }
.match-row:first-of-type { border-top: 0; }
/* The اقترح buttons are tapped mid-call inside FieldClientSheet; their inline minHeight:34px was
   shrinking .btn below the floor. Base .btn is ~40px, so this rule is load-bearing, not belt-and-braces. */
.matchlist .btn, .matchlist .linklike { min-height: 44px; }
/* Project files organizer: thumbnail grids per section/unit + a sticky move bar for the selected file.
   Buttons (not divs) so keyboard users can select a file too; 74px tiles = a real ≥44px hit area. */
.files-grid { display: flex; flex-wrap: wrap; gap: 8px; }
.files-thumb { position: relative; width: 74px; height: 74px; border-radius: 8px; overflow: hidden;
               border: 2px solid var(--line); padding: 0; background: var(--card); cursor: pointer; }
.files-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.files-thumb.sel { border-color: var(--brand); box-shadow: 0 0 0 2px rgba(20,69,47,.25); }
.files-video { display: flex; align-items: center; justify-content: center; color: var(--muted); }
.files-video a { color: inherit; display: flex; }
.files-add { margin-inline-start: auto; font-size: var(--t2); cursor: pointer; }
.files-movebar { position: sticky; bottom: -18px; margin: 12px -18px -18px; padding: 10px 18px;
                 background: var(--panel); border-top: 1px solid var(--line);
                 display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
/* This bar IS the touch path (l.291 says drag is desktop-only), so its actions must clear the floor.
   Base .btn is ~40px — removing the inline minHeight:34px alone would not get there. */
.files-movebar .btn { min-height: 44px; }
/* Drag & drop (desktop pointer; touch keeps the tap → move-bar flow). Zones mirror the kanban columns. */
.files-zone { border-radius: 10px; }
.files-zone.drag-over { outline: 2px dashed var(--brand); outline-offset: 2px; background: rgba(20,69,47,.06); }
.files-thumb.dragging { opacity: .45; }

/* Project → its matching units. The header carries the project; the rows under it are the apartments,
   one of which the client actually takes. */
.match-group { border-top: 1px solid var(--line); }
.match-group:first-of-type { border-top: 0; }
.match-grouphead { display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap;
                   padding: 10px 12px; background: var(--panel); }
.match-group .match-row { padding-inline-start: 26px; }
.match-ic { color: var(--teal); flex: none; }
.match-name { font-weight: 600; color: var(--ink); flex: 1; min-width: 0; }
.match-price { font-size: var(--t2); color: var(--muted); white-space: nowrap; }
.match-price b { color: var(--ink); }
.ro-wrap { border: none; padding: 0; margin: 0; min-width: 0; }
.maplink { color: var(--teal); text-decoration: none; font-size: var(--t3); }
.maplink:hover { text-decoration: underline; }

.area-card { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 14px; margin-bottom: 10px; }
/* The review queue, Deals, Tenants and the Team roster are clickable .area-card rows that gave no
   feedback but the OS cursor. Scoped to [role="button"] — cardA11y (app.js:1680) sets that role, and
   the six non-clickable .area-card call sites (empty states, Users, Areas) must not light up.
   Border-colour, not the .card lift: the full-width-row convention here is .action-row:hover. */
.area-card[role="button"] { transition: border-color var(--d1) var(--ease), background-color var(--d1) var(--ease); }
.area-card[role="button"]:hover { border-color: var(--brand); background: var(--card); }
.area-card[role="button"]:active { background: var(--panel); }
.stat-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-bottom: 10px; }
.stat-card { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 14px; text-align: center; }
.stat-n { font-size: var(--t12); font-weight: 700; color: var(--ink); }
.stat-l { font-size: var(--t2); color: var(--muted); margin-top: 2px; }
@media (max-width: 560px) { .stat-row { grid-template-columns: repeat(2, 1fr); } }

/* Dashboard (landing) */
.dash-hero { background: var(--panel); border: 1px solid var(--line); border-radius: 14px; padding: 22px 24px; margin-bottom: 18px; }
.dash-eyebrow { display: flex; align-items: center; gap: 6px; color: var(--teal); font-size: var(--t3); font-weight: 700; }
.dash-hero h2 { color: var(--ink); font-size: var(--t11); margin: 8px 0; letter-spacing: -.01em; }
.dash-lead { color: var(--text); font-size: var(--t5); line-height: 1.6; max-width: 70ch; margin: 0; }
.dash-h { display: flex; align-items: center; gap: 8px; color: var(--ink); font-size: var(--t5); font-weight: 700; margin: 22px 0 12px; }
.dash-days { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.day-card { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 14px; }
.day-card.today { border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand); }
.day-top { display: flex; align-items: center; gap: 6px; color: var(--muted); }
.day-name { font-weight: 700; color: var(--ink); }
/* rhythmCards is shared markup: LTR office dashboard (app.js:6935) AND the dir=rtl field report
   (app.js:3910). A physical left-auto put the "today" pill against the text in RTL. */
.day-top .pill { margin-inline-start: auto; }
.day-theme { font-weight: 600; color: var(--ink); margin: 6px 0 4px; }
.dash-targets { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.target-row { display: flex; align-items: flex-start; gap: 12px; background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 14px; }
.target-ic { color: var(--teal); display: inline-flex; padding-top: 1px; }
.target-title { font-weight: 700; color: var(--ink); }
@media (max-width: 720px) { .dash-days { grid-template-columns: repeat(2, 1fr); } .dash-targets { grid-template-columns: 1fr; } }
@media (max-width: 460px) { .dash-days { grid-template-columns: 1fr; } }
.prio { font-weight: 700; color: var(--gold); }

/* Home's set-once material (role blurb, weekly rhythm, unmeasured goals) behind a native <details>.
   NOTE: none of .dash-hero / .dash-eyebrow / .dash-days / .day-card / .dash-targets / .target-row /
   .stat-row / .stat-card were deleted when Home stopped using them — FieldReport renders all of them
   in the Arabic RTL field view, and .dash-days / .dash-targets are still used inside this disclosure. */
.dash-more { margin-top: var(--s5); border-top: 1px solid var(--line); padding-top: var(--s3); }
.dash-more > summary { display: flex; align-items: center; gap: var(--s2); min-height: 44px;
  color: var(--muted); font-size: var(--t3); font-weight: 700; cursor: pointer; }
.dash-more > summary:hover { color: var(--ink); }
.dash-more[open] > summary { margin-bottom: var(--s2); }
/* The "review" tag on a merged row must not be squeezed by a long record name. */
.action-row .pill { flex: none; }

/* Dashboard "what needs follow-up" action list */
.dash-actions { display: flex; flex-direction: column; gap: 8px; }
.action-row { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left; background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 12px 14px; cursor: pointer; color: var(--text); font: inherit; }
.action-row:hover { border-color: var(--brand); }
.action-ic { color: var(--teal); display: inline-flex; flex: none; }
.action-main { flex: 1 1 auto; min-width: 0; }
.action-name { font-weight: 700; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.action-row > svg:last-child { color: var(--muted); flex: none; }

/* Goals repeater + weekly chip editor — shared by the Users modal and the My-week-&-goals modal */
.goal-edit { display: flex; flex-direction: column; gap: 8px; margin-bottom: 4px; }
/* Wraps because a measured goal carries up to six controls — on a laptop modal they'd otherwise crush the
   number inputs below a usable width. */
.goal-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.goal-row .goal-label { flex: 2 1 180px; margin: 0; }
.goal-row .goal-cadence { flex: 1 1 0; margin: 0; }
.goal-row .goal-metric { flex: 1 1 190px; margin: 0; }
.goal-row .goal-project { flex: 1 1 130px; margin: 0; }
.goal-row span.goal-project { display: flex; }
.goal-row span.goal-project input { width: 100%; margin: 0; }
.goal-row span.goal-project.meta { flex: 0 1 auto; align-items: center; }
.goal-row .goal-target { flex: 0 0 74px; margin: 0; }
.goal-row .goal-per, .goal-row .goal-dir { flex: 0 1 auto; margin: 0; }

/* --- Weekly scorecard (goals with numbers) --- */
.score-lane { margin-bottom: 14px; }
.score-lane-h { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin: 0 0 6px; color: var(--ink); font-size: var(--t4); }
.score-row { display: flex; align-items: center; gap: 14px; background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; margin-bottom: 6px; }
/* Status rides the bar fill + its status word, with no side stripe and no full-card tint: side-stripe cards
   are a named anti-reference (PRODUCT.md), and a column of solid red/green blocks reads as an alarm on a
   page that's opened every morning. The bar already carries the colour, and the word carries it for anyone
   who can't see the colour. */
.score-main { flex: 1 1 auto; min-width: 0; }
.score-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 5px; }
.score-title { font-weight: 600; color: var(--ink); font-size: var(--t4); }
.score-bar { height: 6px; border-radius: 999px; background: var(--bg); overflow: hidden; }
/* scaleX, not width: animating width relayouts the row on every +1. --p is the 0–1 fill set inline. */
.score-fill { height: 100%; width: 100%; border-radius: 999px; background: var(--line); transform-origin: left; transform: scaleX(var(--p, 0)); transition: transform var(--d3) var(--ease); }
.score-row.green .score-fill { background: var(--green); }
.score-row.amber .score-fill { background: var(--gold); }
.score-row.red .score-fill { background: var(--danger); }
.score-row .score-main .meta { font-size: var(--t2); color: var(--muted); }
.score-row.green .score-main .meta { color: var(--green-ink); }
.score-row.amber .score-main .meta { color: var(--gold-ink); }
.score-row.red .score-main .meta { color: var(--danger-ink); }
/* The trend strip: one box per week, oldest → newest. Deliberately tiny — it's peripheral vision for "am I
   trending up", not a chart to read precisely. */
.score-wks { display: flex; gap: 3px; flex: none; }
.wk-dot { display: flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: 4px; background: var(--bg); border: 1px solid var(--line); color: var(--muted); font-size: var(--t1); font-weight: 600; }
.wk-dot.has { background: rgba(109,143,94,.16); color: var(--green-ink); border-color: rgba(109,143,94,.4); }
.wk-dot.now { outline: 2px solid var(--brand); outline-offset: 1px; }
.score-act { flex: none; display: flex; gap: 6px; }
/* 44px both ways — PRODUCT.md's floor, and these are the fat-finger +1 taps on a phone. */
.score-act .btn { min-width: 44px; min-height: 44px; }
/* `.meta` has no base rule in this stylesheet (only `.card .meta`), so outside a card it inherits its
   parent's ink and size — the lane note would read as loud as the lane title. Scoped here rather than adding
   a global `.meta`, which would restyle 133 unrelated call sites. */
.score-lane-h .meta, .score-note, .score-week { color: var(--muted); font-size: var(--t2); font-weight: 400; }
.score-note { margin-top: 6px; }
@media (max-width: 640px) {
  .score-row { flex-wrap: wrap; gap: 8px; }
  .score-wks { order: 3; }
}
/* 44px floor (PRODUCT.md:45,53), already enforced at .score-act .btn (l.392) and .wa-back (l.908).
   .iconbtn was 36px and .btn.sm ~28px — and .btn.sm is the only edit affordance on every exec row.
   Both are already inline-flex centred, so the content does not move; only the hit area grows. */
.iconbtn { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; border: 1px solid var(--line); border-radius: 8px; background: var(--card); color: var(--muted); cursor: pointer; }
.iconbtn:hover { color: var(--danger); border-color: var(--danger); }
.btn.sm { padding: 6px 12px; font-size: var(--t3); align-self: flex-start; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }
.week-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 12px; }
.week-chip { padding: 8px 14px; border: 1px solid var(--line); border-radius: 999px; background: var(--card); color: var(--muted); font-weight: 700; font-size: var(--t3); cursor: pointer; min-height: 44px; }
.week-chip.on { background: var(--brand); border-color: var(--brand); color: #fff; }
.wk-cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.wk-card { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 12px; }
.wk-card-top { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.wk-card .wk-day { font-weight: 700; color: var(--ink); display: inline-flex; align-items: center; gap: 5px; }
.wk-card select { width: auto; flex: none; margin: 0; }
.wk-card input { margin: 0 0 6px; }
.wk-card input:last-child { margin-bottom: 0; }
@media (max-width: 560px) { .wk-cards { grid-template-columns: 1fr; } }

.modal-bg { position: fixed; inset: 0; background: rgba(60,52,42,.45); display: flex; align-items: flex-start; justify-content: center; padding: 20px; overflow: auto; z-index: var(--z-modal); }
.modal { background: var(--panel); border: 1px solid var(--line); border-radius: 14px; padding: 18px; width: 100%; max-width: 520px; box-shadow: var(--sh5); }
.modal h2 { margin: 0 0 8px; font-size: var(--t7); color: var(--ink); }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.grid3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; }
/* dvh, not vh: this hosts the login form and the boot "Loading…" — the first things anyone sees — and
   on mobile Safari 100vh is the address-bar-retracted height, so the centred card sat low and shifted
   as the bar collapsed. The WhatsApp panes already use dvh (l.880+); this is the same call. */
.center { min-height: 100dvh; display: flex; align-items: center; justify-content: center; }
.login { width: 100%; max-width: 320px; padding: 24px; }
.login h1 { color: var(--brand); display: flex; align-items: center; justify-content: center; gap: 10px; }
.err { color: var(--danger); font-size: var(--t3); margin-top: 8px; }
/* "It exists now" — shown when a New-building modal stays open on the record it just created. */
.saved-note { display: flex; align-items: center; gap: 6px; color: var(--green-ink); background: rgba(109,143,94,.12); border-radius: var(--r1); padding: 7px 10px; font-size: var(--t3); font-weight: 600; margin-top: 8px; }

/* --- Import (Gemini/Drive) --- */
.import-grid { display: grid; grid-template-columns: 280px 1fr; gap: 14px; align-items: start; }
.filelist { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 10px; }
.filelist h3 { margin: 4px 4px 10px; font-size: var(--t2); color: var(--teal); text-transform: uppercase; letter-spacing: .06em; }
.fileitem { background: var(--card); border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px; margin-bottom: 6px; cursor: pointer; }
.fileitem:hover { border-color: var(--brand); }
.fileitem.active { border-color: var(--brand); box-shadow: 0 0 0 2px rgba(20,69,47,.15); }
.fileitem .name { font-weight: 600; color: var(--ink); font-size: var(--t4); word-break: break-word; }
.chatpane { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 14px; min-height: 200px; }
.chat { display: flex; flex-direction: column; gap: 10px; margin-bottom: 12px; padding: 2px; scroll-behavior: smooth; }
.msgrow { display: flex; align-items: flex-end; gap: 8px; max-width: 88%; animation: msgin var(--d3) var(--ease) both; }
.msgrow.ai { align-self: flex-start; }
.msgrow.me { align-self: flex-end; flex-direction: row-reverse; }
.avatar {
  flex: none; width: 28px; height: 28px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; font-size: var(--t4);
  background: linear-gradient(135deg, var(--brand), var(--orange)); color: #fff;
  box-shadow: 0 2px 6px rgba(20,69,47,.30);
}
.msg { padding: 10px 13px; border-radius: 14px; white-space: pre-wrap; line-height: 1.45; word-break: break-word; }
.msgrow.ai .msg { background: var(--card); border: 1px solid var(--line); border-bottom-left-radius: 4px; box-shadow: var(--sh1); }
.msgrow.me .msg { background: var(--brand); color: #fff; border-bottom-right-radius: 4px; box-shadow: 0 2px 8px rgba(20,69,47,.22); }
.msg strong { font-weight: 700; }
.msg code { background: rgba(87,83,76,.10); border-radius: 5px; padding: 1px 5px; font-size: .92em; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.msgrow.me .msg code { background: rgba(255,255,255,.22); }
.msg .li { display: block; padding-left: 16px; position: relative; margin: 2px 0; }
.msg .li::before { content: "•"; position: absolute; left: 4px; color: var(--orange); }
.msgrow.me .msg .li::before { color: rgba(255,255,255,.85); }

/* Typing indicator */
.msg.typing { display: inline-flex; gap: 4px; align-items: center; padding: 13px; }
.msg.typing span { width: 6px; height: 6px; border-radius: 50%; background: var(--muted); animation: blink 1.2s infinite ease-in-out both; }
.msg.typing span:nth-child(2) { animation-delay: .2s; }
.msg.typing span:nth-child(3) { animation-delay: .4s; }

/* Chat input bar */
.chatbar { display: flex; gap: 8px; align-items: center; margin-top: 10px; }
.chatbar input { flex: 1; width: auto; min-width: 0; border-radius: 999px; padding: 11px 16px; }
.sendbtn {
  flex: none; width: 42px; height: 42px; border-radius: 50%; border: none;
  background: var(--brand); color: #fff; font-size: var(--t9); line-height: 1;
  display: flex; align-items: center; justify-content: center;
  transition: transform var(--d1) var(--ease), filter var(--d1) var(--ease), opacity var(--d1) var(--ease);
}
.sendbtn:hover:not(:disabled) { filter: brightness(.96); transform: translateY(-1px); }
.sendbtn:active:not(:disabled) { transform: scale(.92); }
.sendbtn:disabled { opacity: .4; cursor: default; }

@keyframes msgin { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes blink { 0%, 80%, 100% { opacity: .25; transform: translateY(0); } 40% { opacity: 1; transform: translateY(-3px); } }
@media (prefers-reduced-motion: reduce) {
  .msgrow { animation: none; }
  .msg.typing span { animation: none; opacity: .5; }
  .chat { scroll-behavior: auto; }
}
.proposal { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 14px; margin-top: 8px; }
.proposal h3 { margin: 0 0 8px; color: var(--orange); font-size: var(--t5); }
.matchwarn { background: rgba(204,122,82,.14); border: 1px solid var(--orange); color: var(--orange); border-radius: 8px; padding: 6px 10px; font-size: var(--t3); margin-bottom: 10px; }
.btn:disabled { opacity: .55; cursor: default; }

/* Embedded "read a brochure or a pasted message and fill this form" helper inside record modals.
   It used to be a bare text link between two form rows; it is now the offered shortcut at the top of
   the New-building modal — the only WhatsApp-paste surface in the office — so it reads as a panel. */
.capfill { margin: 4px 0 16px; border: 1px dashed var(--line); border-radius: var(--r2); background: var(--panel); }
.capfill-toggle { display: flex; align-items: center; gap: var(--s2); width: 100%; padding: 12px var(--s3); background: transparent; border: none; border-radius: var(--r2); text-align: start; color: var(--orange); font-weight: 600; font-size: var(--t4); cursor: pointer; }
.capfill-toggle > span { flex: 1; }
.capfill-toggle.open { border-bottom: 1px dashed var(--line); border-end-start-radius: 0; border-end-end-radius: 0; }
.capfill-body { padding: 0 var(--s3) var(--s3); }
.capfill-body label:first-child { margin-top: var(--s2); }
.capfill .chat { max-height: 220px; overflow-y: auto; }

/* Units read out of a brochure/message, listed before they exist — written on Save. */
.staged { border: 1px solid var(--line); border-radius: var(--r2); padding: var(--s3); background: var(--panel); }
.staged-row { display: flex; align-items: center; gap: var(--s2); padding: 5px 0; font-size: var(--t3); }
.staged-row + .staged-row { border-top: 1px solid var(--line); }
.staged-row > span { flex: 1; }

/* Grouping for the long office record modals: a labelled block instead of a flat run of fields. */
.formblock { border: 0; padding: 0; margin: 0 0 var(--s4); }
.formblock > .fc-steplabel { margin-bottom: var(--s2); padding-bottom: 6px; border-bottom: 1px solid var(--line); }
.formblock label:first-of-type { margin-top: 0; }

/* Flat "all entries" table */
.rectable { width: 100%; border-collapse: collapse; font-size: var(--t3); }
.rectable th, .rectable td { text-align: start; padding: 8px 10px; border-bottom: 1px solid var(--line); white-space: nowrap; }
.rectable th { color: var(--teal); text-transform: uppercase; letter-spacing: .05em; font-size: var(--t1); }
.rectable tbody tr { cursor: pointer; }
.rectable tbody tr:hover { background: var(--panel); }
/* The rows are focusable now (app.js RecordsTable). The global ring at l.77 draws an outline around
   the row box, which a table row clips awkwardly — tint the row instead, matching :hover. */
.rectable tbody tr:focus-visible { background: var(--panel); outline: 2px solid var(--brand); outline-offset: -2px; }
.rectable td.name { font-weight: 600; color: var(--ink); }

/* Tablet: 4/5-col Kanban boards get crushed before the 760px full-stack breakpoint — step down to 2
   columns first. Discard strip keeps its collapsed-44px/open-1fr trailing column at this width too.
   1100, not the old 1024: the sidebar now takes width out of the content column, so the boards run
   out of room at a WIDER viewport than they used to. Derived from the 56px rail plus .wrap's 2x16px
   padding — at a 1080px viewport the content column is back down to the ~1024px this rule was
   originally written for, so it steps a little early rather than a little late. */
@media (max-width: 1100px) {
  .board, .board.cols5 { grid-template-columns: repeat(2, 1fr); }
  .board.cols5.has-discard { grid-template-columns: repeat(2, 1fr) 44px; }
  .board.cols5.has-discard.discard-open { grid-template-columns: repeat(2, 1fr) 1fr; }
}

@media (max-width: 760px) {
  body { overflow-x: hidden; }

  /* Topbar: wrap divswitch to its own full-width row. env(safe-area-inset-top) covers the
     notch/status bar once the app runs standalone from the home screen (viewport-fit=cover). */
  .topbar { flex-wrap: wrap; padding: 10px 12px; row-gap: 6px; padding-top: calc(10px + env(safe-area-inset-top)); }
  /* 5 divisions don't fit a phone width — scroll the pill row instead of crushing the labels. */
  .divswitch { order: 10; width: 100%; min-width: 0; overflow-x: auto; scrollbar-width: none; }
  .divswitch::-webkit-scrollbar { display: none; }
  .divtab { flex: none; white-space: nowrap; text-align: center; padding: 8px 12px; font-size: var(--t3); }
  .spacer { display: none; }
  .who { font-size: var(--t2); }
  .subnav { padding: var(--s2) var(--s3) 0; gap: 6px; }
  .subnav-item { min-height: 44px; }
  .tab { padding: 7px 10px; font-size: var(--t4); }
  /* These sat at 33-36px and the rules above make them smaller, not larger — while the field shell
     reuses .topbar/.linklike for its language, Office View and Logout buttons, tapped one-handed
     outdoors. PRODUCT.md's 44px floor, applied where the rest of the file already applies it. */
  .linklike, .tab, .divtab, .profile-pill { min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }
  /* .profile-menu rows keep their own left-aligned layout (l.107 wins on specificity) — they only
     want the 44px. */
  .wrap { padding: 10px 12px; }

  /* Board / grids (.board.cols5 outranks the bare .board rule, so name it here too) */
  .board, .board.cols5, .board.cols5.has-discard, .board.cols5.has-discard.discard-open { grid-template-columns: 1fr; }
  /* On one column the discard strip is a full-width row — lay the tab out horizontally */
  .col.discard.collapsed { min-height: auto; padding: 10px; justify-content: flex-start; }
  .discard-tab { flex-direction: row; }
  .discard-tab span { writing-mode: horizontal-tb; }
  .grid2 { grid-template-columns: 1fr; }
  .grid3 { grid-template-columns: 1fr; }
  .import-grid { grid-template-columns: 1fr; }

  /* Modal: full-width, slide up from bottom */
  .modal-bg { padding: 0; align-items: flex-end; }
  .modal { border-radius: 16px 16px 0 0; max-height: 92vh; overflow-y: auto; }
  /* .modal is the scroll container here, so the title would scroll away — pin it, and pull the negative
     margins out to the padding edges so the sticky bar covers the full sheet width. Back gesture,
     backdrop tap and Escape all close; this only keeps "where am I" visible while scrolling.
     COUPLED: the -18px here and on .actions below must equal `.modal { padding }`. Change one without
     the others and a gutter opens at each edge of the sticky bar with the backdrop showing through —
     phone-only, so it ships unnoticed. Do not tokenize .modal's padding without these two. */
  .modal h2 { position: sticky; top: 0; z-index: var(--z-sticky); background: var(--panel);
    margin: -18px -18px 8px; padding: 18px 18px 10px; }

  /* Chat messages can go nearly full width on small screens */
  .msgrow { max-width: 94%; }

  /* iOS zooms the whole page when a focused control renders under 16px — match it and the jump disappears. */
  input:not([type="file"]), textarea, select { font-size: 16px; }

  /* Long record modals: keep Save / Approve / Reject visible instead of two screens down. */
  .modal .actions { position: sticky; bottom: 0; z-index: var(--z-sticky); background: var(--panel);
    margin: 8px -18px -18px; padding: 10px 18px calc(10px + env(safe-area-inset-bottom));
    border-top: 1px solid var(--line); }
}

/* ── Field bottom nav: thumb-reachable, RTL-safe, 5 destinations ── */
.fieldwrap { padding-bottom: calc(64px + env(safe-area-inset-bottom)); }
/* Above page content, below .modal-bg so open sheets aren't covered by the nav. The whole layering
   contract now lives as --z-* tokens in :root rather than as raw numbers scattered per rule. */
.bottomnav { position: fixed; inset-inline: 0; bottom: 0; z-index: var(--z-nav); display: flex;
  background: var(--card); border-top: 1px solid var(--line);
  padding-bottom: env(safe-area-inset-bottom); box-shadow: 0 -1px 10px rgba(0, 0, 0, .05); }
.bottomnav button { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 3px;
  min-height: 56px; padding: 8px 4px; background: transparent; border: none; cursor: pointer;
  color: var(--muted); font-weight: 600; font-size: var(--t1); }
.bottomnav button .ic { width: 22px; height: 22px; }
.bottomnav button.active { color: var(--brand); }
/* Colour was the only thing marking the current destination — invisible to a red-green colour-blind
   worker, since --brand and --muted differ mainly in hue. A shape cue costs no layout shift (a
   font-weight bump would reflow the label). inset-inline, so it flips with the RTL field view. */
.bottomnav button { position: relative; }
.bottomnav button.active::before { content: ''; position: absolute; top: 0; inset-inline: 22%; height: 2px;
  background: currentColor; border-radius: 0 0 2px 2px; }
.bottomnav button:active { background: var(--panel); }
.bottomnav button:focus-visible { outline: 2px solid var(--brand); outline-offset: -3px; border-radius: 8px; }

/* Home quick-links into the other tabs */
.field-quick { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin: 4px 0 18px; }
.field-quick button { display: flex; flex-direction: column; align-items: center; gap: 6px; min-height: 44px;
  padding: 14px 8px; border: 1px solid var(--line); border-radius: 12px; background: var(--card);
  color: var(--text); font-weight: 700; font-size: var(--t3); cursor: pointer; }
.field-quick button .ic { width: 22px; height: 22px; color: var(--brand); }
.field-quick button:active { filter: brightness(.97); }

/* Client cards + quick sheet: search, call/WhatsApp, one-tap status chips */
.client-search { width: 100%; margin: 2px 0 14px; }
.client-actions { display: flex; gap: 8px; margin-top: 10px; }
.client-actions a { flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 46px; border-radius: 10px; font-weight: 700; font-size: var(--t5); color: #fff; text-decoration: none; }
.client-actions a:active { filter: brightness(.96); }
.act-call { background: var(--brand); }
.act-wa { background: var(--whatsapp); }
.act-nav { background: var(--teal); }
/* The latest note, at the top of the client sheet. The one thing on this screen that must be read before
   anything is tapped, so it gets a tinted surface and a brand rule down its leading edge — the same
   left-rule + pale-fill vocabulary .match-head already uses, one weight louder. Body type, not meta type:
   this is content, and the log below it is where the grey small print lives. */
.latest-update { margin-top: var(--s3); padding: var(--s2) var(--s3); background: rgba(20,69,47,.06);
  border: 1px solid var(--line); border-inline-start: 3px solid var(--brand); border-radius: var(--r1); }
.latest-update-h { display: flex; align-items: center; gap: var(--s1); color: var(--brand);
  font-size: var(--t1); font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
.latest-update-text { margin-top: 3px; color: var(--ink); font-size: var(--t6); font-weight: 600; line-height: 1.4; white-space: pre-wrap; }
.latest-update-meta { margin-top: var(--s1); color: var(--muted); font-size: var(--t2); }
/* Nothing written yet is a quiet fact, not an empty box shouting for attention. */
.latest-update.none { background: none; border-inline-start-color: var(--line); color: var(--muted); font-size: var(--t3); }
.status-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 12px; }
.status-chip { min-height: 42px; padding: 9px 15px; border-radius: 999px; border: 1px solid var(--line);
  background: var(--panel); color: var(--text); font-weight: 600; font-size: var(--t4); cursor: pointer; }
.status-chip:active { filter: brightness(.96); }
.status-chip:disabled { opacity: .55; cursor: default; }
.status-chip.on { background: var(--brand); color: #fff; border-color: var(--brand); }
/* The two exits from a client list (back to the pool / discard). Held below a rule, on their own tinted
   surface: they are the only hard-to-take-back buttons on a screen whose every other control is a chip
   the thumb has just been tapping. Same 44px floor as the chips — this half is used in a car. */
.drop-actions { margin-top: var(--s4); padding-top: var(--s3); border-top: 1px solid var(--line); }
/* Sized to their labels, not 50/50: إرجاع لقائمة الاتصال is three times the width of استبعاد, and an
   equal split wrapped it onto two lines with the icon stranded at the far edge. Both fit one row at 390px. */
.drop-actions .btn { min-height: 44px; display: inline-flex; align-items: center; gap: var(--s2); }
.drop-panel { margin-top: var(--s4); padding: var(--s3); border: 1px solid var(--line); border-radius: var(--r2); background: var(--panel); }
.drop-panel .fc-steplabel { margin-bottom: var(--s2); font-size: var(--t5); }
.drop-panel > .meta { margin-bottom: var(--s3); }
.drop-panel input { width: 100%; }
.drop-panel .btn { min-height: 44px; }
.pill.lost { background: rgba(192,80,77,.14); color: var(--danger-ink); border-color: var(--danger); }
/* لم يرد: muted on purpose — nothing has happened yet. Still needs its own fill so a caller scanning
   عملائي can pick out who to ring back, which a bare .pill (same as جديد) wouldn't allow. */
.pill.no-answer { background: rgba(106,98,87,.12); color: var(--muted); border-color: rgba(106,98,87,.42); }

/* "Clients for this building" — matched-client rows with call/WhatsApp */
.matched-clients { display: grid; gap: 8px; }
.matched-client { display: flex; align-items: center; gap: 10px; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: 10px; background: var(--card); }
.matched-actions { display: flex; gap: 8px; flex: none; margin-inline-start: auto; }
.matched-actions a { display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border-radius: 10px; color: #fff; text-decoration: none; }
/* The اقترح button sits in this row beside those 44px call/WhatsApp links; it had an inline
   minHeight:34px and would otherwise fall back to base .btn (~40px). */
.matched-actions .btn { min-height: 44px; }

/* ── Field Worker capture: Arabic, RTL, big touch targets ── */
.field-capture { max-width: 560px; margin: 0 auto; }
.fc-title { margin: 4px 0 18px; font-size: var(--t10); text-align: center; }
.fc-step { background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 16px; margin-bottom: 16px; }
.fc-steplabel { font-weight: 700; font-size: var(--t6); margin-bottom: 12px; display: flex; align-items: center; gap: 8px; }
.fc-num { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border-radius: 50%; background: var(--teal); color: #fff; font-size: var(--t4); flex: none; }
.fc-hint { font-weight: 500; font-size: var(--t2); color: var(--orange); }
/* "or" rule between two equal sources inside one capture step (photograph it / paste the message). */
.fc-or { display: flex; align-items: center; gap: var(--s3); margin: var(--s4) 0 var(--s3); color: var(--muted); font-size: var(--t2); text-transform: uppercase; letter-spacing: .06em; }
.fc-or::before, .fc-or::after { content: ''; flex: 1; height: 1px; background: var(--line); }
.fc-sublabel { display: flex; align-items: center; gap: 6px; font-weight: 700; font-size: var(--t5); margin-bottom: var(--s2); }
.fc-btnrow { display: flex; gap: 10px; flex-wrap: wrap; }
.fc-bigbtn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; cursor: pointer;
  background: var(--brand); color: #fff; font-weight: 700; font-size: var(--t7); padding: 16px 20px; border-radius: 12px; border: none; flex: 1; min-width: 130px; }
.fc-bigbtn:active { filter: brightness(.95); transform: scale(.99); }
.fc-bigbtn.fc-video { background: var(--teal); }
.fc-btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; cursor: pointer;
  background: var(--panel); color: var(--text); border: 1px solid var(--line); font-weight: 600; font-size: var(--t5); padding: 14px 16px; border-radius: 12px; flex: 1; min-width: 130px; }
.fc-input { width: 100%; margin-top: 10px; padding: 13px 14px; border-radius: 10px; text-align: start; }
.fc-geo { margin-top: 8px; font-size: var(--t4); color: var(--teal); font-weight: 600; }
/* Read-only key/value detail for a field worker's own project (FieldProjectDetails). RTL-safe: the
   label column leads, so it flips with dir="rtl" without a second rule. */
.fc-details { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
.fc-details:empty { display: none; }
.fc-detail { display: flex; gap: 10px; justify-content: space-between; background: var(--card); padding: 9px 12px; font-size: var(--t4); }
.fc-detail-k { color: var(--muted); font-weight: 600; flex: none; }
.fc-detail-v { text-align: end; min-width: 0; overflow-wrap: anywhere; }
.fc-note { margin-top: 10px; background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 9px 12px; font-size: var(--t4); }
.fc-note .fc-detail-k { display: block; margin-bottom: 3px; }
/* "Called 2 days ago · Updated 1 hour ago" on client cards. Wraps rather than truncating — an age the
   worker can't read is the same as not showing it. .never is a nudge, not an error. */
.lasttouch { display: flex; flex-wrap: wrap; gap: 4px 12px; margin-top: 6px; }
.lasttouch > span { display: inline-flex; align-items: center; gap: 4px; }
.lasttouch .never { color: var(--orange); font-weight: 600; }
.fc-thumbs { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.fc-thumb { position: relative; }
.fc-thumb img { width: 78px; height: 78px; object-fit: cover; border-radius: 8px; display: block; }
.fc-pdf { width: 78px; height: 78px; border-radius: 8px; background: var(--panel); display: flex; align-items: center; justify-content: center; text-align: center; font-size: var(--t1); padding: 4px; overflow: hidden; }
.fc-rm { position: absolute; top: -7px; inset-inline-start: -7px; width: 22px; height: 22px; border-radius: 50%; border: none; background: var(--danger); color: #fff; cursor: pointer; line-height: 22px; padding: 0; }
.fc-rm::before { content: ''; position: absolute; top: 50%; left: 50%; width: 44px; height: 44px; transform: translate(-50%, -50%); }
.fc-videoset { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
.fc-submit { width: 100%; font-size: var(--t8); padding: 16px; border-radius: 12px; margin-top: 4px; }
.fc-nudge { text-align: center; font-size: var(--t3); color: var(--orange); margin-top: 10px; }
.fc-manuallink { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; margin-bottom: 16px; padding: 13px; border: 1px dashed var(--line); border-radius: 12px; background: transparent; color: var(--teal); font-weight: 600; font-size: var(--t5); cursor: pointer; }
.fc-manuallink:active { filter: brightness(.95); }
.fc-back { display: block; margin: 0 0 14px; padding: 6px 0; background: transparent; border: none; color: var(--muted); font-weight: 600; font-size: var(--t5); cursor: pointer; }
.fc-manual .fc-step label:first-child { margin-top: 0; }
.fc-more-toggle { display: flex; align-items: center; justify-content: space-between; width: 100%; background: transparent; border: none; padding: 2px 0; font-weight: 700; font-size: var(--t5); color: var(--text); cursor: pointer; }
.fc-unit { border: 1px solid var(--line); border-radius: 12px; padding: 12px; margin-bottom: 12px; background: var(--panel); }
.fc-unit-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; }
.fc-unit-title { font-weight: 700; font-size: var(--t5); }
.fc-rm-unit { display: inline-flex; align-items: center; gap: 4px; background: transparent; border: none; color: var(--danger); font-weight: 600; font-size: var(--t3); cursor: pointer; padding: 4px; }
.fc-ppsqm { text-align: end; font-size: var(--t3); font-weight: 600; color: var(--teal); margin-top: 4px; }
.fc-unit-photo { margin-top: 12px; width: 100%; }
.fc-unit-photo img { width: 40px; height: 40px; object-fit: cover; border-radius: 6px; }
.fc-add-unit { width: 100%; padding: 12px; border: 1px dashed var(--line); border-radius: 12px; background: transparent; color: var(--teal); font-weight: 600; font-size: var(--t5); cursor: pointer; }
.fc-add-unit:active { filter: brightness(.95); }

/* ── Attach a property to a client (the أضف عقار picker) ── */
/* Header keeps the section label and the add button on one line, and wraps instead of squeezing the
   button to an unreadable width on a narrow phone. */
.offers-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.offers-add { flex: none; }
.picker { border: 1px solid var(--line); border-radius: 12px; background: var(--card); padding: 10px; margin-bottom: 12px; }
.picker-head { display: flex; align-items: center; gap: 8px; }
/* No font-size here: the mobile block already forces 16px on inputs, which is what stops the iOS
   focus-zoom jump. Restating it would just be a second place to keep in sync. */
.picker-search { flex: 1; min-width: 0; }
/* Capped and scrollable so a long property list never pushes the offer rows below it off-screen —
   the same reason on a phone and in the desktop modal, which is why there's no breakpoint here. */
.picker-list { margin-top: 8px; max-height: 46vh; overflow-y: auto; border: 1px solid var(--line); border-radius: 10px; }
.picker-row { display: flex; align-items: center; gap: 10px; width: 100%; min-height: 44px; padding: 9px 12px;
  border: 0; border-top: 1px solid var(--line); background: none; font: inherit; text-align: start; cursor: pointer; }
.picker-row:first-child { border-top: 0; }
.picker-row:hover:not(:disabled) { background: var(--panel); }
/* Attached rows stay visible but inert — hiding them reads as "it's missing" and sends you looking again. */
.picker-row:disabled { cursor: default; opacity: .55; }
.picker-name { flex: 1; min-width: 0; font-weight: 600; color: var(--ink); }
/* .meta is a <span> here (it's inside a <button>, which can't hold a <div>), so it needs display:block
   to drop to its own line — .match-name gets that free from its <div>. Without it the district runs
   straight into the property name: "مشروع راف 25الزهراء". Caught on a phone-width screenshot, not in review. */
.picker-name .meta { display: block; font-weight: 400; }
.picker-add { flex: none; color: var(--teal); display: inline-flex; align-items: center; }

/* ── الطلب: what a client is looking for ── */
/* Category picks reuse the 42px .status-chip tap target; the icon just needs room beside the label,
   so five categories read as shapes at a glance instead of five words to parse. */
.status-chip.with-ic { display: inline-flex; align-items: center; gap: 7px; }
/* The district results list. Capped height because the list is ~100 rows and this sits mid-form — an
   uncapped one would push المدينة/المساحة/الميزانية off a phone screen every time the field is focused.
   text-align:inherit, not right: the same component renders in the LTR office modal. */
.district-list { max-height: 220px; overflow-y: auto; margin-top: 4px; background: var(--card);
  border: 1px solid var(--line); border-radius: 10px; }
.district-opt { display: block; width: 100%; min-height: 44px; padding: 10px 12px; text-align: inherit;
  border: 0; border-bottom: 1px solid var(--line); background: none; color: var(--text);
  font: inherit; cursor: pointer; }
.district-opt:last-child { border-bottom: 0; }
.district-opt:hover, .district-opt:focus-visible { background: var(--panel); }
.district-empty { padding: 12px; }

/* الأحياء المطلوبة: each district you've picked becomes a small chip above the search input.
   Deliberately smaller than .status-chip — these are answers already given, not choices being offered. */
.req-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 4px; }
.req-chip { display: inline-flex; align-items: center; gap: 2px; padding-block: 5px;
  padding-inline: 11px 5px; border-radius: 999px; border: 1px solid var(--line);
  background: var(--panel); color: var(--text); font-size: var(--t3); font-weight: 600; }
.req-chip button { position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; padding: 0; border: none; border-radius: 50%;
  background: transparent; color: var(--muted); cursor: pointer; }
.req-chip button:hover { background: var(--danger); color: #fff; }
/* 24px glyph, 44px reach — the same trick .fc-rm uses, so a thumb isn't asked to hit the icon exactly. */
.req-chip button::before { content: ''; position: absolute; top: 50%; left: 50%;
  width: 44px; height: 44px; transform: translate(-50%, -50%); }
/* Amber, not red: a client with no طلب isn't broken, they're just not matchable yet. */
.req-missing { color: var(--orange); }
/* Office modal only: it has no .fc-step cards, so the الطلبات block needs its own seam. */
.req-head { display: flex; align-items: center; gap: 8px; margin: 22px 0 2px;
  font-size: var(--t5); font-weight: 700; color: var(--ink); }
.fc-done { text-align: center; padding: 30px 12px; }
.fc-done .fc-bigbtn { width: 100%; flex: none; }
/* The files organizer sits inside the centred done panel — its own content is a form, not a headline.
   Its toggle is a primary action on a phone, so it takes the full 44px target the shared .fc-more-toggle
   (a "More details" disclosure inside an already-tapped form) doesn't need. */
.fc-donefiles { text-align: start; margin-bottom: 18px; border: 1px solid var(--line); border-radius: var(--r2); padding: var(--s3); background: var(--panel); }
.fc-donefiles > .fc-more-toggle { min-height: 44px; }
.fc-done-check { width: 64px; height: 64px; margin: 0 auto 14px; border-radius: 50%; background: var(--green); color: #fff; font-size: 34px; line-height: 64px; }
.fc-done-text { font-size: var(--t8); font-weight: 700; margin-bottom: 22px; }
.fc-confirm { display: flex; gap: 10px; margin-top: 12px; }
.fc-yes { flex: 2; font-size: var(--t7); padding: 15px; background: var(--green); }
.fc-fix { flex: 1; font-size: var(--t6); padding: 15px; }
.field-capture .chatbar input { text-align: start; }

/* Big inline loading bar for capture (AI read + save) */
.fc-loader { display: flex; align-items: center; justify-content: center; gap: 12px; width: 100%;
  margin-top: 4px; padding: 18px; border-radius: 12px; background: var(--card); border: 1px solid var(--line); }
.fc-loader-text { font-size: var(--t6); font-weight: 700; color: var(--text); }
.fc-spinner { width: 24px; height: 24px; flex: none; border-radius: 50%;
  border: 3px solid var(--line); border-top-color: var(--teal); animation: fc-spin .8s linear infinite; }
@keyframes fc-spin { to { transform: rotate(360deg); } }

/* Investor call-list (field worker) */
.fc-poolcount { text-align: center; color: var(--muted); font-weight: 600; margin: -8px 0 16px; }
.fc-callcard { text-align: center; }
.fc-phone { font-size: 30px; font-weight: 800; letter-spacing: .02em; color: var(--ink); direction: ltr; }
.fc-callname { font-size: var(--t7); color: var(--text); margin-top: 6px; }
.fc-callpills { display: flex; gap: 6px; justify-content: center; flex-wrap: wrap; margin-top: 10px; }
/* "Last attempt: Khalid · 3 days ago — No answer" on a number that has come round again. Hairlines above
   and below rather than a box: it has to register before the caller dials without competing with the phone
   number or pushing the Call button off a small screen. Quiet by default — most leads have no history. */
.fc-lastcall { margin-top: 12px; padding: 8px 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 2px; align-items: center; font-size: var(--t3); color: var(--muted); line-height: 1.5; }
.fc-lastcall strong { font-weight: 700; color: var(--text); }
.fc-callbtn { background: var(--green); width: 100%; flex: none; margin-top: 14px; }
.fc-outcomes { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.fc-outcomes .btn { font-size: var(--t6); padding: 16px; border-radius: 12px; }
/* The most repeated action in the call flow, on a full-width centred row where a miss lands on the
   outcome buttons above it — it was ~34px. */
.fc-skip { display: block; width: 100%; text-align: center; margin-top: 14px; min-height: 44px; }

/* Team scoreboard (per-caller KPIs) */
.kpi-table { width: 100%; border-collapse: collapse; font-size: var(--t4); min-width: 460px; }
.kpi-table th, .kpi-table td { padding: 8px 10px; text-align: right; border-bottom: 1px solid var(--line); }
/* This table is the charts' accessible twin — six columns of integers read week over week. The body
   stack leads with SF, whose figures are proportional, so digits did not line up column-wise.
   td only: .viz-tile-v's opt-out (l.796) is deliberate and stays. */
.kpi-table td { font-variant-numeric: tabular-nums; }
.kpi-table th:first-child, .kpi-table td:first-child { text-align: left; font-weight: 600; color: var(--ink); }
.kpi-table th { color: var(--muted); font-weight: 600; font-size: var(--t2); text-transform: uppercase; letter-spacing: .03em; }
.kpi-table tbody tr:last-child td { border-bottom: none; }

/* --- Team sales dashboard (hand-rolled charts) --- */
.viz { position: relative; }
.viz-card { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 16px; margin-bottom: 12px; }
.viz-h { display: flex; align-items: center; gap: 7px; color: var(--ink); font-size: var(--t4); font-weight: 700; margin: 0; }
.viz-sub { color: var(--muted); font-size: var(--t2); margin: 3px 0 14px; }
/* Refetch holds the previous render at reduced opacity — no skeleton flash, no layout jump. */
.viz-busy { opacity: .45; transition: opacity var(--d1) var(--ease); }

.viz-filters { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 14px; }
/* Pull-to-refresh indicator. Sits above the top edge and is dragged into view by the gesture, so it costs
   nothing visually until someone actually pulls. */
.ptr { position: fixed; inset-inline: 0; top: -34px; height: 34px; z-index: var(--z-pop); pointer-events: none;
  display: flex; align-items: center; justify-content: center; transition: transform var(--d2) var(--ease); }
.ptr::before { content: '↻'; font-size: var(--t7); font-weight: 700; color: var(--muted);
  background: var(--card); border: 1px solid var(--line); border-radius: 999px; width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center; box-shadow: var(--sh2); }
.ptr.ready::before { color: #fff; background: var(--brand); border-color: var(--brand); }

.viz-chips { display: flex; flex-wrap: wrap; gap: 6px; }
/* Date range filter. Inputs sized to their content — a full-width date field next to the person chips
   reads as the primary control, and it isn't. */
.viz-dates { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.viz-dates label { font-size: var(--t2); font-weight: 600; color: var(--muted); }
.viz-dates input { width: auto; min-width: 0; padding: 6px 9px; font-size: var(--t3); }
/* Marks a tile that deliberately ignores the date filter (see StatTile's `note`). */
.viz-asof { font-size: var(--t1); font-weight: 600; color: var(--muted); }
.viz-chip { padding: 6px 12px; border-radius: 999px; border: 1px solid var(--line); background: var(--card); color: var(--text); font-size: var(--t3); font-weight: 600; min-height: 44px; }
.viz-chip.active { background: var(--teal); border-color: var(--teal); color: #fff; }

.viz-tiles { display: grid; grid-template-columns: repeat(6, 1fr); gap: 10px; margin-bottom: 12px; }
.viz-tile { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 12px 14px; }
.viz-tile-l { font-size: var(--t2); color: var(--muted); }
/* Proportional figures on purpose: tabular-nums makes a 3-digit value look loose at display size. */
.viz-tile-v { font-size: var(--t12); font-weight: 700; color: var(--ink); line-height: 1.15; margin-top: 1px; }
.viz-tile-b { display: flex; align-items: flex-end; justify-content: space-between; gap: 8px; min-height: 24px; margin-top: 3px; }
.viz-delta { font-size: var(--t2); font-weight: 700; }
.viz-delta.up { color: var(--green-ink); } .viz-delta.down { color: var(--danger-ink); } .viz-delta.flat { color: var(--muted); }

.viz-row { display: grid; grid-template-columns: 116px 1fr; align-items: center; gap: 10px; }
.viz-row-l { font-size: var(--t3); color: var(--text); text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Marks the filtered person on charts that can't de-emphasise with color (the ordinal stage stack). */
.viz-row-l.on { color: var(--ink); font-weight: 700; }
.viz-track { display: flex; align-items: center; gap: 8px; min-height: 26px; width: 100%; background: none; border: none; padding: 0; text-align: left; }
button.viz-track { cursor: pointer; }
.viz-barwrap { flex: 1; min-width: 0; display: block; }
.viz-bar { display: block; height: 18px; border-radius: 0 4px 4px 0; background: var(--viz-1); min-width: 2px; }
.viz-bar.dim { background: var(--viz-dim); }
.viz-val { font-size: var(--t2); font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
/* Stacked segments separated by a 2px gap in the surface color — never a border around the mark. */
.viz-stack { display: flex; gap: 2px; height: 18px; flex: 1; min-width: 0; }
.viz-seg { min-width: 2px; }
.viz-seg:last-child { border-radius: 0 4px 4px 0; }
.viz-empty { height: 18px; flex: 1; border-radius: 4px; background: var(--bg); }

.viz-legend { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 12px; font-size: var(--t2); color: var(--muted); }
.viz-key { display: inline-flex; align-items: center; gap: 6px; }
.viz-key i { width: 10px; height: 10px; border-radius: 3px; display: inline-block; flex: none; }

.viz-tip { position: absolute; z-index: var(--z-pop); pointer-events: none; background: var(--ink); color: #fff; border-radius: 8px; padding: 8px 10px; font-size: var(--t2); line-height: 1.45; box-shadow: var(--sh3); transform: translate(-50%, calc(-100% - 10px)); white-space: nowrap; }
.viz-tip-t { color: rgba(255,255,255,.7); font-size: var(--t1); margin-bottom: 3px; }
.viz-tip-r { display: flex; align-items: center; gap: 6px; }
.viz-tip-r i { width: 12px; height: 2px; border-radius: 1px; display: inline-block; flex: none; }
.viz-tip-v { font-weight: 700; font-variant-numeric: tabular-nums; }
.viz-tip-n { color: rgba(255,255,255,.7); }

/* Latest client updates — the notes log read across clients instead of inside one. */
.upd { border-bottom: 1px solid var(--line); padding: 11px 2px; cursor: pointer; }
.upd:last-of-type { border-bottom: none; }
.upd:hover, .upd:focus-visible { background: var(--panel); }
.upd-top { display: flex; align-items: center; gap: 8px; }
.upd-top .name { font-weight: 600; color: var(--ink); }
/* The note is the point, so it gets body ink and room to breathe — clamped to keep rows scannable. */
.upd-note { color: var(--text); font-size: var(--t3); line-height: 1.5; margin: 3px 0 2px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.upd-note.none { color: var(--muted); font-style: italic; }
.upd-meta { font-size: var(--t2); }

/* The plot scrolls inside its own container on narrow screens; the page never scrolls sideways. */
.viz-scroll { overflow-x: auto; }
@media (max-width: 620px) { .viz-scroll > svg { min-width: 520px; } }

@media (max-width: 900px) { .viz-tiles { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 560px) {
  .viz-tiles { grid-template-columns: repeat(2, 1fr); }
  .viz-row { grid-template-columns: 86px 1fr; }
  .viz-tile-v { font-size: var(--t10); }
}

/* ===================== WhatsApp inbox (admin) ===================== */
/* Desktop: list + thread side by side. Mobile (≤760px): drill-in — the thread replaces the list. */
.wa-layout { display: grid; grid-template-columns: 340px 1fr; gap: 14px; align-items: stretch; }
.wa-list { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 8px; display: flex; flex-direction: column; gap: 2px; max-height: calc(100dvh - var(--chrome-h)); overflow-y: auto; }
/* h2-qualified so it out-cascades .wrap h2 (same specificity, later source order wins). */
h2.wa-list-title { display: flex; align-items: center; gap: 8px; font-size: var(--t6); color: var(--ink); margin: 4px 6px 8px; }
.wa-item { display: flex; align-items: center; gap: 10px; padding: 10px; min-height: 44px; border: none; background: transparent; border-radius: 10px; text-align: start; width: 100%; cursor: pointer; color: var(--text); }
.wa-item:hover { background: var(--card); }
.wa-item.selected { background: var(--card); box-shadow: inset 0 0 0 1px var(--line); }
.wa-item .mini, .wa-thread-head .mini { width: 34px; height: 34px; font-size: var(--t3); }
.wa-item-main { flex: 1; min-width: 0; }
.wa-item-top { display: flex; justify-content: space-between; gap: 8px; align-items: baseline; }
.wa-item-top strong { color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wa-item-preview { color: var(--muted); font-size: var(--t3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wa-item.unread .wa-item-preview { color: var(--text); font-weight: 600; }
.wa-unread { background: var(--whatsapp); margin-inline-start: 0; flex-shrink: 0; } /* composes with .review-badge */
.wa-thread { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; display: flex; flex-direction: column; max-height: calc(100dvh - var(--chrome-h)); min-height: 380px; }
.wa-thread-head { display: flex; align-items: center; gap: 10px; padding: 10px 14px; border-bottom: 1px solid var(--line); }
.wa-thread-who { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.wa-thread-who strong { color: var(--ink); }
.wa-msgs { flex: 1; overflow-y: auto; padding: 14px; margin-bottom: 0; }
.wa-msgs .msg a { color: inherit; text-decoration: underline; } /* attachment links readable on both bubble colors */
.wa-thread .chatbar { padding: 10px 14px; margin-top: 0; border-top: 1px solid var(--line); }
.wa-time { display: block; font-size: var(--t1); opacity: .65; margin-top: 4px; text-align: end; }
.wa-failed { color: var(--danger); font-weight: 700; }
.msgrow.me .wa-failed { color: #ffd9d6; } /* readable on the brand-green bubble */
.wa-empty { display: grid; place-items: center; min-height: 320px; border: 1px dashed var(--line); border-radius: 12px; }
.wa-back { display: none; }
@media (max-width: 760px) {
  .wa-layout { grid-template-columns: 1fr; }
  .wa-layout.has-sel .wa-list, .wa-layout:not(.has-sel) .wa-empty { display: none; }
  .wa-back { display: inline-flex; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; } /* the only way out of a thread — real hit area */
  /* Retuning the token retunes both panes; the per-pane max-height override that used to live here
     said the same thing twice. The phone shell is taller (the drawer trigger bar), hence 190. */
  :root { --chrome-h: 190px; }
  .wa-thread { min-height: calc(100dvh - 220px); }
}

/* ── Clients desk (office) ─────────────────────────────────────────────────────────────────────
   One tab, two answers: the follow-up strip says who to call; the board says where everyone stands.
   New class names throughout — nothing here is reachable from the field shell. */

/* 5 working columns + Won/Lost as collapsed 44px strips (the Outreach discard mechanic, reused
   verbatim via .col.discard/.discard-tab). Opening a strip trades its rail for a real column. */
.board.cboard { grid-template-columns: repeat(5, 1fr) 44px 44px; }
/* Grid children floor at min-content by default — without this the 7-track board and the 3-group
   strip push the page past the viewport instead of compressing their cards. */
.board.cboard .col { min-width: 0; }
.board.cboard.won-open { grid-template-columns: repeat(5, 1fr) 1fr 44px; }
.board.cboard.lost-open { grid-template-columns: repeat(5, 1fr) 44px 1fr; }
.board.cboard.won-open.lost-open { grid-template-columns: repeat(5, 1fr) 1fr 1fr; }
/* Stage accents: the client pipeline is ordinal until it terminates — muted → brand as it warms,
   then the two terminal strips carry their semantic green/red. */
.board.cboard .col:nth-child(1) { --st: var(--muted); }   /* new */
.board.cboard .col:nth-child(2) { --st: var(--gold); }    /* no answer */
.board.cboard .col:nth-child(3) { --st: var(--orange); }  /* contacted */
.board.cboard .col:nth-child(4) { --st: var(--teal); }    /* viewing */
.board.cboard .col:nth-child(5) { --st: var(--brand); }   /* negotiating */
.board.cboard .col:nth-child(6) { --st: var(--green); }   /* won */
.board.cboard .col:nth-child(7) { --st: var(--danger); }  /* lost */
/* Call + WhatsApp are real click targets on the cards too, not 13px text links hiding between two meta
   lines — but OUTLINED, not filled. Filled, every card carried two saturated green blocks and the board
   out-shouted the queue above it, which is the thing that is actually meant to be worked. Scoped to
   .cboard: .client-actions itself belongs to the Arabic field sheet, where the big filled targets are
   right, because that surface IS the call screen and it is used one-handed in a car. */
.board.cboard .client-actions { gap: 6px; margin-top: 8px; }
.board.cboard .client-actions a { min-height: 32px; font-size: var(--t2); border-radius: var(--r1);
  background: var(--card); border: 1px solid var(--line); }
.board.cboard .act-call { color: var(--brand); }
.board.cboard .act-wa { color: var(--whatsapp); }
.board.cboard .act-call:hover { border-color: var(--brand); }
.board.cboard .act-wa:hover { border-color: var(--whatsapp); }

/* ===== The Clients desk: header, tools, and the follow-up QUEUE =====
   Replaces the old three-panel strip. Three parallel columns could say WHAT was due but never which to
   attack first, so it read as a status display; a queue you burn down has to be one ordered list.
   All of this is .cdesk/.cq-scoped on purpose: .card, .btn, .client-search and .divtab are shared with
   the Arabic field view, and restyling those ships straight to the field workers' phones. */
.cdesk-head { display: flex; align-items: center; gap: var(--s2); margin-bottom: var(--s2); }
.cdesk-head h2 { margin: 0; }
/* Search and the people filters, demoted to their own quiet row: they qualify the view, they are not
   the work. The old header crowded all of it onto one line with the page title. */
.cdesk-tools { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; margin-bottom: var(--s3); }
.cdesk-search { max-width: 260px; }

.cq { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r3); padding: var(--s3); margin-bottom: var(--s4); }
.cq-head { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; margin-bottom: var(--s2); }
.cq-head h3 { margin: 0; font-size: var(--t6); font-weight: 800; color: var(--ink); letter-spacing: -.01em;
  display: flex; align-items: center; gap: 6px; }
/* What's left and what you've done are one thought, so they share a line. */
.cq-prog { display: flex; align-items: center; gap: var(--s2); margin-inline-start: auto; }
.cq-bar { width: 120px; height: 6px; border-radius: var(--rp); background: var(--line); overflow: hidden; }
/* Scaled, not resized: transform is composited, where animating width would relayout on every frame.
   No radius of its own — the track already clips to a pill, and a scaled radius renders as squashed ends. */
.cq-bar span { display: block; width: 100%; height: 100%; background: var(--brand);
  transform-origin: left center; transition: transform var(--d3) var(--ease); }
.cq-prog-n { font-size: var(--t2); color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }

.cq-list { display: flex; flex-direction: column; gap: 6px; }
.cq-item { display: flex; align-items: center; gap: var(--s3); min-width: 0; background: var(--card);
  border: 1px solid var(--line); border-inline-start: 3px solid var(--line); border-radius: var(--r2);
  padding: var(--s2) var(--s3); cursor: pointer;
  transition: border-color var(--d1) var(--ease), box-shadow var(--d1) var(--ease); }
.cq-item:hover { border-color: var(--brand); box-shadow: var(--sh2); }
/* The only saturated ink on the surface: a rule down the leading edge of anything genuinely late. */
.cq-item.late { border-inline-start-color: var(--danger); }
.cq-item.late:hover { border-color: var(--danger); }
.cq-main { flex: 1 1 auto; min-width: 0; }
.cq-line { display: flex; align-items: center; gap: var(--s2); min-width: 0; flex-wrap: wrap; }
.cq-name { font-weight: 700; color: var(--ink); font-size: var(--t4); max-width: 24ch;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cq-when { font-size: var(--t2); color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.cq-when.late { color: var(--danger-ink); font-weight: 700; }
.cq-note { margin-top: 2px; color: var(--muted); font-size: var(--t2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Calling IS the work, so Call is the ONLY filled control in the row. WhatsApp was filled too at first and
   the row had two equally loud buttons — which is exactly the "which do I press" the queue exists to kill.
   It keeps its brand colour in the ink and the border, so it still reads as WhatsApp, one rung quieter. */
.cq-act { display: inline-flex; align-items: center; gap: var(--s1); flex: none; }
.cq-call { display: inline-flex; align-items: center; gap: 6px; min-height: 34px; padding: 0 14px;
  border-radius: var(--r1); background: var(--brand); color: #fff; font-size: var(--t3); font-weight: 700;
  text-decoration: none; white-space: nowrap; }
.cq-wa { display: inline-flex; align-items: center; justify-content: center; min-width: 34px; min-height: 34px;
  border-radius: var(--r1); background: var(--card); color: var(--whatsapp); border: 1px solid var(--line);
  text-decoration: none; }
.cq-wa:hover { border-color: var(--whatsapp); background: rgba(31,168,85,.08); }
.cq-call:hover { filter: brightness(1.1); }
/* The date chip is "12 Aug" on most rows and "+ follow-up" on a quiet one. Left free, that width difference
   ragged the right edge of every action group in the list. A floor on the chip lines them up. */
.cq-act .fu-chip { min-width: 96px; justify-content: center; }
.cq-done { white-space: nowrap; }
.cq-empty { color: var(--muted); font-size: var(--t3); padding: var(--s2) 0; }

/* Upcoming: present and plannable, and visibly NOT today's work — no card surface, lighter type, and
   outlined actions instead of filled ones. Same row component, turned down. */
.cq-later { margin-top: var(--s3); padding-top: var(--s3); border-top: 1px dashed var(--line); }
.cq-later h4 { margin: 0 0 var(--s2); display: flex; align-items: center; gap: 6px; font-size: var(--t1);
  font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; }
.cq-later h4 b { color: var(--ink); font-variant-numeric: tabular-nums; }
.cq-later .cq-item { background: none; border-color: transparent; border-inline-start-color: var(--line);
  padding-top: 6px; padding-bottom: 6px; }
.cq-later .cq-item:hover { background: var(--card); border-color: var(--line); box-shadow: none; }
.cq-later .cq-name { font-weight: 600; font-size: var(--t3); }
.cq-later .cq-call { background: none; color: var(--brand); border: 1px solid var(--line); }
.cq-later .cq-wa { background: none; color: var(--whatsapp); border: 1px solid var(--line); }

/* Phone: the actions drop to their own line rather than squeezing the name to one letter, and Call takes
   the space it deserves. `flex: none` and `inline-flex` are both overridden on purpose — left as they are,
   the action group refuses to wrap and Done runs off the right edge of the screen. */
@media (max-width: 760px) {
  .cq-item { flex-wrap: wrap; gap: var(--s1) var(--s2); }
  .cq-name { max-width: none; }
  .cq-act { display: flex; flex-wrap: wrap; flex: 1 1 100%; width: 100%; margin-top: 6px; gap: var(--s1); }
  /* Call takes a line of its own — sized like the thing you are meant to press — and the three secondary
     controls share the next one. Letting Call merely flex left Done stranded on a third line and made
     every row four deep, which is how a queue of five turns into a page of scrolling. */
  .cq-call { flex: 1 1 100%; justify-content: center; min-height: 40px; }
  .cq-act .fu-chip { min-width: 0; flex: 0 1 auto; }
  .cq-done { flex: 0 1 auto; }
  /* The count and the progress meter stack instead of the meter being pushed off the edge. */
  .cq-head { gap: var(--s1) var(--s2); }
  .cq-prog { margin-inline-start: 0; flex: 1 1 100%; }
  .cq-bar { flex: 1 1 auto; width: auto; }
}

/* The date chip: a quiet pill that becomes the native picker on click. */
.fu-chip { display: inline-flex; align-items: center; gap: 4px; min-height: 28px; padding: 2px 10px;
  border: 1px solid var(--line); border-radius: var(--rp); background: transparent;
  color: var(--muted); font: inherit; font-size: var(--t2); font-weight: 600; cursor: pointer; }
.fu-chip:hover { color: var(--brand); border-color: var(--brand); }
.fu-chip.overdue { color: var(--danger-ink); border-color: rgba(192,80,77,.45); }
.fu-date { width: 150px; padding: 3px 6px; font-size: var(--t3); }

/* Unclaimed marker: deliberately hollow — status hues stay reserved for status. */
.pill.unclaimed { background: transparent; color: var(--muted); }
/* Claim reads as the action ON that pill, so it sits beside it rather than stealing width from the
   stage select below (which truncated "Viewing" to "View" at laptop widths). */
.claim-link { padding: 2px 8px; font-size: var(--t2); font-weight: 600; color: var(--brand); }
/* ===== The follow-up picker: your real day, not a blank date box =====
   Absolute placement inside a fixed-height grid, because an event has to sit at the minute it starts and a
   flow layout can only stack things in order. Office Clients tab only — the Arabic field sheet keeps its
   one-handed +2d / +1w row. */
.fum-cal { margin: var(--s3) 0; border: 1px solid var(--line); border-radius: var(--r2); padding: var(--s2); background: var(--panel); }
.fum-cal-h { font-size: var(--t1); font-weight: 700; color: var(--muted); text-transform: uppercase;
  letter-spacing: .06em; margin-bottom: var(--s1); }
.fum-off { padding: 2px 0; }
.fum-allday { font-size: var(--t2); color: var(--ink); background: var(--card); border: 1px solid var(--line);
  border-radius: var(--r1); padding: 2px 8px; margin-bottom: 4px; }
.fum-grid { position: relative; background: var(--card); border: 1px solid var(--line); border-radius: var(--r1); overflow: hidden; }
/* Each hour is a button: clicking an empty stretch is how you pick a time you can actually see is free. */
.fum-hour { position: absolute; left: 0; right: 0; display: flex; align-items: flex-start; padding: 1px 6px;
  background: none; border: 0; border-top: 1px solid var(--line); font: inherit; text-align: start; cursor: pointer; }
.fum-hour:first-child { border-top: 0; }
.fum-hour span { font-size: var(--t1); color: var(--muted); font-variant-numeric: tabular-nums; }
.fum-hour:hover { background: rgba(20,69,47,.06); }
.fum-hour.on { background: rgba(20,69,47,.12); box-shadow: inset 2px 0 0 var(--brand); }
.fum-hour.on span { color: var(--brand); font-weight: 700; }
/* Busy blocks sit ON TOP of the hour buttons and stay click-through, so a booked stretch can't be picked by
   accident but the gap beneath a short meeting still can. */
.fum-ev { position: absolute; inset-inline: 46px 4px; pointer-events: none; overflow: hidden;
  background: color-mix(in srgb, var(--teal) 18%, var(--card)); border-inline-start: 3px solid var(--teal);
  border-radius: var(--r1); padding: 1px 6px; font-size: var(--t1); color: var(--teal-ink); white-space: nowrap;
  text-overflow: ellipsis; }

/* The field sheet echoes its follow-up date in words: the native picker prints its placeholder in
   the browser's locale, which reads "mm/dd/yyyy" on an Arabic screen. */
.fu-set { display: flex; align-items: center; gap: 6px; margin: 2px 0 8px; font-size: var(--t3); color: var(--text); }
.fu-set b { font-weight: 700; color: var(--ink); }
.fu-set.none { color: var(--muted); }

/* Same tablet/phone step-downs the other boards take (specificity beats the generic .board rules,
   so the cboard needs its own lines). Strips keep their rail at 2-col, everything stacks at 760. */
@media (max-width: 1100px) {
  .board.cboard, .board.cboard.won-open, .board.cboard.lost-open, .board.cboard.won-open.lost-open { grid-template-columns: repeat(2, 1fr) 44px 44px; }
}
@media (max-width: 760px) {
  .board.cboard, .board.cboard.won-open, .board.cboard.lost-open, .board.cboard.won-open.lost-open { grid-template-columns: 1fr; }
}

/* ══ Website desk (public-site editor) — office-only; every class below is NEW, nothing
   the field shell renders is touched. ══════════════════════════════════════════════ */
.wdesk-head { position: sticky; top: 0; z-index: var(--z-sticky); background: var(--bg); padding: var(--s2) 0; }
.wdesk-block { background: var(--card); border: 1px solid var(--line); border-radius: var(--r2); padding: var(--s3) var(--s4); margin-bottom: var(--s3); }
.wdesk-block .wdesk-block { background: var(--panel); } /* nested page/section groups sit a step down */
.wdesk-pair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s2) var(--s3); align-items: start; }
.wdesk-path { color: var(--muted); font-size: var(--t2); margin: 10px 0 2px; }
.wdesk-thumb { width: 120px; height: 63px; object-fit: cover; border-radius: var(--r1); border: 1px solid var(--line); }
@media (max-width: 900px) { .wdesk-pair { grid-template-columns: 1fr; } }

/* ══ SEO tab (inside the Website desk) — every class below is NEW; the Content tab and
   the field shell render none of them. ══════════════════════════════════════════════ */
.seo-switcher { margin-bottom: var(--s3); width: fit-content; }
.pill.seo-ok   { background: rgba(109,143,94,.15); color: var(--green-ink);  border-color: rgba(109,143,94,.45); }
.pill.seo-warn { background: rgba(208,162,63,.15); color: var(--gold-ink);   border-color: rgba(208,162,63,.45); }
.pill.seo-fail { background: rgba(192,80,77,.14);  color: var(--danger-ink); border-color: rgba(192,80,77,.45); }
.pill.seo-new  { background: rgba(63,138,140,.13); color: var(--teal-ink);   border-color: rgba(63,138,140,.4); margin-inline-start: 6px; }

/* Headline numbers: clicks, impressions, position. */
.seo-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--s3); margin: var(--s3) 0; }
.seo-stat { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r1); padding: var(--s3); }
.seo-stat b { display: block; font-size: var(--t5, 24px); color: var(--ink); line-height: 1.1; }
.seo-stat span { display: block; color: var(--muted); font-size: var(--t2); margin-top: 2px; }
.seo-stat em { display: block; color: var(--muted); font-size: var(--t1); font-style: normal; margin-top: 4px; }

.seo-table { width: 100%; border-collapse: collapse; margin-top: var(--s2); font-size: var(--t2); }
.seo-table th { text-align: start; color: var(--muted); font-weight: 600; padding: 6px 8px; border-bottom: 1px solid var(--line); }
.seo-table td { padding: 6px 8px; border-bottom: 1px solid var(--line); vertical-align: top; }
.seo-table tr:last-child td { border-bottom: none; }
.seo-move { font-size: var(--t1); font-weight: 600; }
.seo-move.up { color: var(--green-ink); }
.seo-move.down { color: var(--danger-ink); }

.seo-group { margin-top: var(--s3); }
.seo-group-head { color: var(--muted); font-size: var(--t2); font-weight: 600; margin-bottom: var(--s2); }
/* The severity is carried by a border on the leading edge, so it reads the same in RTL. */
.seo-finding { border-inline-start: 3px solid var(--line); background: var(--panel);
  border-radius: var(--r1); padding: var(--s2) var(--s3); margin-bottom: var(--s2); }
.seo-finding.fail { border-inline-start-color: var(--danger); }
.seo-finding.warn { border-inline-start-color: var(--gold); }
.seo-finding.info { border-inline-start-color: var(--teal); }
.seo-fix { margin-top: 6px; display: flex; gap: 6px; align-items: flex-start; }

.seo-proposal { margin-top: var(--s2); padding-top: var(--s2); border-top: 1px dashed var(--line); }
/* Arabic copy, so it needs its own direction regardless of the surrounding LTR office UI. */
.seo-diff { background: var(--card); border: 1px solid var(--line); border-radius: var(--r1);
  padding: 8px 10px; margin: 6px 0; font-size: var(--t2); word-break: break-word; }
.seo-diff.old { color: var(--muted); text-decoration: line-through; }
.seo-change { border-top: 1px solid var(--line); padding-top: var(--s2); margin-top: var(--s2); }
.seo-change:first-of-type { border-top: none; }

.seo-switch { align-items: flex-start; gap: var(--s2); margin-top: var(--s3); cursor: pointer; }
.seo-switch input { margin-top: 3px; }
