/* ============ NUDGE — Place design system ============ */
:root {
  --accent: #F37021;
  --accent-soft: color-mix(in oklch, #F37021 14%, white);
  --accent-softer: color-mix(in oklch, #F37021 7%, white);
  --accent-hover: color-mix(in oklch, #F37021 88%, black);
  /* The brand orange is only 2.94:1 on white — fine for dots, borders, bars and fills,
     but NOT for text or a focus ring. Use --accent-ink (5.18:1) wherever the accent
     carries meaning as a foreground colour. */
  --accent-ink: #C2410C;

  /* CANVAS vs SURFACE. This split is the single biggest reason Follow Up Boss reads
     calmer than we did: it puts white cards on a grey page, so each block reads as an
     object. We had one undifferentiated white sheet, so nothing read as a card no matter
     how the borders were tuned. --bg is kept as an alias of --surface so no third-party
     or forgotten rule breaks; new code should use --canvas or --surface explicitly. */
  --canvas: #EEF0EC;            /* PLACE 2026 Fog. Was FUB's #F1F4F8; Adam moved the
                                   app onto the brand palette 2026-11-03. */
  --surface: #FFFFFF;           /* anything that should read as a card ON the page */
  --bg: var(--surface);         /* legacy alias */
  --sidebar-bg: #22343C;        /* PLACE 2026 Ocean. Was #364650. Every ink on the rail
                                   GAINED contrast: white 9.77 -> 12.93, --sidebar-ink
                                   6.36 -> 8.41, --sidebar-ink-dim 5.13 -> 6.78. */
  --sidebar-ink: #C7D2DA;       /* nav labels on the rail, 8.41:1 on Ocean */
  /* 6.78:1 on Ocean (was 5.13:1 on the old slate). FUB's own #93A5B2 was tried here first
     and measured 3.85:1, which fails for the nav icons — and those are glyphs carrying
     meaning, not decoration. This is the same trap as --text-3 before it: a grey that looks
     right and is not legible. */
  --sidebar-ink-dim: #AEBECA;
  --hover: #F8F9FB;
  --row-hover: #F7F9FB;         /* was #FAFAFA, a warm grey that reads dirty beside slate */

  /* Softer than a near-black without going near a contrast floor: #364650 is 9.77:1 on
     white and 8.52:1 on the Fog canvas. NOT FUB's own muted grey #93A5B2 — that measures
     2.54:1, the same value already removed from body copy here once. #5B6B78 still clears
     4.5:1 on Fog (5.50:1 on white / 4.79:1 on canvas). #64748B was rejected at 4.31:1.
     NOTE the canvas move to Fog cost every ink here ~0.2, so 4.79 is the tightest margin
     in the file after --accent-ink. Re-measure before darkening the canvas again. */
  --text: #364650;
  --text-2: #5B6B78;
  /* --text-3 was #9CA3AF (2.54:1) and had leaked into real body copy. It is now a
     legible secondary; --text-disabled keeps the old value for non-informational
     pixels only (placeholders, separators, disabled states). */
  --text-3: #5B6B78;
  --text-disabled: #9CA3AF;

  --border: #E8ECF0;
  --border-strong: #D1D5DB;
  --border-soft: #EEF0F3;

  --radius: 4px;        /* already FUB's input + message-bubble radius */
  --radius-lg: 5px;     /* FUB's card radius */
  /* FUB's card shadow is in the BORDER colour, which is why its cards read flat rather
     than floaty. Distinct from --shadow-sm, which is a real drop shadow. */
  --shadow-card: 0 0 3px 0 #E8ECF0;

  /* One type scale, collapsed toward FUB's. FUB ships THREE sizes in the entire product:
     20/500 for a record title, 16/400 for section headers, and 14/400 for literally
     everything else. We had five tokens plus a parallel rem scale in the templates, and
     a rendered contact page measured 16 distinct sizes against FUB's 3.

     --fs-micro is 13px rather than 14px on purpose. It has the most call sites (95),
     including pills, badges and dense table cells, and jumping straight to 14 wraps the
     stage pills on /people and /pipeline at 375px. Take it to 14 after a narrow pass.
     13px is also still above the readability floor the mobile rules enforce. */
  --fs-micro: 13px;
  --fs-sm: 14px;     /* alias of base; kept so existing call sites resolve */
  --fs-base: 14px;
  --fs-md: 16px;
  --fs-lg: 20px;     /* page and record titles */
  --fs-xl: 26px;     /* display figures only: KPI values, report headline numbers */

  /* GLYPH SIZES ARE NOT THE TYPE SCALE. An icon, an avatar's initials or a dial-pad digit
     is sized to its box, not to the reading scale, and FUB does the same (its avatar
     initials are 12px while nothing it asks you to READ is under 14). These are the only
     literal font-sizes that should survive in this file; everything a user reads belongs
     on a --fs-* token. Named so a sweep can tell the two apart at a glance. */
  --glyph-sm: 10px;   /* .avatar.sm initials */
  --glyph-md: 14px;   /* inline icons in rows and menus */
  --glyph-lg: 18px;   /* .avatar.xl, dialer keys, large affordances */

  --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.04);
  --shadow-md: 0 4px 12px rgba(15, 23, 42, 0.06), 0 1px 3px rgba(15, 23, 42, 0.05);
  --shadow-lg: 0 16px 32px -8px rgba(15, 23, 42, 0.12), 0 6px 12px rgba(15, 23, 42, 0.06);

  /* 224px left "To Call (no phone reach)" 18px short, so it wrapped to two lines. The
     longest real nav label measures 157px; 248px gives it 163px. Labels still truncate
     below, because smart-list names are user-editable and no width is future-proof. */
  --sidebar-w: 248px;
  /* The arithmetic, because this number cannot be eyeballed: the rail's own padding is 12px
     and .nav-item's is 10px, so an 18px .nav-icon occupies 22px to 40px from the rail's left
     edge. 40 + 10 (the item's right padding) + 12 (the rail's) = 62px, and 64 leaves 2px so a
     scrollbar or a border can never clip a glyph. Collapsing hands 184px back to the page —
     at 900px wide that is the contact table's Last contact column, or +55% on the message
     thread. Was 60px and unused by anything, which would have clipped the icons by 2px. */
  --sidebar-w-collapsed: 64px;
  --topbar-h: 56px;

  --font: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

* { box-sizing: border-box; }

/* The UA's [hidden]{display:none} is a bare attribute selector, so ANY class rule that
   sets display (flex/grid/block) silently outranks it and the element stays visible.
   That trap has now bitten five times (bulk bar, dialer panels, kanban cards, the offline
   banner, and every conditional `<label hidden>` inside form.stacked). Close it once, here,
   for every current and future component. Nothing in the app relies on showing a [hidden]
   element — verified by sweeping the templates and JS. */
[hidden] { display: none !important; }

/* Focus was previously removed (outline:none) and replaced with a 1.07:1 orange glow that
   is invisible in daylight. One visible ring for every interactive element, in the
   text-safe accent. Keyboard users only — :focus-visible never fires on a mouse click. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent-ink);
  outline-offset: 2px;
  border-radius: var(--radius);
}
html, body { height: 100%; }
/* Keep the scrollbar's space reserved whether or not the page scrolls. Where scrollbars take up
   width (Windows, or a Mac with a mouse), a short list and a long one otherwise differ by
   about 15px of page width, and every column on the contacts list moved by that much. */
html { scrollbar-gutter: stable; }
@supports not (scrollbar-gutter: stable) { html { overflow-y: scroll; } }
body {
  margin: 0;
  /* The PAGE is the canvas. Cards sit on top of it. Changing this one line is what makes
     every .table-wrap, .card, .kpi and .tl-entry suddenly read as a card. */
  background: var(--canvas);
  color: var(--text);
  font-family: var(--font);
  /* 14px, matching FUB. The token alone was not enough: almost everything inherits from
     here, so the literal had to move too. Mobile already forced 14px at <=820px, so
     desktop and mobile now agree instead of diverging. Line-height stays 1.45 — FUB uses
     1.0, which suits its single-line rows and is wrong for notes and message bodies. */
  font-size: 14px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
input, textarea, select { font: inherit; color: inherit; }
a { color: inherit; text-decoration: none; }
a:hover { color: var(--accent-ink); }
::placeholder { color: var(--text-3); }

/* ============ APP SHELL ============ */
.app {
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  min-height: 100vh;
  width: 100%;
  /* ONE element animates and the whole shell follows. The rail has no width of its own — it
     fills this track — so transitioning the track moves the rail and the page content in the
     same 0.3s, instead of sliding a fixed rail over content that then reflows a frame later.
     Transitions never run on an element's initial style, so a page that loads collapsed
     simply renders collapsed; there is nothing to animate away. */
  transition: grid-template-columns .3s ease-in-out;
}

/* ============ SIDEBAR ============ */
/* The dark chrome anchor. FUB puts a 70px slate bar across the top; we put the same slate
   on the rail instead, because FUB has 8 nav items and we have 23 — a horizontal bar cannot
   hold them. Copying the anchor, not the geometry. */
.sidebar {
  background: var(--sidebar-bg);
  border-right: 1px solid #2B3942;
  display: flex;
  flex-direction: column;
  padding: 14px 12px;
  gap: 8px;
  min-width: 0;
  height: 100vh;
  height: 100dvh;               /* dvh wins where supported: correct under mobile chrome */
  position: sticky;
  top: 0;
  /* The rail is 23 items tall and the box is pinned at 100vh, so on a short window the
     content ran PAST the painted background: at 700px the content is 947px, and Tasks,
     Calendar, Open Houses, Vendors, Reporting, Admin and the footer all rendered on the
     page canvas with no rail behind them. Worse than cosmetic, because `position: sticky`
     means the rail does not move when the page scrolls, so those items could not be
     reached at all. Scrolling the rail's own content fixes both. */
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain; /* stop a rail scroll from chaining to the page */
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, .25) transparent;
}
/* WebKit equivalent of scrollbar-color, so the bar does not render as a light slab on Ocean. */
.sidebar::-webkit-scrollbar { width: 8px; }
.sidebar::-webkit-scrollbar-track { background: transparent; }
.sidebar::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .22); border-radius: 4px; }
.sidebar::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, .34); }
/* The wordmark and the collapse chevron share one row. The padding, the divider and the 56px
   floor live HERE rather than on .brand, so that the collapsed rail can drop the wordmark and
   keep the chevron without the divider rising to meet it. The 10px horizontal padding puts
   the row's content on the same x as the nav icons below it (12px rail + 10px = 22px). */
.rail-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 10px 16px 10px;
  border-bottom: 1px solid rgba(255, 255, 255, .10);
  margin-bottom: 8px;
  /* height, not min-height. A floor lets a tall uploaded logo grow this row, and because
     collapsing hides the logo the row would then CHANGE height mid-animation and carry all
     23 nav items up the rail with it. A fixed box means the first nav row starts at the same
     y in both states, which is the whole point of the icons not moving.
     flex-shrink: 0 is the other half: height alone is shrinkable, and the rail is a flex
     column of 23 items that overflows on a short window, so the head was being squeezed to
     49px. min-height resisted that; a plain height does not. */
  height: 56px;
  flex-shrink: 0;
}
.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1;
  min-width: 0;   /* a long team name shrinks the wordmark, it does not shove the chevron out */
}
/* An uploaded team logo. Capped to the 36px the 56px head actually has, so no brokerage's
   mark can change the height of the row the whole nav hangs from. */
.brand-logo {
  display: block;
  width: 100%;
  max-width: 168px;
  max-height: 36px;
  height: auto;
  object-fit: contain;
  object-position: left center;
}
.rail-toggle {
  flex: 0 0 auto;
  /* Off by default; the collapsed-rail block turns it on at 821px and up. Below that the
     rail is a bottom tab bar with nothing to collapse, and a chevron there would be a
     control that does nothing. */
  display: none;
  place-items: center;
  width: 28px; height: 28px;
  border-radius: var(--radius);
  color: var(--sidebar-ink-dim);
  cursor: pointer;
  transition: background .12s ease, color .12s ease;
}
.rail-toggle:hover { background: rgba(255, 255, 255, .10); color: #FFFFFF; }
/* One chevron, rotated, on the same timing as the rail itself — rather than two icons
   swapped at the halfway point, which reads as a flicker. */
.rail-toggle svg { transition: transform .3s ease-in-out; }
/* The team wordmark shown when no logo is uploaded. White, because the rail is slate:
   this was inline var(--text) and became invisible the moment the rail went dark. */
.brand-name {
  display: block;
  max-width: 168px;
  font-size: 19px;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: #FFFFFF;
  overflow-wrap: anywhere;
}
.brand-wordmark {
  display: flex;
  flex-direction: column;
  line-height: 1;
  letter-spacing: -0.02em;
}
.wm-place {
  font-size: 26px;
  font-weight: 700;
  color: #FFFFFF;   /* was #000 — invisible on the slate rail */
  letter-spacing: -0.03em;
}
.wm-sub {
  font-size: 9px;
  font-weight: 400;
  color: var(--sidebar-ink-dim);
  letter-spacing: 0.32em;
  text-transform: lowercase;
  margin-top: 2px;
  padding-left: 1px;
}

.nav { display: flex; flex-direction: column; gap: 2px; }
.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 10px;
  border-radius: var(--radius);
  /* was a hardcoded #374151, one of four near-identical dark greys the audit found */
  color: var(--sidebar-ink);
  font-size: var(--fs-base);
  font-weight: 500;
  position: relative;
  width: 100%;
  transition: background .12s ease, color .12s ease;
}
.nav-item:hover { background: rgba(255, 255, 255, .07); color: #FFFFFF; text-decoration: none; }
/* "You are here". On the old light rail this was the brand orange on a tint of itself. On
   slate that tint is invisible and --accent-ink goes muddy, so the state is carried by a
   white wash plus a 3px brand bar: the orange stays as the cue, white carries the contrast
   (15.3:1), and the bar means the state survives greyscale rather than resting on hue. */
.nav-item.active {
  background: rgba(255, 255, 255, .11);
  color: #FFFFFF;
  font-weight: 600;
}
.nav-item.active::before {
  content: "";
  position: absolute;
  left: 0; top: 6px; bottom: 6px;
  width: 3px;
  border-radius: 0 2px 2px 0;
  /* NOT --accent. The brand orange measures 2.42:1 against the active row's composited
     background (11% white over the slate), under the 3:1 floor for a non-text indicator,
     and this bar IS the cue that survives greyscale. A lighter tint of the same hue clears
     it at 3.65:1 while still reading as the brand orange on a dark rail. */
  background: #FDA65A;
}
.nav-item.active:hover { color: #FFFFFF; background: rgba(255, 255, 255, .15); }
/* flex-shrink: 0 because a flex item shrinks in proportion to its basis, so without it the
   GLYPH squeezes along with the label — visible for a whole 0.3s every time the rail
   collapses, and subtly wrong before that whenever a nav label is long. */
.nav-icon { display: inline-grid; place-items: center; color: var(--sidebar-ink-dim); width: 18px; height: 18px; flex-shrink: 0; font-size: 14px; }
.nav-item.active .nav-icon { color: #FFFFFF; }
/* Smart lists + Ponds nested under Contacts: the same row, one step quieter and indented,
   so the queues read as belonging to Contacts rather than competing with it. */
.nav-sub {
  padding-left: 26px;
  font-size: var(--fs-sm);
  color: var(--sidebar-ink-dim);
}
.nav-sub .nav-icon { font-size: 12px; width: 14px; }
/* An expanding nav group (Smart Lists): the header is a button that shows or hides its
   items; the caret turns to point down while open. base.html restores the state before
   first paint and opens it on any list page. */
.nav-toggle { background: none; border: 0; font: inherit; text-align: left; cursor: pointer; }
.nav-toggle.here { color: #FFFFFF; }
.nav-caret { margin-left: auto; font-size: 18px; line-height: 1; color: var(--sidebar-ink); opacity: .7; transition: transform .15s ease; }
.nav-group.open > .nav-toggle .nav-caret { transform: rotate(90deg); }
.nav-group-items { display: flex; flex-direction: column; gap: inherit; }
@media (prefers-reduced-motion: reduce) { .nav-caret { transition: none; } }
/* NOTHING IN THE CHROME WRAPS. A nav or inbox label that runs to two lines pushes every
   row below it out of rhythm and makes the rail look broken. One line, ellipsis, and the
   full string stays reachable via the title attribute the templates set. min-width:0 is
   required: a flex child defaults to min-width:auto and refuses to shrink below its
   content, so overflow/text-overflow alone would do nothing here. */
/* The .12s delay is for EXPANDING only. A transition takes its timing from the END state,
   so this rule governs the fade back IN: without the delay the labels reach full opacity at
   180ms while the rail is still ~150px wide, and you watch finished text get clipped by a
   moving edge. 0.12 + 0.18 lands the fade exactly as the track arrives. Collapsing takes its
   timing from the collapsed rule below, which starts the fade immediately. */
.nav-label { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; transition: opacity .18s ease .12s; }
.nav-badge {
  flex-shrink: 0;               /* a long label must eat the ellipsis, never the count */
  background: #fff;
  border: 1px solid var(--border);
  color: var(--text-2);
  font-size: var(--fs-micro);
  padding: 1px 6px;
  border-radius: 10px;
  font-weight: 600;
}
.nav-item.active .nav-badge { background: #FFFFFF; border-color: transparent; color: var(--sidebar-bg); }
.sidebar-foot { margin-top: auto; padding-top: 8px; border-top: 1px solid rgba(255,255,255,.10); font-size: 11px; color: var(--sidebar-ink-dim); padding: 8px 10px; display: flex; flex-direction: column; gap: 2px; }
/* Dotted underline rather than a solid link colour: on the slate rail an accent-coloured
   link fails contrast (the orange is 2.42:1 there), so these inherit --sidebar-ink-dim at
   5.13:1 and signal "link" with the underline instead of with hue. */
.foot-link { color: inherit; text-decoration: none; border-bottom: 1px dotted currentColor; }
.foot-link:hover { color: #FFFFFF; text-decoration: none; }

/* ============ THE RAIL, COLLAPSED ============ */
/* Every selector here is `.app > .sidebar`, never a bare `.sidebar`. Three admin pages
   (action plans, templates, email templates) nest their OWN <aside class="sidebar"> inside
   the content area as a light folder rail; a bare selector would collapse those too, and
   they are not even on the dark palette these rules assume.

   The whole block is inside min-width 821px because below that the rail IS the bottom tab
   bar and not one of these rules is true of it.

   <html data-rail="open|closed"> is written by the inline script in base.html's <head>
   BEFORE first paint, and kept current by ux.js. The default — collapsed at 1100px and
   under — is decided there rather than by a media query here, so this block has one
   condition to match instead of two, and so the attribute and the pixels can never
   disagree about which state the rail is in. */
@media (min-width: 821px) {
  .rail-toggle { display: grid; }

  /* The only line that moves anything. Everything below is tidying up after it. */
  html[data-rail="closed"] .app { --sidebar-w: var(--sidebar-w-collapsed); }

  /* THE POINT OF THE EXERCISE: the icons do not move. Each label is squeezed out to the
     RIGHT of an icon that holds its original x (22px from the rail's edge, in both states),
     so the eye keeps its place in a 23-item list instead of re-reading it. Anything that
     would shift a glyph sideways is undone below. */
  html[data-rail="closed"] .app > .sidebar .nav-label {
    opacity: 0;
    transition-delay: 0s;          /* going out, the label leads; coming back, it follows */
    /* The label is a flex child with basis 0, so the narrowing rail squeezes it to nothing
       over the same 0.3s rather than snapping. `clip` not `ellipsis`, or every row flickers
       through "Open Hou…", "Ope…", "…" on the way down. */
    text-overflow: clip;
  }
  /* Smart lists and Ponds indent 26px to read as children of Contacts. With no labels there
     is no hierarchy left to show, and the indent would push those icons 16px off the column. */
  html[data-rail="closed"] .app > .sidebar .nav-sub { padding-left: 10px; }
  html[data-rail="closed"] .app > .sidebar .nav-caret { display: none; }
  /* They also shrink their glyph to 14px for the same reason, which leaves the column ragged
     once the glyphs are all that is left. */
  html[data-rail="closed"] .app > .sidebar .nav-sub .nav-icon { width: 18px; font-size: 14px; }

  /* A count has nowhere to go at 64px, so it becomes a dot on the icon's shoulder: "there is
     something here". The number is not lost — it is in the item's title attribute (see
     base.html) and still in the DOM, so a screen reader reads "Tasks 18" exactly as before.
     It is clipped, not removed. */
  html[data-rail="closed"] .app > .sidebar .nav-badge {
    position: absolute;
    top: 3px; left: 21px;          /* the top-right corner of an 18px icon sitting at 10px */
    /* 12, not 8. `* { box-sizing: border-box }` is global here, so the 2px ring below is
       subtracted from this width rather than added to it: at 8px the visible orange core
       was 4px across. This is the only unread signal left on a 64px rail. */
    width: 12px; height: 12px;
    min-width: 0;
    padding: 0;
    border-radius: 50%;
    background: #FDA65A;           /* the active-bar orange, 3.65:1 on Ocean */
    border: 2px solid var(--sidebar-bg);
    color: transparent;
    font-size: 0;
    overflow: hidden;
  }
  html[data-rail="closed"] .app > .sidebar .nav-item.active .nav-badge { background: #FFFFFF; }

  /* The head keeps its 56px so the first nav row starts at the same y in both states. The
     wordmark goes; the chevron turns around and moves onto the icon column — 5px of row
     padding puts the centre of a 28px button at 31px, which is the centre of an 18px icon
     sitting at 22px. */
  html[data-rail="closed"] .app > .sidebar .rail-head { padding-left: 5px; padding-right: 5px; }
  html[data-rail="closed"] .app > .sidebar .brand { display: none; }
  html[data-rail="closed"] .app > .sidebar .rail-toggle svg { transform: rotate(180deg); }

  /* The version is the ONLY link to /changelog anywhere in the app, so it survives the
     collapse; the "NUDGE ·" wrapped around it does not, and System status has a second path
     through Admin. */
  html[data-rail="closed"] .app > .sidebar .foot-pre,
  html[data-rail="closed"] .app > .sidebar .foot-sys { display: none; }
  /* The footer is margin-top: auto, so anything that wraps here grows UPWARDS and pushes the
     last nav items off the rail. A version string longer than "v1" would do exactly that in
     a 20px content box, so it clips instead. */
  html[data-rail="closed"] .app > .sidebar .sidebar-foot { white-space: nowrap; overflow: hidden; }
}

/* Someone who has asked the OS to stop animating things gets the same two states with no
   journey between them. The rail still collapses; it just arrives. */
@media (prefers-reduced-motion: reduce) {
  .app, .nav-label, .rail-toggle svg { transition: none; }
}

/* ============ MAIN + TOPBAR ============ */
.main {
  display: flex;
  flex-direction: column;
  min-width: 0;
  /* The canvas. Every card in the app floats on this. */
  background: var(--canvas);
}
.topbar {
  height: var(--topbar-h);
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 24px;
  border-bottom: 1px solid var(--border);
  background: var(--bg);
  position: sticky;
  top: 0;
  z-index: 5;
}
.topbar-spacer { width: 200px; }
.search {
  flex: 1;
  max-width: 560px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px;
  height: 34px;
  background: var(--hover);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-3);
}
.search:focus-within { background: #fff; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-softer); }
/* min-width:0 is load-bearing: an <input> has an intrinsic min-content width, so without
   it the search box refuses to shrink and pushes the topbar (and the page) wider than the
   screen instead of narrowing. */
.search input { flex: 1; min-width: 0; border: 0; background: transparent; outline: none; color: var(--text); font-size: 13px; }
.search kbd { font-family: var(--font); font-size: 11px; color: var(--text-3); background: #fff; border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px; }

.topbar-right { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; margin-left: auto; justify-content: flex-end; }
.topbar-divider { width: 1px; height: 24px; background: var(--border); margin: 0 4px; flex-shrink: 0; }

/* Quick-action buttons */
.quick-actions-bar { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }
.qa-btn {
  display: inline-flex; align-items: center; gap: 5px;
  height: 30px; padding: 0 10px;
  border-radius: var(--radius);
  font-size: 12.5px; font-weight: 500;
  color: var(--text);
  background: transparent;
  border: 1px solid transparent;
  text-decoration: none;
  transition: background .12s ease, border-color .12s ease, color .12s ease;
  flex-shrink: 0;
  white-space: nowrap;
}
.qa-btn:hover {
  background: var(--accent-softer);
  border-color: var(--accent-soft);
  color: var(--accent-ink);
  text-decoration: none;
}
.qa-btn .qa-icon { font-size: 13px; line-height: 1; }
.qa-btn .qa-label { font-size: var(--fs-micro); }
.qa-btn:hover .qa-icon { transform: none; }
@media (max-width: 1240px) {
  .qa-btn .qa-label { display: none; }
}
.icon-btn {
  width: 34px; height: 34px;
  display: grid; place-items: center;
  border-radius: var(--radius);
  color: var(--text-2);
  position: relative;
  transition: background .12s ease, color .12s ease;
  text-decoration: none;
}
.icon-btn:hover { background: var(--hover); color: var(--text); text-decoration: none; }
.notif-dot {
  position: absolute;
  top: 8px; right: 8px;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: #EF4444;
  border: 2px solid #fff;
}
.user-chip {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 8px 4px 4px;
  border-radius: var(--radius);
}
.user-chip:hover { background: var(--hover); text-decoration: none; }
.user-meta { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.15; }
.user-name { font-size: var(--fs-micro); font-weight: 600; color: var(--text); }
.user-role { font-size: var(--fs-micro); color: var(--text-2); }
/* The topbar is the one row that cannot wrap, and .topbar-right is flex:0 0 auto, so its
   content sets a hard floor on the bar's width. Between 821px (just above the mobile
   breakpoint) and ~917px that floor exceeded the available width and the OVERFLOW WENT TO
   THE PAGE: every route scrolled sideways by 83px at 834px wide, which is iPad Pro 11" and
   iPad Air 11" in portrait. The name/role block is the 89px that does not fit, and it is
   the only thing in the bar that is pure decoration, so it goes first -- the avatar still
   identifies the user and still opens the same menu. */
@media (max-width: 1000px) {
  .user-meta { display: none; }
  /* the last 7px the bar needs at 821px -- and there is no Cmd+K on a tablet anyway */
  .topbar .search kbd { display: none; }
}

/* ============ AVATAR ============ */
.avatar {
  display: inline-grid;
  place-items: center;
  width: 28px; height: 28px;
  border-radius: 50%;
  color: white;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.01em;
  flex-shrink: 0;
}
.avatar.lg { width: 40px; height: 40px; font-size: 15px; }
.avatar.xl { width: 56px; height: 56px; font-size: 18px; }
.avatar.sm { width: 22px; height: 22px; font-size: 10px; }

/* ============ CONTENT + PAGE HEAD ============ */
.content { flex: 1; display: flex; flex-direction: column; min-height: 0; }
.page-head {
  padding: 18px 24px 12px;
  display: flex;
  align-items: center;
  justify-content: flex-start;   /* breadcrumb + title pack left (admin pages); actions float right via margin-left:auto */
  gap: 16px;
  flex-wrap: wrap;
}
/* Normalize every top-level page title to one scale, incl. bare admin <h1> with a breadcrumb. */
.page-head h1 {
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0;
}
.page-title {
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.01em;
  display: flex; align-items: baseline; gap: 10px;
  margin: 0;
}
.title-count { font-size: var(--fs-base); font-weight: 500; color: var(--text-2); }
.page-title .crumb { font-size: var(--fs-base); font-weight: 500; color: var(--text-2); }
.page-title .crumb a { color: inherit; }
.page-title .crumb a:hover { color: var(--accent-ink); }
/* flex-wrap belongs on the base rule, not just the mobile one: these clusters are 428-482px
   of buttons, so they overflowed the page on a tablet in portrait (51px on /tasks at 834px
   wide) while the ONLY rule that let them wrap was gated to <=820px. Wrapping costs nothing
   at a width where they already fit. */
.page-actions { display: flex; gap: 8px; align-items: center; margin-left: auto; flex-wrap: wrap; }

/* ============ BUTTONS ============ */
/* Base sizing applies to ALL button variants — incl. bare button.primary/.ghost/.danger
   (many CTAs omit the .btn class), so every button gets consistent, comfortable padding. */
.btn, button.btn,
button.primary, .btn-primary,
button.ghost, button.danger {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  background: #fff; color: var(--text);
  padding: 9px 17px;
  font-size: 13px; font-weight: 500; line-height: 1.2;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  cursor: pointer; white-space: nowrap;
  transition: background .12s ease, border-color .12s ease, box-shadow .12s ease, transform .05s ease;
}
.btn:hover, button.btn:hover { background: var(--hover); border-color: var(--border-strong); text-decoration: none; }
.btn:active, button.btn:active, button.primary:active, button.ghost:active, button.danger:active { transform: translateY(1px); }
.btn:focus-visible, button.btn:focus-visible, button.primary:focus-visible {
  outline: none; box-shadow: 0 0 0 3px var(--accent-softer);
}
.btn:disabled, .btn[disabled], button.btn:disabled,
button.primary:disabled, button.ghost:disabled, button.danger:disabled {
  opacity: .5; cursor: not-allowed; box-shadow: none; transform: none;
}
.btn:disabled:hover, .btn[disabled]:hover { background: #fff; border-color: var(--border); }
.btn.primary:disabled:hover, button.primary:disabled:hover { background: var(--accent); border-color: var(--accent); }

/* White 13px bold on #F37021 is 2.94:1 — below AA for anything but large text, and this is
   the app's main CTA on 43 screens. --accent-ink is the same hue a few steps deeper and
   reaches 5.18:1 with white. To go back to the lighter brand orange, change this one
   declaration (and accept the contrast cost). */
.btn.primary, button.btn.primary, button.primary, .btn-primary {
  background: var(--accent-ink); color: white;
  border-color: var(--accent-ink);
  box-shadow: var(--shadow-sm);
  font-weight: 600;
}
.btn.primary:hover, button.primary:hover, .btn-primary:hover {
  background: var(--accent-hover); border-color: var(--accent-hover); color: white;
  box-shadow: var(--shadow-md);
}
.btn.ghost, button.ghost { background: transparent; border-color: transparent; color: var(--text-2); box-shadow: none; }
.btn.ghost:hover, button.ghost:hover { background: var(--hover); color: var(--text); }
.btn.danger, button.danger { color: #B91C1C; border-color: #FCA5A5; background: #fff; box-shadow: none; }
.btn.danger:hover { background: #FEF2F2; border-color: #F87171; }

input, select, textarea {
  background: white;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 7px 10px;
  color: var(--text);
  font-size: 13px;
}
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-softer); }
textarea { resize: vertical; min-height: 60px; }

/* ============ TABS ============ */
.tabs {
  display: flex;
  gap: 2px;
  border-bottom: 1px solid var(--border);
  margin: 0 24px;
  overflow-x: auto;
}
.tab {
  padding: 8px 12px;
  font-size: 13px;
  color: var(--text-2);
  font-weight: 500;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  display: inline-flex; align-items: center; gap: 6px;
  white-space: nowrap;
  text-decoration: none;
}
.tab:hover { color: var(--text); text-decoration: none; }
.tab.active { color: var(--text); border-bottom-color: var(--accent); }
.tab-count {
  background: var(--hover);
  border-radius: 10px;
  font-size: var(--fs-micro);
  padding: 1px 7px;
  font-weight: 600;
  color: var(--text-2);
}
.tab.active .tab-count { background: var(--accent-soft); color: var(--accent-ink); }

/* ============ TOOLBAR + CHIPS ============ */
.toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 24px;
  gap: 12px;
}
.toolbar-left, .toolbar-right { display: flex; align-items: center; gap: 8px; }
.toolbar-meta { color: var(--text-2); font-size: 12.5px; }
.filter-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 10px;
  font-size: var(--fs-micro);
  border-radius: var(--radius);
  border: 1px solid var(--border);
  color: var(--text);
  background: #fff;
}
.filter-chip:hover { background: var(--hover); text-decoration: none; }
.filter-chip strong { color: var(--accent-ink); font-weight: 600; }

.bulk-bar {
  display: flex; align-items: center; gap: 14px;
  flex-wrap: wrap;   /* on a tablet the one-row bar pushed Delete off the side of the page */
  padding: 8px 14px;
  margin: 0 24px;
  background: var(--accent-softer);
  border: 1px solid var(--accent-soft);
  border-radius: var(--radius);
  font-size: 12.5px;
  color: var(--accent-ink);
}
/* Superseded by the global [hidden] rule at the top of this file; kept as documentation
   of why it existed. The class's display:flex outranks the UA [hidden]{display:none}, so JS's `hidden = n===0`
   (people_list.html) never actually hid the bar — it showed even with nothing selected. */
.bulk-bar[hidden] { display: none; }
.bulk-bar strong { color: var(--accent-ink); }
.bulk-bar .sep { width: 1px; height: 14px; background: var(--accent-soft); }
.bulk-bar .bulk-action {
  font-size: 12.5px; color: var(--text);
  font-weight: 500; padding: 3px 8px; border-radius: 4px;
  background: none; border: 0;
}
.bulk-bar .bulk-action:hover { background: rgba(0,0,0,.04); }
.bulk-bar .bulk-action.danger { color: #B91C1C; }
.bulk-bar select, .bulk-bar input { padding: 4px 7px; font-size: 12.5px; }
.bulk-bar .bulk-spacer { flex: 1; }

/* ============ STAGE PILLS + TAGS ============ */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 9px;
  border-radius: 12px;
  font-size: var(--fs-micro);
  font-weight: 500;
  white-space: nowrap;
  max-width: 100%;   /* never past its container; .pill-label ends a long name in "…" */
  min-width: 0;
}
/* Stage pills are rendered with an inline `background:<hex>1F; color:<hex>` — painting the
   label in the stage's own hue on a 12% tint of that same hue, which fails contrast for 10
   of the 13 default stages (pale greens/cyans are unreadable). Keep the hue where it works
   — the tint and the dot — and always paint the LABEL in body ink. `!important` is what
   beats the template's inline style, so this one rule fixes all 7 template sites at once. */
.pill[style] { color: var(--text) !important; }
.pill-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
/* A pill never runs past its container. On the contact card a long stage name wraps inside
   its pill (the whole name matters there); anywhere tighter it ends in "…". */
.pill-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.field-list .pill { white-space: normal; line-height: 1.3; padding: 3px 10px; }
.field-list .pill-label { overflow: visible; overflow-wrap: anywhere; }

.tag {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 8px;
  font-size: 11.5px;
  border-radius: 10px;
  background: #F1F2F4;
  color: #4B5563;
  font-weight: 500;
  white-space: nowrap;
  margin-right: 4px;
}
/* Tag and group chips on the contact card: a wrapping row with real gaps, so a second
   line of chips sits below the first instead of touching it. */
.tag-list { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.tag-list > form { display: inline-flex; margin: 0; }
.tag-list .tag { margin: 0; line-height: 1.45; padding: 3px 9px; }
.tag-list-add { flex-basis: 100%; margin-top: 2px; }
.tag.add-tag { background: transparent; border: 1px dashed var(--border-strong); color: var(--text-2); cursor: pointer; }
.tag.add-tag:hover { color: var(--accent-ink); border-color: var(--accent); }
.tag .x { color: var(--text-3); background: none; border: 0; padding: 0; cursor: pointer; font-size: 12px; line-height: 1; }
.tag .x:hover { color: #B91C1C; }

/* ============ TABLES ============ */
.table-wrap {
  margin: 0 24px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  /* Shadow in the BORDER colour, which is how FUB's cards read flat rather than floaty. */
  box-shadow: var(--shadow-card);
  overflow: auto;
}
.grid-table, .contacts-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: var(--fs-base);
}
/* FUB's column headers are the SAME size, weight and colour as the body text: 14px/400,
   sentence case, no tracking. Ours were 11.5px/600 uppercase with letter-spacing, which is
   the loudest single mismatch on every list in the app. The background has to become the
   card surface rather than #FAFBFC, or rows scroll under a header that no longer has a
   fill of its own. */
.grid-table thead th, .contacts-table thead th {
  position: sticky;
  top: 0;
  background: var(--surface);
  z-index: 1;
  padding: 11px 12px;
  text-align: left;
  font-size: var(--fs-base);
  font-weight: 400;
  color: var(--text);
  text-transform: none;
  letter-spacing: normal;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.grid-table tbody tr, .contacts-table tbody tr { transition: background .1s ease; }
.grid-table tbody tr:hover, .contacts-table tbody tr:hover { background: var(--row-hover); }
.grid-table tbody td, .contacts-table tbody td {
  padding: 10px 12px;
  border-bottom: 1px solid var(--border-soft);
  color: var(--text);
  vertical-align: middle;
}
.grid-table tbody tr:last-child td, .contacts-table tbody tr:last-child td { border-bottom: 0; }
.name-cell { display: flex; align-items: center; gap: 10px; min-width: 0; white-space: nowrap; }
.name-text { font-weight: 500; }
.mono-soft { color: var(--text); font-variant-numeric: tabular-nums; white-space: nowrap; }
.muted-cell { color: var(--text-2); }

/* ============ KPIS / METRIC CARDS ============ */
.kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 10px;
}
.kpi, .metric {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  text-decoration: none;
  color: var(--text);
  transition: border-color .12s ease, box-shadow .12s ease;
}
.kpi:hover, .metric:hover { border-color: var(--border-strong); box-shadow: var(--shadow-sm); text-decoration: none; }
.kpi .lbl, .metric-label { font-size: 11.5px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-2); }
.kpi .val, .metric-value { font-size: 26px; font-weight: 700; letter-spacing: -0.02em; color: #000; line-height: 1.1; }
.kpi.alert .val { color: #B91C1C; }
.kpi.success .val { color: #047857; }
.metric-foot { display: flex; align-items: center; gap: 6px; font-size: 11px; }
.metric-delta.up { color: #047857; font-weight: 600; }
.metric-delta.down { color: #B91C1C; font-weight: 600; }
.metric-delta.flat { color: var(--text-3); font-weight: 500; }
.metric-sub { color: var(--text-2); }

/* ============ CONTACT TYPEAHEAD (quick-action dialogs) ============ */
/* Namespaced ctp- (contact typeahead picker), NOT ta-. A `.ta-list` here collided
   with the dashboard's own "take action" .ta-list further down this file: that rule
   only redefines `display`, so this block's position/inset/shadow leaked straight
   onto it and the task list tore out of flow and floated over the page while you
   scrolled. Short prefixes in one shared global stylesheet WILL collide. */
.ctp-wrap { position: relative; }
.ctp-list {
  /* The picker lives INSIDE the field's <label>, which is uppercased and letter-spaced
     for the caption. Contact names inherited that and came out "KOFI MENSAH". A person's
     name is data, not a caption, so the list opts out of the label's typography. */
  text-transform: none; letter-spacing: normal;
  position: absolute; z-index: 20; left: 0; right: 0; top: 100%;
  margin: 2px 0 0; padding: 4px; list-style: none;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-md);
  max-height: 240px; overflow-y: auto;
}
.ctp-opt {
  display: flex; align-items: baseline; gap: 8px;
  padding: 7px 8px; border-radius: var(--radius); cursor: pointer;
  /* 44px on a phone without stretching the row on desktop. */
  min-height: 32px;
}
.ctp-opt.on { background: var(--accent-softer); }
.ctp-opt-name { font-size: var(--fs-base); font-weight: 600; color: var(--text);
               white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ctp-opt-sub  { font-size: var(--fs-micro); color: var(--text-2); margin-left: auto;
               flex-shrink: 0; }
/* A committed pick shows a NAME in a field whose placeholder promised a number, so it
   needs to look chosen rather than mistyped. */
/* (0,3,1) — beats `form.stacked label input` at (0,1,3) on its own, so no !important.
   That selector is the one that silently killed the iOS 16px rule; check specificity
   against it before adding anything here. */
.ctp-wrap.ctp-picked > input[type="text"] {
  border-color: var(--accent);
  padding-right: 26px;
}
.ctp-wrap.ctp-picked::after {
  content: "\2713"; position: absolute; right: 9px; top: 50%; transform: translateY(-50%);
  color: var(--accent-ink); font-weight: 700; font-size: var(--fs-micro); pointer-events: none;
}
@media (max-width: 820px) { .ctp-opt { min-height: 44px; } }

/* ============ SEGMENTED CONTROL ============ */
/* The old selected pill had dissolved: white on a #F4F5F7 track is 1.09:1, and that track
   was 1.01:1 against the old #F1F4F8 canvas (1.04:1 on Fog), so neither pill nor control
   was visible. Measured every candidate light track and none beats ~1.35:1 against a white
   pill, so FILL CANNOT CARRY THIS. What actually carries it, in order of measured strength:
     weight  500 -> 600                         (non-colour cue, so this also satisfies
                                                 "don't signal state with colour alone")
     ink     --text-2 5.50:1 -> --text 9.77:1   on the pill
     fill    1.19:1   |  border 1.61:1  |  shadow
   The last three are weak as ratios but they are what gives the pill its SHAPE; contrast
   ratio is calibrated for text legibility, not for large-area figure/ground. Do not drop
   the weight or ink change and expect the fill to hold this up on its own.
   Every .seg-btn carries a TRANSPARENT border so turning one on never shifts layout. */
.seg {
  display: inline-flex;
  background: var(--border);          /* recessed well; reads on white AND on canvas */
  border-radius: 5px;
  padding: 2px;
}
.seg-btn {
  padding: 4px 10px;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-2);
  border: 1px solid transparent;      /* reserved so .on's border adds no width */
  border-radius: 4px;
  text-decoration: none;
  display: inline-block;
}
.seg-btn:hover { color: var(--text); text-decoration: none; }
.seg-btn.on {
  background: var(--surface);
  border-color: #AEBECA;
  color: var(--text);
  font-weight: 600;
  box-shadow: 0 1px 2px rgba(54, 70, 80, 0.18);
}
.seg-sm .seg-btn { font-size: 11px; padding: 3px 8px; }

/* ============ DASHBOARD ============ */
.dash {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: 20px;
  padding: 20px 24px 24px;
  flex: 1;
}
@media (max-width: 1100px) { .dash { grid-template-columns: 1fr; } }
.dash-main { min-width: 0; display: flex; flex-direction: column; gap: 20px; }
.dash-section { display: flex; flex-direction: column; gap: 12px; }
.dash-section-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.dash-h2 { font-size: 14px; font-weight: 600; color: var(--text); margin: 0; }

.welcome { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; }
.welcome-h { font-size: 24px; font-weight: 700; letter-spacing: -0.025em; color: #000; margin: 0 0 4px; }
.welcome-sub { font-size: 14px; color: var(--text-2); margin: 0; }
.welcome-mark { opacity: 0.85; }

/* Win the Day banner */
.win-banner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px; flex-wrap: wrap;
  background: linear-gradient(135deg, #0F0F0F 0%, #1F1F1F 60%, #2A1810 100%);
  color: #fff;
  padding: 16px 20px;
  border-radius: var(--radius-lg);
  position: relative;
  overflow: hidden;
}
.win-banner::after {
  content: '';
  position: absolute;
  top: -40px; right: -20px;
  width: 220px; height: 220px;
  background: radial-gradient(circle, var(--accent) 0%, transparent 70%);
  opacity: 0.18;
  pointer-events: none;
}
.win-banner-l { display: flex; align-items: center; gap: 14px; position: relative; z-index: 1; }
.win-icon { width: 38px; height: 38px; border-radius: 50%; background: rgba(243,112,33,0.18); color: var(--accent); display: grid; place-items: center; flex-shrink: 0; font-size: 18px; }
.win-title { font-size: 15px; font-weight: 600; }
.win-sub { font-size: 12px; color: rgba(255,255,255,0.6); margin-top: 3px; }
.win-banner-r { display: flex; align-items: center; gap: 16px; position: relative; z-index: 1; }
.win-progress { width: 140px; height: 6px; background: rgba(255,255,255,0.12); border-radius: 3px; overflow: hidden; }
.win-progress-bar { height: 100%; background: var(--accent); border-radius: 3px; transition: width .3s ease; }
.win-cta {
  display: inline-flex; align-items: center; gap: 4px;
  background: rgba(255,255,255,0.1); color: #fff;
  padding: 7px 12px; border-radius: var(--radius);
  font-size: 12.5px; font-weight: 500;
  border: 1px solid rgba(255,255,255,0.15);
  text-decoration: none;
}
.win-cta:hover { background: rgba(255,255,255,0.18); color: white; text-decoration: none; }

/* Recent activity row */
.recent-list { background: #fff; border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; }
.recent-row {
  display: grid;
  grid-template-columns: 28px 1fr 1.5fr 90px auto auto;
  gap: 14px; align-items: center;
  padding: 10px 14px;
  text-decoration: none;
  border-bottom: 1px solid var(--border-soft);
  color: var(--text);
}
.recent-row:last-child { border-bottom: 0; }
.recent-row:hover { background: var(--row-hover); text-decoration: none; }
.recent-name { font-weight: 600; }
.recent-detail { color: var(--text-2); font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.recent-time { color: var(--text-2); font-size: 11.5px; }
.recent-agent { color: var(--text-2); display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; }
.dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; flex-shrink: 0; }

/* Right rail */
.dash-rail { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.rail-card { background: #fff; border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; display: flex; flex-direction: column; }
.rail-card-head { display: flex; align-items: center; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--border-soft); font-size: 13px; font-weight: 600; }
.rail-card-head > span:nth-child(2) { flex: 1; }
.upcoming-list { display: flex; flex-direction: column; max-height: 360px; overflow: auto; }
/* Two columns only: date + body. A third `auto` track for the kind pill used to
   collapse the title's 1fr track to 0px and balloon the pill — the kind now rides
   inline on the sub-line as a small tag, so the title always gets the full width. */
.up-item { display: grid; grid-template-columns: 44px 1fr; gap: 12px; align-items: start; padding: 11px 14px; border-bottom: 1px solid var(--border-soft); color: var(--text); text-decoration: none; }
.up-item:last-child { border-bottom: 0; }
.up-date { background: #F4F5F7; border-radius: 5px; padding: 5px 0; text-align: center; line-height: 1; align-self: start; }
.up-day { font-size: 15px; font-weight: 700; letter-spacing: -0.01em; color: var(--text); }
.up-mon { font-size: 9px; color: var(--text-2); text-transform: uppercase; letter-spacing: 0.08em; margin-top: 2px; }
/* min-width:0 lets the title clamp instead of overflowing the 1fr track */
.up-body { min-width: 0; }
.up-title { font-size: 13px; font-weight: 600; line-height: 1.35; color: var(--text); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.up-sub { display: flex; align-items: center; gap: 6px; margin-top: 3px; min-width: 0; }
/* Namespaced kind classes (kind-task / kind-appt) — NOT bare `.task`, which is a
   global Tasks-page row style (display:flex + padding) that would otherwise win on
   source order and balloon this tag. */
.up-kind { display: inline-flex; align-items: center; flex: none; font-size: 9px; font-weight: 600; padding: 1px 6px; border-radius: 3px; text-transform: uppercase; letter-spacing: 0.04em; white-space: nowrap; background: #F1F3F5; color: var(--text-2); }
.up-meta { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11.5px; color: var(--text-2); }

/* The card head's trailing link ("Calendar →"). .rail-card-head's nth-child(2) already
   takes the free space, so this just needs to stop looking like body copy. */
.rail-card-link { font-size: 11.5px; font-weight: 500; color: var(--text-2); text-decoration: none; white-space: nowrap; }
.rail-card-link:hover { color: var(--accent-ink); text-decoration: none; }
/* Dashboard "Deal next steps" (dashboard.html .deal-next) */
.dn-list { display: flex; flex-direction: column; }
.dn-item { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 14px; border-bottom: 1px solid var(--border-soft); color: inherit; text-decoration: none; }
.dn-item:last-child { border-bottom: 0; }
.dn-item:hover { background: var(--hover); text-decoration: none; }
.dn-main { min-width: 0; }
.dn-step { font-size: 13px; font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dn-deal { font-size: 11.5px; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dn-side { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex-shrink: 0; }
.dn-due { font-size: 11px; font-weight: 600; color: var(--text-2); }
.dn-due.overdue { color: #B91C1C; }
.dn-due.today { color: var(--accent-ink); }
.dn-prog { font-size: 11px; color: var(--text-3); font-variant-numeric: tabular-nums; }

/* All / Tasks / Appts. Built on the app's own .seg control so it matches the range picker
   sitting a few hundred pixels away on the same page. */
.up-tabs { margin: 10px 14px 0; }
.up-tabs .seg-btn { flex: 1; justify-content: center; gap: 4px; }
.up-tab-n { font-variant-numeric: tabular-nums; opacity: .6; font-weight: 600; }
.seg-btn.on .up-tab-n { opacity: .75; }
.up-empty { padding: 20px; text-align: center; color: var(--text-2); font-size: 12px; }

/* .up-item became an <a> (every row has somewhere to go: the contact, or the calendar day
   for an appointment with no contact), so its base rule above carries the link reset. */
.up-item:hover { background: var(--row-hover); text-decoration: none; }

/* Appointment kinds reuse the calendar's colour vocabulary verbatim (.cal-chip.appt.showing
   and friends, further down this file) so one showing is the same green on both pages. The
   two pre-existing values moved onto the calendar's as part of the same tidy-up: the task
   badge was a near-accent orange here and amber there, for the same row. */
.up-kind.kind-task { background: #fff3d6; color: #92600a; }
.up-kind.kind-appt { background: #e6efff; color: #1e40af; }
.up-kind.kind-showing { background: #e7f7ec; color: #166534; }
.up-kind.kind-closing { background: #fdeede; color: #9a3412; }
.up-kind.kind-open_house { background: #f3e8ff; color: #6b21a8; }

/* ===== Synology Documents card / file tree ===== */
.docs-path { font-size: 11px; margin: 2px 0 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.filetree { font-size: 13px; }
.ftree-msg { color: var(--text-2); font-size: 12px; padding: 4px 0; }
.ftree-err { color: #B91C1C; }
.ftree { list-style: none; margin: 0; padding: 0; }
.ftree .ftree { padding-left: 16px; }
.ftree-row { margin: 1px 0; }
.ftree-toggle { display: flex; align-items: center; gap: 6px; width: 100%; background: none; border: 0; padding: 4px; cursor: pointer; font: inherit; color: var(--text); text-align: left; border-radius: 4px; }
.ftree-toggle:hover { background: var(--hover); }
.ftree-caret { font-size: 9px; color: var(--text-3); transition: transform .12s; display: inline-block; }
.ftree-toggle.open .ftree-caret { transform: rotate(90deg); }
.ftree-file { display: flex; align-items: center; gap: 6px; padding: 4px 4px 4px 19px; border-radius: 4px; color: var(--text); text-decoration: none; }
.ftree-file:hover { background: var(--hover); }
.ftree-ico { flex: none; }
.ftree-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ftree-size { margin-left: auto; color: var(--text-3); font-size: 10.5px; flex: none; padding-left: 8px; }

.take-action .rail-card-head { background: #0F0F0F; color: #fff; border-bottom-color: transparent; }
.take-action .rail-card-head .ta-icon { color: var(--accent); }
.ta-list { display: flex; flex-direction: column; }
.ta-item { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 14px; border-bottom: 1px solid var(--border-soft); text-decoration: none; color: var(--text); }
.ta-item:last-child { border-bottom: 0; }
.ta-item:hover { background: var(--row-hover); text-decoration: none; }
.ta-l { display: flex; align-items: center; gap: 10px; min-width: 0; }
.ta-name { font-size: 12.5px; font-weight: 600; }
.ta-why { font-size: 11px; color: var(--text-2); margin-top: 2px; }
.ta-urgency { font-size: 10px; font-weight: 600; padding: 2px 6px; border-radius: 3px; text-transform: uppercase; letter-spacing: 0.04em; }
.ta-urgency.urgent { background: #FEF2F2; color: #B91C1C; }
.ta-urgency.high { background: #FFF1E8; color: #C2410C; }
.ta-urgency.low { background: #F3F4F6; color: var(--text-2); }

/* ============ INBOX ============ */
.inbox-shell {
  display: grid;
  grid-template-columns: 200px 320px 1fr;
  grid-template-rows: minmax(0, 1fr);   /* bound the row so panes scroll instead of growing */
  min-height: 0;
  /* dvh (2nd) wins where supported — correct under mobile browser chrome; vh is the fallback.
     NOT flex:1 — that would set flex-basis:0 and let content override this fixed height. */
  height: calc(100vh - var(--topbar-h));
  height: calc(100dvh - var(--topbar-h));
  overflow: hidden;
}
.inbox-rail {
  border-right: 1px solid var(--border);
  padding: 14px 10px;
  display: flex; flex-direction: column; gap: 2px;
  min-height: 0;
  overflow: auto;
  background: #fff;
}
/* was 10px/600 uppercase with 0.08em tracking — the smallest, shoutiest label in the app */
.rail-label { font-size: var(--fs-sm); font-weight: 600; color: var(--text-2); text-transform: none; letter-spacing: normal; padding: 10px 8px 5px; }
.rail-label strong { color: var(--accent-ink); }
.rail-item {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 8px;
  padding: 6px 8px; border-radius: var(--radius);
  font-size: var(--fs-base); color: var(--text); text-decoration: none;
}
/* Inbox names are user-entered in Admin > Phone Numbers and routinely carry a phone number
   ("Adam line 604-675-0000"), so one line cut the part that tells two lines apart. Up to two
   lines, then "…"; the count stays top-right. The full name is the item's title. */
.rail-item > span:first-child {
  min-width: 0; overflow: hidden; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2;
  line-height: 1.35;
}
.rail-item .rail-count { line-height: 1.35; }
.rail-item:hover { background: var(--hover); text-decoration: none; }
.rail-item.active { background: var(--accent-softer); color: var(--accent-ink); font-weight: 600; }
.rail-count { font-size: 11px; color: var(--text-2); flex-shrink: 0; }  /* a long name eats the ellipsis, never the count */
.rail-item.active .rail-count { color: var(--accent-ink); }
.rail-divider { height: 1px; background: var(--border); margin: 10px 4px; }

.conv-list { border-right: 1px solid var(--border); display: flex; flex-direction: column; min-height: 0; overflow: hidden; background: #fff; }
/* Two rows: the inbox's name and number with the ✎ button, then the All / Unread / Missed /
   Closed filter across the full width. Side by side, the filter needed ~360px of a 320px
   column, so it was clipped on the right and squeezed the name onto four lines. */
.conv-list-head { display: flex; flex-direction: column; align-items: stretch; padding: 12px 14px 10px; gap: 10px; }
.conv-head-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; min-width: 0; }
.conv-head-name { min-width: 0; }
.conv-head-title { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.conv-head-sub { font-size: 11px; color: var(--text-2); }
.conv-list-head .seg.conv-filter { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); width: 100%; box-sizing: border-box; }
.conv-list-head .seg.conv-filter .seg-btn { justify-content: center; white-space: nowrap; padding-left: 4px; padding-right: 4px; font-size: 12px; min-width: 0; }
.conv-section-label { padding: 0 14px 8px; font-size: 11px; color: var(--text-2); font-weight: 500; }
.conv-items { flex: 1; overflow: auto; }
.conv-item {
  display: grid;
  grid-template-columns: 32px 1fr auto;
  gap: 10px;
  align-items: center;
  padding: 10px 14px;
  border-left: 2px solid transparent;
  text-decoration: none; color: var(--text);
}
.conv-item:hover { background: var(--hover); text-decoration: none; }
.conv-item.active { background: var(--accent-softer); border-left-color: var(--accent); }
.conv-mid { min-width: 0; line-height: 1.3; }
.conv-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.conv-name { font-size: 12.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.conv-time { font-size: 11px; color: var(--text-2); flex-shrink: 0; }
.conv-preview { font-size: 11.5px; color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 2px; }
.conv-unread { background: var(--accent); color: white; font-size: 10px; font-weight: 700; padding: 1px 6px; border-radius: 9px; min-width: 18px; text-align: center; }

/* ----- Missed-call rows (inbox "Missed" tab) -----
   Same .conv-item grid as a text thread so the list reads as one list, but the row is a
   <div> with a stretched link: a call-back <button> cannot legally nest inside an <a>,
   and nesting it anyway gives you a control that swallows its own click. The overlay
   takes the whole row, the button is raised above it. */
.conv-missed { position: relative; }
.conv-row-link { position: absolute; inset: 0; z-index: 0; }   /* out of grid flow */
.conv-missed > .avatar, .conv-missed > .conv-mid { position: relative; z-index: 1; pointer-events: none; }
.conv-missed .conv-preview { display: flex; align-items: center; gap: 6px; }
.miss-chip {
  flex-shrink: 0;
  background: #FDECEC; color: #9B2C2C;      /* 6.59:1 — a red that still reads as text */
  font-size: 10px; font-weight: 700; letter-spacing: .02em;
  padding: 1px 5px; border-radius: 3px;
}
.btn-callback {
  position: relative; z-index: 1;
  background: var(--surface); color: var(--text);
  border: 1px solid #AEBECA; border-radius: var(--radius);
  font-size: 11px; font-weight: 600; padding: 4px 8px; white-space: nowrap;
  cursor: pointer;
}
.btn-callback:hover { background: var(--row-hover); border-color: #AEBECA; }
/* 44px tap target without a 44px-tall button: growing the control would stretch every
   row and thin the list out. Same invisible-::after trick the task checkboxes use. */
.btn-callback::after {
  content: ""; position: absolute; left: 0; right: 0;
  top: 50%; height: 44px; transform: translateY(-50%);
}
/* The tab count only shouts when there is something to do; a bold zero is noise. */
.count-attn { color: #9B2C2C; font-weight: 700; }

.thread { display: flex; flex-direction: column; min-height: 0; overflow: hidden; background: var(--bg); }
.thread-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 18px; border-bottom: 1px solid var(--border); gap: 12px; }
.thread-head-l { display: flex; align-items: center; gap: 10px; }
.thread-name { font-size: 14px; font-weight: 600; }
.thread-name a { color: inherit; text-decoration: none; }
.thread-name a:hover { color: var(--accent-ink); text-decoration: underline; }
.thread-sub { font-size: 11.5px; color: var(--text-2); }
.thread-head-r { display: flex; align-items: center; gap: 4px; }

.thread-body { flex: 1; overflow: auto; padding: 20px 24px; display: flex; flex-direction: column; gap: 14px; background: #FAFAFA; }

/* The "next piece of work" panel that fills the thread pane when nothing is selected. Its
   CTA carries a contact name and .btn is globally white-space:nowrap, so the block sized to
   that one unbreakable line: 293px of content in a 266px pane at 834px wide, and .thread's
   overflow:hidden cut the text off mid-word. Cap the card to the pane it sits in and let
   this one button wrap; 40px of side padding is also too much to spend at that width. */
.inbox-empty { display: grid; place-items: center; flex: 1; text-align: center; padding: 40px 20px; }
.inbox-empty-card { width: 100%; max-width: 380px; }
.inbox-empty-card .btn { white-space: normal; }

/* Inbox on a tablet in portrait: 200px rail + 320px list + the 248px sidebar leaves the
   thread 66px at 834px wide and 256px at 1024px -- the pane you actually read is the one
   that gets squeezed, because the two beside it are fixed-width. Above 820px the mobile
   single-pane rule does not apply, so the rail moves to a horizontal strip across the top
   (the same move mobile makes) and the thread gets that 200px back: 266px at 834, 456px at
   1024. 1133px and up keeps all three columns. */
@media (min-width: 821px) and (max-width: 1100px) {
  .inbox-shell {
    grid-template-columns: 320px minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
  }
  .inbox-rail {
    grid-column: 1 / -1;
    flex-direction: row;
    overflow-x: auto;
    border-right: 0;
    border-bottom: 1px solid var(--border);
  }
  .inbox-rail > * { flex: 0 0 auto; }
  .rail-divider { display: none; }   /* a 1px horizontal rule reads as nothing in a row */
}
.bubble, .msg-bubble {
  max-width: 70%;
  padding: 9px 13px;
  border-radius: 14px;
  font-size: 13px;
  line-height: 1.45;
  white-space: pre-wrap;
}
.bubble.bubble-out, .msg.mine .msg-bubble { background: var(--accent); color: #fff; border-bottom-right-radius: 4px; align-self: flex-end; margin-left: auto; }
.bubble.bubble-in, .msg.theirs .msg-bubble { background: #fff; border: 1px solid var(--border); border-bottom-left-radius: 4px; align-self: flex-start; }
.bubble-meta, .msg-time { font-size: 10.5px; color: var(--text-3); padding: 0 6px; margin-top: 4px; }
.bubble.bubble-out + .bubble-meta { text-align: right; }

.thread-composer, .composer { border-top: 1px solid var(--border); background: #fff; padding: 12px 18px 14px; display: flex; flex-direction: column; gap: 8px; }
.composer-seg { display: flex; gap: 4px; }
.composer-tab { font-size: 12px; color: var(--text-2); padding: 4px 10px; border-radius: 4px; font-weight: 500; background: none; border: 0; }
.composer-tab:hover { color: var(--text); background: var(--hover); }
.composer-tab.active { background: var(--accent-softer); color: var(--accent-ink); }
.thread-composer textarea, .composer textarea { width: 100%; min-height: 60px; }
.compose-actions { display: flex; gap: 6px; align-items: center; }
.compose-actions .spacer { flex: 1; }
.template-chips, .quick-actions { display: flex; gap: 6px; flex-wrap: wrap; }
.template-chips button, .quick {
  font-size: 11.5px; padding: 4px 9px; border-radius: 12px;
  background: var(--hover); border: 1px solid var(--border); color: var(--text-2);
}
.template-chips button:hover, .quick:hover { color: var(--accent-ink); border-color: var(--accent); background: var(--accent-softer); }

/* ============ PERSON DETAIL (3-col) ============ */
/* The contact record. FUB's version is three columns of white cards floating on the grey
   canvas; ours was three flat white panels divided by hairlines, which is why the canvas
   change alone did nothing visible here. The columns now carry the canvas and each block
   is a .pcard sitting on it. */
.person-shell { display: grid; grid-template-columns: 320px minmax(0, 1fr) 340px; min-height: calc(100vh - var(--topbar-h)); background: var(--canvas); }
.person-left, .person-right {
  background: transparent;          /* the canvas shows through; the cards are the surface */
  border: 0;
  padding: 14px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 12px;                        /* the gutter BETWEEN cards, not margins on each */
}
/* The center is a card like the ones beside it: inset 14px from the top bar so its top edge
   lines up with the first card in each side column (it used to start flush under the bar). */
.person-center { padding: 0; background: var(--surface); display: flex; flex-direction: column; min-width: 0; border: 1px solid var(--border); border-radius: var(--radius-lg); margin: 14px 0; }

/* 320px + 340px of fixed rails leave the center column -- the timeline and composer, the
   part you actually work in -- only what is left over, and on a tablet that is nothing:
   measured 2px at 834px wide and 116px at 1024px (iPad Pro 12.9 portrait). Only the 834px
   case tripped an overflow check; 1024px just quietly rendered a 114px timeline. Below
   1240px the deals rail moves under the record and the center takes that width back: 266px
   at 834, 456px at 1024, 565px at 1133.
   Same move as the inbox rail, but a higher breakpoint on purpose: the inbox reserves 520px
   of fixed panes and still leaves a 365px thread at 1133px, while this layout reserves 660px
   and leaves 225px, so it has to give up a rail ~140px of viewport sooner. 1240px is already
   a breakpoint in this file, so the two tablet bands end where the chrome already changes. */
@media (min-width: 821px) and (max-width: 1240px) {
  .person-center { margin: 14px 14px 14px 0; }   /* the right rail moved below; keep a gutter */
  .person-shell { grid-template-columns: 320px minmax(0, 1fr); }
  .person-right { grid-column: 1 / -1; border-top: 1px solid var(--border); }
}

/* One block on the contact record: a header and its content, as a single object.
   Wrap a flat `.section-h` + content group in this, or put the class on a <details>,
   which already wraps its own summary and body. */
.pcard {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: 12px 14px;
}
/* A header that opens a card has no space above it and no rule beneath: the card edge is
   the separation now. This overrides the global .section-h margin, which exists for blocks
   that are NOT in cards. */
.pcard > .section-h,
.pcard > summary.section-h { margin: 0 0 8px; padding-bottom: 0; }
.pcard > .section-h:only-child,
.pcard > summary.section-h:only-child { margin-bottom: 0; }
/* <details> as a card: the summary is the whole header row when collapsed. */
details.pcard { padding: 12px 14px; }
details.pcard > summary { cursor: pointer; list-style: none; }
details.pcard > summary::-webkit-details-marker { display: none; }
details.pcard[open] > summary.section-h { margin-bottom: 10px; }

.person-head { display: flex; gap: 12px; align-items: center; margin-bottom: 12px; }
.person-head h2 { margin: 0; font-size: var(--fs-lg); font-weight: 500; letter-spacing: -0.01em; }
.person-head .sub { color: var(--text-2); font-size: var(--fs-micro); margin-top: 2px; }

/* ---- Inline edit on the contact card (templates/_inline_edit.html, static/ux.js [data-ie]) ----
   A pencil, and for phones and emails a copy button, appear on hover and swap the value for a
   text box or dropdown in place. Always visible where there is no hover (phones, tablets). */
.ie-btn {
  display: inline-grid; place-items: center; width: 26px; height: 26px; padding: 0; margin-left: 4px;
  border: 0; border-radius: 6px; background: none; color: var(--text-3); cursor: pointer;
  vertical-align: middle; opacity: 0; transition: opacity .12s ease;
}
.ie-btn:hover { color: var(--text); background: var(--border-soft, #EEF1F4); }
[data-ie]:hover .ie-btn, [data-ie]:focus-within .ie-btn, .ie-btn:focus-visible { opacity: 1; }
@media (hover: none) { .ie-btn { opacity: 1; } }
[data-ie] { border-radius: 8px; }
[data-ie]:hover, [data-ie].ie-editing { background: var(--hover); }
.field-list .field.ie { padding: 4px 6px; margin: 0 -6px; }
.field-list .field.ie .v.ie-view { display: flex; align-items: center; justify-content: space-between; gap: 6px; min-width: 0; overflow-wrap: anywhere; }
.ie-empty { color: var(--text-3); }
.ie-form { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin: 0; }
.ie-form input:not([type=checkbox]), .ie-form select, .ie-form textarea {
  flex: 1 1 140px; min-width: 0; font-size: var(--fs-base); padding: 5px 8px;
}
.ie-form textarea { flex-basis: 100%; min-height: 88px; }
.ie-actions { display: flex; gap: 6px; }
.ie-manage { flex: 1 1 100%; font-size: var(--fs-micro); }
.ci { display: flex; align-items: flex-start; gap: 8px; padding: 3px 6px; margin: 0 -6px; }
.ci-icon { flex: 0 0 auto; line-height: 26px; }
.ci-body { flex: 1 1 auto; min-width: 0; line-height: 26px; overflow-wrap: anywhere; }
/* One line per value (two for an address); a value too long for the card ends in an ellipsis
   with the whole of it in a tooltip, rather than wrapping mid-word. */
.ci-line { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ci-actions { display: flex; flex: 0 0 auto; margin-left: auto; }
.ci-actions form { margin: 0; display: inline; }
/* With a mouse, the copy / edit / remove buttons float over the end of the row instead of
   reserving their width, so a long email gets the whole line (FUB does the same). They take no
   clicks until the row is hovered or focused: invisible buttons over the address would otherwise
   copy it when you meant to email it. On a touch screen they stay in the row, always shown. */
@media (hover: hover) {
  .ci { position: relative; }
  .ci-actions { position: absolute; top: 3px; right: 6px; padding-left: 14px; pointer-events: none; }
  .ci:hover .ci-actions, .ci:focus-within .ci-actions {
    pointer-events: auto;
    background: linear-gradient(to right, transparent, var(--hover) 12px);
  }
  /* ...and the text steps back so it ends before them: a click on the end of an email opens
     the composer, never the copy button sitting over it. Lines do not wrap, so the row's
     height never changes on hover; only where the ellipsis falls. */
  .ci:hover .ci-body, .ci:focus-within .ci-body { padding-right: var(--ci-act, 70px); }
}
.ci-add { border: 0; background: none; padding: 0; font: inherit; cursor: pointer; }
.ci-add:hover { color: var(--accent-ink); text-decoration: underline; }
.ci-label { color: var(--text-3); font-size: var(--fs-micro); margin-left: 4px; }
/* Label suggestions under a type / label box (static/ux.js, input[data-suggest]). */
.suggest-pop {
  position: fixed; z-index: 60; max-width: 300px; padding: 10px 12px 12px;
  background: var(--surface, #fff); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: 0 8px 24px rgba(16, 24, 40, .14);
  display: flex; flex-wrap: wrap; gap: 6px;
}
.suggest-head { flex: 1 1 100%; display: flex; align-items: center; justify-content: space-between;
  font-size: var(--fs-micro); color: var(--text-3); margin-bottom: 2px; }
.suggest-x { border: 0; background: none; color: var(--text-3); cursor: pointer; font-size: 13px; padding: 2px 4px; }
.suggest-chip {
  border: 0; border-radius: 999px; padding: 5px 12px; cursor: pointer;
  background: var(--border-soft); color: var(--text); font: inherit; font-size: var(--fs-base);
}
.suggest-chip:hover { background: var(--border); }
.suggest-chip.on { background: var(--text-3); color: #fff; }
@media (max-width: 820px) { .suggest-chip { padding: 9px 14px; } }
/* "+ another phone, email or address" on the contact card */
.ci-more { margin-top: 4px; }
.ci-more > summary { font-size: var(--fs-micro); color: var(--text-3); cursor: pointer; }
.ci-add-form { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.ci-add-form input { flex: 1 1 120px; min-width: 0; font-size: var(--fs-base); padding: 5px 8px; }
.rel-add { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 0.4rem; }
.plan-list { list-style: none; padding: 0; margin: 0; }
.plan-row { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid var(--border-soft); font-size: var(--fs-base); }
.plan-row:last-child { border-bottom: 0; }
.plan-row form { margin: 0 0 0 auto; }
.plan-name { min-width: 0; overflow-wrap: anywhere; }
.plan-when { display: block; font-size: var(--fs-micro); color: var(--text-3); }
.plan-stopped .plan-name, .plan-finished .plan-name { color: var(--text-2); }
.rel-add .ctp-wrap { flex: 1 1 100%; }
.rel-add .ctp-wrap input[type="text"] { width: 100%; }
.rel-add input[name="label"] { flex: 1 1 120px; min-width: 0; }

.field-list { font-size: var(--fs-base); }
/* minmax(0, 1fr), not 1fr: a bare 1fr track never shrinks below its content, so a long
   no-wrap value (a stage pill like "Sphere - Friends & Family") widened the column and ran
   past the card's right edge. */
.field-list .field { display: grid; grid-template-columns: 88px minmax(0, 1fr); gap: 12px; padding: 6px 0; align-items: center; }
/* The one place FUB uses weight 700 in the entire product: the field label on a contact
   record. Sentence case at body size, in the body colour, sitting on one line with its
   value. Ours was 11.5px/500 uppercase with tracking, which read as a filing label and
   made the densest panel in the app noisier than anything FUB shows. */
.field-list .field .k { color: var(--text); font-size: var(--fs-base); text-transform: none; letter-spacing: normal; font-weight: 700; }
/* FUB's section headers are 16px/400 in the body colour, sentence case, no rule beneath.
   Ours were 11.5px/600 uppercase with a border, which reads as a filing label rather than
   a heading. The underline goes because the block itself is a card now. */
.section-h {
  display: flex; align-items: center; gap: 6px;
  margin: 18px 0 10px;
  padding-bottom: 0;
  font-size: var(--fs-md);
  font-weight: 400;
  color: var(--text);
  text-transform: none;
  letter-spacing: normal;
}
.section-h .add { margin-left: auto; color: var(--accent-ink); cursor: pointer; text-decoration: none; font-weight: 500; }

.compose-tabs {
  display: flex; border-bottom: 1px solid var(--border); background: #fff;
  padding: 0 24px;
}
.compose-tabs button {
  background: transparent; border: 0; border-bottom: 2px solid transparent;
  padding: 10px 12px; margin-bottom: -1px;
  color: var(--text-2); font-weight: 500; font-size: 13px; border-radius: 0;
}
.compose-tabs button:hover { color: var(--text); }
.compose-tabs button.active { color: var(--text); border-bottom-color: var(--accent); }
.compose-tabs button:disabled { color: var(--text-3); cursor: not-allowed; }

.compose-body { padding: 14px 24px; border-bottom: 1px solid var(--border); background: white; }
.compose-body textarea { width: 100%; min-height: 80px; }

.timeline-filters { display: flex; gap: 2px; padding: 6px 24px; background: var(--hover); border-bottom: 1px solid var(--border); align-items: center; overflow-x: auto; }
.timeline-filters a {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 10px; border-radius: 6px; color: var(--text-2); font-size: 12.5px;
  text-decoration: none;
}
.timeline-filters a:hover { background: white; color: var(--text); }
.timeline-filters a.active { background: white; color: var(--accent-ink); font-weight: 600; box-shadow: var(--shadow-sm); }
.timeline-filters .count { background: var(--hover); padding: 0 6px; border-radius: 8px; font-size: 11px; font-weight: 600; }
.timeline-filters a.active .count { background: var(--accent-softer); color: var(--accent-ink); }

.timeline { padding: 16px 24px 24px; background: var(--hover); flex: 1; overflow-y: auto; }
.tl-entry { background: white; border: 1px solid var(--border); border-radius: var(--radius-lg); margin-bottom: 8px; padding: 12px 14px; display: grid; grid-template-columns: 28px 1fr; gap: 12px; }
.tl-entry .icon { color: var(--text-2); font-size: 16px; text-align: center; padding-top: 2px; }
.tl-entry .body { white-space: pre-wrap; }
.tl-entry .meta { color: var(--text-3); font-size: 11px; margin-top: 4px; }
.tl-entry.sms-out .icon { color: var(--accent-ink); }
.tl-entry.sms-in .icon, .tl-entry.email-in .icon { color: #4a8fb5; }
.tl-entry.email-out .icon { color: var(--accent-ink); }
.tl-entry.call .icon { color: #047857; }
.tl-entry.note { background: #FFFBEA; border-color: #FEF3C7; }
.tl-entry.note .icon { color: #a37d00; }
.tl-entry.activity { background: var(--hover); }

.pager { display: flex; align-items: center; gap: 0.5rem; color: var(--text-2); font-size: 12px; }
.pager a { color: var(--text-2); padding: 0.2rem 0.4rem; }
.pager a:hover { color: var(--accent-ink); text-decoration: none; }

/* ============ TASKS ============ */
.task-list {
  flex: 1; overflow: auto;
  padding: 16px 24px 24px;
  display: flex; flex-direction: column; gap: 22px;
}
.task-bucket { display: flex; flex-direction: column; gap: 8px; }
.bucket-head { display: flex; align-items: center; gap: 10px; padding: 4px 0; }
.bucket-label { font-size: 13px; font-weight: 600; color: var(--text); }
.bucket-count { background: var(--hover); border-radius: 10px; font-size: 11px; font-weight: 600; padding: 1px 8px; color: var(--text-2); }
.bucket-head.danger .bucket-label { color: #B91C1C; }
.bucket-head.danger .bucket-count { background: #FEF2F2; color: #B91C1C; }
.bucket-head.accent .bucket-label { color: var(--accent-ink); }
.bucket-head.accent .bucket-count { background: var(--accent-softer); color: var(--accent-ink); }

.bucket-items { background: #fff; border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; }
.task {
  display: grid;
  grid-template-columns: 22px 6px 1fr auto 84px;
  gap: 12px; align-items: center;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border-soft);
}
.task:last-child { border-bottom: 0; }
.task:hover { background: var(--row-hover); }
.task.done { opacity: 0.55; }
.task.done .task-title { text-decoration: line-through; color: var(--text-2); }

.task-check {
  width: 18px; height: 18px;
  border: 1.5px solid var(--border-strong);
  border-radius: 50%;
  background: #fff;
  display: grid; place-items: center;
}
.task-check:hover { border-color: var(--accent); }
.task-check.on { background: var(--accent); border-color: var(--accent); color: white; }

.task-prio { width: 6px; height: 24px; border-radius: 2px; display: inline-block; background: #9CA3AF; }
.task-prio.high { background: #DC2626; }
.task-prio.med  { background: #F59E0B; }
.task-prio.low  { background: #9CA3AF; }

.task-main { min-width: 0; }
.task-title { font-size: 13px; font-weight: 500; line-height: 1.3; }
.task-meta { display: flex; align-items: center; gap: 8px; margin-top: 3px; font-size: 11.5px; color: var(--text-2); }
.task-link { display: inline-flex; align-items: center; gap: 5px; color: var(--text-2); font-size: 11.5px; text-decoration: none; }
.task-link:hover { color: var(--accent-ink); }
.task-agent { display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; color: var(--text-2); }
.task-actions { display: flex; gap: 2px; justify-content: flex-end; }
.task-actions form { margin: 0; }

/* ============ PIPELINE / KANBAN ============ */
.pipeline { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; }
.pipeline-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px;
  padding: 16px 24px 14px;
  border-bottom: 1px solid var(--border);
}
.pipeline-stats { display: flex; gap: 32px; }
.stat { display: flex; flex-direction: column; gap: 2px; }
.stat-k { font-size: 20px; font-weight: 700; color: #000; letter-spacing: -0.02em; line-height: 1.1; }
.stat-l { font-size: 11px; color: var(--text-2); text-transform: uppercase; letter-spacing: 0.04em; font-weight: 500; }
.pipeline-tools { display: flex; gap: 8px; align-items: center; }

.kanban {
  display: grid;
  grid-auto-columns: minmax(232px, 1fr);
  grid-auto-flow: column;
  gap: 12px;
  padding: 14px 24px 24px;
  overflow-x: auto;
  flex: 1;
  align-items: start;
}
.col { background: #FAFBFC; border: 1px solid var(--border); border-radius: var(--radius-lg); display: flex; flex-direction: column; max-height: 100%; }
.col-head { display: flex; align-items: center; justify-content: space-between; padding: 10px 12px; border-bottom: 1px solid var(--border-soft); gap: 8px; overflow: hidden; }
/* flex:1 + min-width:0 lets the title shrink so the value/count never get pushed
   off the column edge; the name ellipsizes inside the input instead. */
.col-title { display: flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 600; flex: 1; min-width: 0; }
.col-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.col-count { background: #fff; border: 1px solid var(--border); font-size: 10.5px; padding: 1px 6px; border-radius: 9px; color: var(--text-2); font-weight: 600; flex-shrink: 0; }
.col-value { font-size: 11.5px; color: var(--text-2); font-variant-numeric: tabular-nums; flex-shrink: 0; white-space: nowrap; }
.col-body { padding: 8px; display: flex; flex-direction: column; gap: 6px; overflow-y: auto; min-height: 60px; }
.kanban-card {
  background: #fff; border: 1px solid var(--border); border-radius: var(--radius);
  padding: 10px; display: flex; flex-direction: column; gap: 7px;
  text-decoration: none; color: var(--text);
  transition: border-color .12s ease, box-shadow .12s ease;
}
.kanban-card:hover { border-color: var(--border-strong); box-shadow: var(--shadow-sm); text-decoration: none; }
.card-top { display: flex; align-items: center; gap: 8px; min-width: 0; }
.card-name { font-size: 12.5px; font-weight: 600; min-width: 0; overflow-wrap: anywhere; }
.card-value { font-size: 13px; font-weight: 700; color: var(--accent-ink); letter-spacing: -0.01em; }
.card-foot { display: flex; align-items: center; justify-content: space-between; font-size: 11px; color: var(--text-2); }

/* ============ SIDEBAR (smart-lists/admin variants) ============ */
.split { display: grid; gap: 0; min-height: calc(100vh - var(--topbar-h)); }
.split.cols-2 { grid-template-columns: 240px 1fr; }
.split .sidebar { background: var(--bg); height: auto; position: static; padding: 14px 0; }
.split .sidebar h3 { margin: 12px 16px 4px; font-size: 10px; font-weight: 700; color: var(--text-2); text-transform: uppercase; letter-spacing: 0.08em; }
.sb-item {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 16px; color: var(--text); font-size: 12.5px;
  border-left: 2px solid transparent;
  text-decoration: none;
}
.sb-item:hover { background: var(--hover); text-decoration: none; }
.sb-item.active { background: var(--accent-softer); color: var(--accent-ink); font-weight: 600; border-left-color: var(--accent); }
.sb-item .icon { width: 16px; text-align: center; color: var(--text-2); font-size: 13px; }
.sb-item.active .icon { color: var(--accent-ink); }
.sb-item .count { margin-left: auto; background: var(--hover); border-radius: 10px; padding: 0 6px; font-size: 11px; color: var(--text-2); min-width: 22px; text-align: center; font-weight: 600; }
.sb-item.active .count { background: white; color: var(--accent-ink); }
.sb-footer { margin-top: 1rem; padding: 12px 16px; border-top: 1px solid var(--border); }

/* ============ STAGES ADMIN ============ */
.stage-row { display: grid; grid-template-columns: 24px 1fr 80px 80px 80px; gap: 8px; align-items: center; padding: 8px 14px; border-bottom: 1px solid var(--border-soft); background: white; }
.stage-row:hover { background: var(--row-hover); }
.stage-row .swatch { width: 12px; height: 12px; border-radius: 50%; display: inline-block; margin-right: 8px; vertical-align: middle; }

/* ============ PLAN EDITOR ============ */
.plan-timeline { position: relative; padding-left: 24px; border-left: 2px solid var(--border); margin-left: 8px; }
.plan-step { background: white; border: 1px solid var(--border); border-radius: var(--radius-lg); margin: 6px 0 6px -14px; padding: 10px 12px; box-shadow: var(--shadow-sm); }
.plan-step.on-enroll { background: var(--hover); border-style: dashed; }
.plan-step-head { display: flex; align-items: center; gap: 8px; font-size: 12.5px; font-weight: 500; }
.plan-step-body { margin-top: 6px; font-size: 12px; color: var(--text-2); }
.plan-delay { color: var(--text-2); font-size: 11px; padding: 4px 0; margin-left: -14px; }

/* ============ MISC ============ */
.banner { padding: 8px 12px; border-radius: var(--radius); margin: 12px 24px 0; font-size: 12.5px; }
.banner.warn { background: #FFF7ED; border: 1px solid #FED7AA; color: #7C2D12; }
.muted { color: var(--text-2); }
.spacer { flex: 1; }
.padded { padding: 20px 24px; }

form.inline { display: flex; gap: 6px; align-items: center; }
form.stacked { display: flex; flex-direction: column; gap: 12px; max-width: 460px; }
form.stacked label { display: flex; flex-direction: column; gap: 4px; font-size: 11.5px; color: var(--text-2); font-weight: 500; text-transform: uppercase; letter-spacing: 0.04em; }
form.stacked label input, form.stacked label select, form.stacked label textarea { font-size: 13px; color: var(--text); font-weight: 400; text-transform: none; letter-spacing: 0; }
form.stacked button { align-self: flex-start; }
fieldset { border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 12px 16px; }
fieldset legend { padding: 0 6px; font-size: 11.5px; color: var(--text-2); font-weight: 500; text-transform: uppercase; letter-spacing: 0.04em; }
details > summary { cursor: pointer; color: var(--text-2); font-size: 12.5px; }

/* ============ RESPONSIVE / MOBILE ============ */
@media (max-width: 820px) {
  /* App becomes single column; sidebar drops to a fixed bottom tab bar */
  /* transition: none because the two states have different track counts and cannot be
     interpolated anyway — and because the rail is position: fixed here, so anything that
     did animate would be the bar flying across the screen on a rotate. */
  .app { grid-template-columns: 1fr; transition: none; }
  .sidebar {
    position: fixed; bottom: 0; left: 0; right: 0; top: auto;
    height: auto; min-height: 0;
    flex-direction: row;
    align-items: stretch;
    padding: 4px 4px env(safe-area-inset-bottom, 6px);
    border-right: 0; border-top: 1px solid var(--border);
    z-index: 30;
    overflow-x: auto;
  }
  /* .rail-head, not .brand: the wordmark now sits in a 56px row that carries the divider and
     the padding, so hiding only the <a> inside it would leave a 56px empty band wedged into
     a 54px tab bar. */
  .sidebar .rail-head, .sidebar .sidebar-foot { display: none; }
  .sidebar .nav { flex-direction: row; gap: 0; width: 100%; }
  .nav-item { flex-direction: column; gap: 2px; font-size: 10px; padding: 6px 8px; flex: 1; text-align: center; white-space: nowrap; }
  /* The rail becomes a fixed bottom tab bar here, on PLACE Ocean. --accent-ink measures
     2.50:1 against it and the brand orange only 4.40:1, so neither can mark the active tab.
     White carries it: 9.50:1 on the composited active background (10% white over Ocean =
     #384850). The lighter orange #FDA65A marks the icon at 4.88:1 and draws a top bar, so
     the state still survives greyscale as it does on desktop. The desktop ::before rail
     would be a vertical sliver here, so it is re-pointed to the top.
     ALL of these improved when the rail moved from #364650 to Ocean: white 7.33 -> 9.50,
     the icon 3.76 -> 4.88, which lifted the icon above the 4.5 floor it had been under. */
  .nav-item.active { background: rgba(255, 255, 255, .10); color: #FFFFFF; }
  .nav-item.active .nav-icon { color: #FDA65A; }
  .nav-item.active::before {
    left: 6px; right: 6px; top: 0; bottom: auto;
    width: auto; height: 2px;
    border-radius: 0 0 2px 2px;
  }
  .nav-icon { font-size: 16px; }
  .nav-badge { position: absolute; top: 2px; right: 50%; margin-right: -16px; }
  .main { padding-bottom: 60px; } /* clear the bottom bar */

  /* Topbar: hide centered search width cap, shrink */
  .topbar { padding: 0 12px; gap: 8px; }
  .topbar-spacer { display: none; }          /* the 200px desktop-only spacer forced 146px of overflow on every page */
  .search { margin: 0 4px; min-width: 0; }   /* let the search box shrink instead of pushing the bar wide */
  /* The four quick actions ate ~250px of a 375px bar as unlabelled 30px emoji, starving
     search down to a ~65px stub. They now live at the top of the More sheet with real
     labels, which hands that width back to a search field you can actually type in. */
  .quick-actions-bar, .topbar-divider { display: none; }
  .search input { font-size: 16px; }         /* 16px stops iOS zoom-on-focus */

  /* Quick actions inside the More sheet */
  .mms-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 10px; }
  .mms-action {
    display: flex; align-items: center; gap: 8px; min-height: 48px; padding: 10px 12px;
    border: 1px solid var(--border); border-radius: var(--radius-lg);
    background: var(--hover); color: var(--text); font: inherit; font-weight: 500;
    text-align: left; cursor: pointer;
  }
  .mms-action span { font-size: 16px; }

  /* Multi-column layouts collapse */
  .split.cols-2, .split.cols-3 { grid-template-columns: 1fr; }
  .person-shell { grid-template-columns: 1fr; }
  /* was border-bottom: the columns are transparent on the canvas now, so that hairline
     floated on grey below the last card and read as a rendering fault. The canvas gap
     separates the stacked columns instead. */
  .person-left, .person-right { border: 0; }
  .person-center { margin: 0; border-radius: 0; border-left: 0; border-right: 0; }   /* edge to edge on a phone */
  .dash { grid-template-columns: 1fr; }
  /* Inline two-column grids collapse to one. The `.padded > div` child selector missed the
     DOMINANT pattern, where `.padded` ITSELF carries the inline grid — which is why deal
     detail and call detail kept a fixed 320-360px sidebar next to a crushed main column and
     ran ~373px off-screen. Matching the element itself as well as its children fixes
     /deals/:id, /calls/:id, action-plan, checklist and inbox detail in one rule. */
  /* minmax(0,1fr), NOT 1fr: a bare `1fr` resolves to minmax(auto,1fr), so a fixed-width child
     (the 160px progress bar in the deal checklist header) sets the track's min-content and
     pushes the whole grid past the viewport. This was the last horizontal overflow in the app. */
  .padded[style*="grid-template-columns"],
  .padded > div[style*="grid-template-columns"],
  .stacked [style*="grid-template-columns"] { grid-template-columns: minmax(0, 1fr) !important; }
  /* Any remaining inline grid, wherever it lives, must not exceed the viewport. */
  [style*="grid-template-columns"] { max-width: 100%; }

  /* Inbox: stack to single pane (show list; thread when a person is selected) */
  .inbox-shell { grid-template-columns: 1fr; height: auto; }
  .inbox-rail { flex-direction: row; overflow-x: auto; border-right: 0; border-bottom: 1px solid var(--border); }
  .conv-list { border-right: 0; border-bottom: 1px solid var(--border); }

  /* Tables scroll horizontally inside their wrapper */
  .table-wrap { overflow-x: auto; }
  /* EVERY list table scrolls inside itself rather than widening the document. Previously only
     .dedupe-table did, so the admin and reporting tables pushed the whole page sideways —
     the single most common mobile failure in the app. `.people-cards` opts out below because
     it becomes real cards. */
  .dedupe-table,
  .grid-table:not(.people-cards),
  .contacts-table:not(.people-cards) { display: block; overflow-x: auto; max-width: 100%; }

  /* The contacts list's own cards live in people_list.html, switched on the list's width
     (a container query) rather than the window's. */
  /* Toolbars + the People bulk-action bar wrap onto multiple rows instead of overflowing;
     let the stage/plan/search controls shrink to fit. */
  .toolbar, .toolbar-left, .toolbar-right, .bulk-bar { flex-wrap: wrap; }
  .toolbar select, .toolbar input:not([type="checkbox"]), .toolbar .search,
  .bulk-bar select, .bulk-bar input:not([type="checkbox"]) { max-width: 100%; min-width: 0; }
  .bulk-bar .bulk-spacer { flex-basis: 100%; }   /* push Delete onto its own row rather than stretching the bar */
  /* Long unbreakable strings (webhook URLs in <code>, etc.) wrap instead of overflowing. */
  code { overflow-wrap: anywhere; }
  /* No form control (incl. inline fixed-width inputs) may exceed its container on a phone. */
  input, select, textarea { max-width: 100%; box-sizing: border-box; }

  /* Kanban scrolls (already does) but narrow columns */
  .kanban { grid-auto-columns: 80vw; }

  /* Dialogs near-fullwidth */
  dialog.dlg { width: 96vw; }
  dialog.cmdk { width: 96vw; margin-top: 8vh; }

  /* Page head wraps */
}

/* ============ AI SUGGESTIONS ============ */
.ai-suggest-list { display: flex; flex-direction: column; gap: 4px; margin-top: 8px; }
.ai-chip {
  text-align: left;
  background: linear-gradient(135deg, #FFF7ED 0%, #FFE9D6 100%);
  border: 1px solid #FED7AA;
  color: var(--text);
  padding: 8px 12px;
  border-radius: var(--radius);
  font-size: 13px;
  line-height: 1.4;
  cursor: pointer;
  white-space: normal;
  transition: border-color .12s ease, box-shadow .12s ease;
}
.ai-chip:hover { border-color: var(--accent); box-shadow: var(--shadow-sm); }
.ai-suggest-empty { font-size: 11.5px; color: var(--text-2); padding: 6px 0; }
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator { display: inline; }

/* ============ BROWSER SOFTPHONE ============ */
.dialer {
  position: fixed; bottom: 20px; right: 20px; z-index: 250;
  width: 320px;
  background: #1F2937; color: white;
  border-radius: 14px;
  box-shadow: 0 20px 48px -12px rgba(0,0,0,0.5);
  padding: 16px;
  animation: dialer-in .22s cubic-bezier(0.2,0.8,0.2,1) both;
}
@keyframes dialer-in { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }
.dialer-head { display: flex; align-items: center; gap: 12px; }
.dialer-info { flex: 1; min-width: 0; }
.dialer-name { font-size: 15px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dialer-name-link { color: inherit; text-decoration: none; }
.dialer-name-link:hover { text-decoration: underline; }
.dialer-status { font-size: 12px; color: #9CA3AF; margin-top: 2px; }
.dialer-timer { font-size: 14px; font-variant-numeric: tabular-nums; color: #D1D5DB; }
.dialer-controls { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-top: 16px; }
/* Incoming-call row: just Accept + Decline — split the full width so there's no dead space. */
#dialer-incoming { grid-template-columns: 1fr 1fr; }
.dialer-ctl {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  background: rgba(255,255,255,0.08); color: white;
  border: 0; border-radius: 10px; padding: 10px 4px; cursor: pointer;
  transition: background .12s ease;
}
.dialer-ctl:hover { background: rgba(255,255,255,0.16); }
.dialer-ctl.on { background: var(--accent); }
.dialer-ctl.danger { background: #DC2626; }
.dialer-ctl.danger:hover { background: #B91C1C; }
.dialer-ctl .ic { font-size: 17px; line-height: 1; }
.dialer-ctl .lb { font-size: 10.5px; }
.dialer-keypad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 12px; }
.dialer-key {
  background: rgba(255,255,255,0.08); color: white; border: 0; border-radius: 10px;
  padding: 12px 0; font-size: 18px; cursor: pointer; transition: background .1s ease;
}
.dialer-key:hover { background: rgba(255,255,255,0.2); }
.dialer-key:active { background: var(--accent); }
.dialer-transfer-row { display: flex; gap: 6px; margin-top: 12px; }
.dialer-transfer-row input { flex: 1; background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.15); color: white; border-radius: 8px; padding: 8px 10px; }
.dialer-transfer-row input::placeholder { color: #9CA3AF; }
/* Collapse the keypad / transfer panels when their `hidden` attribute is set.
   A bare class rule (display:grid/flex) outranks the UA [hidden]{display:none},
   so the attribute selector below restores collapse with higher specificity. */
.dialer-keypad[hidden], .dialer-transfer-row[hidden], .dialer-controls[hidden] { display: none; }
@media (max-width: 820px) {
  .dialer { right: 12px; left: 12px; width: auto; bottom: 70px; }
}

/* ============ QUICK-CALL DIAL PAD (Place-call dialog) ============ */
.callpad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin: 14px 0 8px; }
.callpad-actions { display: flex; gap: 8px; }
.callpad-key {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  min-height: 48px; padding: 6px 0;
  background: var(--hover); color: var(--text);
  border: 1px solid var(--border); border-radius: 10px;
  cursor: pointer; user-select: none; -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;   /* no double-tap-zoom wait — taps register instantly */
  transition: background .1s ease, border-color .1s ease, transform .04s ease;
}
/* Hover only where a real hover exists — on touch screens the tint would "stick" after a tap. */
@media (hover: hover) {
  .callpad-key:hover { background: var(--accent-softer); border-color: var(--accent-soft); }
}
.callpad-key:active { background: var(--accent); border-color: var(--accent); color: #fff; transform: scale(.97); }
.callpad-key .d { font-size: 20px; font-weight: 600; line-height: 1.1; }
.callpad-key .s { font-size: 8.5px; letter-spacing: 1.5px; color: var(--text-3); font-weight: 700; }
.callpad-key:active .s { color: rgba(255,255,255,.85); }
.callpad-actions .callpad-key { flex: 1; min-height: 42px; }
@media (max-width: 820px) {
  .callpad-key { min-height: 56px; }
  .callpad-key .d { font-size: 22px; }
}

/* ============ OFFLINE BANNER ============ */
.offline-banner {
  position: sticky; top: 0; z-index: 500;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  background: #B45309; color: #fff;
  font-size: 12.5px; font-weight: 600; text-align: center;
  padding: 7px 14px; padding-top: calc(7px + env(safe-area-inset-top, 0px));
  box-shadow: 0 2px 8px rgba(0,0,0,0.18);
  animation: offline-in .25s ease both;
}
/* A bare class rule sets display:flex, which outranks the UA [hidden]{display:none};
   restore the hide with a higher-specificity attribute selector. */
.offline-banner[hidden] { display: none; }
.offline-banner .ob-dot { width: 8px; height: 8px; border-radius: 50%; background: #fff; flex: none; animation: ob-pulse 1.4s ease-in-out infinite; }
@keyframes offline-in { from { transform: translateY(-100%); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
@keyframes ob-pulse { 0%, 100% { opacity: .3; } 50% { opacity: 1; } }

/* ============ ENVIRONMENT RIBBON (staging / local preview) ============ */
.env-ribbon {
  position: sticky; top: 0; z-index: 499;
  text-align: center; font-size: 11.5px; font-weight: 700; letter-spacing: .4px;
  padding: 5px 12px; color: #fff;
}
.env-ribbon.env-staging { background: #7C3AED; }
.env-ribbon.env-preview { background: #0EA5E9; }
/* Not sticky like the environment ribbons: it is up for weeks on production, so it scrolls
   away instead of covering the top of the sticky topbar (or a STAGING ribbon) all day. */
.env-ribbon.sending-paused { background: #B45309; position: static; }

/* ============ WORKFLOW CANVAS (action plan builder) ============ */
.wf-canvas {
  display: flex; flex-direction: column; align-items: center;
  background:
    radial-gradient(circle, var(--border) 1px, transparent 1px);
  background-size: 18px 18px;
  background-color: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 20px 16px;
}
.wf-node {
  width: min(420px, 100%);
  background: white;
  border: 1px solid var(--border-2);
  border-radius: 8px;
  padding: 10px 14px;
  box-shadow: var(--shadow-sm);
  font-size: 13px;
  text-align: center;
}
.wf-node.wf-start, .wf-node.wf-end {
  background: #111827; color: white; border-color: #111827;
  border-radius: 999px; font-weight: 600; width: auto; padding: 7px 18px;
}
.wf-node .wf-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); margin-right: 6px; }
.wf-node.wf-onenroll { background: var(--accent-softer); border-color: var(--accent-soft); color: var(--accent-strong); font-weight: 500; width: auto; padding: 6px 14px; }
.wf-node.wf-branch { background: #FFF7ED; border: 1px dashed #FDBA74; color: #9A3412; font-size: 12.5px; }
.wf-node.wf-step { text-align: left; }
.wf-node-head { display: flex; align-items: center; gap: 8px; }
.wf-node-head .wf-icon { font-size: 15px; }
.wf-node-head strong { font-size: 13px; }
.wf-node-head .wf-step-num { margin-left: auto; background: var(--hover); border-radius: 999px; width: 20px; height: 20px; display: grid; place-items: center; font-size: 11px; color: var(--text-2); }
.wf-node-body { margin-top: 6px; font-size: 12.5px; color: var(--text-2); white-space: pre-wrap; }
.wf-node.wf-send_sms { border-left: 3px solid var(--accent); }
.wf-node.wf-send_email { border-left: 3px solid #4a8fb5; }
.wf-node.wf-change_stage { border-left: 3px solid #7C3AED; }
.wf-node.wf-create_task { border-left: 3px solid #059669; }
.wf-conn {
  width: 2px; min-height: 22px; background: var(--border-2);
  position: relative; display: flex; align-items: center; justify-content: center;
}
.wf-conn .wf-delay {
  position: absolute; left: 12px; white-space: nowrap;
  background: white; border: 1px solid var(--border); border-radius: 999px;
  padding: 1px 8px; font-size: 11px; color: var(--text-2);
}

/* ============ KEYBOARD NAV HIGHLIGHT ============ */
.kbd-row > td { box-shadow: inset 2px 0 0 var(--accent); background: var(--accent-softer) !important; }

/* ============ TOASTS ============ */
.toast {
  position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%);
  background: #1F2937; color: white;
  padding: 11px 18px; border-radius: 8px;
  font-size: 13px; font-weight: 500;
  box-shadow: var(--shadow-lg); z-index: 200;
  animation: toast-in .22s cubic-bezier(0.2,0.8,0.2,1) both;
  max-width: 90vw;
}
.toast.toast-success { background: #15803D; }
.toast.toast-error { background: #B91C1C; }
.toast.toast-info { background: #1F2937; }
.toast.gone { opacity: 0; transform: translate(-50%, 8px); transition: opacity .3s ease, transform .3s ease; }
@keyframes toast-in { from { opacity: 0; transform: translate(-50%, 12px); } to { opacity: 1; transform: translate(-50%, 0); } }

/* ============ COMMAND PALETTE (Cmd+K) ============ */
dialog.cmdk {
  border: 0; padding: 0; margin-top: 12vh;
  width: min(600px, 92vw);
  border-radius: 10px;
  background: white;
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
dialog.cmdk::backdrop { background: rgba(15,23,42,0.4); backdrop-filter: blur(2px); }
dialog.cmdk #cmdk-input {
  width: 100%; border: 0; border-bottom: 1px solid var(--border);
  padding: 16px 18px; font-size: 15px; border-radius: 0;
}
dialog.cmdk #cmdk-input:focus { outline: none; box-shadow: none; }
.cmdk-results { max-height: 56vh; overflow-y: auto; padding: 6px; }
.cmdk-row {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px; border-radius: 6px; cursor: pointer;
}
.cmdk-row.sel { background: var(--accent-softer); }
.cmdk-icon { width: 20px; text-align: center; color: var(--text-2); font-size: 14px; }
.cmdk-name { font-size: 13.5px; font-weight: 500; color: var(--text); }
.cmdk-sub { font-size: 11.5px; color: var(--text-2); }
.cmdk-kind { font-size: 10px; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.04em; }
.cmdk-empty { padding: 24px; text-align: center; color: var(--text-2); font-size: 13px; }
.cmdk-foot { padding: 8px 14px; border-top: 1px solid var(--border); background: var(--hover); font-size: 11px; color: var(--text-3); }
.cmdk-foot kbd { background: white; border: 1px solid var(--border); border-radius: 3px; padding: 0 4px; font-family: var(--font); }

/* ============ PERSONA SWITCHER ============ */
.persona-wrap { position: relative; }
.persona-pop {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  background: white;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  width: 260px;
  z-index: 50;
  overflow: hidden;
}
.persona-pop-head {
  padding: 10px 14px;
  font-size: 11.5px;
  color: var(--text-2);
  border-bottom: 1px solid var(--border);
}
.persona-pop-head strong { color: var(--text); font-weight: 600; }
.persona-list { padding: 4px; max-height: 320px; overflow: auto; }
.persona-item {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px;
  font-size: 12.5px;
  color: var(--text);
  border-radius: var(--radius);
  text-decoration: none;
}
.persona-item:hover { background: var(--hover); text-decoration: none; }
.persona-item.active { background: var(--accent-softer); color: var(--accent-ink); font-weight: 600; }
.persona-pop-foot {
  padding: 8px 14px;
  border-top: 1px solid var(--border);
  background: var(--hover);
  display: flex; gap: 12px;
}
.persona-pop-foot .link { font-size: 11.5px; }

/* ============ MODAL DIALOGS ============ */
dialog.dlg {
  border: 0; padding: 0;
  border-radius: var(--radius-lg);
  width: min(520px, 92vw);
  max-height: 90vh;
  background: white;
  box-shadow: var(--shadow-lg);
  color: var(--text);
}
dialog.dlg::backdrop {
  background: rgba(15, 23, 42, 0.35);
  backdrop-filter: blur(2px);
}
dialog.dlg .dlg-form { display: flex; flex-direction: column; max-height: 90vh; }
dialog.dlg .dlg-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
}
dialog.dlg .dlg-head h2 {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
}
dialog.dlg .dlg-body {
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  overflow-y: auto;
}
dialog.dlg .dlg-body label {
  display: flex; flex-direction: column; gap: 4px;
  font-size: 11.5px; color: var(--text-2);
  font-weight: 500; text-transform: uppercase; letter-spacing: 0.04em;
}
dialog.dlg .dlg-body input, dialog.dlg .dlg-body select, dialog.dlg .dlg-body textarea {
  font-size: 13px;
  font-weight: 400;
  color: var(--text);
  text-transform: none;
  letter-spacing: 0;
}
/* A contact search inside a dialog fills its field like every other input. */
dialog.dlg .dlg-body .ctp-wrap > input[type="text"] { width: 100%; box-sizing: border-box; }
/* Two fields side by side (When / Ends), stacking on a phone. */
dialog.dlg .dlg-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; }
/* A checkbox row reads as a sentence, not as an uppercase field label. */
dialog.dlg .dlg-body label.dlg-check {
  flex-direction: row; align-items: center; gap: 8px;
  font-size: 13px; color: var(--text); text-transform: none; letter-spacing: 0;
}
dialog.dlg .dlg-foot {
  display: flex; justify-content: flex-end; gap: 8px;
  padding: 12px 18px;
  border-top: 1px solid var(--border);
  background: var(--hover);
}
dialog.dlg .contact-picker {
  /* native select; styled like the rest */
  background: white;
}

/* ===== Notifications dropdown ===== */
.notif-wrap { position: relative; }
.notif-badge {
  position: absolute; top: -4px; right: -4px; min-width: 16px; height: 16px;
  padding: 0 4px; border-radius: 9px; background: var(--accent); color: #fff;
  font-size: 10px; font-weight: 700; line-height: 16px; text-align: center;
}
.notif-pop {
  position: absolute; top: 38px; right: 0; width: 340px; max-height: 70vh; overflow: auto;
  background: #fff; border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 16px 40px rgba(15,23,42,0.18); z-index: 60; padding: 6px;
}
.notif-head { display: flex; align-items: center; justify-content: space-between; padding: 8px 10px 6px; }
.notif-head strong { font-size: 13px; }
.notif-list { display: flex; flex-direction: column; }
.notif-item {
  display: flex; gap: 10px; padding: 9px 10px; border-radius: 8px; text-decoration: none;
  color: inherit; align-items: flex-start;
}
.notif-item:hover { background: var(--surface-2, #f5f6f8); }
.notif-item.unread { background: #fff6f0; }
.notif-ic { font-size: 15px; line-height: 1.2; }
.notif-body { display: flex; flex-direction: column; min-width: 0; }
.notif-title { font-size: 12.5px; font-weight: 600; }
.notif-who { font-weight: 700; color: var(--text); }
.notif-head-actions { display: inline-flex; align-items: center; gap: 12px; }
.notif-settings { color: var(--text-2); text-decoration: none; font-size: 14px; }
.notif-settings:hover { color: var(--accent-ink); text-decoration: none; }
.notif-sub { font-size: 12px; color: var(--text-3, #6b7280); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 250px; }
.notif-time { font-size: 11px; color: var(--text-3, #9aa1ad); margin-top: 2px; }
.notif-empty { padding: 22px 10px; text-align: center; color: var(--text-3, #9aa1ad); font-size: 13px; }

/* ===== Calendar ===== */
.cal-nav { display: inline-flex; gap: 4px; }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: 10px; overflow: hidden; }
.cal-dow { background: #fafafa; padding: 7px 8px; font-size: 11px; font-weight: 600; color: var(--text-3, #6b7280); text-transform: uppercase; letter-spacing: 0.04em; }
.cal-cell { background: #fff; min-height: 104px; padding: 5px 6px; }
.cal-blank { background: #fbfbfc; }
.cal-today { background: #fff6f0; }
.cal-today .cal-daynum { background: var(--accent); color: #fff; }
.cal-daynum { display: inline-block; min-width: 20px; height: 20px; line-height: 20px; text-align: center; font-size: 12px; font-weight: 600; border-radius: 50%; }
.cal-items { display: flex; flex-direction: column; gap: 3px; margin-top: 3px; }
.cal-chip {
  display: block; font-size: 11px; padding: 2px 6px; border-radius: 5px; text-decoration: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  background: #eef1f6; color: #334155;
}
.cal-chip b { font-weight: 700; }
.cal-chip.appt { background: #e6efff; color: #1e40af; }
.cal-chip.appt.showing { background: #e7f7ec; color: #166534; }
.cal-chip.appt.closing { background: #fdeede; color: #9a3412; }
.cal-chip.appt.open_house { background: #f3e8ff; color: #6b21a8; }
.cal-chip.task { background: #fff3d6; color: #92600a; }
.cal-chip:hover { filter: brightness(0.96); }

/* ===== Dedupe ===== */
.dedupe-group { border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; margin-bottom: 14px; background: #fff; }
.dedupe-key { font-size: 12px; color: var(--text-3, #6b7280); margin-bottom: 6px; }
.dedupe-table { width: 100%; border-collapse: collapse; font-size: 13px; margin-bottom: 10px; }
.dedupe-table th { text-align: left; font-size: 11px; color: var(--text-3, #6b7280); text-transform: uppercase; letter-spacing: 0.04em; padding: 4px 8px; border-bottom: 1px solid var(--border); }
.dedupe-table td { padding: 6px 8px; border-bottom: 1px solid #f1f2f4; }

/* ===== Opt-out banner ===== */
.optout-banner {
  display: flex; align-items: center; flex-wrap: wrap; gap: 4px;
  background: #fdeaea; border: 1px solid #f3b9b9; color: #b91c1c;
  font-size: 12px; padding: 6px 9px; border-radius: 8px; margin-top: 6px;
}
.optout-banner .link { color: #b91c1c; font-weight: 600; }

/* ===== Pipeline board: drag, editable titles, filter ===== */
.kanban-card[hidden] { display: none; }       /* let the buyer/seller filter hide cards */
.kanban-card { cursor: grab; }
.kanban-card:active { cursor: grabbing; }
.card-ghost { opacity: 0.4; background: #fff4ec; border-style: dashed; }
.col-title-input {
  flex: 1; min-width: 0; border: 1px solid transparent; background: transparent;
  font: inherit; font-size: 12px; font-weight: 600; color: var(--text);
  padding: 2px 4px; border-radius: 5px; margin: 0;
  text-overflow: ellipsis;   /* long stage names truncate (…) when not focused */
}
.col-title-input:hover { background: rgba(0,0,0,0.03); }
.col-title-input:focus { outline: none; background: #fff; border-color: var(--accent); }
.col-add {
  margin-top: 6px; padding: 6px; font-size: 11.5px; color: var(--text-3);
  border: 1px dashed var(--border-strong); border-radius: var(--radius);
  display: flex; align-items: center; justify-content: center; gap: 4px; text-decoration: none;
}
.col-add:hover { color: var(--text); border-color: var(--accent); text-decoration: none; }

/* ===== Tabbed admin navigation ===== */
.admin-nav { margin-bottom: 20px; padding: 16px 24px 0; }
.admin-tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); overflow-x: auto; }
.admin-tab {
  padding: 10px 14px; font-size: 13px; font-weight: 500; color: var(--text-3, #6b7280);
  text-decoration: none; border-bottom: 2px solid transparent; white-space: nowrap;
}
.admin-tab:hover { color: var(--text); text-decoration: none; }
.admin-tab.active { color: var(--accent-ink); border-bottom-color: var(--accent); font-weight: 600; }
.admin-subtabs { display: flex; gap: 6px; margin-top: 12px; flex-wrap: wrap; }
.admin-subtab {
  padding: 5px 12px; font-size: 12.5px; border-radius: 999px; text-decoration: none;
  color: var(--text-2, #4b5563); background: #f1f2f4; border: 1px solid transparent;
}
.admin-subtab:hover { color: var(--text); text-decoration: none; }
.admin-subtab.active { background: var(--accent); color: #fff; }

/* ===== IDX / property activity on contact ===== */
.idx-block { margin: 8px 0 14px; }
.idx-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-3,#6b7280); margin-bottom: 5px; }
.idx-search { display: flex; flex-direction: column; gap: 1px; padding: 6px 8px; border: 1px solid var(--border); border-radius: 8px; margin-bottom: 5px; text-decoration: none; color: var(--text); background: #fff; }
.idx-search:hover { border-color: var(--accent); text-decoration: none; }
.idx-search-name { font-size: 12.5px; font-weight: 600; }
.idx-search-meta { font-size: 11.5px; color: var(--text-3,#6b7280); }
.idx-listing { display: flex; gap: 9px; padding: 6px; border: 1px solid var(--border); border-radius: 8px; margin-bottom: 6px; text-decoration: none; color: var(--text); background: #fff; }
.idx-listing:hover { border-color: var(--accent); text-decoration: none; }
.idx-thumb { width: 52px; height: 44px; border-radius: 6px; object-fit: cover; flex-shrink: 0; }
.idx-thumb-ph { display: grid; place-items: center; background: #f1f2f4; font-size: 18px; }
.idx-listing-info { display: flex; flex-direction: column; min-width: 0; }
.idx-listing-addr { font-size: 12.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.idx-listing-meta { font-size: 11.5px; color: var(--text-2,#4b5563); }
.idx-listing-time { font-size: 11px; color: var(--text-3,#9aa1ad); }

/* ===== Reporting suite ===== */
.rep-range { display:inline-flex; gap:2px; background:#f1f2f4; border-radius:8px; padding:2px; }
.rep-range a { padding:4px 10px; border-radius:6px; font-size:12.5px; color:var(--text-2,#4b5563); text-decoration:none; }
.rep-range a.on { background:#fff; color:var(--text); box-shadow:0 1px 2px rgba(0,0,0,0.06); font-weight:600; }
.rep-cards { display:grid; grid-template-columns:repeat(auto-fit, minmax(150px,1fr)); gap:12px; margin-bottom:1.6rem; }
.rep-card { background:#fff; border:1px solid var(--border); border-radius:10px; padding:14px; display:flex; flex-direction:column; gap:3px; }
.rep-k { font-size:24px; font-weight:700; line-height:1; }
.rep-l { font-size:12px; color:var(--text-3,#6b7280); }
.rep-h { font-size:0.95rem; margin:1.4rem 0 0.6rem; }
.rep-grid2 { display:grid; grid-template-columns:1fr 1fr; gap:20px; margin-bottom:0.5rem; }
.rep-bar { display:block; height:8px; border-radius:4px; background:var(--accent); }
.goal-bar { background:#eceef1; border-radius:5px; height:8px; overflow:hidden; margin-bottom:3px; }
.goal-bar span { display:block; height:100%; }
@media (max-width:820px){ .rep-grid2 { grid-template-columns:1fr; } }

/* Google-synced calendar events */
.cal-chip.gcal { background: #e8eaed; color: #3c4043; border-left: 2px solid #4285F4; }

/* Contact-page footer actions (Google / delete) */
.person-foot { margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--border-soft, #eee); display: flex; flex-direction: column; gap: 7px; }
.person-foot .muted-link { font-size: 12px; color: var(--text-3, #6b7280); text-decoration: none; }
.person-foot .muted-link:hover { text-decoration: underline; }

/* ===== Social / enrichment + at-a-glance + file dropzone ===== */
.social-links { display: flex; flex-wrap: wrap; gap: 5px; }
.social-chip {
  font-size: 11px; padding: 2px 8px; border-radius: 999px; text-decoration: none;
  background: #f1f2f4; color: var(--text-2, #4b5563); border: 1px solid var(--border);
}
.social-chip:hover { background: var(--accent); color: #fff; border-color: var(--accent); text-decoration: none; }
.glance-row { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 10px; }
.glance-chip {
  display: flex; flex-direction: column; background: #fff; border: 1px solid var(--border);
  border-radius: 8px; padding: 5px 9px; min-width: 60px;
}
.glance-chip b { font-size: 12.5px; }
.glance-chip span { font-size: 10px; color: var(--text-3, #9aa1ad); text-transform: uppercase; letter-spacing: 0.03em; }
.filedrop { border: 1px dashed var(--border-strong, #cbd0d6); border-radius: 8px; padding: 8px; margin-top: 6px; transition: background .12s, border-color .12s; }
.filedrop.over { background: #fff4ec; border-color: var(--accent); }

/* ===== Lead prev/next navigation ===== */
.lead-nav { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 12px; }
.lead-nav-label { font-size: 11.5px; color: var(--text-3, #6b7280); text-align: center; flex: 1; }
.lead-nav-btn {
  display: inline-grid; place-items: center; width: 28px; height: 28px; border-radius: 7px;
  border: 1px solid var(--border); background: #fff; color: var(--text); text-decoration: none;
  font-size: 18px; line-height: 1; flex-shrink: 0;
}
.lead-nav-btn:hover { border-color: var(--accent); color: var(--accent-ink); text-decoration: none; }
.lead-nav-btn.off { opacity: 0.35; cursor: default; }

/* ===== Lead scoring (Hot Leads) ===== */
.btn.sm, button.btn.sm { padding: 6px 11px; font-size: var(--fs-micro); line-height: 1.3; }
.score-cell { display: flex; align-items: center; gap: 8px; }
.score-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 30px; height: 22px; padding: 0 6px; border-radius: 11px;
  font-size: 12px; font-weight: 700; color: #fff;
}
.score-badge.hot  { background: #E0451F; }
.score-badge.warm { background: #E1A13A; }
.score-badge.cold { background: #9AA1AB; }
.score-bar { position: relative; flex: 1; height: 6px; border-radius: 3px; background: var(--border); overflow: hidden; min-width: 60px; }
.score-bar-fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 3px; }
.score-bar-fill.hot  { background: #E0451F; }
.score-bar-fill.warm { background: #E1A13A; }
.score-bar-fill.cold { background: #9AA1AB; }
.factor-chip {
  display: inline-block; margin: 1px 3px 1px 0; padding: 1px 7px;
  font-size: 11px; border-radius: 10px;
  background: var(--accent-softer); color: var(--accent-ink); border: 1px solid var(--accent-soft);
}
/* One real empty state. 40 were hand-rolled across 31 templates in three different grades
   (a bare sentence, a centred div, a styled card), and several are day-one screens the team
   sees before any data exists. Use with templates/_empty.html. */
.empty-state {
  display: grid;
  place-items: center;
  gap: 6px;
  padding: 48px 24px;
  max-width: 460px;
  margin: 0 auto;
  text-align: center;
  color: var(--text-2);
}
.empty-state .empty-icon { font-size: 28px; line-height: 1; opacity: .85; }
.empty-state h2 { font-size: var(--fs-md); font-weight: 600; color: var(--text); margin: 0; }
.empty-state p { font-size: var(--fs-base); color: var(--text-2); margin: 0; max-width: 46ch; }
.empty-state .btn { margin-top: 6px; }
.score-inline { display: inline-flex; align-items: center; gap: 6px; }

/* ===== AI features: snapshot, briefing, triage, NBA ===== */
.ai-snapshot-wrap { margin: 10px 0 12px; }
.ai-snapshot {
  background: linear-gradient(180deg, var(--accent-softer), #fff);
  border: 1px solid var(--accent-soft); border-radius: var(--radius-lg);
  padding: 10px 12px;
}
.ai-snapshot.loading, .ai-snapshot.empty { background: var(--bg); border-style: dashed; border-color: var(--border); }
.ai-snapshot-head {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--accent-ink); margin-bottom: 4px;
}
.ai-refresh { background: none; border: 0; cursor: pointer; color: var(--text-3); font-size: 12px; padding: 0 2px; }
.ai-refresh:hover { color: var(--accent-ink); }
.ai-snapshot-body { margin: 0; font-size: 13px; line-height: 1.5; color: var(--text); }
.ai-nba {
  margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--accent-soft);
  font-size: 12.5px; color: var(--text);
}

/* Daily briefing */
.briefing-list { display: flex; flex-direction: column; gap: 8px; }
.briefing-item {
  display: flex; align-items: flex-start; gap: 10px;
  background: #fff; border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 12px;
}
.briefing-rank {
  flex-shrink: 0; width: 22px; height: 22px; border-radius: 50%;
  background: var(--accent); color: #fff; font-size: 12px; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
}
.briefing-main { flex: 1; min-width: 0; }
.briefing-name { font-weight: 600; font-size: 13.5px; text-decoration: none; color: var(--text); }
.briefing-name:hover { color: var(--accent-ink); }
.briefing-why { font-size: 12px; color: var(--text-2); margin-top: 1px; }
.briefing-opener { font-size: 12px; color: var(--text); margin-top: 4px; font-style: italic; }

/* Inbox triage chip */
.conv-intent { display: flex; align-items: center; gap: 6px; margin-top: 3px; }
.intent-chip {
  flex-shrink: 0; font-size: 10px; font-weight: 600; padding: 1px 6px; border-radius: 8px;
  background: var(--accent-softer); color: var(--accent-ink); border: 1px solid var(--accent-soft);
  white-space: nowrap;
}
.intent-sum { font-size: 11px; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Hot Leads next-best-action */
.hot-nba { margin-top: 5px; font-size: 11.5px; color: var(--text-2); line-height: 1.4; }

/* ===== Ask-your-CRM + AI campaign draft ===== */
.ask-form { display: flex; gap: 8px; align-items: center; }
.ask-input {
  flex: 1; font-size: 15px; padding: 11px 14px; border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg); font-family: var(--font);
}
.ask-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-softer); }
.ask-examples { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.ask-chip {
  font-size: 12.5px; padding: 5px 11px; border-radius: 16px; text-decoration: none;
  background: var(--accent-softer); color: var(--accent-ink); border: 1px solid var(--accent-soft);
}
.ask-chip:hover { background: var(--accent-soft); text-decoration: none; }
.ask-result-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
  margin-top: 16px; font-size: 13px;
}
.ask-filter-chips { display: flex; flex-wrap: wrap; gap: 6px; }

.ai-draft-box {
  background: linear-gradient(180deg, var(--accent-softer), #fff);
  border: 1px solid var(--accent-soft); border-radius: var(--radius-lg);
  padding: 12px 14px; margin-bottom: 14px;
}
.ai-draft-head { font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--accent-ink); margin-bottom: 8px; }
.ai-draft-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }

/* ===== Mobile app polish (PWA) ===== */
/* "More" sheet + sticky person action bar are hidden on desktop by default. */
.nav-more { display: none; }
.mobile-more-sheet, .mobile-more-backdrop, .mobile-actionbar { display: none; }

@media (max-width: 820px) {
  /* --- Bottom bar: 5 tabs (4 primary + More); no horizontal scroll --- */
  .sidebar { overflow-x: hidden; }
  .sidebar .nav .nav-item:not(.nav-primary):not(.nav-more) { display: none; }
  .nav-more { display: flex; background: none; border: 0; cursor: pointer; font-family: inherit; }
  .nav-item { min-height: 54px; justify-content: center; }
  body.more-open .nav-more { color: var(--accent-ink); }

  /* Bigger tap targets everywhere */
  .btn, button.btn { min-height: 44px; padding: 11px 14px; }
  /* The iOS zoom-on-focus fix used to live here as `font-size: 16px`, but at specificity
     (0,0,1) it lost to `form.stacked label input` (0,1,3) at line ~1020 — so it was dead for
     every stacked form in the app while reading as solved. Moved to the end of this file,
     where it is stated once and enforced. */
  input, select, textarea { min-height: 44px; }
  textarea { min-height: 80px; }
  .tab, .seg-btn { min-height: 44px; display: inline-flex; align-items: center; }
  /* The carding pass made summary.section-h the primary tap target for 8 collapsible cards
     on the contact record. It was in none of these lists. */
  summary.section-h, .pcard > summary { min-height: 44px; display: flex; align-items: center; }
  /* Everything that ISN'T a .btn was still 30px or less — the icon buttons, checkboxes,
     tag removers and range pills. Listed centrally so new pages inherit the floor. */
  .icon-btn, .qa-btn, .rep-range a, .chk, .tag .x, .sb-item, .mms-item,
  .bulk-action, .filter-chip { min-height: 44px; min-width: 44px; }
  .icon-btn, .qa-btn { display: inline-flex; align-items: center; justify-content: center; }
  /* Checkboxes stay visually small but get a 44px hit area around them. */
  input[type="checkbox"], input[type="radio"] { width: 20px; height: 20px; min-height: 20px; position: relative; }
  input[type="checkbox"]::after, input[type="radio"]::after {
    content: ""; position: absolute; top: 50%; left: 50%;
    width: 44px; height: 44px; transform: translate(-50%, -50%);
  }

  /* A real mobile type scale: nothing a user must read drops below 12px, and the base
     lifts to 14px for daylight readability. 87 rules sat below 11.5px before this. */
  body { font-size: 14px; }
  .muted, .title-count, .kpi .lbl, .metric-sub, .tab-count,
  .nav-label, .pill, .tag, small { font-size: var(--fs-micro); }

  /* Second-level nav strips clipped silently — 6 of 9 reports and 6 of 10 admin sections
     were simply invisible with no hint they existed. Scroll them, and fade the right edge
     so "there is more" is legible. */
  .admin-tabs, .admin-subnav, .tabs, .inbox-rail, .rep-range {
    overflow-x: auto; flex-wrap: nowrap; -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
            mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
  }
  .admin-tabs > *, .admin-subnav > *, .tabs > *, .rep-range > * { flex: 0 0 auto; scroll-snap-align: start; }

  /* --- "More" overflow sheet --- */
  .mobile-more-backdrop {
    display: block; position: fixed; inset: 0; background: rgba(0,0,0,.35);
    opacity: 0; pointer-events: none; transition: opacity .2s; z-index: 40;
  }
  body.more-open .mobile-more-backdrop { opacity: 1; pointer-events: auto; }
  .mobile-more-sheet {
    display: block; position: fixed; left: 0; right: 0; bottom: 0; z-index: 41;
    background: #fff; border-top-left-radius: 16px; border-top-right-radius: 16px;
    padding: 8px 10px calc(70px + env(safe-area-inset-bottom, 0px));
    box-shadow: 0 -8px 30px rgba(0,0,0,.18);
    transform: translateY(110%); transition: transform .24s ease;
    /* The sheet is the ONLY route to the 15 nav items the bottom bar hides, and it holds
       one row per saved list, so its height is user-controlled: 20 items measured 1294px.
       Pinned to bottom:0 with no bound, its top went to -482px on a 375x812 phone and
       -794px at 500px tall, putting Team Chat, Hot Leads and the first saved lists above
       the screen edge -- unreachable, because a fixed box does not move when the page
       scrolls and this one could not scroll itself. Bound it and let it scroll; the gap
       above also keeps the backdrop tappable to dismiss. */
    max-height: 85vh;
    max-height: 85dvh;
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }
  body.more-open .mobile-more-sheet { transform: translateY(0); }
  .mms-grip { width: 40px; height: 4px; border-radius: 2px; background: var(--border-strong); margin: 6px auto 10px; }
  .mms-item {
    display: flex; align-items: center; gap: 12px; padding: 14px 12px; font-size: 15px;
    color: var(--text); text-decoration: none; border-radius: 10px;
  }
  .mms-item span { width: 22px; text-align: center; font-size: 18px; }
  .mms-item:active { background: var(--hover); }
  button.mms-item { background: none; border: 0; width: 100%; font-family: inherit; text-align: left; cursor: pointer; }

  /* --- Sticky thumb-reachable action bar on the contact page --- */
  .mobile-actionbar {
    display: flex; position: fixed; left: 0; right: 0; bottom: calc(56px + env(safe-area-inset-bottom, 0px));
    z-index: 25; background: #fff; border-top: 1px solid var(--border); padding: 6px;
    gap: 6px; box-shadow: 0 -2px 10px rgba(0,0,0,.06);
  }
  .mobile-actionbar button {
    flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px;
    background: none; border: 0; cursor: pointer; font-family: inherit;
    font-size: 11px; color: var(--text-2); padding: 6px 0; min-height: 46px;
  }
  .mobile-actionbar button span { font-size: 19px; }
  body:has(.mobile-actionbar) .main { padding-bottom: 124px; }

  /* --- Swipe-to-call on inbox rows --- */
  .swipe-wrap { position: relative; overflow: hidden; }
  .swipe-actions {
    position: absolute; top: 0; right: 0; bottom: 0; display: flex; align-items: stretch; z-index: 0;
  }
  .swipe-call {
    border: 0; background: var(--accent); color: #fff; font-weight: 600; font-size: 13px;
    width: 96px; cursor: pointer; font-family: inherit;
  }
  .conv-item.swipeable { position: relative; z-index: 1; background: #fff; touch-action: pan-y; will-change: transform; }
}

/* ===== Lead Flow: claim banner ===== */
.claim-banner {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin: 8px 0; padding: 8px 12px; border-radius: var(--radius);
  background: var(--accent-softer); border: 1px solid var(--accent-soft); font-size: 13px;
}
.claim-banner .btn { white-space: nowrap; }

/* ===== Mobile calling: cellular bridge vs WiFi toggle ===== */
.call-mode-row { display: flex; align-items: center; gap: 10px; margin-top: 8px; flex-wrap: wrap; }
/* `button` needs its UA chrome reset here, but note the specificity trap: this selector is
   (0,2,1) and OUTRANKS `.seg-btn.on` at (0,2,0). That is why the selected call-mode pill had
   no fill at all — `background: transparent` was quietly beating `.on`'s white, so the
   WiFi / My phone toggle looked identical whichever side was active. Reset only what the UA
   adds, then restate the selected state at (0,3,1) so it wins. */
.call-mode-seg button.seg-btn {
  border: 1px solid transparent;
  background: transparent;
  font-family: inherit;
  cursor: pointer;
}
.call-mode-seg button.seg-btn.on {
  background: var(--surface);
  border-color: #AEBECA;
}
.call-toast { z-index: 60; }

/* ===== Team chat (Slack-style board) ===== */
.chat-shell { display: grid; grid-template-columns: 200px 1fr; flex: 1; min-height: 0; }
.chat-rail { border-right: 1px solid var(--border); padding: 14px 10px; overflow-y: auto; background: var(--bg); }
.chat-rail-head { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em; color: var(--text-3); padding: 0 6px 6px; }
.chat-chan { display: block; padding: 6px 8px; border-radius: 7px; font-size: 13px; color: var(--text-2); text-decoration: none; }
.chat-chan:hover { background: var(--hover); text-decoration: none; }
.chat-chan.active { background: var(--accent-softer); color: var(--accent-ink); font-weight: 600; }
.chat-chan-new input { width: 100%; border: 1px dashed var(--border-strong); border-radius: 7px; padding: 5px 8px; font-size: 12px; background: transparent; margin-top: 4px; }
.chat-team-member { display: flex; align-items: center; gap: 7px; padding: 4px 6px; font-size: 12.5px; color: var(--text-2); }

.chat-main { display: flex; flex-direction: column; min-height: 0; }
.chat-head { padding: 12px 18px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 8px; }
.chat-scroll { flex: 1; overflow-y: auto; padding: 14px 18px; display: flex; flex-direction: column; gap: 12px; }
.chat-empty { text-align: center; padding: 40px 20px; max-width: 420px; margin: 0 auto; }

.chat-msg { display: flex; gap: 10px; align-items: flex-start; }
.chat-msg.announcement { background: var(--accent-softer); border: 1px solid var(--accent-soft); border-radius: var(--radius-lg); padding: 10px 12px; }
.chat-msg-main { min-width: 0; flex: 1; }
.chat-msg-head { display: flex; align-items: baseline; gap: 8px; }
.chat-msg-author { font-weight: 600; font-size: 13px; }
.chat-msg-time { font-size: 11px; color: var(--text-3); }
.chat-announce-pill { font-size: 10.5px; font-weight: 700; color: var(--accent-ink); background: #fff; border: 1px solid var(--accent-soft); border-radius: 9px; padding: 1px 7px; }
.chat-msg-del { margin-left: auto; }
.chat-msg-del button { background: none; border: 0; color: var(--text-3); cursor: pointer; font-size: 14px; padding: 0 4px; }
.chat-msg-del button:hover { color: #B91C1C; }
.chat-msg-body { font-size: 13.5px; line-height: 1.5; overflow-wrap: anywhere; }
.chat-mention { background: var(--accent-softer); color: var(--accent-ink); border-radius: 5px; padding: 0 3px; font-weight: 600; }
.chat-leadref { background: #EFF6FF; color: #1D4ED8; border-radius: 5px; padding: 0 3px; font-weight: 600; text-decoration: none; }
.chat-leadref:hover { text-decoration: underline; color: #1D4ED8; }

.chat-composer { position: relative; border-top: 1px solid var(--border); padding: 10px 18px 14px; }
.chat-composer textarea { width: 100%; resize: vertical; }
.chat-composer-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 6px; gap: 10px; }
.chat-announce-opt { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-2); }
.chat-suggest {
  position: absolute; bottom: calc(100% - 4px); left: 18px; right: 18px; max-width: 340px;
  background: #fff; border: 1px solid var(--border-strong); border-radius: 10px;
  box-shadow: var(--shadow-lg, 0 8px 24px rgba(0,0,0,.12)); overflow: hidden; z-index: 20;
}
.chat-suggest-row { display: block; width: 100%; text-align: left; padding: 8px 12px; border: 0; background: none; font-size: 13px; cursor: pointer; font-family: inherit; }
.chat-suggest-row:hover { background: var(--accent-softer); }

@media (max-width: 820px) {
  .chat-shell { grid-template-columns: 1fr; }
  .chat-rail { display: flex; flex-direction: row; align-items: center; gap: 4px; overflow-x: auto; border-right: 0; border-bottom: 1px solid var(--border); padding: 8px 10px; }
  .chat-rail-head, .chat-team-member { display: none; }
  .chat-chan { white-space: nowrap; }
  .chat-chan-new input { width: 110px; margin-top: 0; }
  .chat-scroll { padding-bottom: 20px; }
}


/* ============================================================================
   UX REVIEW PASS — rules requested by the per-page fixes.
   Appended so source order beats the generic mobile fallbacks above (several of the
   card-layout rules depend on that). Grouped by the page that asked for them.
   ============================================================================ */

/* [1] Global, and it bites any segmented control that shows a count. At ≤820px `.seg-btn` computes to display:flex, and a flex container drops the whitespac */
/* .seg-btn becomes a flex box at this breakpoint, which swallows the literal space between
   a label and an inline count ("All10"). A gap survives both the block and flex layouts. */
@media (max-width: 820px) {
  .seg-btn { gap: 4px; }
}

/* [2] This is the canonical home for the calendar blocker fix (the finding names style.css line 1405). I have already shipped it as a page-scoped rule in ca */
/* `1fr` resolves to minmax(auto, 1fr), so one long chip forced its column to min-content,
   pushed .cal-grid past its container and — with overflow:hidden — silently clipped the last
   column. Saturday was simply not on the page at 1440px. */
.cal-grid { grid-template-columns: repeat(7, minmax(0, 1fr)); }
.cal-cell { min-width: 0; }

/* [3] The finding places the ask-chip touch target in the ≤820px block of style.css (line 1627 area). Shipped page-scoped in ask.html; this is the version t */
/* These five chips are the only onboarding /ask has and they were the smallest touch targets
   on the page — 30px, while .btn gets 40px and .seg-btn 38px in the same breakpoint. */
@media (max-width: 820px) {
  .ask-chip { min-height: 40px; display: inline-flex; align-items: center; }
}

/* [4] /ponds finding "The inline Lead Flow link does not look like a link" is systemic: any <a> inside p.muted inherits the muted grey and is visually ident */
/* Links inside muted copy were invisible: they inherited the grey and had no underline,
   so the only clickable word in a sentence read as plain text (measured 60x15px on /ponds).
   --accent-ink is the text-safe accent. */
.muted a, p.muted a, .empty-state a:not(.btn) {
  color: var(--accent-ink);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.muted a:hover { text-decoration-thickness: 2px; }

/* [5] I needed a screen-reader-only utility for the unlabelled actions column headers on /people and /people/hot and had to define it locally in both files. */
/* Screen-reader-only label. Used for table columns that carry only icon buttons, where a
   visible "ACTIONS" header would be noise but an empty <th> leaves the column unnamed. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* [6] /deal-flow high finding — the deal table is still a sideways-scrolling wall on mobile. deal_flow.html now carries the `deal-cards` class and the colum */
/* /deal-flow → a card per deal, the same idea as .people-cards on /people. The generic
     rule above makes every .contacts-table a horizontally scrolling block; .deal-cards opts
     out and becomes real cards instead. Column order in deal_flow.html is load-bearing:
     1 Deal · 2 Contact · 3 Side · 4 Value · 5 Checklist · 6 Progress · 7 Next step · 8 Close */
  .deal-cards.contacts-table { display: block; overflow-x: visible; }
  .deal-cards thead { display: none; }
  .deal-cards, .deal-cards tbody, .deal-cards tr, .deal-cards td { display: block; }
  .deal-cards tr {
    background: #fff;
    border: 1px solid var(--border); border-radius: var(--radius-lg);
    padding: 12px 14px; margin-bottom: 10px;
  }
  .deal-cards tbody tr:hover { background: #fff; }
  .deal-cards td { padding: 0 !important; border: 0 !important; max-width: none !important; }
  .deal-cards td:nth-child(1) { font-weight: 600; font-size: var(--fs-md); }        /* deal name */
  .deal-cards td:nth-child(2),                                                       /* contact  */
  .deal-cards td:nth-child(3),                                                       /* side     */
  .deal-cards td:nth-child(4) { display: inline-block; margin: 6px 10px 0 0; vertical-align: middle; }
  .deal-cards td:nth-child(4) { font-weight: 600; color: var(--text); }              /* value    */
  .deal-cards td:nth-child(5) { display: none; }                                     /* checklist template */
  .deal-cards td:nth-child(6) { margin-top: 10px; }                                  /* progress */
  .deal-cards td:nth-child(7) {                                                      /* next step */
    margin-top: 8px; color: var(--accent-ink) !important;
    white-space: normal !important; overflow: visible !important; text-overflow: clip !important;
  }
  .deal-cards td:nth-child(8) { margin-top: 6px; }                                   /* close date */
  .deal-cards td:nth-child(8) > div { display: inline-block; margin-right: 6px; }
  .deal-cards td[colspan] { padding: 0 !important; }                                 /* empty-state row */

/* [7] /deals/1 low finding — seventeen bare 🗑 buttons at ~18px. deal_detail.html now tags each row .step-row and each button .step-remove (aria-label added  */
/* inside @media (max-width: 820px) — real thumb target for the per-step remove button */
  .step-remove { min-width: 44px; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }

/* top level — 17 trash cans should not compete with the step titles on a pointer device */
@media (hover: hover) and (pointer: fine) {
  .step-row .step-remove { opacity: 0; transition: opacity .12s ease; }
  .step-row:hover .step-remove,
  .step-row .step-remove:focus-visible { opacity: 1; }
}

/* [8] /vendors, medium: "Vendor rows become a sideways-scrolling strip on the phone." Looking up the inspector's number mid-showing is the most common phone */
/* ===== /vendors: card-per-vendor on mobile =====
   Mirrors the .people-cards treatment. The six-column table put the phone number off the
   right edge behind a horizontal swipe, which is the one thing an agent opens this page for
   mid-showing. Must sit AFTER the .dedupe-table mobile fallback above (equal specificity,
   source order decides). Column order: 1 vendor, 2 category, 3 phone, 4 email, 5 intros,
   6 actions. */
@media (max-width: 820px) {
  .vendor-cards, .vendor-cards tbody, .vendor-cards tr, .vendor-cards td { display: block; }
  .vendor-cards { overflow: visible; margin-bottom: 0; }
  .vendor-cards thead { display: none; }
  .vendor-cards tr {
    background: #fff; border: 1px solid var(--border); border-radius: var(--radius-lg);
    padding: 12px 14px; margin-bottom: 10px;
  }
  .vendor-cards td { padding: 0 !important; border: 0 !important; white-space: normal !important; }
  .vendor-cards td:nth-child(2) { margin-top: 6px; }                                    /* category chip */
  .vendor-cards td:nth-child(3) { margin-top: 10px; font-size: var(--fs-md); font-weight: 600; }  /* phone: the payload */
  .vendor-cards td:nth-child(4) { margin-top: 2px; font-size: var(--fs-base); }         /* email */
  .vendor-cards td:nth-child(5) { margin-top: 6px; font-size: var(--fs-sm); color: var(--text-2); }
  .vendor-cards td:nth-child(5)::before { content: "Intros sent: "; }                   /* header row is hidden */
  .vendor-cards td:nth-child(6) { margin-top: 12px; }
  .vendor-cards .vendor-actions { gap: 14px; }
  .vendor-cards .vendor-actions .btn { flex: 1; min-height: 44px; }
}

/* [9] /openhouses, medium: "Mobile falls back to a sideways-scrolling table, not cards." This is the page that matters most on a phone — Saturday morning, t */
/* ===== /openhouses: card-per-open-house on mobile =====
   The kiosk link is the whole point of this page and it was the fifth of six columns, i.e.
   off the right edge on a 375px screen at exactly the moment the agent is standing at the
   door. Must sit AFTER the .dedupe-table mobile fallback (equal specificity, source order
   decides). Column order: 1 address, 2 date, 3 host, 4 guests, 5 kiosk, 6 close/reopen. */
@media (max-width: 820px) {
  .oh-cards, .oh-cards tbody, .oh-cards tr, .oh-cards td { display: block; }
  .oh-cards { overflow: visible; margin-bottom: 0; }
  .oh-cards thead { display: none; }
  .oh-cards tr {
    background: #fff; border: 1px solid var(--border); border-radius: var(--radius-lg);
    padding: 12px 14px; margin-bottom: 10px;
  }
  .oh-cards td { padding: 0 !important; border: 0 !important; }
  .oh-cards td:nth-child(1) { font-size: var(--fs-md); }                                /* address */
  .oh-cards td:nth-child(2), .oh-cards td:nth-child(3) {                                 /* date · host */
    display: inline-block; margin-top: 4px; font-size: var(--fs-sm); color: var(--text-2);
  }
  .oh-cards td:nth-child(3)::before { content: "\00b7\00a0"; }
  .oh-cards td:nth-child(4) { margin-top: 6px; font-size: var(--fs-base); }              /* guest count */
  .oh-cards td:nth-child(5) { margin-top: 12px; }                                        /* kiosk */
  .oh-cards .oh-kiosk .btn { flex: 1 1 100%; min-height: 44px; }
  .oh-cards td:nth-child(6) { margin-top: 8px; }                                         /* close / reopen */
  .oh-cards td:nth-child(6) .btn { width: 100%; min-height: 44px; }
}

/* [10] /reporting/deals — every .rep-k is 24px/700, so "Lost 0" is typographically as loud as "$7,860,000 Active volume". For a commission-driven team the mo */
/* Reporting: money tiles outrank count tiles. .rep-card--money is set on Won volume,
   Commission, Avg sale price and Active volume in reporting/deals.html. */
.rep-card--money .rep-k { font-size: 30px; }
.rep-card--money .rep-l { color: var(--accent-ink); }

/* [11] /reporting/calls — .rep-cards is repeat(auto-fit, minmax(150px,1fr)), i.e. two columns at 375px, so the fifth tile (Avg duration) sits alone at half w */
/* An odd last tile stranded on its own phone row reads louder than the row above it. */
@media (max-width: 820px) {
  .rep-card--odd { grid-column: span 2; }
}

/* [12] /reporting/lead-sources — the bar column costs ~28% of the table width on every viewport; on a phone that is exactly the width Conversion and Deal val */
/* Lead sources: drop the decorative bar column on phones — it costs the width the
   dollars need. Cells carry .rep-bar-cell in reporting/lead_sources.html. */
@media (max-width: 820px) {
  .rep-bar-cell { display: none; }
}

/* [13] /reporting/leaderboard — the number one agent gets the same 13px grey row as everyone else, so the page creates none of the motivation the feature exi */
/* Leaderboard: the leader should look like the leader. .is-leader is set on the top row
   in reporting/leaderboard.html only when that row has a non-zero score. */
.dedupe-table tr.is-leader { background: var(--accent-softer); }
.dedupe-table tr.is-leader td { font-weight: 600; }

/* [14] /reporting/agent-activity and /reporting/leaderboard — the nine- and seven-column tables scroll sideways on a phone with no shadow or fade at the cut  */
/* Every reporting/admin table becomes display:block; overflow-x:auto on a phone with no
   sign that it scrolls. Fade the cut edge, the same way the nav strips already do. */
@media (max-width: 820px) {
  .dedupe-table {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 20px), transparent);
            mask-image: linear-gradient(to right, #000 calc(100% - 20px), transparent);
  }
}

/* [15] Optional consolidation only — nothing is blocked. I needed a quiet, named destructive row control on three pages (users, stages, inbox detail) and wro */
/* Quiet destructive row control: red text in a table row, outline only on hover.
   `.ghost.danger` currently resolves to the full .danger button (white bg, red border)
   because `.btn.danger, button.danger` is declared after `button.ghost`. Row-level
   Delete / Deactivate / Remove should be named but not the loudest pixel in the row. */
.btn.ghost.danger, button.ghost.danger {
  border-color: transparent;
  background: transparent;
  box-shadow: none;
  color: #B91C1C;
}
.btn.ghost.danger:hover, button.ghost.danger:hover {
  background: #FEF2F2;
  border-color: #F87171;
  color: #B91C1C;
}

/* [16] Every form in the app nests helper prose inside a <label>, and form.stacked label's uppercase/letter-spacing/11.5px is inherited by it, so full senten */
/* Helper prose nested inside a stacked-form label must not inherit the label's
   uppercase micro-type treatment. */
form.stacked label .muted,
form.stacked label .fld-help { text-transform: none; letter-spacing: 0; font-weight: 400;
                              font-size: var(--fs-micro); color: var(--text-2); }

/* [17] --success and --muted are referenced but never defined, so `color: var(--muted)` / `background: var(--success)` compute to nothing. I replaced every u */
/* Referenced by several templates but never defined, so they silently computed to
   nothing (grey text rendered as body ink; state pills rendered as plain text). */
:root {
  --muted: #6B7280;          /* alias of --text-2, for templates that ask for it */
  --success: #DCFCE7;        /* pale green pill tint; pair with a #16A34A dot */
  --success-ink: #166534;
}

/* [18] /admin/smart-lists at 375px: the table scrolls inside itself (global fix) but the name column still wraps to three lines and the Edit/delete cell is o */
/* Smart lists on a phone: drop the conditions column so Name · People · actions fit
   without a sideways scroll. The conditions are still readable on the edit screen. */
@media (max-width: 820px) {
  .sl-index td:nth-child(2), .sl-index th:nth-child(2) { display: none; }
}

/* [19] /admin/smart-lists/new and /edit at 375px: the filter builder is a 4-column table, so setting a value means scrolling the table sideways. Stacking eac */
/* Smart list builder on a phone: each filter becomes a stacked card instead of a
   4-column row you have to scroll sideways to reach the Value control. */
@media (max-width: 820px) {
  #filters-table { display: block; overflow-x: visible; }
  #filters-table thead { display: none; }
  #filters-table tbody, #filters-table tr, #filters-table td { display: block; }
  #filters-table tr {
    background: #fff; border: 1px solid var(--border);
    border-radius: var(--radius-lg); padding: 12px 14px; margin-bottom: 10px;
    position: relative;
  }
  #filters-table td { border: 0 !important; padding: 4px 0 !important; }
  #filters-table td:last-child { position: absolute; top: 8px; right: 8px; padding: 0 !important; }
  #filters-table select, #filters-table .sl-value { width: 100%; max-width: 100% !important; }
}

/* [20] Finding on /admin/import (applies to every admin page): at 375px the .admin-tabs strip scrolls but gives no cue that six more sections exist, and the  */
/* Admin tab strip on a phone: fade the right edge so it reads as scrollable rather
   than as a bar that simply ends after 'Pipeline'. */
@media (max-width: 820px) {
  .admin-tabs {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent 100%);
    mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent 100%);
  }
}

/* [21] login.html now needs a red alarm banner for the AUTH_REQUIRED-is-off state, but style.css only ships .banner and .banner.warn, so the page carries the */
/* Alarm-weight banner. .warn is amber advice; .danger is "this is unsafe right now" —
   used by the login page when AUTH_REQUIRED is unset and anyone can sign in as any user. */
.banner.danger { background: #FEF2F2; border: 1px solid #FCA5A5; color: #991B1B; }

/* [22] Both admin pages I own had h2s at 0.95rem that read as bold body text. I defined .section-title in each template's own <style>, scoped under .reg-page */
/* One in-page section heading. Sits between the 20px .page-title and 13px body copy;
   replaces the per-template inline `style="font-size:0.95rem"` h2s. */
.section-title { font-size: 14px; font-weight: 600; letter-spacing: .02em; margin: 22px 0 8px; }
.section-title:first-of-type { margin-top: 0; }

/* [23] Tap targets: the width half. The mobile rules above set a 44px min-HEIGHT, but an
   icon-only button's width is content-driven, so `💬` and `→` measured 28-30px wide on
   /tasks and /people (44px tall, but a narrow ribbon to hit). Square them off, and catch the
   second-level nav rows that were still 28-30px tall on /admin/settings. */
@media (max-width: 820px) {
  /* NOT .task-check. It is a fixed 24px circle with border-radius:50%, so a min-width of
     44px stretched it to 44x24 and rendered it as a visible ELLIPSE — Adam's "the radio
     buttons on mobile are a strange shape". It gets its 44px target from an invisible
     ::after in tasks.html instead, which grows the hit area without touching the shape.
     Rule for anything round: size the target with a pseudo-element, never with min-width. */
  .btn.sm, .icon-btn, a.icon-btn, button.icon-btn { min-width: 44px; }
  .admin-subnav a, .admin-tabs a, .rep-range a, .sub-item, .seg-btn { min-height: 44px; }
  .admin-subnav a, .admin-tabs a { display: inline-flex; align-items: center; }
}

/* [24] The admin/settings second-level tabs are `.admin-subtab` (not the class guessed in
   [23]) and the settings jump-links carry no class at all — both sat at 28-30px tall on a
   phone, in the densest nav in the app. */
@media (max-width: 820px) {
  .admin-subtab, .admin-subnav a, .settings-jump a { min-height: 44px; display: inline-flex; align-items: center; }
}

/* ============ REPORTING ON A PHONE ============================================
   From Adam's review notes. Three real problems, all visible at 375px:

   1. The time-range control. An earlier tap-target pass gave .rep-range a 44px floor AND
      swept it into the generic "second-level nav strips scroll with a fade" rule, so on
      every reporting page it became a horizontally-scrolling strip that clipped "90d"/"365"
      off the right edge and cost ~90px of vertical space beside the title. Three or four
      ranges fit 375px comfortably: make it one full-width row of equal segments, no scroll,
      no mask. Keeps the 44px touch height, drops the wasted block.
   2. Column headers wrapped to two lines ("DEALS WON", "NEW LEADS"), which set the header
      row height for the whole table. These tables already scroll horizontally, so nowrap
      costs nothing and buys a single-line header.
   3. The empty state inside a scrolling table was centred on the TABLE's scroll width, not
      the viewport, so "Nothing on the board yet" sat off-centre with its copy cut off at the
      right edge. Pin that cell to the visible width instead.
   ============================================================================ */
@media (max-width: 820px) {
  .rep-range {
    display: flex; width: 100%; margin: 8px 0 0; padding: 2px;
    overflow: visible; flex-wrap: nowrap;
    -webkit-mask-image: none; mask-image: none;
  }
  .rep-range a {
    flex: 1 1 0; min-width: 0; padding: 0 4px; text-align: center;
    display: inline-flex; align-items: center; justify-content: center;
    white-space: nowrap;
  }
  .page-head .page-actions { margin-left: 0; width: 100%; }

  .dedupe-table th { white-space: nowrap; }

  /* The empty-state row must measure against the screen, not the scroll canvas. */
  .dedupe-table td[colspan] {
    display: block; width: calc(100vw - 34px); max-width: calc(100vw - 34px);
    box-sizing: border-box; text-align: center; white-space: normal;
  }
  .dedupe-table td[colspan] .empty-state { max-width: 100%; padding: 32px 12px; }
}

/* When the only row is the empty state, the header row has nothing to describe — and now
   that the empty cell sizes to the screen rather than the scroll canvas, the headers would
   squeeze into that width and read as a broken "#" stub. Hide them; the empty state is the
   message. Data tables are untouched: this matches only a table containing a colspan cell. */
@media (max-width: 820px) {
  .dedupe-table:has(td[colspan]) thead { display: none; }
}

/* ============ iOS: STOP THE PAGE ZOOMING WHEN A FIELD IS FOCUSED ==============
   Safari on iPhone zooms the whole viewport in whenever a focused text control has a font
   smaller than 16px, and it does not zoom back out on blur — the user has to pinch. Every
   form in the app was doing this: a survey of 15 screens found 84 controls at 12-15px.

   The topbar search already had a one-off `.search input { font-size: 16px }` for exactly
   this reason. This generalises that fix instead of repeating it per form.

   16px is the iOS threshold, not a design choice, so it is scoped to the phone breakpoint;
   desktop keeps the 13px density the app is designed around. Controls that never receive
   text entry are excluded — their font drives their box size, and checkboxes, radios and
   buttons are sized deliberately elsewhere.

   !important is deliberate, and is the reason this now works. A plain `input, select,
   textarea` rule is specificity (0,0,1) and loses to ordinary component selectors like
   `form.stacked label input` (0,1,3) and `.bulk-bar select` (0,1,1) — that is exactly how
   the previous attempt at this fix died quietly. The alternative is chasing every component
   rule that sets a font-size on a field, forever, and losing the race each time someone adds
   one. 16px here is a platform requirement rather than a style preference, so it is stated
   once, loudly, in the same spirit as the global `[hidden] { display: none !important }`. */
@media (max-width: 820px) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="file"]):not([type="range"]):not([type="color"]):not([type="image"]),
  textarea,
  select {
    font-size: 16px !important;
  }

  /* Fallout from the line above, fixed at the cause rather than per page. A <select> is as
     wide as its longest <option>, so raising the font widened every dropdown; on
     /admin/automations that pushed the page 39px past the viewport. A fieldset additionally
     defaults to `min-width: min-content` and flatly refuses to shrink below its contents,
     which is what turned one wide select into whole-page horizontal scroll. */
  select { max-width: 100%; }
  fieldset { min-width: 0; }
}

/* ---- Deep link target: the exact item a notification opened (ux.js focusDeepLink) ---- */
@keyframes deeplink-flash {
  0%, 35% { box-shadow: 0 0 0 3px var(--accent); background-color: color-mix(in srgb, var(--accent) 12%, transparent); }
  100% { box-shadow: 0 0 0 3px transparent; background-color: transparent; }
}
.deeplink-flash { animation: deeplink-flash 2.4s ease-out 1; border-radius: 10px; scroll-margin: 80px; }
.msg.deeplink-flash { animation: none; }
.msg.deeplink-flash .msg-bubble { animation: deeplink-flash 2.4s ease-out 1; }
@media (prefers-reduced-motion: reduce) {
  .deeplink-flash, .msg.deeplink-flash .msg-bubble { animation: none; box-shadow: 0 0 0 3px var(--accent); }
}

/* ---- 💡 Feedback button (base.html #dlg-feedback, crm/feedback.py) ----
   Small and quiet in the bottom right of every app page. On the inbox it rises above the
   thread composer so it never sits on the Send button; on a phone it is in the More sheet. */
.fb-fab {
  position: fixed; right: 18px; bottom: 18px; z-index: 400;
  width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center; font-size: 18px; line-height: 1;
  background: #fff; border: 1px solid var(--border); box-shadow: 0 2px 8px rgba(15, 23, 42, .12);
  cursor: pointer; opacity: .85; transition: opacity .12s ease, transform .12s ease;
}
.fb-fab:hover, .fb-fab:focus-visible { opacity: 1; transform: translateY(-1px); }
body:has(#thread-form) .fb-fab, body:has(#chat-form) .fb-fab { bottom: 150px; }
body:has(#dialer:not([hidden])) .fb-fab { display: none; }   /* never over a live call's buttons */
@media (max-width: 820px) { .fb-fab { display: none; } }
@media print { .fb-fab { display: none; } }
.fb-shot { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
.fb-shot-note { font-size: var(--fs-micro); }
.fb-preview { display: flex; flex-direction: column; gap: 6px; }
.fb-preview img { max-width: 100%; max-height: 180px; border: 1px solid var(--border); border-radius: 6px; object-fit: cover; object-position: top; }
.fb-status { margin: 0; font-size: var(--fs-micro); min-height: 1em; }

/* ===== Deal card (templates/_deal_card.html, static/deal_card.js) =====
   Opened from a Pipeline card and from a deal's own page. Everything is dc-* or scoped to
   dialog.dlg-deal, so it cannot restyle another feature's markup. */
dialog.dlg.dlg-deal { width: min(760px, 94vw); }
dialog.dlg-deal .dlg-head.dc-head { align-items: flex-start; gap: 12px; padding: 18px 22px 14px; }
dialog.dlg-deal .dlg-body.dc-body { padding: 18px 22px; gap: 16px; }
dialog.dlg-deal .dlg-foot.dc-foot { padding: 12px 22px; align-items: center; }
dialog.dlg-deal button.link {
  background: none; border: 0; padding: 0; font: inherit; font-size: 13px;
  color: var(--accent-ink); cursor: pointer; text-transform: none; letter-spacing: 0;
}
dialog.dlg-deal button.link:hover { text-decoration: underline; }
.dc-titlebox { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
dialog.dlg-deal input.dc-title {
  font-size: 20px; font-weight: 600; letter-spacing: -0.01em; color: var(--text);
  border: 1px solid transparent; border-radius: var(--radius); padding: 2px 6px; margin-left: -7px;
  background: transparent; width: 100%; box-sizing: border-box;
}
dialog.dlg-deal input.dc-title:hover { border-color: var(--border); }
dialog.dlg-deal input.dc-title:focus { border-color: var(--accent); background: white; outline: none; }
.dc-meta { font-size: 13px; color: var(--text-2); }
.dc-meta a { color: var(--accent-ink); }
.dc-crumb { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 2px; }
.dc-crumb select { font-size: 13px; color: var(--accent-ink); font-weight: 500; }
.dc-crumb-sep { color: var(--text-3); }
.dc-switch {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 22px; background: var(--hover); border-bottom: 1px solid var(--border);
  font-size: 13px;
}
.dc-switch select { font-size: 13px; max-width: 100%; }
.dc-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 28px; align-items: start; }
.dc-splits { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.dc-split-out { grid-column: 1 / -1; font-size: 12px; min-height: 1em; font-variant-numeric: tabular-nums; }
.dc-sec { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.dc-k {
  font-size: 11.5px; color: var(--text-2); font-weight: 500;
  text-transform: uppercase; letter-spacing: 0.04em;
}
.dc-chips { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.dc-chip {
  display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
  padding: 3px 6px 3px 3px; border: 1px solid var(--border); border-radius: 999px;
  background: white; font-size: 13px; color: var(--text); text-decoration: none;
}
a.dc-chip:hover, .dc-chip-link:hover .dc-chip-name { color: var(--accent-ink); }
.dc-chip-link { display: inline-flex; align-items: center; gap: 6px; color: inherit; text-decoration: none; min-width: 0; }
.dc-chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 180px; }
.dc-tag {
  font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--text-3); padding-right: 4px;
}
.dc-x {
  border: 0; background: none; color: var(--text-3); cursor: pointer;
  font-size: 11px; line-height: 1; padding: 2px 4px; border-radius: 999px;
}
.dc-x:hover { color: #B91C1C; background: #FEE2E2; }
.dc-add {
  width: 28px; height: 28px; border-radius: 50%; border: 1px dashed var(--border);
  background: var(--hover); color: var(--text-2); cursor: pointer; font-size: 15px; line-height: 1;
}
.dc-add:hover { border-color: var(--accent); color: var(--accent-ink); }
.dc-addrow { display: flex; gap: 8px; align-items: center; }
.dc-addrow .ctp-wrap, .dc-addrow select, .dc-addrow input { flex: 1 1 auto; min-width: 0; }
.dc-linkrow { flex-wrap: wrap; }
.dc-sub { font-size: 12.5px; color: var(--text-2); }
.dc-none { font-size: 13px; }
.dc-hint { margin: 0; font-size: 12px; color: var(--text-3); }
.dc-lede { margin: 0; font-size: 13.5px; color: var(--text-2); }
.dc-error {
  margin: 0; padding: 8px 10px; border-radius: var(--radius);
  background: #FEF2F2; border: 1px solid #FECACA; color: #991B1B; font-size: 13px;
}
.dc-checklist { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.dc-checklist select { flex: 1 1 220px; min-width: 0; }
.dc-link { font-size: 13px; color: var(--accent-ink); }
.dc-progress { display: flex; align-items: center; gap: 10px; font-size: 12.5px; }
.dc-bar { flex: 0 0 120px; height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; }
.dc-bar > div { height: 100%; background: var(--accent); }
.dc-files { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.dc-files li { display: flex; align-items: center; gap: 8px; font-size: 13px; min-width: 0; }
.dc-files li a { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; color: var(--accent-ink); }
.dc-file-by { font-size: 12px; margin-left: auto; white-space: nowrap; }
.dc-file-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 13px; }
.dc-busy-note { font-size: 12px; }
dialog.dlg-deal .dc-foot .dc-delete { color: #B91C1C; margin-right: auto; }
@media (max-width: 640px) {
  .dc-grid { grid-template-columns: 1fr; }
  dialog.dlg-deal .dlg-head.dc-head, dialog.dlg-deal .dlg-body.dc-body, dialog.dlg-deal .dlg-foot.dc-foot { padding-left: 16px; padding-right: 16px; }
  .dc-switch { padding-left: 16px; padding-right: 16px; }
  .dc-file-by { display: none; }
}
