/* ════════════════════════════════════════════════════════════════
   Components — the pieces the app was missing.

   Loaded AFTER saleshunt.css, so anything here intentionally wins over
   a legacy rule of equal specificity. Everything is built from tokens.css;
   there are no literal colours or durations below.

   What lives here: feedback (toasts, skeletons, empty states), surfaces
   (drawer, sticky page header), and small primitives the board and list
   views need (avatar, tag, density).
════════════════════════════════════════════════════════════════ */

/* ── Toasts ───────────────────────────────────────────────────────
   Replaces the stack of banner alerts that pushed page content down on
   every save. Transient feedback should not reflow the page you are
   looking at. Driven by window.toast() in static/js/app.js. */
.bm-toast-region {
  position:fixed; right:var(--space-5); bottom:var(--space-5);
  z-index:var(--z-toast);
  display:flex; flex-direction:column; gap:var(--space-2);
  max-width:min(420px,calc(100vw - var(--space-8)));
  pointer-events:none;
}
.bm-toast {
  pointer-events:auto;
  display:flex; align-items:flex-start; gap:var(--space-3);
  background:var(--c-neutral-000);
  border:1px solid var(--c-neutral-200);
  border-left:3px solid var(--c-neutral-400);
  border-radius:var(--radius-md);
  box-shadow:var(--elevation-3);
  padding:var(--space-3) var(--space-4);
  font-size:var(--text-base); color:var(--c-neutral-700);
  animation:bm-toast-in var(--duration-slow) var(--ease-out);
}
.bm-toast--success { border-left-color:var(--c-success-500); }
.bm-toast--warning { border-left-color:var(--c-warning-500); }
.bm-toast--danger  { border-left-color:var(--c-danger-500); }
.bm-toast--info    { border-left-color:var(--c-info-500); }
.bm-toast-icon { font-size:var(--text-md); line-height:1.2; }
.bm-toast--success .bm-toast-icon { color:var(--c-success-600); }
.bm-toast--warning .bm-toast-icon { color:var(--c-warning-600); }
.bm-toast--danger  .bm-toast-icon { color:var(--c-danger-600); }
.bm-toast--info    .bm-toast-icon { color:var(--c-info-600); }
.bm-toast-body { flex:1; }
.bm-toast-close {
  background:none; border:0; cursor:pointer; padding:0 var(--space-1);
  color:var(--c-neutral-400); font-size:var(--text-md); line-height:1;
}
.bm-toast-close:hover { color:var(--c-neutral-600); }
.bm-toast.is-leaving { animation:bm-toast-out var(--duration-base) var(--ease-standard) forwards; }
@keyframes bm-toast-in  { from { opacity:0; transform:translateY(8px) scale(.98); } }
@keyframes bm-toast-out { to   { opacity:0; transform:translateY(4px); } }

/* ── Skeletons ────────────────────────────────────────────────────
   Shown in place of content while it loads, instead of the full-screen
   spinner that blanked the whole app. Keeps the page's shape stable so
   nothing jumps when data lands. */
.bm-skeleton {
  background:linear-gradient(90deg,
    var(--c-neutral-100) 25%, var(--c-neutral-50) 37%, var(--c-neutral-100) 63%);
  background-size:400% 100%;
  animation:bm-shimmer 1.4s ease infinite;
  border-radius:var(--radius-sm);
}
.bm-skeleton-text { height:12px; margin-bottom:var(--space-2); }
.bm-skeleton-text:last-child { width:60%; margin-bottom:0; }
.bm-skeleton-title { height:20px; width:40%; margin-bottom:var(--space-3); }
.bm-skeleton-card { height:96px; border-radius:var(--radius); }
.bm-skeleton-avatar { width:32px; height:32px; border-radius:var(--radius-full); }
@keyframes bm-shimmer { from { background-position:100% 50%; } to { background-position:0 50%; } }

/* ── Empty states ─────────────────────────────────────────────────
   An empty table should say what to do next, not just be blank. */
.bm-empty {
  display:flex; flex-direction:column; align-items:center; text-align:center;
  padding:var(--space-12) var(--space-6); color:var(--c-neutral-500);
}
.bm-empty-icon {
  width:52px; height:52px; border-radius:var(--radius-xl);
  display:flex; align-items:center; justify-content:center;
  background:var(--c-brand-50); color:var(--c-brand-500);
  font-size:var(--text-xl); margin-bottom:var(--space-4);
}
.bm-empty-title {
  font-family:var(--font-display); font-size:var(--text-md);
  font-weight:var(--weight-semibold); color:var(--c-neutral-700);
  margin-bottom:var(--space-1);
}
.bm-empty-text { font-size:var(--text-base); max-width:44ch; margin-bottom:var(--space-4); }

/* ── Sticky page header ───────────────────────────────────────────
   Keeps the title and primary actions reachable on long boards and
   tables instead of scrolling away at the top. */
.bm-page-head--sticky {
  position:sticky; top:0; z-index:var(--z-sticky);
  background:var(--c-neutral-50);
  padding-top:var(--space-3); padding-bottom:var(--space-3);
  border-bottom:1px solid transparent;
  transition:border-color var(--duration-fast) var(--ease-standard),
             box-shadow var(--duration-fast) var(--ease-standard);
}
.bm-page-head--sticky.is-stuck {
  border-bottom-color:var(--c-neutral-200);
  box-shadow:var(--elevation-1);
}

/* ── Avatar ───────────────────────────────────────────────────────
   Initials with a deterministic tint (see avatarTone in app.js), so the
   same person is the same colour on every board. */
.bm-avatar-sm {
  width:24px; height:24px; border-radius:var(--radius-full);
  display:inline-flex; align-items:center; justify-content:center;
  font-size:var(--text-xs); font-weight:var(--weight-semibold);
  letter-spacing:0; flex-shrink:0; user-select:none;
}
.bm-avatar-stack { display:inline-flex; }
.bm-avatar-stack .bm-avatar-sm + .bm-avatar-sm {
  margin-left:-6px; box-shadow:0 0 0 2px var(--c-neutral-000);
}
.bm-avatar-tone-1 { background:var(--c-brand-50);   color:var(--c-brand-600); }
.bm-avatar-tone-2 { background:var(--c-info-50);    color:var(--c-info-600); }
.bm-avatar-tone-3 { background:var(--c-success-50); color:var(--c-success-600); }
.bm-avatar-tone-4 { background:var(--c-warning-50); color:var(--c-warning-600); }
.bm-avatar-tone-5 { background:var(--c-danger-50);  color:var(--c-danger-600); }
.bm-avatar-tone-6 { background:var(--c-neutral-100);color:var(--c-neutral-600); }

/* ── Tags ─────────────────────────────────────────────────────────
   Any colour a tenant picks, including a custom hex via --tag-color. */
.bm-tag {
  display:inline-flex; align-items:center; gap:var(--space-1);
  padding:2px var(--space-2); border-radius:var(--radius-full);
  font-size:var(--text-xs); font-weight:var(--weight-semibold);
  line-height:1.6; white-space:nowrap;
  background:color-mix(in srgb, var(--tag-color,var(--c-brand-500)) 12%, transparent);
  color:color-mix(in srgb, var(--tag-color,var(--c-brand-500)) 78%, var(--c-neutral-800));
  border:1px solid color-mix(in srgb, var(--tag-color,var(--c-brand-500)) 26%, transparent);
}
.bm-tag-remove {
  border:0; background:none; padding:0; cursor:pointer; color:inherit; opacity:.6;
}
.bm-tag-remove:hover { opacity:1; }

/* ── Kanban card as a link ────────────────────────────────────────
   The card opens the full lead page. The title anchor is stretched over
   the card rather than wrapping it, so the delete button stays a real
   button instead of an interactive element nested inside an anchor. */
.kanban-card { position:relative; }
.kanban-card-link { color:inherit; text-decoration:none; }
.kanban-card-link::after { content:""; position:absolute; inset:0; border-radius:inherit; }
.kanban-card-link:hover { text-decoration:none; }
.kanban-card:focus-within { border-color:var(--c-brand-500); box-shadow:var(--focus-ring); }
/* Anything the user must be able to click sits above the stretched link. */
.kcard-del { position:relative; z-index:1; }

/* Form controls follow the brand rather than the browser's default blue. */
input[type="checkbox"], input[type="radio"] { accent-color:var(--c-brand-500); }

/* ── Legacy-markup bridge ─────────────────────────────────────────
   Screens still written with raw Bootstrap cards. The base sheet already
   gives `.card` the right border and radius; this finishes the job so a
   page nobody has rewritten still reads as part of the system — hover
   behaviour, header rhythm, and a title that matches `.bm-card-title`.

   This is deliberately a bridge, not a home: new markup should use
   `.bm-card`. It exists so the remaining screens aren't visibly older
   than the ones the feature work touched. */
.card { transition:border-color var(--duration-base) var(--ease-standard),
                   box-shadow var(--duration-base) var(--ease-standard); }
.card:hover { border-color:var(--c-brand-100)!important; box-shadow:var(--elevation-2); }
.card-header {
  background:transparent; border-bottom:1px solid var(--c-neutral-200);
  padding:var(--space-4) var(--space-5);
  font-family:var(--font-display); font-weight:var(--weight-semibold);
  color:var(--c-neutral-700);
}
.card-title {
  font-family:var(--font-display); font-size:var(--text-md);
  font-weight:var(--weight-semibold); color:var(--c-neutral-700);
}
/* Bootstrap tables inherit the app's table rhythm. */
.table > :not(caption) > * > * { padding:var(--space-3) var(--space-4); }
.table thead th { font-weight:var(--weight-semibold); }

/* Any wide element can scroll inside its own box rather than pushing the
   page sideways — the failure mode on a laptop screen with a wide table. */
.bm-scroll-x { overflow-x:auto; -webkit-overflow-scrolling:touch; }

/* ── My Work list ─────────────────────────────────────────────────
   One row per task, scannable: tick, title, where it came from, when it
   is due. Buckets carry the urgency, so rows stay uniform. */
.work-item {
  display:flex; align-items:center; gap:var(--space-3);
  padding:var(--space-2) 0;
  border-top:1px solid var(--c-neutral-100);
}
.work-item:first-of-type { border-top:0; }
.work-item-title { font-size:var(--text-base); color:var(--c-neutral-700); }
.work-item.is-done .work-item-title { color:var(--c-neutral-400); }
.work-check {
  width:18px; height:18px; flex-shrink:0; padding:0; cursor:pointer;
  border:2px solid var(--c-neutral-300); border-radius:var(--radius-sm);
  background:transparent;
}
.work-check:hover { border-color:var(--c-brand-500); background:var(--c-brand-50); }
.work-bucket-danger { color:var(--c-danger-600); }
.work-bucket-warning { color:var(--c-warning-600); }
.work-bucket-info { color:var(--c-info-600); }
.work-bucket-muted { color:var(--c-neutral-500); }

/* ── View toggle ──────────────────────────────────────────────────── */
.bm-viewtoggle {
  display:inline-flex; border:1px solid var(--c-neutral-200);
  border-radius:var(--radius-md); overflow:hidden;
}
.bm-viewtoggle-btn {
  padding:var(--space-1) var(--space-3);
  color:var(--c-neutral-500); text-decoration:none;
  font-size:var(--text-base); line-height:1.8;
}
.bm-viewtoggle-btn:hover { background:var(--c-neutral-50); text-decoration:none; }
.bm-viewtoggle-btn.is-active { background:var(--c-brand-50); color:var(--c-brand-600); }

/* ── Inline edit cells ────────────────────────────────────────────
   The save state has to be visible without stealing attention: a brief
   tint on success, a red edge while a value is refused. */
.bm-table [data-field] { border-color:transparent; background:transparent; }
.bm-table tr:hover [data-field] { border-color:var(--c-neutral-200); background:var(--c-neutral-000); }
.bm-table [data-field]:focus { border-color:var(--c-brand-500); background:var(--c-neutral-000); }
.bm-table [data-field].is-saving { opacity:.6; }
.bm-table [data-field].is-saved { background:var(--c-success-50); border-color:var(--c-success-500); }
.bm-table thead a { color:inherit; text-decoration:none; white-space:nowrap; }
.bm-table thead a:hover { color:var(--c-brand-600); text-decoration:none; }

/* ── Lead notes ───────────────────────────────────────────────────
   Notes is its own tab (see #tab-notes) — no longer a rail beside the
   tabs, so this is just the plain single-column shell. */
.lead-with-notes { display:block; }
.lead-main { min-width:0; }
.lead-main .lead-tabs { margin-bottom:0; row-gap:var(--space-1); }
/* Twelve tabs: tighter padding keeps them on one line on a wide monitor;
   below that they wrap onto a neat second row rather than scrolling, so no
   tab is ever hidden. */
.lead-main .lead-tabs .nav-link { padding:10px var(--space-3); }

/* Task quick-add: a title, a due date, an assignee and a button do not fit on
   one line inside a half-width card. Wrapping keeps the title usable instead of
   crushing it to a stub. */
.task-add-row { display:flex; flex-wrap:wrap; gap:var(--space-2); }
.task-add-title { flex:1 1 100%; min-width:160px; }
.task-add-due { flex:1 1 190px; min-width:170px; }
.task-add-assignee { flex:1 1 150px; min-width:140px; }
.task-add-row .bm-btn { flex:0 0 auto; }
@media (min-width:1500px){
  /* Wide enough for one row. */
  .task-add-title { flex:1 1 auto; }
}

/* Reminder quick-add: same wrap as .task-add-row, so the reason gets a full
   row and the date + button wrap to a second row inside the narrower Activity
   tab columns. */
.rem-add-row { display:flex; flex-wrap:wrap; gap:var(--space-2); }
.rem-add-reason { flex:1 1 100%; min-width:160px; }
.rem-add-due { flex:1 1 190px; min-width:170px; }
.rem-add-row .bm-btn { flex:0 0 auto; }
@media (min-width:1500px){
  .rem-add-reason { flex:1 1 auto; }
}
.lead-note-list { display:flex; flex-direction:column; gap:var(--space-3); }
.lead-note {
  padding:var(--space-3);
  background:var(--c-neutral-50);
  border:1px solid var(--c-neutral-200);
  border-radius:var(--radius-md);
}
.lead-note-head { display:flex; align-items:center; gap:var(--space-2); margin-bottom:var(--space-2); }
.lead-note-author { font-size:var(--text-sm); font-weight:var(--weight-semibold); color:var(--c-neutral-700); }
.lead-note-when { font-size:var(--text-xs); color:var(--c-neutral-400); }
.lead-note-actions { margin-left:auto; display:flex; gap:var(--space-1); opacity:0; transition:opacity var(--duration-fast); }
.lead-note:hover .lead-note-actions,
.lead-note:focus-within .lead-note-actions { opacity:1; }
.lead-note-body { font-size:var(--text-base); color:var(--c-neutral-700); white-space:pre-wrap; word-break:break-word; }

/* ── Field grid ───────────────────────────────────────────────────
   Two columns on a consistent rhythm. Forms previously mixed full-width
   rows with ad-hoc 7/5 splits, so labels and inputs did not line up
   down a card. Fields opt into full width instead of each row inventing
   its own layout. */
.bm-field-grid {
  display:grid;
  grid-template-columns:repeat(2, minmax(0, 1fr));
  gap:var(--space-3) var(--space-4);
  margin-bottom:var(--space-4);
}
.bm-field { display:flex; flex-direction:column; gap:var(--space-1); min-width:0; }
.bm-field--full { grid-column:1 / -1; }
.bm-field .bm-input,
.bm-field .bm-select,
.bm-field .bm-textarea { width:100%; }
.bm-field .bm-input:disabled { opacity:.6; background:var(--c-neutral-50); }
@media (max-width:720px){ .bm-field-grid { grid-template-columns:1fr; } }

/* ── Filter bar ───────────────────────────────────────────────────
   One row of controls on a single GET form: filters combine, the URL is
   shareable, and Back works. Replaces three separate forms that each
   dropped the others' state on submit. */
.bm-filterbar {
  display:flex; align-items:center; flex-wrap:wrap; gap:var(--space-2);
  padding:var(--space-3);
  background:var(--c-neutral-000);
  border:1px solid var(--c-neutral-200);
  border-radius:var(--radius-md);
  margin-bottom:var(--space-4);
}
/* The base sheet sizes form controls to 100% for stacked forms; inside the
   filter bar they must sit on one row and size to their content. */
.bm-filterbar .bm-select,
.bm-filterbar .bm-input { width:auto; flex:0 0 auto; }
/* Fixed, not min/max-clamped: a range still lets each select shrink to its
   own option text, so "Any assignee" and "All entities" render at visibly
   different widths sitting in the same row. */
.bm-filterbar .bm-select { width:180px; }
.bm-filterbar-search { position:relative; flex:1 1 260px; min-width:220px; }
.bm-filterbar-search i {
  position:absolute; left:var(--space-3); top:50%; transform:translateY(-50%);
  color:var(--c-neutral-400); font-size:var(--text-sm); pointer-events:none;
}
.bm-filterbar-search .bm-input { padding-left:var(--space-8); width:100%; }
.bm-check-inline {
  display:inline-flex; align-items:center; gap:var(--space-2);
  font-size:var(--text-sm); color:var(--c-neutral-600); white-space:nowrap;
}
.bm-filter-pop { position:relative; }
.bm-filter-pop > summary { list-style:none; cursor:pointer; }
.bm-filter-pop > summary::-webkit-details-marker { display:none; }
.bm-filter-pop-body {
  position:absolute; top:calc(100% + var(--space-1)); left:0; z-index:var(--z-sticky);
  min-width:220px; max-height:320px; overflow-y:auto;
  padding:var(--space-3);
  background:var(--c-neutral-000);
  border:1px solid var(--c-neutral-200);
  border-radius:var(--radius-md);
  box-shadow:var(--elevation-3);
}
/* The card's avatar and delete button sit above the stretched title link. */
.kcard-actions { position:relative; z-index:1; margin-left:var(--space-2); }

/* ── Board drag states (SortableJS) ───────────────────────────────
   The placeholder is what makes a drop position legible: a dashed gap
   opens exactly where the card will land, instead of the card only
   appending to the end of a column. */
.kanban-card--ghost {
  opacity:1;
  background:var(--c-brand-50);
  border:1px dashed var(--c-brand-500);
  box-shadow:none;
}
.kanban-card--ghost > * { visibility:hidden; }
.kanban-card--chosen { cursor:grabbing; }
/* Brief highlight for the card just added, after the "Add lead" redirect
   lands on the board instead of the lead's detail page. */
.kanban-card--new {
  animation:kanban-card-new-flash 2.6s ease-out;
}
@keyframes kanban-card-new-flash {
  0%, 35% { box-shadow:0 0 0 2px var(--c-brand-500), var(--elevation-3); background:var(--c-brand-50); }
  100% { box-shadow:none; background:var(--white); }
}
.kanban-card--drag {
  box-shadow:var(--elevation-4);
  transform:rotate(1.5deg);
  opacity:.95;
}
.kanban-grip { cursor:grab; color:var(--c-neutral-400); }
.kanban-grip:active { cursor:grabbing; }

/* ── Table density ────────────────────────────────────────────────
   The list view shows many rows; compact mode fits ~40% more on screen
   without shrinking the type below readable size. */
.bm-table--compact th, .bm-table--compact td {
  padding:var(--space-2) var(--space-3);
  font-size:var(--text-sm);
}
.bm-table--zebra tbody tr:nth-child(even) { background:var(--c-neutral-50); }
.bm-table tbody tr.is-selected { background:var(--c-brand-50); }

/* ── Drawer ───────────────────────────────────────────────────────
   Side panel for quick detail without leaving the board. */
.bm-drawer-backdrop {
  position:fixed; inset:0; background:rgba(27,36,48,.35);
  z-index:var(--z-drawer); opacity:0; pointer-events:none;
  transition:opacity var(--duration-base) var(--ease-standard);
}
.bm-drawer-backdrop.is-open { opacity:1; pointer-events:auto; }
.bm-drawer {
  position:fixed; top:0; right:0; bottom:0; width:min(520px,100vw);
  background:var(--c-neutral-000); box-shadow:var(--elevation-4);
  z-index:calc(var(--z-drawer) + 1);
  transform:translateX(100%);
  transition:transform var(--duration-slow) var(--ease-out);
  display:flex; flex-direction:column;
}
.bm-drawer.is-open { transform:none; }
.bm-drawer-head {
  padding:var(--space-4) var(--space-5);
  border-bottom:1px solid var(--c-neutral-200);
  display:flex; align-items:center; justify-content:space-between; gap:var(--space-3);
}
.bm-drawer-body { padding:var(--space-5); overflow-y:auto; flex:1; }

/* ── Focus visibility ─────────────────────────────────────────────
   One consistent ring. :focus-visible so a mouse click doesn't ring,
   but Tab always does. */
.bm-btn:focus-visible,
.bm-input:focus-visible,
.bm-select:focus-visible,
.bm-nav-item:focus-visible,
a.bm-stat-link:focus-visible {
  outline:2px solid var(--c-brand-500);
  outline-offset:2px;
  box-shadow:var(--focus-ring);
}

/* Screen-reader-only text for icon-only controls. */
.bm-sr-only {
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0;
}

/* ── two-factor nudge ──────────────────────────────────────────────────────
   Optional MFA needs an ask somewhere. Sits above page content, dismissible,
   and quiet enough not to read as an error. */
.mfa-nudge {
  display:flex; align-items:center; gap:var(--space-3);
  padding:10px var(--space-3); margin-bottom:var(--space-4);
  border:1px solid var(--amber-border, #fde68a); border-radius:var(--radius-md);
  background:var(--amber-light, #fffbeb); color:var(--text);
}
.mfa-nudge > .bi { font-size:18px; color:#b45309; flex:none; }
.mfa-nudge-text { flex:1 1 auto; font-size:13px; line-height:1.45; min-width:0; }
.mfa-nudge form { margin:0; }
.mfa-nudge-close {
  border:0; background:transparent; color:var(--text-muted);
  padding:4px 6px; border-radius:var(--radius-sm); cursor:pointer; line-height:1;
}
.mfa-nudge-close:hover { background:rgba(0,0,0,.06); color:var(--text); }
@media (max-width:640px) { .mfa-nudge { flex-wrap:wrap; } .mfa-nudge-text { flex:1 1 100%; } }

/* ── Busy submit button ────────────────────────────────────────────────────
   Immediate proof the press landed, while the full-screen loader waits out
   its anti-flash delay. Paired with static/js/loader.js. */
.btn-spinner {
  display:inline-block; width:13px; height:13px; margin-right:8px;
  vertical-align:-2px; border-radius:50%;
  border:2px solid currentColor; border-top-color:transparent;
  animation:app-spin .7s linear infinite;
}
[aria-busy="true"].bm-btn { cursor:progress; }
.bm-btn:disabled, .bm-btn[disabled] { opacity:.72; cursor:not-allowed; }

/* ── Workspace branding ────────────────────────────────────────────────────
   A workspace with no logo shows its name in the sidebar's logo slot. That
   slot is a fixed-height row, so a long workspace name has to truncate rather
   than push the layout around. */
.bm-logo-text--name {
  font-size:17px; line-height:1.2; min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* The logo shares a 240px fixed-height row with the workspace name, so it
   gets a hard cap and the name takes what is left — a wide wordmark must not
   squeeze the name out, and neither may push the row taller. */
.bm-sidebar-logo-img {
  height:30px; width:auto; max-width:92px; object-fit:contain; flex:none;
}
.bm-sidebar-logo-img + .bm-logo-text--name { font-size:15px; }
/* The anchor is itself a flex item; without min-width:0 it grows to fit its
   text instead of letting the label inside it ellipsize, and a long workspace
   name runs straight out of the sidebar. */
.bm-sidebar-logo > a { flex:1 1 auto; min-width:0; overflow:hidden; }
.brand-preview {
  display:flex; align-items:center; gap:var(--space-3);
  padding:var(--space-3); border:1px dashed var(--border);
  border-radius:var(--radius-md); background:var(--off2); min-height:64px;
}
.brand-preview img { height:30px; width:auto; max-width:180px; object-fit:contain; }
.brand-preview-name { font-weight:700; font-size:17px; color:var(--dark); }

/* ── Profile pictures ──────────────────────────────────────────────────────
   Same footprint as the initials badge it replaces, so a board with a mix of
   photos and initials keeps its alignment. */
.bm-avatar-photo { object-fit:cover; background:var(--c-neutral-100); }
img.bm-avatar { border-radius:9px; object-fit:cover; }

.profile-identity { display:flex; align-items:center; gap:var(--space-3); }
.profile-identity-text { min-width:0; }
.profile-photo {
  width:64px; height:64px; border-radius:var(--radius-full); flex:none;
  object-fit:cover; background:var(--c-neutral-100);
}
.profile-photo--empty {
  display:flex; align-items:center; justify-content:center;
  font-size:24px; font-weight:var(--weight-semibold); color:var(--c-neutral-600);
}

/* ── Switch ───────────────────────────────────────────────────────────────
   For settings that take effect the moment they are saved: on/off reads at a
   glance across a long list, which a column of identical square checkboxes
   does not. The native input stays in the DOM (and in the form post) rather
   than being replaced, so keyboard focus, labels and form submission are the
   browser's, not ours. */
.bm-switch {
  display:inline-flex; align-items:center; gap:var(--space-2);
  cursor:pointer; user-select:none; white-space:nowrap;
  font-size:var(--text-sm); font-weight:var(--font-medium);
  color:var(--c-neutral-600);
}
.bm-switch input {
  position:absolute; opacity:0; width:0; height:0; margin:0;
}
.bm-switch-track {
  position:relative; flex:0 0 auto;
  width:38px; height:22px; border-radius:999px;
  background:var(--c-neutral-300);
  transition:background .18s ease;
}
.bm-switch-track::after {
  content:""; position:absolute; top:3px; left:3px;
  width:16px; height:16px; border-radius:50%;
  background:var(--c-neutral-000);
  box-shadow:var(--elevation-1);
  transition:transform .18s ease;
}
.bm-switch input:checked + .bm-switch-track { background:var(--accent); }
.bm-switch input:checked + .bm-switch-track::after { transform:translateX(16px); }
.bm-switch input:focus-visible + .bm-switch-track {
  outline:2px solid var(--accent); outline-offset:2px;
}
.bm-switch input:disabled + .bm-switch-track { opacity:.5; cursor:not-allowed; }
/* The word carries the state for anyone who reads rather than scans. */
.bm-switch-label { min-width:52px; }
.bm-switch input:checked ~ .bm-switch-label { color:var(--accent2); }
@media (prefers-reduced-motion:reduce) {
  .bm-switch-track, .bm-switch-track::after { transition:none; }
}
