/* ══════════════════════════════════════════════════════════
   GLOBAL BASE STYLESHEET
   Shared across every page: selection/focus/accent-color reset,
   and the custom Dropdown component (see /js/dropdown.js).
   ══════════════════════════════════════════════════════════ */

/* ── SELECTION / FOCUS / ACCENT ──
   Single source of truth for outline-on-click suppression app-wide.
   :focus-visible (keyboard) still gets a visible ring; plain mouse-click
   focus does not. Applies to every element via `*`, not just form controls,
   so custom interactive elements (dropdown trigger/options, cards, etc.)
   are covered without per-component overrides. */
::selection { background: #3a3a3c; color: #ffffff; }
html.dark ::selection { background: #F5F0E8; color: #1a1a1a; }

*:focus:not(:focus-visible) { outline: none; }
:focus-visible { outline: 2px solid #3a3a3c; outline-offset: 2px; }
html.dark :focus-visible { outline: 2px solid #F5F0E8; outline-offset: 2px; }

* { accent-color: #3a3a3c; -webkit-tap-highlight-color: transparent; }
html.dark * { accent-color: #F5F0E8; }

/* ── GLOBAL SCROLLBAR STYLE ("minimal line") ──
   Single source of truth for scrollbar appearance app-wide — thin thumb,
   no visible track, no arrow buttons. Applied via `*` so every scrollable
   container (list panels, dropdown/select listboxes, modals, the command
   bar response panel, tables with internal scroll, etc.) inherits it
   automatically without per-component overrides.
   The `scrollbar-width`/`scrollbar-color` standard fallback below is
   scoped to browsers that DON'T support `::-webkit-scrollbar` (i.e.
   Firefox) — once an element has scrollbar-width/color set, Chromium
   renders scrollbars via the standard-properties path exclusively and
   ignores the ::-webkit-scrollbar* pseudo-elements entirely, which would
   silently bring back the native track box and arrow buttons in Chrome.
   Gating the fallback with @supports keeps Chromium/WebKit on the
   pseudo-elements (full control over width/track/buttons) while Firefox
   still gets a matching thin thumb via the only mechanism it supports. */
@supports not selector(::-webkit-scrollbar) {
  * { scrollbar-width: thin; scrollbar-color: #b0b0b0 transparent; }
  html.dark * { scrollbar-color: #5a5a5c transparent; }
}

*::-webkit-scrollbar { width: 6px; height: 6px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background: #b0b0b0; border-radius: 3px; }
*::-webkit-scrollbar-thumb:hover { background: #8a8a8a; }
*::-webkit-scrollbar-button { display: none; width: 0; height: 0; }

html.dark *::-webkit-scrollbar-thumb { background: #5a5a5c; }
html.dark *::-webkit-scrollbar-thumb:hover { background: #7a7a7c; }

/* ── NUMBER INPUT STEPPER ARROWS (dark mode) ──
   The native up/down spinner on <input type="number"> renders as a small
   dark-on-white box regardless of page theme (browser chrome, not styled by
   our CSS) — `color-scheme: dark` tells the browser to draw its native form
   control chrome (this spinner, plus checkboxes/date pickers/scrollbars if
   ever used) in dark-appropriate colors itself, so it sits flush with a dark
   input background instead of showing a jarring white square. */
html.dark input[type="number"] { color-scheme: dark; }

/* ── GLOBAL BUTTON HOVER TRANSITION ──
   Every button app-wide (real <button> elements, plus .nav-btn and
   .avatar-dd-link, the two button-styled <a> tags) gets the same smooth
   timing on hover-triggered property changes, matching the mic/send
   button feel in index.html's .cmd-bar (opacity 0.4 -> 1 over
   `transition: opacity 0.15s ease`). This rule only standardizes the
   TRANSITION -- duration and easing -- not the colors: a component's own
   more-specific :hover rule still owns which property changes and to what
   value. Buttons that already declare their own transition (nav-btn,
   theme-btn, dd-trigger, etc.) keep it unchanged, since a class selector
   there beats this element-level one; this mainly fills the gap for
   buttons that had no transition at all (.btn-solid, .btn-outline, ...),
   which previously snapped instantly on hover instead of fading. */
button, .nav-btn, .avatar-dd-link {
  transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease, box-shadow 0.15s ease;
}

/* ── CUSTOM DROPDOWN COMPONENT ──
   Progressive enhancement over a native <select> (kept in the DOM, hidden,
   as the source of truth) — see /js/dropdown.js. */
.dd-wrap { position: relative; display: block; width: 100%; }
.dd-wrap.dd-inline { display: inline-block; width: auto; }
.dd-wrap.dd-flexfill { display: inline-flex; flex: 1; min-width: 200px; max-width: 340px; width: auto; }

select.dd-native { display: none; }

.dd-trigger {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; box-sizing: border-box;
  padding: 11px 16px;
  font-family: var(--font-family), sans-serif;
  font-size: 16px; font-weight: 500;
  color: var(--text); background: var(--surface);
  border: 1px solid var(--border, rgba(0,0,0,0.15));
  border-radius: 6px !important;
  cursor: pointer; text-align: left;
  transition: background 0.12s ease, border-color 0.12s ease;
}
.dd-trigger:hover { background: rgba(0,0,0,0.03); }
html.dark .dd-trigger:hover { background: rgba(255,255,255,0.05); }
.dd-trigger:disabled { cursor: not-allowed; opacity: 0.55; }
.dd-trigger-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dd-chevron { flex-shrink: 0; display: flex; color: var(--text-muted); transition: transform 0.18s ease; }
.dd-wrap.dd-open .dd-chevron { transform: rotate(180deg); }

.dd-listbox {
  position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 400;
  list-style: none; margin: 0; padding: 6px;
  max-height: 300px; overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border, rgba(0,0,0,0.12));
  border-radius: 6px !important;
  box-shadow: 0 10px 32px rgba(0,0,0,0.16);
  opacity: 0; visibility: hidden; transform: translateY(-4px) scale(0.98);
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0s linear 0.18s;
}
html.dark .dd-listbox { box-shadow: 0 10px 32px rgba(0,0,0,0.5); }
.dd-wrap.dd-open .dd-listbox {
  opacity: 1; visibility: visible; transform: translateY(0) scale(1);
  transition: opacity 0.18s ease, transform 0.18s ease;
}
.dd-wrap.dd-drop-up .dd-listbox { top: auto; bottom: calc(100% + 6px); transform: translateY(4px) scale(0.98); }
.dd-wrap.dd-drop-up.dd-open .dd-listbox { transform: translateY(0) scale(1); }
.dd-listbox:focus { outline: none; }

.dd-option {
  padding: 10px 14px;
  font-family: var(--font-family), sans-serif;
  font-size: 15px; font-weight: 500;
  color: var(--text);
  border-radius: 5px !important;
  cursor: pointer;
  transition: background 0.12s ease;
}
.dd-option:hover, .dd-option.dd-highlighted { background: rgba(0,0,0,0.045); }
html.dark .dd-option:hover, html.dark .dd-option.dd-highlighted { background: rgba(255,255,255,0.07); }
.dd-option[aria-selected="true"] { background: #F5F0E8; color: #1a1a1a; font-weight: 600; }
.dd-option[aria-selected="true"].dd-highlighted, .dd-option[aria-selected="true"]:hover { background: #EFE7D6; }
html.dark .dd-option[aria-selected="true"] { background: rgba(245,240,232,0.18); color: #F5F0E8; }
html.dark .dd-option[aria-selected="true"].dd-highlighted, html.dark .dd-option[aria-selected="true"]:hover { background: rgba(245,240,232,0.27); }

/* ── HEADER AVATAR COMPONENT ──
   Single source of truth for the top-right profile avatar + dropdown,
   shared by every page with a header (previously duplicated verbatim in
   each page's own <style> block, which is how a header-only avatar fix
   could be applied to some pages and not others).
   The ring is drawn with box-shadow, not a real `border`: a real border
   shrinks the img's content box, and the overflow:hidden circular clip
   then leaves a hairline of the button's own near-black background
   exposed at the antialiased clip edge — visible as a second, fainter
   ring between the bold border and the photo. Rather than continue
   chasing that as a rendering artifact, it's now an intentional second
   ring (2026-07-20 (k)/(l)).
   (l) supersedes an intermediate (k) version that drew the gap as a
   SECOND stacked box-shadow layer (0 0 0 3px <bg>, 0 0 0 5px <ring>) —
   reported as visibly off-center in light mode, aligned fine in dark.
   Two independently-rounded circular box-shadow expansions from the same
   box can disagree by a sub-device-pixel amount at non-integer display
   scaling (e.g. Windows 125%/150%); that disagreement is far more
   perceptible at high contrast (black ring on near-white gap, light mode)
   than low contrast (cream ring on dark gray gap, dark mode) — same
   underlying pixel math, different perceptibility. Fixed by using only
   ONE box-shadow layer total: the photo/initials are sized smaller
   (40px) than the button's own clipped circle (46px), and the button's
   own `background` IS the gap color (a real paint, not a shadow
   approximation) — provably concentric since there's only one expansion
   operation (the outer ring) instead of two. */
.avatar-wrap { position: relative; display: flex; align-items: center; gap: 14px; }
.avatar-name-label { font-size: 16px; font-weight: 700; color: #1a1a1a; white-space: nowrap; }
.avatar-btn {
  width: 46px; height: 46px; border-radius: 50% !important; overflow: hidden; position: relative;
  background: var(--bg); border: none; cursor: pointer;
  box-shadow: 0 0 0 2px #000;
  display: flex; align-items: center; justify-content: center;
  /* This is a real <button>, not a <div> — strip native button chrome
     (Safari in particular can retain a faint default bevel/shadow on
     buttons even with border/background explicitly set, unless appearance
     is reset) so the box-shadow ring above is the only border source. */
  -webkit-appearance: none; appearance: none;
}
/* border-radius here MUST be !important: every page that includes this
   component also has its own `* { border-radius: 0 !important }` reset in
   its inline <style> block, which otherwise wins over this rule (equal
   !important beats higher specificity) and squares the photo off, clipped
   only by .avatar-btn's own circular overflow at the corners. transform:
   scale(1.18) enlarges the (already-circular) photo past the button's own
   46px clipped circle — 40px × 1.18 ≈ 47px, just over 46px — so it fully
   covers the ring with no background gap showing at the edges. */
.avatar-btn img { width: 40px; height: 40px; border-radius: 50% !important; object-fit: cover; transform: scale(1.18); }
#avatarInitials {
  width: 46px; height: 46px; border-radius: 50% !important; background: var(--accent); color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: 18px; font-weight: 700; font-family: var(--font-family), sans-serif;
  transition: background 0.12s;
}
.avatar-btn:hover #avatarInitials { background: var(--accent-hover); }
.avatar-dropdown {
  position: absolute; top: calc(100% + 6px); right: 0; width: 220px; padding: 14px 16px;
  background: #fff; border: 0.5px solid rgba(0,0,0,0.12);
  box-shadow: 0 8px 32px rgba(0,0,0,0.12); z-index: 500; display: none;
}
.avatar-dropdown.open { display: block; }
.avatar-dd-name { font-size: 13px; font-weight: 600; color: #1a1a1a; margin-bottom: 2px; }
.avatar-dd-brokerage { font-size: 13px; font-weight: 500; color: #444; margin-bottom: 4px; }
.avatar-dd-email { font-size: 13px; font-weight: 500; color: #444; margin-bottom: 12px; }
.avatar-dd-link {
  display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--accent); font-weight: 500;
  padding: 3px 0; cursor: pointer; background: none; border: none;
  text-align: left; width: 100%; font-family: inherit; text-decoration: none;
}
.avatar-dd-link:hover { color: var(--accent-hover); }

html.dark .avatar-name-label { color: #f0f0f0; }
html.dark .avatar-btn { box-shadow: 0 0 0 2px #F5F0E8; }
html.dark .avatar-dropdown { background: #2c2c2e; border-color: #444444; box-shadow: 0 8px 32px rgba(0,0,0,0.45); }
html.dark .avatar-dd-name, html.dark .avatar-dd-brokerage, html.dark .avatar-dd-email, html.dark .avatar-dd-link { color: #f0f0f0; }

/* ── THEME TOGGLE (bottom-left, every page) ──
   Persistent light/dark switch reachable from anywhere in the app, not just
   Settings. Reads/writes the same `themeMode` localStorage key through the
   shared applyTheme()/toggleThemeFab() in /js/brand.js — one theme state,
   not a second one. Icons are inline SVG (not the mask-based .icon-* classes
   or <img src="/icons/...">) since not every page defines those, and this
   component needs to work identically on all of them. */
.theme-fab {
  position: fixed; left: 16px; bottom: 16px; z-index: 60;
  display: flex; align-items: center; gap: 7px;
  padding: 9px 14px; border: 0.5px solid rgba(0,0,0,0.15);
  background: #fff; color: rgba(0,0,0,0.7);
  font-family: var(--font-family), sans-serif; font-size: 12px; font-weight: 600;
  cursor: pointer; box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.theme-fab:hover { background: rgba(0,0,0,0.04); }
.theme-fab-icon { flex-shrink: 0; display: block; }
.theme-fab-icon-dark, .theme-fab-label-dark { display: none; }
html.dark .theme-fab-icon-light, html.dark .theme-fab-label-light { display: none; }
html.dark .theme-fab-icon-dark { display: block; }
html.dark .theme-fab-label-dark { display: inline; }
html.dark .theme-fab { background: #575759; border-color: #444; color: #fff; }
html.dark .theme-fab:hover { background: rgba(255,255,255,0.08); }

/* ── SIDEBAR (single shared component, 2026-07-24) ──
   Previously duplicated verbatim in every page's own <style> block (like
   the header avatar component used to be, per the comment above) — that's
   how the icon-only redesign landed on index.html but not analytics.html,
   which still had its own independent copy of the pre-redesign CSS. Now
   base.css is the one source of truth; page-specific <style> blocks
   should NOT redeclare .sidebar/.nav-btn/etc. The nav-btn-group MARKUP
   itself is still duplicated per page (no templating in this app), so a
   new page still needs to copy the HTML structure — but the appearance
   and behavior now come from here automatically.

   Two states, toggled by clicking the logo icon (toggleSidebarExpanded()
   in brand.js) and persisted via localStorage's `sidebarExpanded` key
   using the same read-before-paint pattern as `themeMode` (each page's
   own inline <head> script applies html.sidebar-expanded before first
   paint to avoid a flash of the wrong state):
   - Collapsed (default, no html class): 68px icon-only rail.
   - Expanded (html.sidebar-expanded): 250px, nav labels visible, and the
     "realtyhub" wordmark next to the icon (icon-only in collapsed).

   Animation approach: only .sidebar's width, .logo-wordmark's, and
   .nav-btn-label's max-width/opacity actually transition. Every other
   property (icon size/position, nav-btn padding, alignment) is held
   IDENTICAL between states so nothing needs to jump. Previously nav-btn
   switched justify-content/width/padding and nav-btn-label switched
   position:absolute<->static + font-size + background on the same class
   boundary — position and several other properties there aren't
   animatable, so they snapped instantly while width was still easing,
   which read as jerky/uncoordinated. Fixing that meant giving up the
   collapsed-state hover tooltip (position:absolute chip) in favor of a
   native `title` attribute on each nav-btn for the same hover hint. */
.sidebar {
  width: 68px; border-right: 0.5px solid var(--border);
  padding: 16px 12px; display: flex; flex-direction: column; align-items: stretch;
  overflow: visible; flex-shrink: 0; background: var(--sidebar-bg);
  transition: width 0.2s ease-out;
}
html.sidebar-expanded .sidebar { width: 250px; }

/* padding-left centers the 30px icon on the same vertical column as the
   22px nav icons below (nav-btn's 11px padding centers its icon at
   content-start+22px; 7px here centers this bigger icon on that same
   point: content-start+7+15 = content-start+22). Constant in both
   sidebar states so the icon never shifts. gap is likewise constant
   (harmless when collapsed since the wordmark has zero width there). */
.sidebar-logo-wrap {
  display: flex; align-items: center; justify-content: flex-start;
  width: 100%; padding: 0 0 16px 7px; margin-bottom: 12px; box-sizing: border-box;
  border: none; border-bottom: 1px solid rgba(0,0,0,0.07);
  background: none; cursor: pointer; gap: 6px;
  text-decoration: none; transition: opacity 0.15s ease;
}
.sidebar-logo-wrap:hover { opacity: 0.8; }
.brand-logo-mark { width: 30px; height: 30px; object-fit: contain; display: block; flex-shrink: 0; }

/* Wordmark next to the icon, expanded state only. Every value here is
   derived from logo-full.png's actual proportions, not eyeballed --
   see commits e50a0b4 and b84278a for the full derivation:
   - font-size 36px: reproduces logo-full.png's icon-height:cap-height
     ratio (~1.03:1) against the icon's fixed 27.4px ink-height.
   - letter-spacing -0.06em: reproduces logo-full.png's width:cap-height
     tracking ratio (~5.57).
   - gap 6px (above): reproduces logo-full.png's icon-to-text gap ratio
     (~0.24x icon ink width).
   Measured rendered text width is ~150.6px (Range API against the live
   node, not a canvas estimate), comfortably under the 180px max-width --
   so the settled state was never actually clipped. padding-right adds a
   buffer against the one thing that specifically risks clipping the
   LAST character: negative letter-spacing can push a final glyph's ink
   slightly past the text's calculated advance width, and overflow:hidden
   clips exactly at that calculated edge. max-width bumped to match so
   the reveal animation still ends at the same visible text position. */
.logo-wordmark {
  font-family: var(--font-family), sans-serif; font-weight: 700; font-size: 36px;
  letter-spacing: -0.06em; color: var(--text); white-space: nowrap;
  padding-right: 6px; max-width: 0; opacity: 0; overflow: hidden; box-sizing: content-box;
  transition: max-width 0.2s ease-out, opacity 0.15s ease-out;
}
html.sidebar-expanded .logo-wordmark { max-width: 186px; opacity: 1; }

/* ── LIGHT/DARK LOGO SWAP ── */
.logo-dark { display: none; }
.logo-light { display: block; }
html.dark .logo-light { display: none; }
html.dark .logo-dark { display: block; }

.nav-btn-group { display: flex; flex-direction: column; align-items: stretch; gap: 4px; }

.nav-btn {
  display: flex; align-items: center; justify-content: flex-start;
  position: relative;
  width: 100%; height: 44px; padding: 0 11px; gap: 12px; box-sizing: border-box;
  cursor: pointer; color: var(--text-muted);
  font-weight: 600; letter-spacing: -0.025em;
  transition: color 0.12s ease;
  border: none; background: none; text-decoration: none;
  font-family: var(--font-family), sans-serif;
}
.nav-btn:hover { color: var(--text-secondary); }
.nav-btn.active { color: var(--accent); }

/* Always in normal flow (never position:absolute) so max-width/opacity
   are the only things animating — a plain reveal/collapse, no repositioning. */
.nav-btn-label {
  font-size: 16px; text-transform: capitalize; white-space: nowrap;
  max-width: 0; opacity: 0; overflow: hidden;
  transition: max-width 0.2s ease-out, opacity 0.15s ease-out;
}
html.sidebar-expanded .nav-btn-label { max-width: 140px; opacity: 1; }

html.dark .sidebar { background: #434345; border-right-color: #474747; }
html.dark .nav-btn { color: rgba(255,255,255,0.55); }
html.dark .nav-btn:hover { color: rgba(255,255,255,0.85); }
html.dark .nav-btn.active { color: #ffffff; }
/* White-on-dark reads visually heavier than black-on-light at the same
   font-weight (halation/anti-aliasing bleed) -- computed weight is 700
   in both themes, so this is a perceptual correction for the logo text
   specifically, not a fix to an actual weight mismatch. Doesn't touch
   .nav-btn-label (e.g. bold "Dashboard" on the active item is fine). */
html.dark .logo-wordmark { font-weight: 600; }

/* ── SIDEBAR NAV VISIBILITY TOGGLE (reversible hide) ──
   Single on/off switch for which sidebar nav-btn entries are visible.
   This ONLY hides the nav link — it does NOT delete the underlying page,
   route, or backend data, and direct URL navigation to a hidden page still
   works. .nav-btn-group markup is duplicated (not templated) across every
   page that includes base.css, so matching by href here is the one place
   that reaches all of them at once instead of editing each page.

   TO HIDE a nav item: add a `.nav-btn[href="/its-href"]` selector line below.
   TO SHOW a hidden item again: delete (or comment out) just its line —
   nothing else needs to change, anywhere.

   Hidden 2026-07-24 (temporary, per request) while focus is on
   Dashboard / Calendar / Tracking: Message Center, Task List, Home Search,
   Integrations. */
.nav-btn-group .nav-btn[href="/message-center"],
.nav-btn-group .nav-btn[href="/tasks"],
.nav-btn-group .nav-btn[href="/home-search"],
.nav-btn-group .nav-btn[href="/integrations"] {
  display: none !important;
}

/* ── GLOBAL TEXT-ONLY NAV/SELECTION BUTTONS ("Concept A", 2026-07-24) ──
   App-wide, reusable replacement for boxed/bordered navigation &
   selection controls — any future button using these classes matches
   automatically, no per-instance styling needed. Scoped to NAVIGATION/
   SELECTION only: prev/next arrows, single-action text buttons (e.g.
   "Today", "Refresh"), and tab/view-selector groups (e.g. Day/Week/
   Month). Deliberately NOT applied to primary CTAs (Save/Create/+Add
   Event), destructive actions (Delete), or dropdown triggers — those
   keep their boxed affordance since they need to read as distinct,
   weighted controls rather than plain nav text.
   Literal rgba() values (not each page's own --text/--ink-rgb tokens)
   for the muted/hover states, matching .theme-fab's approach above —
   this way the classes render identically on every page regardless of
   what token names (if any) that page defines. The active-tab accent
   color is the one exception: var(--accent) + an explicit html.dark
   override to white, mirroring .nav-btn.active's existing precedent
   (--accent itself never flips for dark mode; every consumer of it for
   text/underline color pins its own dark override, same as here). */
.btn-icon-plain {
  border: none; background: none; padding: 4px; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  color: rgba(0,0,0,0.55); transition: color 0.12s ease;
}
.btn-icon-plain:hover { color: rgba(0,0,0,0.85); }
html.dark .btn-icon-plain { color: rgba(255,255,255,0.55); }
html.dark .btn-icon-plain:hover { color: rgba(255,255,255,0.85); }

.btn-text-plain {
  border: none; background: none; padding: 4px 6px; cursor: pointer;
  font-family: var(--font-family), sans-serif; font-weight: 600;
  letter-spacing: -0.025em; color: rgba(0,0,0,0.6);
  transition: color 0.12s ease;
}
.btn-text-plain:hover { color: rgba(0,0,0,0.9); }
html.dark .btn-text-plain { color: rgba(255,255,255,0.6); }
html.dark .btn-text-plain:hover { color: rgba(255,255,255,0.9); }

.tab-selector { display: flex; align-items: center; gap: 18px; }
.tab-btn {
  border: none; background: none; padding: 4px 0; cursor: pointer;
  font-family: var(--font-family), sans-serif; font-weight: 600;
  letter-spacing: -0.025em; color: rgba(0,0,0,0.45);
  transition: color 0.12s ease;
}
.tab-btn:hover:not(.active) { color: rgba(0,0,0,0.75); }
.tab-btn.active {
  color: var(--accent);
  text-decoration: underline; text-decoration-color: var(--accent);
  text-decoration-thickness: 2px; text-underline-offset: 6px;
}
html.dark .tab-btn { color: rgba(255,255,255,0.45); }
html.dark .tab-btn:hover:not(.active) { color: rgba(255,255,255,0.75); }
html.dark .tab-btn.active { color: #fff; text-decoration-color: #fff; }
