:root {
  --brand-primary: #534AB7;
  --brand-primary-rgb: 83, 74, 183;
  --brand-success: #1D9E75;
  --brand-success-rgb: 29, 158, 117;
  --brand-alert: #D85A30;
  --brand-alert-rgb: 216, 90, 48;
  /* De facto 4th/5th/6th brand colors — same values already hand-typed across the task/
     ticket status badges, priority badges, calendar chips, role badges, and presence dots;
     tokenized here so those all share one source of truth instead of copy-pasted literals. */
  --brand-warning: #D8862D;
  --brand-warning-rgb: 216, 134, 45;
  --brand-info: #3C8CD8;
  --brand-info-rgb: 60, 140, 216;
  --brand-purple: #9333ea;
  --brand-purple-rgb: 168, 85, 247;
  --brand-primary-hover: #443c99;
  --radius-lg: 18px;
  --radius-md: 10px;

  /* Call/meeting UI — deliberately theme-independent (always dark, regardless of the app's
     light/dark toggle), so these don't move to the dark-theme override block below. Navy
     rather than the previous neutral near-black, so it reads as the same family as the
     always-navy sidebar (--sidebar-bg) instead of a disconnected dark tone — --call-bg is
     literally --sidebar-bg's own value, --call-surface one step lighter matching dark
     theme's own --surface-card, same idea as the html[data-bs-theme="dark"] block below
     unifying around this exact navy family. */
  --call-bg: #101B3D;
  --call-surface: #1B2A57;
  --call-border: rgba(255,255,255,0.08);
  /* The empty video canvas (a tile/PIP before a feed arrives, an audio-only participant, the
     camera off). This was a light neutral (#eef0f3), which is what showed up as a bright white
     patch in the middle of the always-dark call window whenever a <video> had no frames — and
     there are many such moments (ringing, connecting, audio-only join, remote camera off).
     Now a step deeper than --call-surface so an empty canvas reads as part of the call UI. */
  --call-video-bg: #0b1531;

  /* Auth pages' decorative brand panel (login/register/onboarding/etc. — see .auth-brand-panel)
     — pulled from the actual logo artwork (assets/img/favicon.jpg): deep navy background,
     electric-cyan glow, orange glow accent. Deliberately its own small palette rather than
     reusing --brand-primary (purple) here: the logo itself has no purple in it, and this panel
     is meant to read as "the logo's own colors," not the app's general button/link color. */
  --auth-navy-deep: #0A1440;
  --auth-navy: #142B63;
  --auth-cyan: #22D3EE;
  --auth-orange: #FF7A29;

  /* Sidebar — same "always dark" reasoning as the call UI above, but brand-colored (a navy
     derived from the SquareCut logo) rather than neutral black, so the rail reads as this
     app's own identity rather than a generic dark panel — the persistent-colored-rail pattern
     itself is common across team-chat apps, but the palette is SquareCut's, not copied from
     any one of them. */
  --sidebar-bg: #101B3D;
  --sidebar-text: #E9EDF7;
  --sidebar-text-muted: #8D97BE;
  --sidebar-border: rgba(255,255,255,0.08);
  --sidebar-hover-bg: rgba(255,255,255,0.07);
  --sidebar-active-bg: rgba(76,141,255,0.2);
  --sidebar-accent: #4C8DFF;

  /* Light theme (default) */
  --surface-page: #F7F7F8;
  --surface-card: #ffffff;
  --surface-sunken: #F3F3F6;
  --border-color: #E7E7EA;
  --text-body: #22222a;
  --text-muted: #6a6a75;
  --text-faint: #b3b3bd;
  --shadow-card: 0 1px 2px rgba(20,20,30,0.04);
  --shadow-pop: 0 10px 30px rgba(20,20,30,0.12);

  --bs-primary: var(--brand-primary);
  --bs-primary-rgb: var(--brand-primary-rgb);
  --bs-success: var(--brand-success);
  --bs-success-rgb: var(--brand-success-rgb);
  --bs-link-color: var(--brand-primary);
  --bs-link-hover-color: var(--brand-primary);

  /* Wires Bootstrap's own theming variables to these same tokens, so native Bootstrap
     components we haven't hand-styled (tables, dropdowns, modals, list-groups, .text-muted)
     pick up the right colors in both themes automatically, instead of Bootstrap's own
     hardcoded dark-mode grays clashing with the navy palette — this only needs setting once
     here since --surface-*/--text-*/--border-color already resolve differently per theme. */
  --bs-body-bg: var(--surface-page);
  --bs-body-color: var(--text-body);
  --bs-border-color: var(--border-color);
  --bs-border-color-translucent: var(--border-color);
  --bs-secondary-color: var(--text-muted);
  --bs-secondary-bg: var(--surface-sunken);
  --bs-tertiary-bg: var(--surface-card);
  --bs-tertiary-color: var(--text-muted);
  --bs-emphasis-color: var(--text-body);
  --bs-heading-color: var(--text-body);
}

/* Navy-tinted rather than the previous neutral gray, so dark mode's content area reads as
   one cohesive palette with the always-navy sidebar/topbar (--sidebar-*) instead of two
   different dark tones sitting next to each other. Light theme is untouched — its white-on-
   navy contrast against the chrome was already working. */
html[data-bs-theme="dark"] {
  --surface-page: #0A1330;
  --surface-card: #1B2A57;
  --surface-sunken: #101B3D;
  --border-color: #3E4C82;
  --text-body: #F2F3FA;
  --text-muted: #AEB7DA;
  --text-faint: #7681AC;
  --shadow-card: 0 1px 2px rgba(0,0,0,0.3);
  --shadow-pop: 0 10px 30px rgba(0,0,0,0.5);
}

* { transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease; }

/* Base size for the whole site — every rem-based size in this file (and Bootstrap's own
   type scale) shrinks proportionally from this, so relative sizing stays intact instead
   of needing every rule touched individually. */
html { font-size: 14px; }

body {
  background: var(--surface-page);
  font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-weight: 400;
  color: var(--text-body);
  font-size: 1rem;
}

/* Bootstrap component overrides so the brand palette + theme tokens apply everywhere */
.btn-primary {
  background-color: var(--brand-primary);
  border-color: var(--brand-primary);
}
.btn-primary:hover, .btn-primary:focus, .btn-primary:active {
  background-color: var(--brand-primary-hover) !important;
  border-color: var(--brand-primary-hover) !important;
}
.btn-outline-primary { color: var(--brand-primary); border-color: var(--brand-primary); }
.btn-outline-primary:hover { background-color: var(--brand-primary); border-color: var(--brand-primary); }
.text-primary { color: var(--brand-primary) !important; }
.bg-primary { background-color: var(--brand-primary) !important; }
.text-bg-primary { background-color: var(--brand-primary) !important; }
.btn-success, .text-bg-success { background-color: var(--brand-success) !important; border-color: var(--brand-success) !important; }
.text-muted { color: var(--text-muted) !important; }
a { color: var(--brand-primary); text-decoration: none; }

/* Formalizes an identical inline-utility pattern that already existed independently on
   task_detail.php and support_detail.php (arrow icon + muted small text, "back to parent"
   affordance) — same computed look, now a shared, reusable class instead of copy-pasted
   Bootstrap utilities, and available for meeting_detail.php, which had no back-link at all. */
.back-link {
  display: inline-flex; align-items: center; gap: 4px;
  color: var(--text-muted); font-size: 0.875em; margin-bottom: 4px;
  text-decoration: none;
}
.back-link:hover { color: var(--brand-primary); }

/* Reusable page-title row (title + primary action) — first applied to pages/meetings.php,
   whose previous header was ad hoc Bootstrap utilities with no shared class. Deliberately a
   plain title/action row with a hairline divider, not a full "hero" treatment, so it
   composes cleanly wherever it's adopted on other pages next, without redesigning the page
   around it. */
.page-header {
  display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px;
  padding-bottom: 16px; margin-bottom: 20px; border-bottom: 0.5px solid var(--border-color);
}
.page-header h1, .page-header h2, .page-header h3, .page-header h4, .page-header h5, .page-header h6 {
  margin: 0; font-weight: 600;
}

.nav-tabs { border-color: var(--border-color); }
.nav-tabs .nav-link { color: var(--text-muted); border: none; border-bottom: 2px solid transparent; }
.nav-tabs .nav-link.active { color: var(--brand-primary); background: none; border-color: var(--brand-primary); font-weight: 600; }
.dropdown-menu { background: var(--surface-card); border-color: var(--border-color); box-shadow: var(--shadow-pop); font-size: 0.86rem; }
.dropdown-item { color: var(--text-body); padding: 0.4rem 1rem; font-weight: 400; }
.dropdown-item:hover, .dropdown-item:focus { background: rgba(var(--brand-primary-rgb), 0.08); color: var(--brand-primary); }
.dropdown-header { font-size: 0.72rem; font-weight: 400; text-transform: uppercase; letter-spacing: 0.03em; color: var(--text-muted); }
.dropdown-item-text { font-size: 0.86rem; font-weight: 400; }
.dropdown-menu .small { font-size: 1em; }
/* Bootstrap's .table sets its own --bs-table-bg/--bs-table-border-color per cell, which in
   dark mode render as a hardcoded gray independent of whatever container (usually one of our
   navy .card's) the table sits inside — the "Compare plans" table looking like a leftover
   different theme is exactly this. Forcing cell backgrounds transparent lets the card's own
   background show through consistently instead. */
.table { color: var(--text-body); --bs-table-bg: transparent; --bs-table-color: var(--text-body); --bs-table-border-color: var(--border-color); }
.table > :not(caption) > * > * { background-color: transparent; border-bottom-color: var(--border-color); }
.form-control, .form-select {
  border-radius: var(--radius-md);
  /* background-color, not the background shorthand: the shorthand resets background-image
     too, which silently wiped out Bootstrap's own dropdown-chevron SVG on every <select
     class="form-select"> in the app (Bootstrap's own .form-select rule sets it via
     background-image, and this rule loads after Bootstrap's so it was winning). */
  background-color: var(--surface-card);
  border-color: var(--border-color);
  color: var(--text-body);
}
.form-control:focus, .form-select:focus { border-color: var(--brand-primary); box-shadow: 0 0 0 3px rgba(var(--brand-primary-rgb), 0.15); }
.btn { border-radius: var(--radius-md); }

/* Show/hide toggle on every password field (see assets/js/password_toggle.js) — plain
   position:relative wrapper + an absolutely-positioned icon button, so it drops onto any
   existing <input type="password"> without touching the input's own markup/validation. */
.password-field { position: relative; }
.password-field input { padding-right: 40px; }
.password-toggle-btn {
  position: absolute; top: 50%; right: 4px; transform: translateY(-50%);
  width: 32px; height: 32px; border: none; background: none; color: var(--text-muted);
  display: inline-flex; align-items: center; justify-content: center; border-radius: var(--radius-md);
}
.password-toggle-btn:hover { color: var(--text-body); background: var(--surface-sunken); }

.chat-icon-btn:focus-visible, .call-btn:focus-visible, .message-actions button:focus-visible,
.message-more-btn:focus-visible, .reaction-chip:focus-visible, .user-trigger:focus-visible,
.user-hover:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--surface-card), 0 0 0 4px var(--brand-primary);
}
/* Fallback for every other focusable element (message content links, attachment cards, plain
   buttons/anchors not covered by the specific list above) — without this they fall back to
   the browser's own default focus outline, which stays a stark light color regardless of
   theme and looks like a stray white box against the dark navy palette. */
a:focus-visible, button:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 1px;
}
.sidebar-nav .bi-plus-lg {
	font-size: 15px;
	line-height: normal;
}
.card {
  background: var(--surface-card);
  border: 0.5px solid var(--border-color);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
}

/* Theme toggle */
.theme-toggle-btn { border: none; background: var(--surface-sunken); color: var(--text-muted); }
.theme-toggle-btn:hover { color: var(--brand-primary); }
html[data-bs-theme="dark"] .theme-toggle-btn .bi-sun { display: inline-block; }
html[data-bs-theme="dark"] .theme-toggle-btn .bi-moon-stars { display: none; }
html:not([data-bs-theme="dark"]) .theme-toggle-btn .bi-sun { display: none; }
html:not([data-bs-theme="dark"]) .theme-toggle-btn .bi-moon-stars { display: inline-block; }

/* Shared look for the topbar's other icon-only buttons (notification bell, mobile sidebar
   toggle) — matches .theme-toggle-btn's theme-aware background instead of Bootstrap's
   .btn-light, which stays stark white/light regardless of theme and reads as mismatched
   against the other two. */
/* A uniform round chip for every icon-only topbar button (bell, invite, sidebar toggle, theme
   toggle) — fixed footprint so they all read as the same family of control regardless of which
   icon/badge is inside, with a small hover lift instead of just a color change. */
.topbar-icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; padding: 0; border: none; border-radius: 999px;
  background: var(--sidebar-hover-bg); color: var(--sidebar-text-muted);
  transition: background-color 0.15s ease, color 0.15s ease, transform 0.15s ease;
}
.topbar-icon-btn:hover { color: #fff; background: var(--sidebar-active-bg); transform: translateY(-1px); }
/* Same treatment as .topbar-icon-btn (same tokens, same "always dark chrome" reasoning) but
   wider — icon-only on mobile, full placeholder text alongside it from md breakpoint up, so
   it reads as a real search bar rather than just another icon button. Fully pill-shaped (not
   --radius-md) to match the rounded chips beside it. */
.topbar-search-trigger {
  display: flex; align-items: center; gap: 8px; border: none; border-radius: 999px;
  background: var(--sidebar-hover-bg); color: var(--sidebar-text-muted); padding: 9px 16px;
  font-size: 0.85rem; cursor: pointer; white-space: nowrap; transition: background-color 0.15s ease, color 0.15s ease;
}
.topbar-search-trigger:hover { color: #fff; background: var(--sidebar-active-bg); }

/* §Trust & Safety — topbar toggle, same pill-chip shape as .topbar-call-indicator. Red/alert
   tone even when off (not just when active) so a platform admin always recognizes it as a
   sensitive control, not a routine preference toggle like the theme switch beside it. */
.master-access-toggle-btn {
  display: flex; align-items: center; gap: 6px; border: none; border-radius: var(--radius-md);
  background: rgba(var(--brand-alert-rgb), 0.12); color: var(--brand-alert); padding: 8px 12px;
  font-size: 0.8rem; font-weight: 600; cursor: pointer; white-space: nowrap;
}
.master-access-toggle-btn:hover { background: rgba(var(--brand-alert-rgb), 0.22); }
.master-access-toggle-btn.active { background: var(--brand-alert); color: #fff; }

/* Full-width strip under the topbar — brand-alert (red), deliberately a different hue from the
   existing #impersonationBanner's amber (header.php, var(--brand-warning)) so the two can never
   be mistaken for the same banner when both show at once (Master Access is the broader
   browsing-mode flag; impersonation is one sub-action taken while it's on). */
.master-access-banner {
  display: flex; align-items: center; gap: 12px; padding: 8px 16px;
  background: var(--brand-alert); color: #fff; font-size: 0.85rem;
}
.master-access-banner-label { font-weight: 700; letter-spacing: 0.03em; display: flex; align-items: center; gap: 6px; }
.master-access-banner-btn { background: rgba(255, 255, 255, 0.18); color: #fff; border: none; }
.master-access-banner-btn:hover { background: rgba(255, 255, 255, 0.3); color: #fff; }

/* §Call Page Shell — always-reachable "end call" fallback in the topbar, see header.php. */
.topbar-call-indicator {
  display: flex; align-items: center; gap: 7px; border: none; border-radius: var(--radius-md);
  background: rgba(var(--brand-alert-rgb), 0.15); color: var(--brand-alert); padding: 8px 12px;
  font-size: 0.8rem; font-weight: 600; cursor: pointer; white-space: nowrap;
}
.topbar-call-indicator:hover { background: var(--brand-alert); color: #fff; }
.topbar-call-group { display: flex; align-items: center; gap: 4px; }
.topbar-call-group.d-none { display: none !important; }
.topbar-call-end {
  display: flex; align-items: center; justify-content: center; border: none; border-radius: var(--radius-md);
  background: var(--brand-alert); color: #fff; padding: 8px 10px; font-size: 0.85rem; cursor: pointer;
}
.topbar-call-end:hover { filter: brightness(1.1); color: #fff; }
.topbar-call-dot {
  width: 8px; height: 8px; border-radius: 50%; background: var(--brand-alert); flex-shrink: 0;
  animation: topbar-call-dot-pulse 1.4s ease-in-out infinite;
}
@keyframes topbar-call-dot-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }

.global-search-section-title { font-size: 0.72rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; color: var(--text-muted); margin: 12px 4px 4px; }
.global-search-section:first-child .global-search-section-title { margin-top: 0; }
.global-search-result {
  display: flex; align-items: center; gap: 10px; padding: 8px; border-radius: var(--radius-md);
  color: var(--text-body); text-decoration: none; font-size: 0.88rem;
}
.global-search-result:hover { background: rgba(var(--brand-primary-rgb), 0.07); color: var(--text-body); }
.global-search-result i { font-size: 1rem; color: var(--text-muted); flex-shrink: 0; }
.global-search-result-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* .theme-toggle-btn is shared with the auth pages (login/register/forgot-password/etc.),
   which have no dark chrome of their own — only override it while it sits inside the topbar,
   matching .topbar-icon-btn above rather than changing the shared base rule. */
.topbar .theme-toggle-btn {
  background: var(--sidebar-hover-bg); color: var(--sidebar-text-muted);
  width: 38px; height: 38px; padding: 0; border-radius: 999px;
  transition: background-color 0.15s ease, color 0.15s ease, transform 0.15s ease;
}
.topbar .theme-toggle-btn:hover { color: #fff; background: var(--sidebar-active-bg); transform: translateY(-1px); }

/* #myStatusTrigger keeps Bootstrap's .btn-light class (stark white/light regardless of
   theme) — override it here the same way .topbar-icon-btn overrides its own .btn-light-like
   default above, so it matches the rest of the now-always-dark topbar. Placed before the
   mobile override just below so that one — same specificity, later in source — still wins at
   narrow widths as originally intended. */
#myStatusTrigger { background: var(--sidebar-hover-bg); border-color: transparent; color: var(--sidebar-text); }
#myStatusTrigger:hover { background: var(--sidebar-hover-bg); }
#myStatusTrigger .text-muted { color: var(--sidebar-text-muted) !important; }

/* Below the breakpoint where the name/status text next to the avatar is hidden (d-sm-flex),
   the trigger button is just the round avatar — drop its own button background/padding so it
   doesn't draw a second, mismatched frame around the already-circular avatar. */
@media (max-width: 575.98px) {
  #myStatusTrigger { background: none; border: none; padding: 0; }
}

/* The topbar's mobile-only workspace logo reuses .workspace-icon verbatim, but its
   drop-shadow (tuned for sitting on a light sidebar background) reads as a muddy glow against
   the topbar's own background — flatten it here without touching the sidebar's own usage. */
.topbar-left .workspace-icon { box-shadow: none; }

/* App shell */
.app-shell { display: flex; min-height: 100vh; }
.sidebar {
  width: 260px;
  flex-direction: column;
  background: var(--sidebar-bg);
  color: var(--sidebar-text);
  border-right: 0.5px solid var(--sidebar-border);
  padding: 20px 14px;
  flex-shrink: 0;
}
/* Desktop sidebar only (the <aside>, not the mobile offcanvas variant which shares the
   .sidebar class but already gets its own scroll region from Bootstrap's offcanvas-body) —
   pins it to the viewport and lets its own nav list scroll independently, instead of the
   whole page (topbar included) scrolling together as one when the nav list overflows. */
aside.sidebar {
  height: 100vh;
  position: sticky;
  top: 0;
  overflow-y: auto;
}
.brand-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--brand-primary); display: inline-block; }
.workspace-header {
  display: flex; align-items: center; gap: 10px; padding: 6px; margin-bottom: 10px; font-weight: 600; font-size: 1rem;
}
.workspace-switcher-trigger {
  width: 100%; border: none; background: none; border-radius: var(--radius-md); color: var(--sidebar-text); text-align: left;
}
.workspace-switcher-trigger:hover { background: var(--sidebar-hover-bg); }
.workspace-switcher-chevron { color: var(--sidebar-text-muted); font-size: 0.8rem; margin-left: 4px; }
.workspace-switcher-menu { width: 260px; max-height: 70vh; overflow-y: auto; }
.workspace-switch-item { display: flex; align-items: center; gap: 8px; }
.workspace-switch-item.active { color: var(--brand-primary); }
/* Blue-to-orange, a small deliberate callback to the SquareCut logo's own gradient — kept to
   this one badge rather than spread across the whole rail, per "don't overuse an accent". */
.workspace-icon {
  width: 28px; height: 28px; border-radius: 9px; background: linear-gradient(135deg, #3B82F6, #F5A623); color: #fff;
  display: inline-flex; align-items: center; justify-content: center; font-size: 0.8rem; font-weight: 700; flex-shrink: 0;
  box-shadow: 0 2px 8px rgba(76,141,255,0.35); overflow: hidden;
}
.workspace-icon-photo { background: var(--sidebar-hover-bg); box-shadow: none; }
.workspace-icon-photo img { width: 100%; height: 100%; object-fit: cover; }
.workspace-icon-sm { width: 22px; height: 22px; font-size: 0.68rem; border-radius: 7px; }
.sidebar-nav { display: flex; flex-direction: column; gap: 3px; }
.sidebar-nav .nav-link {
  color: var(--sidebar-text); padding: 9px 10px; border-radius: var(--radius-md); font-size: 0.92rem;
  display: flex; align-items: center; gap: 9px; border-left: 3px solid transparent; margin-left: -3px;
}
.sidebar-nav .nav-link:hover { background: var(--sidebar-hover-bg); color: #fff; }
.sidebar-nav .nav-link.active { background: var(--sidebar-active-bg); color: #fff; font-weight: 600; border-left-color: var(--sidebar-accent); }
.nav-section-label {
  display: flex; justify-content: space-between; align-items: center;
  font-size: 0.72rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--sidebar-text-muted);
  margin: 20px 10px 6px;
}
.nav-section-label[role="button"] { cursor: pointer; border-radius: var(--radius-md); }
.nav-section-label[role="button"]:hover { color: var(--sidebar-text); }
.nav-section-label[role="button"]:focus-visible {
  outline: none; box-shadow: 0 0 0 2px var(--sidebar-bg), 0 0 0 4px var(--sidebar-accent);
}
/* Chevron rotates to point at the section's current state rather than swapping icons —
   subtle/fast per the app's existing transition convention (e.g. the call panels' 0.2s slide). */
.nav-section-chevron { font-size: 0.85rem; transition: transform 0.15s ease; margin-left: auto; }
.nav-section-label.collapsed .nav-section-chevron { transform: rotate(-90deg); }
.nav-section-label:has(.nav-add) .nav-section-chevron { margin-left: 4px; }
.nav-section-body { overflow: hidden; max-height: 800px; transition: max-height 0.2s ease; }
.nav-section-body.collapsed { max-height: 0; }
.nav-add { color: var(--sidebar-text-muted); }
.nav-add:hover { color: var(--sidebar-accent); }
.nav-empty { padding: 2px 10px; font-size: 0.82rem; color: var(--sidebar-text-muted); }
.project-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; display: inline-block; box-shadow: 0 0 0 3px rgba(255,255,255,0.08); }

.project-group-header { justify-content: flex-start; }
.project-folder-icon { font-size: 0.95rem; }
.channel-sublist { display: flex; flex-direction: column; gap: 1px; margin-left: 14px; }
.channel-sub-link {
  color: var(--sidebar-text-muted); padding: 5px 10px; border-radius: var(--radius-md); font-size: 0.85rem;
  display: block; text-decoration: none;
}
.channel-sub-link:hover { background: var(--sidebar-hover-bg); color: #fff; }
.channel-sub-link.active { background: var(--sidebar-active-bg); color: #fff; font-weight: 600; }
.sidebar .dm-dot-overlay { border-color: var(--sidebar-bg); }
.sidebar .avatar-group { background: rgba(255,255,255,0.18); color: var(--sidebar-text); }
.dm-dot-idle { background: var(--text-faint); }
.dm-avatar-wrap { position: relative; display: inline-flex; flex-shrink: 0; }
.avatar-sm { width: 22px; height: 22px; font-size: 0.68rem; }
.avatar-group { background: var(--text-faint); font-size: 0.7rem; }
.dm-dot-overlay {
  position: absolute; bottom: -1px; right: -1px; width: 8px; height: 8px; border-radius: 50%;
  border: 1.5px solid var(--surface-card); background: var(--text-faint);
}
button.nav-link { cursor: pointer; }

.main-area { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.topbar {
  display: flex; justify-content: space-between; align-items: center;
  padding: 14px 24px; border-bottom: 0.5px solid var(--sidebar-border); background: var(--sidebar-bg); color: var(--sidebar-text);
}
.topbar-left { display: flex; align-items: center; gap: 10px; min-width: 0; flex: 1; }
.topbar-title { font-weight: 600; }
.topbar-actions { display: flex; align-items: center; gap: 10px; }
/* §Topbar quick-nav (see includes/header.php for why only these 3 links exist) — icon-over-
   label links using the topbar's own --sidebar-* palette, not --brand-primary, so they read
   as part of this dark bar rather than a mismatched light-theme accent dropped onto it. */
.topbar-quicknav { display: flex; align-items: center; gap: 4px; margin-right: 12px; flex-shrink: 0; }
.topbar-quicknav-link {
  display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 6px 14px; border-radius: var(--radius-md);
  color: var(--sidebar-text-muted); text-decoration: none; font-size: 0.68rem; font-weight: 500; transition: background-color 0.15s ease, color 0.15s ease;
}
.topbar-quicknav-link i { font-size: 1.05rem; }
.topbar-quicknav-link:hover { background: var(--sidebar-hover-bg); color: #fff; }
.topbar-quicknav-link.active { color: var(--sidebar-accent); }

/* §My Status pill bar — an additive, always-visible way to set your status (the original
   #myStatusTrigger avatar dropdown in includes/header.php is untouched and still fully works;
   this is a second entry point to the same assets/js/availability.js setStatus() call, not a
   replacement). Horizontally scrollable rather than wrapping, so it can never push page content
   down or overflow sideways on a narrow phone — it just becomes a swipeable strip. */
.status-bar {
  display: flex; align-items: center; gap: 8px; padding: 8px 24px;
  background: var(--surface-card); border-bottom: 1px solid var(--border-color);
  overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: thin;
}
.status-bar.collapsed { display: none; }
.status-bar-label { font-size: 0.78rem; font-weight: 600; color: var(--text-muted); flex-shrink: 0; white-space: nowrap; }
.status-bar-pills { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.status-bar-pill {
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; flex-shrink: 0;
  border: 1px solid var(--border-color); background: var(--surface-sunken); color: var(--text-body);
  border-radius: 999px; padding: 5px 12px; font-size: 0.8rem; cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
.status-bar-pill:hover { transform: translateY(-1px); }
/* Category colors reuse the exact 4 groups includes/footer.php's #moreStatusesModal already
   sorts every status into (available/temporary/unavailable/meeting) — no new color meanings. */
.status-bar-pill.cat-available.active { background: rgba(var(--brand-success-rgb), 0.14); border-color: var(--brand-success); color: var(--brand-success); }
.status-bar-pill.cat-temporary.active { background: rgba(var(--brand-warning-rgb), 0.14); border-color: var(--brand-warning); color: var(--brand-warning); }
.status-bar-pill.cat-unavailable.active { background: rgba(var(--brand-alert-rgb), 0.14); border-color: var(--brand-alert); color: var(--brand-alert); }
.status-bar-pill.cat-meeting.active { background: rgba(var(--brand-info-rgb), 0.14); border-color: var(--brand-info); color: var(--brand-info); }
.status-bar-more { border: none; background: none; color: var(--brand-primary); font-size: 0.8rem; font-weight: 600; flex-shrink: 0; white-space: nowrap; padding: 5px 10px; }
.status-bar-toggle { margin-left: auto; border: none; background: none; color: var(--text-muted); flex-shrink: 0; padding: 4px 8px; }
.status-bar-toggle:hover { color: var(--text-body); }
/* A collapsed bar still needs a way back — this tab sits just under the topbar on the right,
   same spot the toggle itself was, so it's never a mystery where the control went. */
.status-bar-reopen {
  display: none; align-items: center; gap: 4px; margin: 6px 24px 0 auto; width: fit-content;
  border: 1px solid var(--border-color); background: var(--surface-card); color: var(--text-muted);
  border-radius: 999px; padding: 4px 12px; font-size: 0.76rem; cursor: pointer;
}
.status-bar.collapsed ~ .status-bar-reopen { display: flex; }

/* §Pill buttons — Meetings' "Create ▾" and the meeting-detail Join buttons. Same Bootstrap
   color classes (btn-primary/btn-outline-primary) they already had; only rounder. */
.btn-pill { border-radius: 999px !important; padding-left: 18px; padding-right: 18px; }

/* §"Today" — reuses .priority-badge/.status-badge (defined further down, "Tasks" section) and
   the app's existing card/surface tokens; no new color palette. Green accent (the reference's
   whole visual identity) comes from --brand-success, already the app's success/positive color. */
.today-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
.today-date-label { font-weight: 600; color: var(--text-body); }
.today-task-row {
  display: flex; align-items: flex-start; gap: 10px; padding: 12px 14px;
  background: var(--surface-card); border: 1px solid var(--border-color); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card); transition: box-shadow 0.15s ease;
}
.today-task-row:hover { box-shadow: var(--shadow-pop); }
.today-task-reorder { display: flex; flex-direction: column; flex-shrink: 0; margin-top: 2px; }
.today-task-move { border: none; background: none; color: var(--text-muted); padding: 0; line-height: 1; font-size: 0.7rem; }
.today-task-move:hover { color: var(--brand-primary); }
.today-task-checkbox {
  flex-shrink: 0; margin-top: 4px; width: 20px; height: 20px; cursor: pointer;
  accent-color: var(--brand-success);
}
.today-task-main { flex: 1; min-width: 0; }
.today-task-title { font-weight: 600; color: var(--text-body); word-break: break-word; }
.today-task-row.today-task-done .today-task-title { text-decoration: line-through; color: var(--text-muted); font-weight: 500; }
.today-task-row.today-task-skipped { opacity: 0.65; }
.today-task-row.today-task-skipped .today-task-title { color: var(--text-muted); font-weight: 500; }
.today-task-reorder-locked { width: 1rem; }
.today-task-notes { font-size: 0.82rem; color: var(--text-muted); margin-top: 2px; word-break: break-word; }
.today-task-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 6px; }
.today-task-time { font-size: 0.78rem; color: var(--text-muted); display: inline-flex; align-items: center; gap: 4px; }
.today-task-menu-btn { border: none; background: none; color: var(--text-muted); padding: 2px 6px; flex-shrink: 0; }
.today-task-menu-btn:hover { color: var(--text-body); }
.today-completed-toggle { border: none; background: none; color: var(--text-muted); font-size: 0.85rem; font-weight: 600; padding: 4px 0; }
.today-completed-toggle:hover { color: var(--text-body); }
@media (max-width: 575.98px) {
  .today-toolbar { flex-direction: column; align-items: flex-start; }
}

.content-area { padding: 24px; flex: 1; }
.app-footer {
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 8px;
  padding: 16px 24px; border-top: 0.5px solid var(--border-color);
  font-size: 0.8rem; color: var(--text-muted);
}
.app-footer a { color: var(--text-muted); text-decoration: none; }
.app-footer a:hover { color: var(--brand-primary); text-decoration: underline; }
.app-footer-sep { color: var(--border-color); }

.avatar-circle {
  width: 32px; height: 32px; border-radius: 50%; background: var(--brand-primary); color: #fff;
  display: inline-flex; align-items: center; justify-content: center; font-size: 0.85rem; font-weight: 600;
  box-shadow: 0 1px 3px rgba(0,0,0,0.15); overflow: hidden;
}
.avatar-photo { background: var(--surface-sunken); }
.avatar-photo img { width: 100%; height: 100%; object-fit: cover; }
.avatar-palette-0 { background: var(--brand-primary); }
.avatar-palette-1 { background: var(--brand-success); }
.avatar-palette-2 { background: var(--brand-warning); }
.avatar-palette-3 { background: var(--brand-info); }
.avatar-palette-4 { background: #C74E7A; }
.avatar-palette-5 { background: #4C9E8E; }

.notif-badge {
  position: absolute; top: 2px; right: 2px; background: var(--brand-alert); color: #fff; font-size: 0.65rem;
}
.notif-dropdown { width: 340px; max-height: 420px; overflow-y: auto; }
.notif-item { padding: 8px 10px; border-radius: var(--radius-md); font-size: 0.85rem; white-space: normal; }
.notif-item.unread { background: rgba(var(--brand-primary-rgb), 0.08); }
.notif-item .avatar-circle { width: 34px; height: 34px; font-size: 0.85rem; }
.notif-type-icon {
  width: 34px; height: 34px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
  color: #fff; font-size: 0.9rem;
}
.notif-sender { font-weight: 600; font-size: 0.85rem; color: var(--text-body); }
.notif-message { font-size: 0.82rem; color: var(--text-body); line-height: 1.35; margin-top: 1px; }
.notif-time { font-size: 0.7rem; color: var(--text-muted); margin-top: 3px; display: flex; align-items: center; gap: 4px; }
.notif-unread-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--brand-primary); margin-top: 6px; }

/* Auth */
/* --auth-bg-image is set inline per-page (style="--auth-bg-image:url('...')") via assetUrl()
   in PHP, not hardcoded here as a CSS url() — this app's own docs flag real fragility around
   its /sqcutchat/ subfolder deployment prefix, and only PHP's assetUrl() resolves that
   correctly; a root-relative path baked into the static CSS file would silently 404 on a
   subfolder deploy. The white wash under it keeps the card readable over a fairly busy
   illustration; var(--surface-page) is the fallback for any page that doesn't set the
   variable. Replaces an earlier icon-scatter placeholder now that a real, non-stock
   background image exists. */
.auth-body {
  position: relative; overflow: hidden; display: flex; align-items: stretch; justify-content: center; min-height: 100vh;
  background:
    linear-gradient(rgba(255, 255, 255, 0.72), rgba(255, 255, 255, 0.72)),
    var(--auth-bg-image, none) center/cover no-repeat,
    var(--surface-page);
}
.auth-shell { position: relative; z-index: 1; display: flex; width: 100%; max-width: 960px; margin: auto; border-radius: 24px; overflow: hidden; box-shadow: var(--shadow-pop); }
/* Navy-to-cyan gradient with soft cyan/orange glow blobs — pulled from the logo artwork's own
   colors (see --auth-* tokens above), replacing the previous flat purple gradient. No source
   illustration asset exists for this panel (unlike a designed reference mockup that has one),
   so the logo itself is the hero visual here instead — enlarged below — rather than an empty
   gradient or a mismatched stock graphic. */
.auth-brand-panel {
  position: relative; overflow: hidden;
  flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 16px; padding: 48px;
  background: linear-gradient(150deg, var(--auth-navy-deep), var(--auth-navy) 55%, #0B2A57 100%);
  color: #fff;
}
.auth-brand-panel::before, .auth-brand-panel::after {
  content: ''; position: absolute; border-radius: 50%; pointer-events: none;
}
.auth-brand-panel::before {
  width: 320px; height: 320px; top: -90px; right: -110px;
  background: radial-gradient(circle, rgba(34, 211, 238, 0.35), transparent 70%);
}
.auth-brand-panel::after {
  width: 260px; height: 260px; bottom: -110px; left: -90px;
  background: radial-gradient(circle, rgba(255, 122, 41, 0.22), transparent 70%);
}
/* Keeps the logo/heading/paragraph above the two glow blobs above, which are positioned
   outside the panel's own padding box and would otherwise sit in normal stacking order
   between the panel's background and its (unpositioned, so implicitly z-index:auto) text. */
.auth-brand-panel > * { position: relative; z-index: 1; }
.auth-brand-panel .brand-dot { background: #fff; }
/* Plain — no circle/box framing it, object-fit:contain so the full logo artwork shows
   uncropped exactly as the source file looks. Sized as this panel's hero visual (see the
   comment above) rather than a small icon next to the heading. */
.auth-brand-logo {
  display: block; width: 88px; height: 88px; flex-shrink: 0; margin-bottom: 4px; transition: transform 0.15s ease;
  filter: drop-shadow(0 10px 28px rgba(34, 211, 238, 0.35));
}
.auth-brand-logo:hover { transform: scale(1.05); }
.auth-brand-logo img { width: 100%; height: 100%; object-fit: contain; }
.auth-brand-panel h2 { font-weight: 700; margin: 0; color: #fff;}
.auth-brand-panel p { color: rgba(255,255,255,0.8); margin: 0; max-width: 320px; }
.auth-card { flex: 1; max-width: 420px; background: var(--surface-card); padding: 48px; display: flex; flex-direction: column; justify-content: center; }
.auth-card .sidebar-brand { display: none; }
.auth-card-header h1 { font-size: 1.5rem; font-weight: 700; margin: 0 0 6px; letter-spacing: -0.01em; }
.auth-card-header p { color: var(--text-muted); font-size: 0.92rem; margin: 0; }
/* A slightly more deliberate primary button than the app's default .btn-primary — this is
   the single action on the page, so it earns a bit more weight/lift than a button competing
   with a dozen others on a dashboard toolbar. */
.auth-submit-btn { padding: 11px 20px; font-weight: 600; border-radius: 999px; transition: transform 0.15s ease, box-shadow 0.15s ease; }
.auth-submit-btn:hover { transform: translateY(-1px); box-shadow: 0 6px 16px -6px rgba(var(--brand-primary-rgb), 0.5); }
/* Pill-shaped, icon-prefixed fields on the auth pages specifically — scoped to .auth-card so
   the rest of the app's forms (dashboard, settings, etc.) keep their existing .form-control
   styling untouched. .auth-input-icon is the wrapper (position:relative, like .password-field
   already is for the show/hide toggle — the two combine cleanly on password fields since
   neither sets the other's positioning). */
.auth-card .auth-input-icon { position: relative; }
.auth-card .auth-input-icon .form-control { border-radius: 999px; padding-left: 42px; }
/* .password-field already adds its own right-side padding for the toggle button (see
   assets/js/password_toggle.js) — this only needs to add the matching left-side icon padding
   on top of that, not redeclare border-radius/padding-left a second time. */
.auth-card .auth-input-icon.password-field .form-control { border-radius: 999px; }
.auth-input-icon-glyph {
  position: absolute; top: 50%; left: 16px; transform: translateY(-50%);
  color: var(--text-muted); pointer-events: none; font-size: 0.95rem;
}
@media (max-width: 991px) {
.topbar-left img {
	width: 100%;
}
.topbar .workspace-icon {
	width: 49px;
	height: 49px;
}
.topbar {
	width: 93%;
	margin: 0 auto;
	border-radius: 24px;
	margin-top: 22px;

}
footer.app-footer {
    width: 93%;
    margin: 0 auto;
    border-radius: 2px;
    margin-bottom: 30px;
}
}
/* Narrow phones: search, notifications, and the avatar menu must stay reachable no matter how
   little width is left, so the theme toggle (least essential — every page still has the same
   theme either way) is the first thing to go, by its own id rather than a fragile "3rd button"
   position that would silently hide the wrong control the moment a button is added/removed. */
@media (max-width: 575.98px) {
  #themeToggleBtn { display: none; }
  .topbar-search-trigger { padding: 8px 12px; }
}
.topbar-actions .btn .badge {
  position: absolute;
  top: -2px;
  right: -2px;
}

@media (max-width: 767.98px) {
  /* Keeps the card as a centered, rounded, shadowed panel instead of full-bleed edge-to-edge
     — reads as a deliberate app screen rather than a bare form. Brand panel stays visible
     (just more compact) rather than disappearing entirely, so the logo/identity isn't lost
     on the majority of real-world traffic to these pages (phones). */
  .auth-body { padding: 20px 0; align-items: flex-start; }
  .auth-shell { flex-direction: column; width: calc(100% - 32px); max-width: 420px; margin: 0 auto; align-self: center; }
  .auth-brand-panel { padding: 28px 24px 20px; gap: 8px; }
  .auth-brand-logo { width: 44px; height: 44px; }
  .auth-brand-panel h2 { font-size: 1.2rem; }
  .auth-brand-panel p { display: none; }
  .auth-card { max-width: 100%; padding: 28px 24px; }
}
.otp-input { letter-spacing: 0.3em; font-size: 1.3rem; }

.onboarding-option {
  display: flex; align-items: center; gap: 12px; padding: 12px 14px; border-radius: var(--radius-md);
  border: 1px solid var(--border-color); cursor: pointer;
}
.onboarding-option:has(input:checked) { border-color: var(--brand-primary); background: rgba(var(--brand-primary-rgb), 0.06); }
.onboarding-option input { margin: 0; }
.onboarding-option-icon {
  width: 36px; height: 36px; border-radius: 50%; background: var(--surface-sunken); color: var(--brand-primary);
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; font-size: 1rem;
}

.plan-card-active { border-color: var(--brand-success); box-shadow: 0 0 0 2px rgba(var(--brand-success-rgb), 0.2); }
.plan-card:hover { border-color: var(--brand-primary); box-shadow: var(--shadow-pop); }
.trial-badge {
  display: inline-flex; align-items: center; gap: 4px; font-size: 0.72rem; padding: 2px 8px;
  border-radius: 999px; background: rgba(var(--brand-warning-rgb), 0.14); color: var(--brand-warning); white-space: nowrap;
}
.trial-badge-restricted { background: rgba(var(--brand-alert-rgb), 0.14); color: var(--brand-alert); }

/* Project cards */
.project-header { display: flex; align-items: center; gap: 12px; }
.project-card {
  display: flex; align-items: center; gap: 12px; padding: 14px; background: var(--surface-card);
  border: 0.5px solid var(--border-color); border-radius: var(--radius-lg); color: inherit; text-decoration: none; height: 100%;
  box-shadow: var(--shadow-card);
}
.project-card:hover { border-color: var(--brand-primary); transform: translateY(-2px); box-shadow: var(--shadow-pop); }
.project-card-name { font-weight: 500; }
.project-card-meta { font-size: 0.78rem; color: var(--text-muted); }
/* §Dashboard redesign — reused by both the Projects Overview stacked rows below and (should a
   future page want a plain progress bar without the full row treatment) anywhere else. */
.project-progress-bar { height: 6px; border-radius: 999px; background: var(--surface-sunken); overflow: hidden; }
.project-progress-bar-fill { height: 100%; border-radius: 999px; }

/* §Dashboard redesign — "Projects Overview" (real data: getProjectsOverviewStats() +
   getProjectsTopMembers()), stacked full-width rows rather than .project-card's 2-column grid
   treatment (still used by the Public Projects section further down this same page). */
.dashboard-projects-overview { display: flex; flex-direction: column; gap: 12px; }
.dashboard-project-row {
  display: block; padding: 14px 16px; background: var(--surface-card); border: 0.5px solid var(--border-color);
  border-radius: var(--radius-lg); color: inherit; text-decoration: none; box-shadow: var(--shadow-card);
  transition: transform 0.1s ease, box-shadow 0.1s ease;
}
.dashboard-project-row:hover { transform: translateY(-1px); box-shadow: var(--shadow-pop); border-color: var(--brand-primary); }
.dashboard-project-percent { font-weight: 700; font-size: 0.92rem; }
.dashboard-project-stats { display: flex; gap: 14px; font-size: 0.8rem; color: var(--text-muted); }
.dashboard-project-avatars { display: flex; }
.dashboard-project-avatar {
  width: 26px; height: 26px; font-size: 0.7rem; border: 2px solid var(--surface-card); margin-left: -8px; flex-shrink: 0;
}
.dashboard-project-avatars .dashboard-project-avatar:first-child { margin-left: 0; }
.dashboard-project-avatar-more { background: var(--surface-sunken); color: var(--text-muted); font-weight: 600; }

/* Team Directory cards (Manage Members + All Team Members) — same card conventions as
   .project-card above (border/radius/shadow/hover-lift), laid out as a column since a photo
   sits at the top rather than beside a title. */
.nav-pills .nav-link.active { background-color: var(--brand-primary); }
.team-member-card {
  display: flex; flex-direction: column; gap: 10px; padding: 16px; background: var(--surface-card);
  border: 0.5px solid var(--border-color); border-radius: var(--radius-lg); box-shadow: var(--shadow-card); height: 100%;
}
.team-member-card:hover { border-color: var(--brand-primary); box-shadow: var(--shadow-pop); }
.team-member-card-header { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 6px; cursor: pointer; }
.team-member-card-name { font-weight: 600; }
.team-member-card-since { font-size: 0.72rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.03em; }
.team-member-card-body { display: flex; flex-direction: column; gap: 6px; font-size: 0.85rem; border-top: 0.5px solid var(--border-color); padding-top: 10px; }
.team-member-card-actions { display: flex; justify-content: center; gap: 6px; border-top: 0.5px solid var(--border-color); padding-top: 8px; margin-top: auto; }
@keyframes teamMemberReveal { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
.team-member-card.card-reveal { animation: teamMemberReveal 0.25s ease; }

/* Chat layout */
.chat-layout { display: flex; gap: 16px; height: calc(100vh - 210px); min-height: 420px; }
.chat-layout-solo { height: calc(100vh - 190px); }
.channel-list { width: 200px; flex-shrink: 0; background: var(--surface-card); border: 0.5px solid var(--border-color); border-radius: var(--radius-lg); padding: 14px; overflow-y: auto; box-shadow: var(--shadow-card); }
.channel-link { display: block; padding: 6px 8px; border-radius: var(--radius-md); color: var(--text-body); font-size: 0.88rem; }
.channel-link:hover { background: rgba(var(--brand-primary-rgb), 0.07); color: var(--brand-primary); }
.channel-link.active { background: rgba(var(--brand-primary-rgb), 0.12); color: var(--brand-primary); font-weight: 600; }
.chat-pane { flex: 1; display: flex; flex-direction: column; background: var(--surface-card); border: 0.5px solid var(--border-color); border-radius: var(--radius-lg); overflow: hidden; min-width: 0; box-shadow: var(--shadow-card); }

.chat-pane-header {
  display: flex; justify-content: space-between; align-items: center;
  padding: 12px 16px; border-bottom: 0.5px solid var(--border-color);
}
.chat-pane-title { display: flex; align-items: baseline; gap: 8px; font-size: 0.95rem; }
.chat-pane-title .text-muted { font-size: 0.82rem; }
.chat-pane-icons { display: flex; gap: 4px; }
.chat-icon-btn {
  border: none; background: none; color: var(--text-muted); width: 32px; height: 32px; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
}
.chat-icon-btn:hover:not(:disabled) { background: rgba(var(--brand-primary-rgb), 0.08); color: var(--brand-primary); }
.chat-icon-btn:disabled { color: var(--text-faint); cursor: not-allowed; }
.chat-search-bar { padding: 8px 16px; border-bottom: 0.5px solid var(--border-color); }

.context-panel { width: 240px; flex-shrink: 0; display: flex; flex-direction: column; gap: 14px; overflow-y: auto; }
.context-card { background: var(--surface-card); border: 0.5px solid var(--border-color); border-radius: var(--radius-lg); padding: 14px; box-shadow: var(--shadow-card); }
.context-card-title { font-size: 0.82rem; font-weight: 600; margin-bottom: 10px; color: var(--text-body); }
.daily-mini-item { display: flex; align-items: flex-start; gap: 8px; padding: 6px 0; }
.daily-dot { width: 9px; height: 9px; border-radius: 50%; margin-top: 5px; flex-shrink: 0; }
.daily-dot-active { background: var(--brand-success); }
.daily-dot-empty { background: var(--text-faint); }
.daily-mini-name { font-size: 0.82rem; font-weight: 500; }
.daily-mini-text { font-size: 0.78rem; color: var(--text-muted); line-height: 1.3; }

.pinned-messages { padding: 6px 16px; font-size: 0.78rem; color: var(--text-muted); border-bottom: 0.5px solid var(--border-color); }
.pinned-messages:empty { display: none; }
.pinned-item { display: inline-flex; align-items: center; gap: 4px; margin-right: 12px; }
.message-list { flex: 1; overflow-y: auto; padding: 16px; }
.message-row { display: flex; gap: 10px; padding: 6px 4px; border-radius: var(--radius-md); }
.message-row:hover { background: rgba(var(--brand-primary-rgb), 0.05); }
.message-row:hover .message-actions { visibility: visible; }
.message-body { flex: 1; min-width: 0; }
.message-meta { font-size: 0.78rem; color: var(--text-muted); margin-left: 6px; }
.message-sender { font-weight: 600; font-size: 0.9rem; }

/* Consecutive same-sender messages (assets/js/chat.js's isGroupedWithPrevious()) — hide the
   repeated header, reveal the timestamp on hover instead. visibility (not display:none) on
   the avatar so it stays correctly excluded from .user-trigger click hit-testing without any
   JS rebinding, since that delegate is bound on document. */
.message-row.grouped .avatar-circle { visibility: hidden; }
.message-row.grouped .message-sender { display: none; }
.message-row.grouped .message-meta { opacity: 0; transition: opacity .15s ease; }
.message-row.grouped:hover .message-meta { opacity: 1; }
.message-content { font-size: 0.92rem; word-wrap: break-word; white-space: pre-wrap; }
.message-content .mention { color: var(--brand-primary); font-weight: 500; }
.message-content p { margin: 0 0 4px; }
.message-content p:last-child { margin-bottom: 0; }
.message-content ul, .message-content ol { margin: 4px 0; padding-left: 22px; }
.message-content blockquote { margin: 4px 0; padding-left: 10px; border-left: 3px solid var(--border-color); color: var(--text-muted); }
.message-content code { background: rgba(var(--brand-primary-rgb), 0.08); border-radius: 4px; padding: 1px 5px; font-size: 0.85em; }
.message-content pre { background: var(--surface-sunken); border: 0.5px solid var(--border-color); border-radius: 8px; padding: 8px 10px; margin: 4px 0; overflow-x: auto; white-space: pre; }
.message-content pre code { background: none; padding: 0; }
.message-content a { color: var(--brand-primary); text-decoration: underline; word-break: break-all; }

/* Bubble layout — sent messages (.mine) align right with no avatar/name, matching the
   messenger convention that you already know who "you" are; received messages keep the
   avatar/name and align left, same as before. Only the bubble (reply-quote/content/
   attachments) gets a background — meta/actions/reactions stay outside it, unchanged
   positions, so hover-reveal and click targets are unaffected by this visual change. */
/* --surface-sunken (not --surface-card): .chat-pane itself already uses --surface-card as its
   own background, so a received bubble using that same token would render with no visible
   fill of its own, just its border — this "sunken chip" tone is a different depth layer,
   giving it real contrast against the pane in both themes. */
.message-bubble { display: inline-block; max-width: 72%; border-radius: 4px 14px 14px 14px; padding: 8px 12px; margin-top: 2px; background: var(--surface-sunken); border: 0.5px solid var(--border-color); }
.message-row.mine .avatar-circle { display: none; }
.message-row.mine .message-body { display: flex; flex-direction: column; align-items: flex-end; }
.message-row.mine .message-sender { display: none; }
.message-row.mine .message-bubble { background: var(--brand-primary); color: #fff; border: none; border-radius: 14px 4px 14px 14px; }
.message-row.mine .message-bubble .message-content a { color: #fff; }
.message-row.mine .message-bubble .message-content code { background: rgba(255,255,255,0.2); }
.message-row.mine .message-bubble .message-content pre { background: rgba(255,255,255,0.12); border-color: rgba(255,255,255,0.25); }
.message-row.mine .message-bubble .message-content blockquote { border-left-color: rgba(255,255,255,0.5); color: rgba(255,255,255,0.85); }
.message-row.mine .message-bubble .message-reply-quote { border-left-color: rgba(255,255,255,0.5); color: rgba(255,255,255,0.8); }
.message-row.mine .message-bubble .message-reply-quote strong { color: #fff; }
.message-row.mine .message-bubble .message-reply-quote:hover { background: rgba(255,255,255,0.12); border-left-color: #fff; }
.message-row.mine .reaction-row { justify-content: flex-end; }
.message-actions { visibility: hidden; display: flex; gap: 4px; font-size: 0.8rem; }
.message-actions.force-visible { visibility: visible; }
.message-actions button { border: none; background: none; color: var(--text-muted); padding: 2px 5px; border-radius: 6px; }
.message-actions button:hover { background: rgba(var(--brand-primary-rgb), 0.1); color: var(--brand-primary); }
.message-more-btn { border: none; background: none; color: var(--text-muted); padding: 2px 6px; border-radius: 6px; font-size: 0.85rem; margin-left: 2px; }
.message-more-btn:hover { background: rgba(var(--brand-primary-rgb), 0.1); color: var(--brand-primary); }
.message-edit-box { margin: 4px 0; }
.message-edit-controls { display: flex; gap: 6px; margin: 4px 0; }
.reaction-row { display: flex; gap: 6px; margin-top: 4px; flex-wrap: wrap; }
.reaction-chip { font-size: 0.78rem; border: 0.5px solid var(--border-color); border-radius: 999px; padding: 1px 8px; cursor: pointer; background: var(--surface-sunken); color: var(--text-body); }
.reaction-chip.mine { border-color: var(--brand-primary); background: rgba(var(--brand-primary-rgb), 0.1); }
.attachment-chip {
  display: inline-flex; align-items: center; gap: 6px; border-radius: var(--radius-md); padding: 6px 10px; margin-top: 6px; font-size: 0.85rem;
  background: transparent; color: #fff !important; border: unset;
}
.attachment-chip a { color: #fff; }
/* The one combination the transparent+white treatment above doesn't fit: a received message's
   bubble (.message-bubble, not .mine) is a light --surface-sunken tint in light theme, so
   white text there would be white-on-near-white. Only sent-message bubbles are always a solid
   dark brand color regardless of theme. */
.message-row:not(.mine) .attachment-chip,
.message-row:not(.mine) .attachment-chip a {
  color: var(--text-body) !important;
}
.attachment-chip.attachment-image-card {
  display: block; padding: 10px; max-width: 260px; background: var(--surface-sunken); position: relative;
}
.attachment-chip.attachment-image-card a { display: block; margin-bottom: 6px; }
.attachment-chip img { max-width: 100%; max-height: 180px; border-radius: 8px; display: block; }

/* §Chat File Delete */
.attachment-delete-btn {
  border: none; background: transparent; color: inherit; opacity: 0.65; padding: 0 2px;
  display: inline-flex; align-items: center; font-size: 0.85rem; cursor: pointer; flex-shrink: 0;
}
.attachment-delete-btn:hover { opacity: 1; color: var(--brand-alert); }
.attachment-delete-btn:disabled { opacity: 0.3; cursor: default; }
.attachment-chip.attachment-image-card .attachment-delete-btn {
  position: absolute; top: 6px; right: 6px; background: rgba(0,0,0,0.55) !important; color: #fff !important;
  border-radius: 6px; padding: 3px 5px; opacity: 0.85;
}
.attachment-chip.attachment-image-card .attachment-delete-btn:hover { background: var(--brand-alert) !important; opacity: 1; }

.composer { display: flex; gap: 8px; align-items: center; padding: 12px 16px; border-top: 0.5px solid var(--border-color); }
.composer .btn { width: 38px; height: 38px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; padding: 0; flex-shrink: 0; }
.composer-toolbar { display: flex; gap: 4px; flex-shrink: 0; }
.composer-toolbar .btn:disabled { color: var(--text-faint); background: transparent; opacity: 1; }

.composer-rich { flex-direction: column; align-items: stretch; gap: 6px; padding: 8px 16px 12px; }
.composer-rich .composer-toolbar { overflow-x: auto; flex-wrap: nowrap; padding-bottom: 2px; }
.composer-rich .composer-toolbar .btn { width: 32px; height: 32px; font-size: 0.85rem; }
.composer-toolbar-sep { width: 1px; align-self: stretch; background: var(--border-color); margin: 3px 2px; flex-shrink: 0; }
.composer-tool-btn.active { background: rgba(var(--brand-primary-rgb), 0.16); color: var(--brand-primary); }
.composer-input-row { display: flex; gap: 8px; align-items: flex-end; }
.composer-editable {
  flex: 1; min-height: 108px; max-height: 160px; overflow-y: auto; border-radius: 18px; padding: 8px 16px;
  background: var(--surface-sunken); font-size: 0.92rem; line-height: 1.4; outline: none; border: 1px solid transparent;
}
.composer-editable:focus { border-color: var(--brand-primary); }
.composer-editable:empty:before { content: attr(data-placeholder); color: var(--text-muted); pointer-events: none; }
.composer-editable p { margin: 0 0 4px; }
.composer-editable p:last-child { margin-bottom: 0; }
.composer-editable ul, .composer-editable ol { margin: 4px 0; padding-left: 22px; }
.composer-editable blockquote { margin: 4px 0; padding-left: 10px; border-left: 3px solid var(--border-color); color: var(--text-muted); }
.composer-editable code { background: rgba(var(--brand-primary-rgb), 0.08); border-radius: 4px; padding: 1px 5px; font-size: 0.85em; }
.composer-editable pre { background: var(--surface-card); border: 0.5px solid var(--border-color); border-radius: 8px; padding: 8px 10px; margin: 4px 0; overflow-x: auto; white-space: pre; }
.composer-editable pre code { background: none; padding: 0; }
.composer-editable .mention { color: var(--brand-primary); font-weight: 500; background: rgba(var(--brand-primary-rgb), 0.1); border-radius: 4px; padding: 0 3px; }
.composer-send-btn { margin-bottom: 1px; }

.reaction-picker { display: flex; gap: 2px; background: var(--surface-card); }
.reaction-picker button { border: none; background: none; font-size: 1.1rem; padding: 4px 6px; border-radius: 6px; }
.reaction-picker button:hover { background: rgba(var(--brand-primary-rgb), 0.08); }

.mention-autocomplete {
  margin: 0 16px; background: var(--surface-card); border: 0.5px solid var(--border-color); border-radius: var(--radius-md);
  box-shadow: var(--shadow-pop); overflow: hidden;
}
.mention-autocomplete .mention-option {
  display: block; width: 100%; text-align: left; border: none; background: none; padding: 8px 12px; font-size: 0.85rem; color: var(--text-body);
}
.mention-autocomplete .mention-option:hover { background: rgba(var(--brand-primary-rgb), 0.08); color: var(--brand-primary); }
.composer-disabled { padding: 12px 16px; border-top: 0.5px solid var(--border-color); color: var(--text-muted); }

/* §Typing Indicator — sits between the message list and the composer/reply-preview, same
   horizontal margin as .reply-preview so both align with the composer below. */
.typing-indicator {
  margin: 0 16px 4px; font-size: 0.78rem; font-style: italic; color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.reply-preview {
  display: flex; align-items: flex-start; gap: 8px; margin: 0 16px 6px; padding: 6px 10px;
  background: var(--surface-sunken); border-left: 3px solid var(--brand-primary); border-radius: 6px; font-size: 0.82rem;
}
.reply-preview i { color: var(--brand-primary); margin-top: 2px; }
.reply-preview-text { flex: 1; min-width: 0; }
.reply-preview-text strong { display: block; font-size: 0.8rem; }
.reply-preview-text span { color: var(--text-muted); display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.message-reply-quote {
  display: block; font-size: 0.8rem; color: var(--text-muted); border-left: 3px solid var(--border-color);
  padding: 2px 8px; margin-bottom: 4px; cursor: pointer; border-radius: 4px; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.message-reply-quote:hover { background: rgba(var(--brand-primary-rgb), 0.06); border-left-color: var(--brand-primary); }
.message-reply-quote strong { color: var(--text-body); }
.message-row.message-flash { background: rgba(var(--brand-primary-rgb), 0.16); transition: background 1.2s ease; }
.attach-preview { padding: 0 16px 10px; font-size: 0.82rem; color: var(--text-muted); }

.files-dropzone.dragover { outline: 2px dashed var(--brand-primary); outline-offset: -6px; border-radius: var(--radius-md); }

@media (max-width: 767.98px) {
  .content-area { padding: 16px; }
  .chat-layout { flex-direction: column; height: auto; }
  .channel-list { width: 100%; }
}

/* Profile page */
.min-width-0 { min-width: 0; }
.text-faint { color: var(--text-faint); }
.profile-header { display: flex; align-items: flex-start; gap: 18px; }
.avatar-lg { width: 76px; height: 76px; font-size: 1.8rem; flex-shrink: 0; }
.profile-avatar-wrap { position: relative; flex-shrink: 0; }
.profile-avatar-edit { position: absolute; bottom: -6px; right: -6px; display: flex; gap: 3px; }
.profile-avatar-edit .btn { width: 26px; height: 26px; padding: 0; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; box-shadow: var(--shadow-card); font-size: 0.75rem; }
.profile-contact-row { display: flex; gap: 18px; flex-wrap: wrap; margin-top: 10px; font-size: 0.86rem; color: var(--text-muted); }
.profile-contact-row i { color: var(--text-faint); margin-right: 4px; }

.role-badge {
  display: inline-block; padding: 3px 12px; border-radius: 999px; font-size: 0.74rem; font-weight: 600;
  border: 0.5px solid transparent;
}
.role-badge-admin { background: rgba(var(--brand-warning-rgb), 0.15); color: var(--brand-warning); border-color: rgba(var(--brand-warning-rgb), 0.3); }
.role-badge-pm { background: rgba(var(--brand-primary-rgb), 0.12); color: var(--brand-primary); border-color: rgba(var(--brand-primary-rgb), 0.25); }
.role-badge-member { background: rgba(var(--brand-success-rgb), 0.12); color: var(--brand-success); border-color: rgba(var(--brand-success-rgb), 0.25); }
.role-badge-guest { background: var(--surface-sunken); color: var(--text-muted); border-color: var(--border-color); }

.profile-info-grid { display: grid; grid-template-columns: 180px 1fr; row-gap: 14px; column-gap: 12px; }
.profile-info-label { color: var(--text-muted); font-size: 0.86rem; }
.profile-info-value { font-size: 0.92rem; }

@media (max-width: 575.98px) {
  .profile-info-grid { grid-template-columns: 1fr; row-gap: 4px; }
  .profile-info-label { font-weight: 600; margin-top: 8px; }
}

/* Admin — Manage Members split panel */
.admin-panel-layout { display: flex; gap: 20px; align-items: flex-start; }
.admin-user-list-col { width: 320px; flex-shrink: 0; }
.admin-user-list { display: flex; flex-direction: column; gap: 2px; max-height: calc(100vh - 230px); overflow-y: auto; }
.admin-user-row {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: var(--radius-md);
  border: none; background: none; text-align: left; width: 100%; cursor: pointer; color: var(--text-body);
}
.admin-user-row:hover { background: rgba(var(--brand-primary-rgb), 0.06); }
.admin-user-row.active { background: rgba(var(--brand-primary-rgb), 0.1); }
.admin-user-row-info { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.admin-user-row-name { font-size: 0.88rem; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.admin-user-row-email { font-size: 0.76rem; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.admin-user-detail-col { flex: 1; min-width: 0; }
.admin-detail-card { min-height: 520px; }
.admin-detail-header { display: flex; align-items: center; gap: 14px; }
.admin-detail-tabs { margin-top: 18px; margin-bottom: 16px; flex-wrap: wrap; }
.admin-tab-content { max-height: calc(100vh - 330px); overflow-y: auto; padding-right: 4px; }

@media (max-width: 991.98px) {
  .admin-panel-layout { flex-direction: column; }
  .admin-user-list-col { width: 100%; }
  .admin-user-list { max-height: 260px; }
  .admin-tab-content, .admin-user-list { max-height: none; }
}

/* Tasks */
.status-badge, .priority-badge {
  display: inline-block; padding: 3px 10px; border-radius: 999px; font-size: 0.72rem; font-weight: 600;
  border: 0.5px solid transparent;
}
.status-not_started { background: var(--surface-sunken); color: var(--text-muted); border-color: var(--border-color); }
.status-wip { background: rgba(var(--brand-info-rgb), 0.14); color: var(--brand-info); border-color: rgba(var(--brand-info-rgb), 0.3); }
.status-pending { background: rgba(var(--brand-warning-rgb), 0.14); color: var(--brand-warning); border-color: rgba(var(--brand-warning-rgb), 0.3); }
.status-completed { background: rgba(var(--brand-success-rgb), 0.14); color: var(--brand-success); border-color: rgba(var(--brand-success-rgb), 0.3); }
.status-cancelled { background: rgba(var(--brand-alert-rgb), 0.12); color: var(--brand-alert); border-color: rgba(var(--brand-alert-rgb), 0.28); }
.status-reopened { background: rgba(var(--brand-purple-rgb), 0.14); color: var(--brand-purple); border-color: rgba(var(--brand-purple-rgb), 0.3); }
/* §QA/Bug Tracking — 'testing'/'on_hold' widened tasks.status (see schema.sql); these two
   never got a badge color since they didn't exist until now. */
.status-testing { background: rgba(var(--brand-warning-rgb), 0.14); color: var(--brand-warning); border-color: rgba(var(--brand-warning-rgb), 0.3); }
.status-on_hold { background: var(--surface-sunken); color: var(--text-muted); border-color: var(--border-color); }

/* Support ticket statuses (distinct enum from task statuses, reuses the same .status-badge shell) */
.status-open { background: var(--surface-sunken); color: var(--text-muted); border-color: var(--border-color); }
.status-assigned { background: rgba(var(--brand-info-rgb), 0.14); color: var(--brand-info); border-color: rgba(var(--brand-info-rgb), 0.3); }
.status-in_progress { background: rgba(var(--brand-primary-rgb), 0.12); color: var(--brand-primary); border-color: rgba(var(--brand-primary-rgb), 0.28); }
.status-waiting_user { background: rgba(var(--brand-warning-rgb), 0.14); color: var(--brand-warning); border-color: rgba(var(--brand-warning-rgb), 0.3); }
.status-resolved { background: rgba(var(--brand-success-rgb), 0.14); color: var(--brand-success); border-color: rgba(var(--brand-success-rgb), 0.3); }
.status-closed { background: rgba(var(--brand-alert-rgb), 0.12); color: var(--brand-alert); border-color: rgba(var(--brand-alert-rgb), 0.28); }

.timeline-note { background: rgba(var(--brand-warning-rgb), 0.25) !important; }
.timeline-note-box { background: rgba(var(--brand-warning-rgb), 0.1) !important; border: 1px dashed rgba(var(--brand-warning-rgb), 0.4); }

.task-id-badge { font-family: ui-monospace, "SF Mono", Consolas, monospace; font-size: 0.8rem; color: var(--text-muted); font-weight: 600; }
a.task-id-badge:hover { color: var(--brand-primary); }
.task-id-large { font-family: ui-monospace, "SF Mono", Consolas, monospace; font-size: 0.95rem; color: var(--brand-primary); font-weight: 700; letter-spacing: 0.02em; }

.mini-progress { width: 70px; height: 6px; border-radius: 999px; background: var(--surface-sunken); overflow: hidden; display: inline-block; vertical-align: middle; }
.mini-progress-fill { height: 100%; background: var(--brand-success); }
.mini-progress-label { font-size: 0.72rem; color: var(--text-muted); margin-left: 6px; }

.percent-input-row { display: flex; align-items: center; gap: 8px; }
.percent-input-row input[type="range"] { flex: 1; }

.priority-low { background: var(--surface-sunken); color: var(--text-muted); border-color: var(--border-color); }
.priority-medium { background: rgba(var(--brand-primary-rgb), 0.12); color: var(--brand-primary); border-color: rgba(var(--brand-primary-rgb), 0.28); }
.priority-high { background: rgba(var(--brand-warning-rgb), 0.14); color: var(--brand-warning); border-color: rgba(var(--brand-warning-rgb), 0.3); }
.priority-critical { background: rgba(var(--brand-alert-rgb), 0.14); color: var(--brand-alert); border-color: rgba(var(--brand-alert-rgb), 0.3); }

.task-progress-bar { height: 8px; border-radius: 999px; background: var(--surface-sunken); }
.task-progress-bar .progress-bar { background: var(--brand-success); border-radius: 999px; }
.task-stat-row { display: flex; flex-wrap: wrap; gap: 18px; font-size: 0.82rem; }
.task-stat-item { display: flex; flex-direction: column; }
.task-stat-value { font-weight: 700; font-size: 1.05rem; }
.task-stat-label { color: var(--text-muted); font-size: 0.74rem; }

/* §Dashboard redesign — live-call banner (real data: getActiveCallForUser()), only rendered
   when a call is genuinely active, so this never needs an empty/placeholder state. */
.dashboard-live-call-banner { border-color: var(--brand-success); }
.dashboard-live-call-dot {
  width: 10px; height: 10px; border-radius: 50%; background: var(--brand-success); flex-shrink: 0;
  box-shadow: 0 0 0 4px rgba(var(--brand-success-rgb), 0.18);
}
/* §Dashboard redesign — "My Tasks" board (real data: getMyTaskBoard()), 4 columns matching
   Squaro's own real task statuses (see TASK_STATUS_LABELS) rather than an invented pipeline. */
.dashboard-task-board { align-items: stretch; }
.dashboard-task-column {
  background: var(--surface-sunken); border: 0.5px solid var(--border-color); border-radius: var(--radius-lg);
  padding: 12px; height: 100%; display: flex; flex-direction: column; gap: 8px;
}
.dashboard-task-column-header {
  display: flex; align-items: center; justify-content: space-between; font-weight: 600; font-size: 0.85rem;
  padding: 2px 2px 6px;
}
.dashboard-task-column-count {
  background: var(--surface-card); color: var(--text-muted); font-weight: 600; font-size: 0.72rem;
  border-radius: 999px; padding: 1px 8px;
}
.dashboard-task-card {
  display: block; background: var(--surface-card); border: 0.5px solid var(--border-color); border-radius: var(--radius-md);
  padding: 10px; color: inherit; text-decoration: none; transition: transform 0.1s ease, box-shadow 0.1s ease;
}
.dashboard-task-card:hover { transform: translateY(-1px); box-shadow: var(--shadow-card); border-color: var(--brand-primary); }
.dashboard-task-card-title { font-size: 0.88rem; font-weight: 500; margin-bottom: 6px; }
/* Shown instead of the 5th+ card once a column is capped (see pages/dashboard.php) — links to
   the real, uncapped list (pages/my_tasks.php) pre-filtered to this same column's status. */
.dashboard-task-view-more {
  display: block; text-align: center; padding: 8px; border-radius: var(--radius-md);
  border: 1px dashed var(--border-color); color: var(--text-muted); font-size: 0.78rem; font-weight: 600;
  text-decoration: none;
}
.dashboard-task-view-more:hover { color: var(--brand-primary); border-color: var(--brand-primary); background: rgba(var(--brand-primary-rgb), 0.05); }
/* Solid color pill (project's own color as background) instead of the dot+text treatment
   .project-card/.dashboard-project-row use elsewhere — a task card is small enough that a
   colored tag reads faster than a tiny dot next to a name. */
.dashboard-task-tag {
  display: inline-block; padding: 2px 9px; border-radius: 999px; color: #fff; font-size: 0.7rem;
  font-weight: 600; max-width: 75%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dashboard-task-due { font-size: 0.76rem; color: var(--text-muted); white-space: nowrap; }

.task-meta-row { display: flex; justify-content: space-between; align-items: center; padding: 7px 0; border-bottom: 0.5px solid var(--border-color); font-size: 0.86rem; }
.task-meta-row:last-child { border-bottom: none; }
.task-meta-label { color: var(--text-muted); }

.task-overdue-text { color: var(--brand-alert); font-weight: 600; }

/* Activity timeline */
.timeline-item { display: flex; gap: 12px; padding-bottom: 18px; position: relative; }
.timeline-item:not(:last-child)::before {
  content: ''; position: absolute; left: 15px; top: 30px; bottom: 0; width: 2px; background: var(--border-color);
}
.timeline-dot {
  width: 32px; height: 32px; border-radius: 50%; background: var(--surface-sunken); color: var(--text-muted);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0; font-size: 0.85rem; z-index: 1;
}
.timeline-dot.timeline-comment { background: rgba(var(--brand-primary-rgb), 0.12); color: var(--brand-primary); }
.timeline-body { flex: 1; min-width: 0; padding-top: 3px; }
.timeline-text { font-size: 0.88rem; }
.timeline-time { font-size: 0.74rem; color: var(--text-muted); margin-top: 2px; }
.timeline-comment-box { background: var(--surface-sunken); border-radius: var(--radius-md); padding: 10px 12px; margin-top: 6px; font-size: 0.88rem; white-space: pre-wrap; }

.attachment-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 0; border-bottom: 0.5px solid var(--border-color); font-size: 0.85rem; }
.attachment-row:last-child { border-bottom: none; }

/* File preview modal — z-index above .call-overlay's 3000 (not just the general-purpose 2900
   it used to share with other page-level modals): opening an attachment from the group-call
   chat panel (assets/js/call_chat.js) needs this to render on top of the still-open call,
   not silently behind it. */
.file-preview-overlay {
  position: fixed; inset: 0; z-index: 3100; background: rgba(10,10,14,0.7);
  display: flex; align-items: center; justify-content: center; padding: 24px;
}
.file-preview-modal {
  width: 100%; max-width: 860px; max-height: 90vh; background: var(--surface-card);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-pop); display: flex; flex-direction: column; overflow: hidden;
}
.file-preview-header {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 14px 18px; border-bottom: 0.5px solid var(--border-color); flex-shrink: 0;
}
.file-preview-title { font-weight: 600; font-size: 0.95rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-preview-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.file-preview-body { flex: 1; overflow: auto; padding: 18px; display: flex; align-items: center; justify-content: center; min-height: 200px; }
.file-preview-image { max-width: 100%; max-height: 76vh; border-radius: var(--radius-md); display: block; margin: auto; }
.file-preview-pdf { width: 100%; height: 76vh; border: none; }
.file-preview-unsupported { text-align: center; padding: 30px 20px; }
.file-preview-unsupported i { font-size: 3.5rem; color: var(--text-faint); }
.file-preview-name { font-weight: 600; margin-top: 12px; word-break: break-word; }
.file-preview-type { color: var(--text-muted); font-size: 0.85rem; margin-top: 2px; }

@media (max-width: 575.98px) {
  .file-preview-overlay { padding: 0; }
  .file-preview-modal { max-width: 100%; max-height: 100vh; height: 100vh; border-radius: 0; }
  .file-preview-image { max-height: 60vh; }
  .file-preview-pdf { height: 65vh; }
}

/* Task calendar */
.cal-view-btn.active { background: var(--brand-primary); border-color: var(--brand-primary); color: #fff; }

.calendar-month-grid {
  display: grid; grid-template-columns: repeat(7, 1fr); border-top: 0.5px solid var(--border-color);
  border-left: 0.5px solid var(--border-color); border-radius: 10px; overflow: hidden;
}
.calendar-month-head {
  padding: 7px 10px; font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--text-muted); border-right: 0.5px solid var(--border-color); border-bottom: 0.5px solid var(--border-color);
  background: var(--surface-sunken);
}
.calendar-day-cell {
  min-height: 84px; padding: 5px 6px; border-right: 0.5px solid var(--border-color); border-bottom: 0.5px solid var(--border-color);
  cursor: pointer; display: flex; flex-direction: column; gap: 2px; transition: background-color 0.1s ease;
}
.calendar-day-cell:hover { background: rgba(var(--brand-primary-rgb), 0.05); }
.calendar-day-cell.calendar-outside-month { background: var(--surface-sunken); }
.calendar-day-cell.calendar-outside-month .calendar-day-number { color: var(--text-faint); }
.calendar-day-cell.calendar-today { background: rgba(var(--brand-primary-rgb), 0.045); }
.calendar-day-cell.calendar-today:hover { background: rgba(var(--brand-primary-rgb), 0.08); }
.calendar-day-number { font-size: 0.76rem; font-weight: 600; align-self: flex-start; line-height: 20px; color: var(--text-muted); }
.calendar-day-cell.calendar-today .calendar-day-number {
  background: var(--brand-primary); color: #fff; width: 20px; height: 20px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; line-height: 1;
}
.calendar-event-chip {
  font-size: 0.68rem; font-weight: 500; line-height: 1.5; padding: 1px 6px; border-radius: 4px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  border-left: 3px solid transparent; cursor: pointer;
}
.calendar-event-chip:hover { filter: brightness(0.96); }
.calendar-more-link { font-size: 0.68rem; color: var(--text-muted); cursor: pointer; padding-left: 6px; }
.calendar-more-link:hover { color: var(--brand-primary); }

/* Status color mapping reused for calendar chips/blocks */
.cal-status-not_started { background: var(--surface-sunken); border-left-color: var(--text-muted); color: var(--text-body); }
.cal-status-wip { background: rgba(var(--brand-info-rgb), 0.14); border-left-color: var(--brand-info); color: #2b6ca8; }
.cal-status-pending { background: rgba(var(--brand-warning-rgb), 0.14); border-left-color: var(--brand-warning); color: #a4661e; }
.cal-status-completed { background: rgba(var(--brand-success-rgb), 0.14); border-left-color: var(--brand-success); color: var(--brand-success); }
.cal-status-cancelled { background: rgba(var(--brand-alert-rgb), 0.12); border-left-color: var(--brand-alert); color: var(--brand-alert); }
.cal-status-reopened { background: rgba(var(--brand-purple-rgb), 0.14); border-left-color: var(--brand-purple); color: var(--brand-purple); }
html[data-bs-theme="dark"] .cal-status-wip,
html[data-bs-theme="dark"] .cal-status-pending { color: #fff; }

.calendar-time-grid { display: grid; grid-template-columns: 56px 1fr; }
/* min-width: 0 on both the grid container and its per-day items — the same fix already
   applied to .calendar-month-grid/.calendar-day-cell, needed here for the identical reason:
   a CSS grid item defaults to min-width: auto and won't shrink below its content's natural
   width, which is what let a 7-column week view overflow a narrow viewport. */
.calendar-time-grid-week .calendar-time-body { display: grid; grid-template-columns: repeat(var(--cal-days, 7), 1fr); position: relative; min-width: 0; }
.calendar-time-grid-day .calendar-time-body { display: grid; grid-template-columns: 1fr; position: relative; }
.calendar-time-col { border-right: 0.5px solid var(--border-color); }
.calendar-hour-label { height: 48px; font-size: 0.7rem; color: var(--text-muted); text-align: right; padding-right: 8px; transform: translateY(-6px); }
.calendar-day-col { position: relative; border-right: 0.5px solid var(--border-color); min-width: 0; }
.calendar-hour-line { height: 48px; border-top: 0.5px solid var(--border-color); }
.calendar-day-col-head { text-align: center; padding: 8px 4px; font-size: 0.78rem; font-weight: 600; border-bottom: 0.5px solid var(--border-color); border-right: 0.5px solid var(--border-color); background: var(--surface-sunken); }
.calendar-event-block {
  position: absolute; left: 4px; right: 4px; border-radius: 6px; padding: 4px 6px; font-size: 0.72rem;
  overflow: hidden; cursor: pointer; border-left: 3px solid transparent; z-index: 2;
}
.calendar-event-block:hover { filter: brightness(0.96); }
.calendar-event-title { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.calendar-event-meta { opacity: 0.85; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

@media (max-width: 767.98px) {
  .calendar-day-cell { min-height: 60px; }
  .calendar-month-head { padding: 6px 4px; font-size: 0.62rem; }
}

/* Team overview dashboard */
.dashboard-header {
  background: linear-gradient(120deg, #14141f, #262640);
  color: #fff; padding: 20px 24px; border-radius: var(--radius-lg);
}

.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 14px; }
.kpi-card {
  background: var(--surface-card); border: 0.5px solid var(--border-color); border-radius: var(--radius-lg);
  border-top: 3px solid var(--brand-primary); padding: 14px 16px; box-shadow: var(--shadow-card);
}
.kpi-card:nth-child(4n+2) { border-top-color: var(--brand-success); }
.kpi-card:nth-child(4n+3) { border-top-color: var(--brand-warning); }
.kpi-card:nth-child(4n+4) { border-top-color: var(--brand-alert); }
.kpi-value { font-size: 1.6rem; font-weight: 700; line-height: 1.2; }
.kpi-label { font-size: 0.74rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.03em; margin-top: 2px; }

.health-icon { font-size: 0.95rem; }

/* Project-scoped special roles */
.role-badge-team_lead { background: rgba(var(--brand-info-rgb), 0.14); color: var(--brand-info); }
.role-badge-project_manager { background: rgba(var(--brand-warning-rgb), 0.14); color: var(--brand-warning); }
.role-badge-project_head { background: rgba(var(--brand-purple-rgb), 0.14); color: var(--brand-purple); }

/* My Availability status */
.status-pill {
  display: inline-flex; align-items: center; gap: 4px; font-size: 0.76rem; padding: 1px 8px;
  border-radius: 999px; background: var(--surface-sunken); color: var(--text-body); white-space: nowrap;
}
.status-catalog-pick.active { background: rgba(var(--brand-primary-rgb), 0.1); color: var(--brand-primary); }
#myStatusTrigger { text-align: left; }

/* Live status dots (sidebar DM/teammate list) — colored by real presence, not decorative */
.dm-dot-available { background: var(--brand-success); }
.dm-dot-temporary { background: var(--brand-warning); }
.dm-dot-unavailable { background: var(--brand-alert); }
.dm-dot-meeting { background: var(--brand-info); }
.dm-dot-offline { background: var(--text-faint); }

/* User profile — hover popover + click modal */
.user-trigger, .user-hover { cursor: pointer; }
.user-name-link:hover { text-decoration: underline; }
.user-hover-popover {
  position: fixed; z-index: 3000; width: 240px; background: var(--surface-card);
  border: 1px solid var(--border-color); border-radius: var(--radius-md); box-shadow: var(--shadow-pop);
  padding: 12px;
}
.user-hover-popover .avatar-circle { width: 40px; height: 40px; font-size: 1rem; }
.user-profile-header { display: flex; align-items: center; gap: 14px; margin-bottom: 18px; }
.user-profile-field { display: flex; justify-content: space-between; gap: 12px; padding: 7px 0; border-bottom: 1px solid var(--border-color); font-size: 0.88rem; }
.user-profile-field:last-child { border-bottom: 0; }
.user-profile-field .label { color: var(--text-muted); flex-shrink: 0; }
.user-profile-field .value { text-align: right; }

/* Reassign / availability panel */
.candidate-card { border: 0.5px solid var(--border-color); border-radius: var(--radius-md); padding: 10px 12px; cursor: pointer; }
.candidate-card:hover { border-color: var(--brand-primary); }
.candidate-card.selected { border-color: var(--brand-primary); background: rgba(var(--brand-primary-rgb), 0.06); }
.candidate-card.recommended { border-color: var(--brand-success); }
.workload-bar { width: 100%; height: 5px; border-radius: 999px; background: var(--surface-sunken); overflow: hidden; margin-top: 4px; }
.workload-bar-fill { height: 100%; }

/* Calls — uses the --call-* tokens defined in the main :root block above (deliberately
   theme-independent) rather than --surface-*/--text-* (which flip with the theme toggle) or
   ad hoc hex literals throughout this block. */
.call-overlay {
  position: fixed; inset: 0; z-index: 3000; background: rgba(6,6,9,0.9);
  display: flex; align-items: center; justify-content: center;
}

/* §Call Page Shell — while a call/meeting is active, navigating anywhere loads the destination
   into this full-viewport iframe instead of a real top-level navigation, so the call's JS
   runtime (WebRTC, polling, the overlay above) is never torn down. z-index sits below
   .call-overlay's 3000 so the call widget always renders on top of whatever's loaded inside.
   See assets/js/call_nav_guard.js. */
#callPageShellFrame { position: fixed; inset: 0; z-index: 2000; width: 100%; height: 100%; border: 0; background: var(--surface-page); }
/* 1:1 call overlay — same containing-block issue .group-call-room had; pins directly to the
   viewport instead of relying on inherited centering. */
#callOverlay {
  position: absolute;
  /* 25% as before, but pulled up on short viewports so a taller card (chat panel open, video
     canvas) still fits on screen instead of running off the bottom. */
  --call-top: clamp(12px, calc(100vh - 680px), 25vh);
  top: var(--call-top);
  right: 5%;
}
.call-card {
  max-height: calc(100vh - var(--call-top, 12px) - 12px); overflow-y: auto;
  width: 420px; max-width: 92vw; background: var(--call-surface); color: #fff; border-radius: 16px;
  padding: 36px 28px 28px; display: flex; flex-direction: column; align-items: center; gap: 6px;
  border: 1px solid var(--call-border); box-shadow: 0 20px 60px rgba(0,0,0,0.5);
  transition: width 0.15s ease, height 0.15s ease, max-height 0.15s ease, border-radius 0.15s ease, padding 0.15s ease;
}
/* Floating call window chrome — drag handle (left) + minimize/maximize/close (right), pulled
   flush to .call-card's own edges via negative margins matching its padding above. Dragging
   itself moves #callOverlay (the positioned ancestor, see the JS), not .call-card — this row
   is only the grab surface and the three window-state buttons. */
.call-window-header {
  display: flex; align-items: center; justify-content: space-between; width: 100%;
  margin: -36px -28px 14px; padding: 10px 12px; cursor: move; border-radius: 16px 16px 0 0;
  touch-action: none;
}
.call-window-drag-handle { color: rgba(255,255,255,0.32); font-size: 0.85rem; }
.call-window-controls { display: flex; gap: 4px; }
.call-window-btn {
  width: 26px; height: 26px; border-radius: 7px; border: none; background: rgba(255,255,255,0.08); color: #fff;
  display: inline-flex; align-items: center; justify-content: center; font-size: 0.7rem; transition: background-color 0.15s ease;
}
.call-window-btn:hover { background: rgba(255,255,255,0.22); }
.call-window-btn-close:hover { background: var(--brand-alert); }

/* Minimized — collapses the whole card into a small horizontal bar (name/avatar/mini-controls
   only); the call itself, its media streams, and its signaling/poll loop are all untouched,
   see setMinimized() in calls.js. The header is unwrapped via display:contents so its button
   cluster becomes an inline part of the row instead of a separate stacked bar; the drag handle
   icon is hidden since the whole minimized bar is the drag surface at that point. */
.call-card.is-minimized {
  flex-direction: row; align-items: center; gap: 8px; width: auto; max-width: none;
  padding: 8px 10px; border-radius: 999px; cursor: move;
}
.call-card.is-minimized .call-window-header { display: contents; }
.call-card.is-minimized .call-window-drag-handle { display: none; }
.call-card.is-minimized .call-window-controls { order: 99; }
.call-card.is-minimized #callMaximizeBtn { display: none; }
/* Unwraps .call-meta-bar the same way .call-window-header above is unwrapped — its two
   children (name, status row) become direct flex items of the minimized pill instead of a
   nested flex row, so the existing minimized-pill sizing rules below apply exactly as they
   did before .call-meta-bar was introduced. */
.call-card.is-minimized .call-meta-bar { display: contents; }
.call-card.is-minimized .call-avatar { width: 32px; height: 32px; font-size: 0.85rem; margin: 0; }
.call-card.is-minimized .call-peer-name { font-size: 0.8rem; max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.call-card.is-minimized .call-status-row { margin: 0; }
.call-card.is-minimized .call-status-text { font-size: 0.68rem; }
.call-card.is-minimized .call-video-area { display: none; }
.call-card.is-minimized .call-controls { gap: 4px; }
.call-card.is-minimized .call-btn, .call-card.is-minimized .call-btn:has(.call-btn-label) { width: 30px; height: 30px; border-radius: 50%; }
.call-card.is-minimized .call-btn-label { display: none; }

/* Maximized — the card grows to fill most of the viewport; #callOverlay itself is
   re-centered by the JS (setMaximized()) rather than staying wherever it was dragged to,
   since a maximized window anchored off to one side would look broken. */
.call-card.is-maximized {
  width: 96vw; max-height: 92vh;
}
.call-card.is-minimized { max-height: none; overflow: visible; }
.call-card.is-maximized .call-video-area #remoteVideo { max-height: 62vh; }
.call-avatar {
  width: 84px; height: 84px; border-radius: 50%; background: var(--brand-primary); color: #fff;
  display: flex; align-items: center; justify-content: center; font-size: 2rem; font-weight: 600; margin-bottom: 8px;
  overflow: hidden;
}
/* §Call avatars — the profile-photo case (see callAvatarInnerHtml() in main.js); the
   initials-letter case above needs no img rule since it's just text content. .call-avatar
   itself is hidden via JS (syncAvatarVisibility() in calls.js) the moment .call-video-area
   actually has a live feed showing, so the two never overlap on screen. */
.call-avatar img { width: 100%; height: 100%; object-fit: cover; }
/* Horizontal meta bar (title/name left, status+duration right) — see the .call-meta-bar
   markup comment in footer.php. align-self:stretch overrides .call-card's own
   align-items:center (which would otherwise shrink this to its content width and center it)
   so the space-between split actually has the full card width to work with. */
.call-meta-bar { display: flex; align-items: center; justify-content: space-between; gap: 10px; align-self: stretch; margin-bottom: 18px; }
.call-peer-name { font-size: 1.15rem; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.call-status-row { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.call-status-text { font-size: 0.85rem; color: rgba(255,255,255,0.65); }
/* Sourced from the peer connection's own connectionState (read-only — see calls.js/
   group_calls.js's existing onconnectionstatechange handlers), not a new signaling channel. */
.call-connection-dot { width: 7px; height: 7px; border-radius: 50%; background: rgba(255,255,255,0.25); flex-shrink: 0; }
.call-connection-dot.connecting { background: var(--brand-warning); }
.call-connection-dot.connected { background: var(--brand-success); }
.call-connection-dot.failed { background: var(--brand-alert); }
.call-video-area { position: relative; width: 100%; margin-bottom: 18px; }
.call-video-area #remoteVideo {
  display: block; width: 100%; max-height: 380px; aspect-ratio: 16 / 10; background: var(--call-video-bg); border-radius: 14px; object-fit: cover;
}
/* Shown over #remoteVideo whenever the peer has no live video (audio-only, camera unavailable/
   off, still connecting) — replaces what used to be an empty light box. Sits before #localVideo
   in the DOM so the self-view PIP paints on top of it. */
.call-remote-placeholder {
  position: absolute; inset: 0; border-radius: 14px; background: var(--call-video-bg);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
  color: rgba(255,255,255,0.7); font-size: 0.85rem;
}
.call-remote-placeholder .call-avatar { width: 72px; height: 72px; font-size: 1.6rem; margin: 0; }
.call-video-area:fullscreen .call-remote-placeholder { border-radius: 0; }
/* The peer is sharing a screen: show all of it (letterboxed), never crop it like a camera feed.
   Set from the peer's meta-channel state (see onRemoteMediaState() in calls.js). */
.call-video-area.remote-sharing #remoteVideo { object-fit: contain; }
.call-remote-mic { color: var(--brand-alert); font-size: 0.8rem; }

/* Phones: the call is a full-screen sheet (it used to be a 420px floating card that left the video
   small with the self-view sitting on top of it). The video area takes all the height the header,
   controls and chat leave, so a shared screen or a camera feed is as large as possible; the
   self-view shrinks to a corner thumbnail. Minimized keeps its small floating pill. Uses
   !important on position only because dragging/minimizing write inline top/left/right. */
@media (max-width: 575.98px) {
  #callOverlay:has(.call-card:not(.is-minimized)) { top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important; transform: none !important; width: 100%; --call-top: 0px; }
  .call-card:not(.is-minimized) { width: 100%; max-width: none; height: 100dvh; max-height: 100dvh; border-radius: 0; border: 0; padding: 14px 14px 18px; }
  .call-card:not(.is-minimized) .call-window-header { margin: -14px -14px 6px; }
  .call-card:not(.is-minimized) .call-meta-bar { margin-bottom: 10px; }
  .call-card:not(.is-minimized) .call-video-area { flex: 1 1 0; min-height: 0; margin-bottom: 10px; }
  .call-card:not(.is-minimized) .call-video-area #remoteVideo,
  .call-card:not(.is-minimized) .call-remote-placeholder { height: 100%; max-height: none; aspect-ratio: auto; }
  .call-card:not(.is-minimized) .call-video-area #localVideo,
  .call-card:not(.is-minimized) .call-video-area #callLocalAvatarPlaceholder { width: 84px; bottom: 8px; right: 8px; }
  .call-card:not(.is-minimized) .call-video-area { display: block; }
  .call-card:not(.is-minimized) .call-video-area.d-none { display: none; }
  .call-card:not(.is-maximized) #callMaximizeBtn { display: none; }
  /* Audio/ringing: avatar block centred vertically, controls pinned to the bottom edge. */
  .call-card:not(.is-minimized) .call-controls { margin-top: auto; }
  .call-card.is-audio:not(.is-minimized) .call-avatar { margin-top: auto; }
}
.call-video-area #localVideo {
  position: absolute; bottom: 10px; right: 10px; width: 96px; border-radius: 10px; border: 2px solid rgba(255,255,255,0.5);
  background: var(--call-video-bg); object-fit: cover;
}
.call-video-area #callLocalAvatarPlaceholder {
  position: absolute; bottom: 10px; right: 10px; width: 96px; aspect-ratio: 4 / 3; border-radius: 10px;
  border: 2px solid rgba(255,255,255,0.5); background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.6);
  display: flex; align-items: center; justify-content: center; font-size: 1.6rem;
}
/* Native Fullscreen API target (#callFullscreenBtn requestFullscreen()s this exact element —
   see calls.js). Without this block #remoteVideo kept its small-card max-height:340px cap even
   though .call-video-area itself was stretched to fill the whole screen by the browser, leaving
   a huge dead black area below a tiny video and stranding #callFullscreenBtn (anchored top/right
   to the now full-viewport container) looking disconnected from the actual visible video. */
.call-video-area:fullscreen {
  display: flex; align-items: center; justify-content: center; width: 100vw; height: 100vh;
  margin: 0; background: #000; border-radius: 0;
}
.call-video-area:fullscreen #remoteVideo { max-height: 100vh; height: 100%; border-radius: 0; }
.call-video-area:fullscreen #localVideo { width: 160px; }
.call-fullscreen-btn {
  position: absolute; top: 8px; right: 8px; width: 30px; height: 30px; border-radius: 8px; border: none;
  background: rgba(0,0,0,0.45); color: #fff; display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.85rem; opacity: 0; transition: opacity 0.15s ease; z-index: 1;
}
.call-video-area:hover .call-fullscreen-btn, .call-fullscreen-btn:focus-visible { opacity: 1; }
/* Three-group toolbar (secondary | primary | additional) — group calls only, a 1:1 call's
   360px card has no room for this layout. The primary .call-controls row itself (mic/camera/
   leave, defined below) is unchanged; this just wraps it with two flanking slots for later
   passes (Chat/Reactions/More/Record/Host) to add buttons into without another restructure. */
/* z-index above the Participants/Chat panels (z-index:2 below) — this row is a normal-flow
   flex child of .group-call-room, but the panels are position:absolute and span the room's
   full height (top:0;bottom:0), so without this the panel could sit visually/interactively
   on top of the Team/Chat toggle buttons living in .call-controls-secondary here instead of
   the other way around. Flex items respect z-index without needing position:relative, but it's
   set explicitly for clarity. */
.call-controls-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; position: relative; z-index: 3; }
.call-controls-secondary, .call-controls-additional { display: flex; align-items: center; gap: 8px; flex: 1; }
.call-controls-additional { justify-content: flex-end; }
.call-icon-btn {
  position: relative; width: 40px; height: 40px; border-radius: 12px; border: none; background: rgba(255,255,255,0.08); color: #fff;
  display: inline-flex; align-items: center; justify-content: center; font-size: 1.05rem; transition: background-color 0.15s ease;
}
.call-icon-btn:hover, .call-icon-btn.active { background: var(--brand-primary); }
.call-controls { display: flex; gap: 14px; }
/* Only inside the new toolbar-bar wrapper does the primary row need to center itself between
   the secondary/additional slots — .call-controls is also reused as-is by the 1:1 card's
   outgoing/incoming/active rows, which aren't in that wrapper and must keep their existing
   (unconstrained) sizing. */
.call-controls-bar .call-controls { justify-content: center; flex: 1; }
.call-btn {
  width: 54px; height: 54px; border-radius: 50%; border: none; display: inline-flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 2px; font-size: 1.2rem; color: #fff;
  transition: transform 0.15s ease, background-color 0.15s ease;
}
.call-btn:hover { transform: translateY(-2px); }
.call-btn:has(.call-btn-label) { width: 66px; height: 60px; border-radius: 16px; }
.call-btn-label { font-size: 0.62rem; font-weight: 500; color: rgba(255,255,255,0.85); }
.call-btn-accept { background: var(--brand-success); }
.call-btn-decline, .call-btn-hangup { background: var(--brand-alert); }
.call-btn-toggle { background: rgba(255,255,255,0.12); }
/* .active previously looked identical (generic lighter white) regardless of which button it
   was — a muted mic and an active screen-share were visually indistinguishable. Split by
   meaning: mute/camera-off is "this capability is currently OFF" (warning tint); screen-share
   is a feature actively working (positive tint). Targeted by id since .call-btn-toggle is
   shared by both meanings across the 1:1 and group call rows — the JS already toggles .active
   on exactly these 5 buttons unchanged, this is a pure CSS split of an existing hook. */
#toggleMicBtn.active, #toggleCamBtn.active, #groupToggleMicBtn.active, #groupToggleCamBtn.active {
  background: rgba(var(--brand-alert-rgb), 0.5);
}
#toggleScreenBtn.active, #groupToggleScreenBtn.active { background: rgba(var(--brand-success-rgb), 0.5); }
#callChatBtn { position: relative; }
#callChatBtn.active { background: rgba(var(--brand-primary-rgb), 0.6); }

/* Audio call — a centred, avatar-led layout (no video canvas to anchor the horizontal meta bar
   to), with a soft pulse while ringing and an explicit muted indicator. Same tokens/components
   as the video layout; only arrangement differs. */
.call-card.is-audio .call-avatar { width: 112px; height: 112px; font-size: 2.6rem; margin: 6px 0 10px; }
.call-card.is-audio .call-meta-bar { flex-direction: column; justify-content: center; gap: 4px; text-align: center; margin-bottom: 14px; }
.call-card.is-audio .call-peer-name { font-size: 1.3rem; max-width: 100%; }
.call-card.is-ringing .call-avatar { animation: call-avatar-pulse 1.6s ease-out infinite; }
@keyframes call-avatar-pulse {
  0% { box-shadow: 0 0 0 0 rgba(var(--brand-primary-rgb), 0.55); }
  100% { box-shadow: 0 0 0 22px rgba(var(--brand-primary-rgb), 0); }
}
.call-mic-state {
  display: inline-flex; align-items: center; gap: 6px; font-size: 0.75rem; color: #fff;
  background: rgba(var(--brand-alert-rgb), 0.5); padding: 3px 10px; border-radius: 999px; margin-bottom: 10px;
}
.call-card.is-minimized .call-mic-state, .call-card.is-minimized .call-chat-panel, .call-card.is-minimized #callChatBtn { display: none !important; }

/* 1:1 in-call chat — inline panel inside the call card (the group room uses a side drawer). */
.call-chat-panel {
  width: 100%; display: flex; flex-direction: column; margin-bottom: 14px; overflow: hidden;
  background: rgba(255,255,255,0.04); border: 1px solid var(--call-border); border-radius: 12px;
}
.call-chat-panel.d-none { display: none !important; }
.call-chat-panel .group-call-chat-messages { flex: none; height: 200px; }
@media (max-height: 900px) { .call-chat-panel .group-call-chat-messages { height: 110px; } }
/* When the card is height-capped (short viewport / chat open) let it scroll instead of squeezing
   its children — that squeezed the round avatar into an oval and clipped the chat input. */
.call-card > * { flex-shrink: 0; }

/* Transient status messages from the call layer (camera fallback, permission problems) —
   non-blocking replacements for alert(), above the call overlay. */
.call-toast-host {
  /* Above the call control row (which sits at the bottom of the call window/sheet) so a toast
     never covers Mute/Leave/Record while it is showing. */
  position: fixed; left: 50%; bottom: calc(104px + env(safe-area-inset-bottom)); transform: translateX(-50%); z-index: 3200;
  display: flex; flex-direction: column; gap: 8px; align-items: center; width: min(92vw, 460px); pointer-events: none;
}
.call-toast {
  background: var(--call-surface); color: #fff; border: 1px solid var(--call-border); border-radius: 12px;
  padding: 10px 14px; font-size: 0.85rem; box-shadow: 0 8px 28px rgba(0,0,0,0.45); pointer-events: auto;
}
.call-toast-error { border-color: var(--brand-alert); }

/* Screen-share chooser — exactly two options; anchored above the Share button. */
.call-share-menu {
  position: fixed; z-index: 3200; min-width: 220px; padding: 6px; display: flex; flex-direction: column; gap: 2px;
  background: var(--call-surface); border: 1px solid var(--call-border); border-radius: 12px; box-shadow: 0 12px 36px rgba(0,0,0,0.5);
}
.call-share-menu button {
  display: flex; align-items: center; gap: 10px; border: none; background: transparent; color: #fff;
  padding: 10px 12px; border-radius: 8px; font-size: 0.85rem; text-align: left;
}
.call-share-menu button:hover, .call-share-menu button:focus-visible { background: rgba(255,255,255,0.1); }

/* Group Calling — a separate room from .call-card above (which is a fixed-width single-
   peer layout with no room for a participant grid). Reuses .call-controls/.call-btn/
   .call-avatar/.call-peer-name/.call-status-text verbatim for the incoming-ring state
   (#groupCallIncoming), same visual language as the 1:1 overlay. */
.group-call-room {
  position: fixed; top: 0; left: 0; right: 0; margin: 0 auto;
  /* Definite height (not content-sized) so #groupCallGrid can fill what's left after the header
     and control bar — that is what removes the dead space around 1-2 tiles. dvh follows the
     mobile browser toolbar; vh is the fallback. */
  width: 96vw; height: min(92vh, 940px); height: min(92dvh, 940px); max-height: 92vh; background: var(--call-bg); color: #fff; border-radius: 16px;
  padding: 20px; display: flex; flex-direction: column; gap: 16px; overflow: hidden;
  border: 1px solid var(--call-border); box-shadow: 0 20px 60px rgba(0,0,0,0.5);
}
.group-call-header { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }
.group-call-header-left, .group-call-header-right { display: flex; align-items: center; gap: 10px; min-width: 0; }
.group-call-title { font-weight: 600; font-size: 1.05rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.group-call-status-text { font-size: 0.82rem; color: rgba(255,255,255,0.65); flex-shrink: 0; }
.group-call-participant-count, .group-call-duration {
  font-size: 0.78rem; color: rgba(255,255,255,0.75); background: rgba(255,255,255,0.08);
  padding: 3px 10px; border-radius: 999px; flex-shrink: 0;
}
.call-fullscreen-btn#groupCallFullscreenBtn {
  position: static; opacity: 1; background: rgba(255,255,255,0.08); width: 30px; height: 30px;
}

/* Minimized group call/meeting — collapses into a small draggable bottom-right bar. Only the
   header (title/count/duration/minimize) and a slimmed Leave-only control bar stay visible;
   grid/panels are hidden via display:none, never removed, so audio/video/WebRTC keep running
   untouched underneath. See "Floating group call/meeting window" in group_calls.js. */
.group-call-room.is-minimized {
  top: auto; left: auto; right: 20px; bottom: 20px; margin: 0;
  width: 260px; height: auto; max-height: none; padding: 10px 12px; gap: 0; box-shadow: 0 8px 28px rgba(0,0,0,0.45);
}
.group-call-room.is-minimized .group-call-header { cursor: move; flex-wrap: nowrap; }
.group-call-room.is-minimized .group-call-title { font-size: 0.85rem; }
.group-call-room.is-minimized .group-call-status-text,
.group-call-room.is-minimized #groupCallFullscreenBtn,
.group-call-room.is-minimized .group-call-grid,
.group-call-room.is-minimized .group-call-presenting,
.group-call-room.is-minimized .group-call-participants-panel,
.group-call-room.is-minimized .group-call-chat-panel,
.group-call-room.is-minimized .call-controls-secondary,
.group-call-room.is-minimized .call-controls-additional,
.group-call-room.is-minimized #groupToggleMicBtn,
.group-call-room.is-minimized #groupToggleCamBtn,
.group-call-room.is-minimized #groupToggleScreenBtn { display: none; }
.group-call-room.is-minimized .group-call-participant-count,
.group-call-room.is-minimized .group-call-duration { font-size: 0.68rem; padding: 2px 7px; }
.group-call-room.is-minimized .call-controls-bar { padding: 0; margin-top: 8px; justify-content: flex-end; }
.group-call-room.is-minimized .call-controls { gap: 4px; }
.group-call-room.is-minimized .call-btn { width: 30px; height: 30px; border-radius: 50%; }
.group-call-room.is-minimized .call-btn-label { display: none; }

/* ===== Group call layout ==========================================================
   updateGridLayout() in group_calls.js only labels #groupCallGrid; every arrangement below is
   CSS, so it follows the room's real size (resize, zoom, rotation) with no JS measuring.

   The grid fills the room's remaining height (the room has a definite height, the grid is
   flex:1 with min-height:0), and tracks are minmax(0,1fr) so tiles share the space exactly —
   no dead area, no overlap, no horizontal scroll. Tiles are cell-sized (video is object-fit:
   cover), not fixed-aspect, which is what lets 1 or 2 people use the whole area.

   Modes
   - no stage : .grid-count-N (1..12), then .grid-count-many (auto-fit, scrolls).
   - stage    : .has-stage — a shared screen (or a manually pinned tile) is the main area,
                everyone else is a side panel (desktop) / a strip under it (portrait phones).
   container-type:size makes the grid a size container so the stage can be exactly as tall as
   the visible grid (100cqh) and stay put (sticky) while a long side panel scrolls. */
.group-call-grid {
  display: grid; gap: 12px; flex: 1 1 0; min-height: 0; overflow-x: hidden; overflow-y: auto;
  container-type: size;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  grid-auto-rows: minmax(140px, 1fr);
}
.group-call-grid.grid-count-1 { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr); }
.group-call-grid.grid-count-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: minmax(0, 1fr); }
.group-call-grid.grid-count-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: repeat(2, minmax(0, 1fr)); }
.group-call-grid.grid-count-6 { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-rows: repeat(2, minmax(0, 1fr)); }
.group-call-grid.grid-count-9 { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-rows: repeat(3, minmax(0, 1fr)); }
.group-call-grid.grid-count-10, .group-call-grid.grid-count-11, .group-call-grid.grid-count-12 {
  grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-rows: repeat(3, minmax(0, 1fr));
}
/* Odd counts: a 4/6-column track grid where each tile spans 2, so the short last row can be
   centred (tiles stay the same size as the rest instead of stretching). */
.group-call-grid.grid-count-3 { grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-rows: repeat(2, minmax(0, 1fr)); }
.group-call-grid.grid-count-5, .group-call-grid.grid-count-7, .group-call-grid.grid-count-8 {
  grid-template-columns: repeat(6, minmax(0, 1fr)); grid-template-rows: repeat(var(--rows), minmax(0, 1fr));
}
.group-call-grid.grid-count-5 { --rows: 2; }
.group-call-grid.grid-count-7, .group-call-grid.grid-count-8 { --rows: 3; }
.group-call-grid.grid-count-3 > .group-call-tile,
.group-call-grid.grid-count-5 > .group-call-tile,
.group-call-grid.grid-count-7 > .group-call-tile,
.group-call-grid.grid-count-8 > .group-call-tile { grid-column: span 2; }
.group-call-grid.grid-count-3 > .group-call-tile:nth-child(3) { grid-column: 2 / span 2; }
.group-call-grid.grid-count-5 > .group-call-tile:nth-child(4) { grid-column: 2 / span 2; }
.group-call-grid.grid-count-7 > .group-call-tile:nth-child(7) { grid-column: 3 / span 2; }
.group-call-grid.grid-count-8 > .group-call-tile:nth-child(7) { grid-column: 2 / span 2; }
.group-call-grid.grid-count-8 > .group-call-tile:nth-child(8) { grid-column: 4 / span 2; }

/* Stage mode — desktop/tablet/landscape: stage left, side panel right. The panel column has a
   sensible width range; its rows share the height (min 120px each) and the grid scrolls when
   there are more people than fit, while the stage stays sticky at full visible height. */
.group-call-grid.has-stage {
  grid-template-columns: minmax(0, 1fr) clamp(170px, 22%, 280px);
  grid-template-rows: repeat(var(--others, 1), minmax(120px, 1fr));
  grid-auto-rows: minmax(120px, 1fr);
}
.group-call-grid.has-stage > .group-call-tile { grid-column: 2; }
.group-call-grid.has-stage > .group-call-tile.is-stage {
  grid-column: 1; grid-row: 1 / span var(--others, 1); align-self: start;
  position: sticky; top: 0; height: 100cqh; z-index: 1;
}

.group-call-presenting {
  display: flex; align-items: center; gap: 8px; padding: 6px 12px; border-radius: 10px;
  background: rgba(255,255,255,0.08); font-size: 0.85rem; font-weight: 600;
}

/* Layout switches (join/leave, share start/stop, pin) fade the tiles in instead of snapping;
   .is-relayout is added for ~250ms by updateGridLayout() only when the mode actually changed. */
.group-call-grid.is-relayout > .group-call-tile { animation: call-tile-relayout 0.25s ease; }
@keyframes call-tile-relayout { from { opacity: 0.55; transform: scale(0.985); } to { opacity: 1; transform: none; } }

.group-call-tile {
  position: relative; min-width: 0; min-height: 0; container-type: size; background: var(--call-video-bg); border-radius: 14px;
  overflow: hidden; display: flex; align-items: center; justify-content: center;
  border: 2px solid transparent; transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.group-call-tile.speaking { border-color: var(--brand-success); box-shadow: 0 0 0 3px rgba(var(--brand-success-rgb), 0.25); }
/* A shared screen must never be cropped — letterbox it on the dark canvas. */
.group-call-tile.is-sharing video { object-fit: contain; background: var(--call-video-bg); }
/* Transparent rather than display:none until it has real video: a hidden <video> is not
   guaranteed to decode or report a size, and the tile needs that to know a frame has arrived
   (see pc.ontrack in group_calls.js). Audio keeps playing either way. */
.group-call-tile video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0; pointer-events: none; }
.group-call-tile.has-video video { opacity: 1; pointer-events: auto; }
.group-call-tile.has-video .group-call-tile-avatar { display: none; }
.group-call-tile-avatar {
  width: clamp(40px, 34cqmin, 132px); height: clamp(40px, 34cqmin, 132px); font-size: clamp(1rem, 12cqmin, 2.8rem); border-radius: 50%; background: var(--brand-primary); color: #fff;
  display: flex; align-items: center; justify-content: center; font-size: 1.3rem; font-weight: 600;
  overflow: hidden;
}
.group-call-tile-avatar img { width: 100%; height: 100%; object-fit: cover; }
.group-call-tile-scrim {
  position: absolute; left: 0; right: 0; bottom: 0; height: 46%; pointer-events: none;
  background: linear-gradient(to top, rgba(0,0,0,0.75), transparent);
}
.group-call-tile-name {
  position: absolute; left: 8px; bottom: 6px; font-size: 0.76rem; font-weight: 500; color: #fff;
  text-shadow: 0 1px 3px rgba(0,0,0,0.8); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: calc(100% - 16px);
}
.group-call-tile-mic-off {
  position: absolute; right: 6px; bottom: 6px; width: 22px; height: 22px; border-radius: 50%;
  background: var(--brand-alert); color: #fff; display: flex; align-items: center; justify-content: center; font-size: 0.7rem;
}
/* Hover controls stay hidden until hover/focus-within, per the brief — pin is a purely local
   display preference (no backend); fullscreen-tile uses the standard Fullscreen API on that
   tile. Deliberately no per-tile "mute" control: there is no backend capability to mute a
   remote participant, only self-mute, so a button that looked like it did would be misleading. */
.group-call-tile-hover-actions {
  position: absolute; top: 6px; right: 6px; display: flex; gap: 4px; opacity: 0; transition: opacity 0.15s ease;
}
.group-call-tile:hover .group-call-tile-hover-actions, .group-call-tile:focus-within .group-call-tile-hover-actions {
  opacity: 1;
}
.group-call-tile-action {
  width: 26px; height: 26px; border-radius: 7px; border: none; background: rgba(0,0,0,0.55); color: #fff;
  display: inline-flex; align-items: center; justify-content: center; font-size: 0.75rem;
}
.group-call-tile-action.active, .group-call-tile-action:hover { background: var(--brand-primary); }

/* Participants panel — a self-contained slide-in scoped to .group-call-room's own stacking
   context (see the comment on .group-call-room's markup for why this isn't Bootstrap's real
   .offcanvas component). */
.group-call-participants-panel {
  position: absolute; top: 0; right: 0; bottom: 0; width: min(280px, 80vw);
  background: var(--call-surface); border-left: 1px solid var(--call-border);
  display: flex; flex-direction: column; transform: translateX(100%); transition: transform 0.2s ease; z-index: 2;
}
.group-call-participants-panel.open { transform: translateX(0); }
.group-call-participants-header {
  display: flex; align-items: center; justify-content: space-between; padding: 14px 16px;
  border-bottom: 1px solid var(--call-border); font-weight: 600; font-size: 0.9rem;
}
.group-call-participants-header span#groupCallParticipantsPanelCount { font-weight: 400; color: rgba(255,255,255,0.6); }
.group-call-participants-list { flex: 1; overflow-y: auto; padding: 8px; }
.group-call-participant-row {
  display: flex; align-items: center; gap: 10px; padding: 8px; border-radius: 10px;
}
.group-call-participant-row:hover { background: rgba(255,255,255,0.06); }
.group-call-participant-name { flex: 1; font-size: 0.85rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.group-call-participant-row i { color: rgba(255,255,255,0.6); font-size: 0.9rem; }

/* Chat panel — same shell shape as the Participants panel above (own class rather than a
   shared base, see the markup comment on group_call_room.php for why). Message rows inside
   reuse .message-row/.message-body/.message-sender/.message-meta/.message-content verbatim
   (defined once, above, for the main chat pane) — this panel just sets an explicit white text
   color so they're legible against the call UI's dark background instead of the app's default
   (light-theme-oriented) text color. */
.group-call-chat-panel {
  position: absolute; top: 0; right: 0; bottom: 0; width: min(300px, 85vw);
  background: var(--call-surface); border-left: 1px solid var(--call-border); color: #fff;
  display: flex; flex-direction: column; transform: translateX(100%); transition: transform 0.2s ease; z-index: 2;
}
.group-call-chat-panel.open { transform: translateX(0); }
.group-call-chat-messages { flex: 1; overflow-y: auto; padding: 8px; }
.group-call-chat-messages .message-sender, .group-call-chat-messages .message-content { color: #fff; }
.group-call-chat-messages .message-meta { color: rgba(255,255,255,0.5); }
/* --surface-card (the default bubble background) is a light-theme-oriented token that would
   be invisible or wrong against this always-dark panel — same reasoning as the sender/content
   color override above. */
.group-call-chat-messages .message-bubble { background: rgba(255,255,255,0.1); border-color: rgba(255,255,255,0.12); }
/* .attachment-chip's own not-mine override (see its definition above) targets --text-body,
   a light-theme-oriented token that's wrong here — this panel is always dark regardless of
   sender, same reasoning as the sender/content/bubble overrides above. */
.group-call-chat-messages .attachment-chip, .group-call-chat-messages .attachment-chip a { color: #fff !important; }
.group-call-chat-form { display: flex; gap: 8px; padding: 10px; border-top: 1px solid var(--call-border); }
.group-call-chat-input {
  flex: 1; min-width: 0; background: rgba(255,255,255,0.08); border: 1px solid var(--call-border);
  border-radius: 10px; padding: 8px 12px; color: #fff; font-size: 0.85rem;
}
.group-call-chat-input::placeholder { color: rgba(255,255,255,0.4); }
.group-call-chat-form .call-icon-btn { width: 38px; height: 38px; flex-shrink: 0; }
.call-icon-btn-badge {
  position: absolute; top: -4px; right: -4px; min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: 999px; background: var(--brand-alert); color: #fff; font-size: 0.62rem; font-weight: 600;
  display: flex; align-items: center; justify-content: center; line-height: 1;
}

@media (max-width: 575.98px) {
  /* Full-screen room on phones. The tile grid itself is handled by the portrait rules below
     (this breakpoint is by width; portrait is by orientation). */
  .group-call-room { width: 100vw; height: 100vh; height: 100dvh; max-height: 100dvh; border-radius: 0; padding: 12px; }
  .group-call-room.is-minimized { width: 260px; height: auto; max-height: none; border-radius: 16px; padding: 10px 12px; }
  .call-btn:has(.call-btn-label) { width: 56px; height: 52px; }
  .call-btn-label { display: none; }
  .group-call-duration, .group-call-participant-count { display: none; }
  .group-call-tile-hover-actions { opacity: 1; }
  /* Bottom sheet instead of a full-width side panel sliding in from the right — matches the
     brief's explicit "convert it into a drawer/sheet/modal" mobile requirement. Same .open
     class toggle as the desktop side panel (see group_calls.js/call_chat.js — neither needed
     any JS change), just a different transform axis and anchor edge at this breakpoint. */
  .group-call-participants-panel, .group-call-chat-panel {
    top: auto; left: 0; right: 0; bottom: 0; width: 100vw; height: 70vh; max-height: 70vh;
    border-left: none; border-top: 1px solid var(--call-border); border-radius: 16px 16px 0 0;
    transform: translateY(100%);
  }
  .group-call-participants-panel.open, .group-call-chat-panel.open { transform: translateY(0); }
}

/* Portrait phones/tablets (narrow + taller than wide): columns are scarce and height is plentiful,
   so tiles go 1-2 columns wide and stack; a shared screen sits on top (sticky, 60% of the visible
   height) with the other people as a 3-up strip below that scrolls with the page instead of
   being squeezed into a side column. Landscape phones keep the desktop layouts above. */
@media (max-width: 767.98px) and (orientation: portrait) {
  .group-call-grid.grid-count-2 { grid-template-columns: minmax(0, 1fr); grid-template-rows: repeat(2, minmax(0, 1fr)); }
  .group-call-grid.grid-count-3, .group-call-grid.grid-count-4, .group-call-grid.grid-count-5, .group-call-grid.grid-count-6,
  .group-call-grid.grid-count-7, .group-call-grid.grid-count-8 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .group-call-grid.grid-count-3 { grid-template-rows: repeat(2, minmax(0, 1fr)); }
  .group-call-grid.grid-count-4 { grid-template-rows: repeat(2, minmax(0, 1fr)); }
  .group-call-grid.grid-count-5, .group-call-grid.grid-count-6 { grid-template-rows: repeat(3, minmax(0, 1fr)); }
  .group-call-grid.grid-count-7, .group-call-grid.grid-count-8 { grid-template-rows: repeat(4, minmax(0, 1fr)); }
  .group-call-grid.grid-count-3 > .group-call-tile, .group-call-grid.grid-count-5 > .group-call-tile,
  .group-call-grid.grid-count-7 > .group-call-tile, .group-call-grid.grid-count-8 > .group-call-tile { grid-column: auto; }
  .group-call-grid.grid-count-3 > .group-call-tile:nth-child(3), .group-call-grid.grid-count-5 > .group-call-tile:nth-child(5),
  .group-call-grid.grid-count-7 > .group-call-tile:nth-child(7) { grid-column: 1 / -1; }
  .group-call-grid.grid-count-5 > .group-call-tile:nth-child(4),
  .group-call-grid.grid-count-8 > .group-call-tile:nth-child(7),
  .group-call-grid.grid-count-8 > .group-call-tile:nth-child(8) { grid-column: auto; }
  .group-call-grid.grid-count-9, .group-call-grid.grid-count-10, .group-call-grid.grid-count-11, .group-call-grid.grid-count-12 {
    grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: none; grid-auto-rows: minmax(120px, 1fr);
  }
  .group-call-grid.grid-count-many { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  .group-call-grid.has-stage {
    grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-rows: auto; align-content: start;
    /* Explicit row height = a 4:3 tile at one third of the width (tiles are size containers, so
       an aspect-ratio on them doesn't size the track by itself). 100cqw is the grid's width. */
    grid-auto-rows: calc((100cqw - 24px) / 4);
  }
  .group-call-grid.has-stage > .group-call-tile { grid-column: auto; }
  /* Stage takes whatever the strip below it doesn't need (--strip-rows is 1 or 2, set by
     updateGridLayout(); each 3-up row is ~96px), never less than 220px. */
  .group-call-grid.has-stage > .group-call-tile.is-stage {
    grid-column: 1 / -1; grid-row: 1; aspect-ratio: auto;
    height: max(220px, calc(100cqh - var(--strip-rows, 1) * ((100cqw - 24px) / 4 + 12px)));
  }
}

/* Short landscape screens (phones on their side): the header + controls would otherwise take half
   the height, so the room goes edge-to-edge with slim chrome and the tile grids run wider instead
   of taller (faces fit portrait-shaped tiles well); big rooms scroll rather than shrink tiles. */
@media (max-height: 520px) and (orientation: landscape) {
  .group-call-room:not(.is-minimized) { width: 100vw; height: 100dvh; max-height: 100dvh; border-radius: 0; padding: 8px 12px; gap: 8px; }
  .group-call-room .call-btn:has(.call-btn-label) { width: 48px; height: 44px; }
  .group-call-room .call-btn-label { display: none; }
  .group-call-grid { gap: 8px; }
  .group-call-grid.grid-count-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-rows: minmax(0, 1fr); }
  .group-call-grid.grid-count-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-rows: minmax(0, 1fr); }
  .group-call-grid.grid-count-7, .group-call-grid.grid-count-8 { grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-rows: repeat(2, minmax(0, 1fr)); }
  .group-call-grid.grid-count-3 > .group-call-tile, .group-call-grid.grid-count-3 > .group-call-tile:nth-child(3),
  .group-call-grid.grid-count-7 > .group-call-tile, .group-call-grid.grid-count-7 > .group-call-tile:nth-child(7),
  .group-call-grid.grid-count-8 > .group-call-tile, .group-call-grid.grid-count-8 > .group-call-tile:nth-child(7),
  .group-call-grid.grid-count-8 > .group-call-tile:nth-child(8) { grid-column: auto; }
  .group-call-grid.grid-count-9, .group-call-grid.grid-count-10, .group-call-grid.grid-count-11, .group-call-grid.grid-count-12 {
    grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-rows: none; grid-auto-rows: minmax(110px, 1fr);
  }
}

/* ---- Call recording (assets/js/call_recording.js) ------------------------------------------
   Persistent "recording" indicator (fixed to the viewport, so it stays visible when a call is
   minimized or behind the page shell) and the consent / status / download dialog. Both sit above
   the call overlay (3000) and toasts (3200). */
.call-rec-indicator {
  position: fixed; top: 10px; left: 50%; transform: translateX(-50%); z-index: 3250;
  display: flex; align-items: center; gap: 10px; max-width: calc(100vw - 24px);
  padding: 7px 14px; border-radius: 999px; background: #7a1f16; color: #fff; font-size: 0.85rem; font-weight: 600;
  border: 1px solid rgba(255,255,255,0.25); box-shadow: 0 6px 20px rgba(0,0,0,0.4);
}
.call-rec-indicator.requesting { background: #6b5511; }
.call-rec-indicator .call-rec-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.call-rec-indicator .call-rec-time { font-variant-numeric: tabular-nums; opacity: 0.9; }
.call-rec-indicator .call-rec-stop { padding: 1px 10px; font-size: 0.75rem; }
.call-rec-dot { width: 10px; height: 10px; border-radius: 50%; background: #ff4b3a; flex-shrink: 0; animation: call-rec-pulse 1.2s ease-in-out infinite; }
@keyframes call-rec-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }

.call-rec-modal {
  position: fixed; inset: 0; z-index: 3300; display: flex; align-items: center; justify-content: center;
  padding: 16px; background: rgba(6,6,9,0.78);
}
.call-rec-modal.d-none { display: none !important; }
.call-rec-card {
  width: min(460px, 100%); max-height: calc(100dvh - 32px); overflow-y: auto; padding: 24px;
  background: var(--call-surface); color: #fff; border: 1px solid var(--call-border); border-radius: 16px;
  box-shadow: 0 20px 60px rgba(0,0,0,0.55);
}
.call-rec-title { font-size: 1.15rem; font-weight: 700; margin: 0 0 12px; color: #fff; }
.call-rec-text p { margin: 0 0 8px; }
.call-rec-note { font-size: 0.82rem; color: rgba(255,255,255,0.65); }
.call-rec-consent { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.call-rec-consent li { padding: 6px 10px; border-radius: 8px; background: rgba(255,255,255,0.06); font-size: 0.9rem; }
.call-rec-consent li.ok { color: #6ee7a3; }
.call-rec-consent li.pending { color: rgba(255,255,255,0.7); }
.call-rec-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px; flex-wrap: wrap; }
/* The "Recording complete" screen's save guidance + its Download action get extra emphasis —
   this is the one screen where missing the action means losing the recording for good. */
.call-rec-save-hint { font-size: 0.85rem; color: #fff; background: rgba(255,255,255,0.08); border-radius: 10px; padding: 10px 12px; margin-top: 4px; }
.call-rec-actions:has(.call-rec-download-btn) { flex-direction: column-reverse; align-items: stretch; }
.call-rec-download-btn { font-weight: 700; font-size: 1rem; padding: 12px 16px; }
@media (max-width: 575.98px) {
  /* Bottom sheet on phones — thumb-reachable buttons, and the call behind stays visible above it. */
  .call-rec-modal { align-items: flex-end; padding: 0; }
  .call-rec-card { width: 100%; border-radius: 16px 16px 0 0; padding: 20px 18px calc(18px + env(safe-area-inset-bottom)); }
  .call-rec-actions .btn { flex: 1 1 0; }
  .call-rec-indicator { top: 6px; font-size: 0.78rem; padding: 6px 12px; }
}
/* Record button: red while I am recording, dimmed while someone else is. */
#toggleRecordBtn.recording, #groupToggleRecordBtn.recording { background: rgba(var(--brand-alert-rgb), 0.55); }
#toggleRecordBtn.recording i, #groupToggleRecordBtn.recording i { color: #ff6b5a; animation: call-rec-pulse 1.2s ease-in-out infinite; }
#toggleRecordBtn.busy, #groupToggleRecordBtn.busy { opacity: 0.55; }
.call-card.is-minimized #toggleRecordBtn, .group-call-room.is-minimized #groupToggleRecordBtn { display: none; }
/* Six controls in a row on a phone: tighter gaps + wrapping instead of overflowing. */
@media (max-width: 575.98px) {
  #activeControls.call-controls { gap: 8px; flex-wrap: wrap; justify-content: center; }
  /* 6 buttons fit one row of a 362px sheet at 52px each (5 gaps of 8px). */
  .call-card:not(.is-minimized) #activeControls .call-btn:has(.call-btn-label) { width: 52px; height: 52px; }
  .group-call-room .call-controls-bar { flex-wrap: wrap; justify-content: center; }
  .group-call-room .call-controls { gap: 8px; }
}

/* ---- Announcements (§8) ---- */
.announcement-card { border-left: 3px solid var(--border-color); }
.announcement-card.announcement-pinned { border-left-color: var(--brand-warning); }
.announcement-card.announcement-unread { border-left-color: var(--brand-primary); background: rgba(var(--brand-primary-rgb), 0.03); }
.announcement-title { font-size: 1rem; }

/* ---- Accessibility: reduced motion (§9) ---- */
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; }
  .project-card:hover { transform: none; }
  .message-row.grouped .message-meta { transition: none; }
  .message-row.message-flash { transition: none; }
}
/* Deliberately not styled like a chat message (.message-content) — a distinct card layout
   so an announcement always reads as a formal notice, not another line in a conversation. */
.announcement-content { font-size: 0.9rem; line-height: 1.5; }
.announcement-content p { margin: 0 0 8px; }
.announcement-content p:last-child { margin-bottom: 0; }
.announcement-content ul, .announcement-content ol { margin: 6px 0; padding-left: 22px; }
.announcement-content a { color: var(--brand-primary); text-decoration: underline; word-break: break-all; }

.announcement-banner {
  display: flex; align-items: flex-start; gap: 12px; padding: 14px 16px; border-radius: var(--radius-md);
  background: rgba(var(--brand-warning-rgb), 0.1); border: 1px solid rgba(var(--brand-warning-rgb), 0.3);
}
.announcement-banner > i { color: var(--brand-warning); font-size: 1.2rem; margin-top: 2px; }
.announcement-banner-content { font-size: 0.85rem; color: var(--text-muted); margin-top: 2px; max-height: 3.2em; overflow: hidden; }
.announcement-banner-content p { margin: 0; }

/* ---------------------------------------------------------------
   Mobile responsiveness (320px and up)
   Each rule targets the actual element that was overflowing, not a blanket
   overflow-x: hidden — see the two @media blocks at the bottom for the
   breakpoint-specific pieces (app shell padding, sidebar drawer sizing).
   --------------------------------------------------------------- */

/* Bootstrap's own offcanvas default (--bs-offcanvas-width) is 400px, wider than a
   320-375px phone — the mobile nav drawer itself would need its own horizontal scroll
   or run off the right edge. Cap it relative to the viewport instead of a fixed value. */
.offcanvas { --bs-offcanvas-width: min(320px, 88vw); }
/* Matches the .sidebar it wraps on mobile — avoids a flash of Bootstrap's own default
   offcanvas background at the slide-in edge during the open/close transition. */
#sidebarOffcanvas { background: var(--sidebar-bg); }

/* Fixed 340px was wider than a 320px viewport outright. */
.notif-dropdown { width: 340px; max-width: calc(100vw - 24px); }

/* Same fixed-width-wider-than-a-phone issue as .notif-dropdown above, just never caught —
   this is also a Popper-positioned Bootstrap dropdown-menu, not an in-flow element that
   inherits a responsive parent's width. */
.workspace-switcher-menu { max-width: calc(100vw - 24px); }

/* Long project/channel/DM/workspace names must truncate, not force the sidebar (a fixed
   260px column) or its mobile drawer copy wider than their container. .nav-link-text wraps
   the actual name text in includes/sidebar_content.php — a flex row's icon/badge siblings
   need the text portion isolated like this before text-overflow: ellipsis can take effect,
   since ellipsis doesn't apply to a flex container's box, only to a block/inline text run. */
.sidebar-nav .nav-link { min-width: 0; }
.nav-link-text, .workspace-name {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1;
}

/* Topbar: a long project/page name must truncate instead of pushing the notification
   bell / avatar menu off the right edge of the screen. */
.topbar-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.topbar { gap: 8px; }

/* Chat pane header: same reasoning as .topbar-title above — without flex:1/min-width:0
   here, a long channel or project name could push the call/search icons off-screen instead
   of the header wrapping/overflowing safely. */
.chat-pane-title { min-width: 0; flex: 1; overflow: hidden; }
.chat-pane-title strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: inline-block; max-width: 100%; vertical-align: bottom; }
.call-peer-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }

/* Calendar month grid: without min-width: 0, a CSS grid item defaults to min-width: auto
   and refuses to shrink below its content's natural (unwrapped) width — the actual cause
   of a 7-column grid demanding more total width than the viewport has, even though each
   column is nominally "1fr". The event chip already truncates text (existing
   overflow/ellipsis rule below); this is what lets that truncation actually take effect
   instead of being overridden by the grid track's own sizing. */
.calendar-day-cell { min-width: 0; }
.calendar-month-grid { min-width: 0; }

/* Chat's right-hand info panel is a fixed 240px column on desktop; the general
   .chat-layout mobile rule below stacks it into a column flow, but flex items keep an
   explicit width even in a column container unless told otherwise. */
@media (max-width: 767.98px) {
  .context-panel { width: 100%; }
}

@media (max-width: 575.98px) {
  .content-area { padding: 12px; }
  .kpi-grid { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; }
  .task-stat-row { gap: 10px; }
  .notif-dropdown { width: calc(100vw - 24px); }
  .workspace-switcher-menu { width: calc(100vw - 24px); }
  .call-card { padding: 24px 18px 20px; }
  .call-card:not(.is-minimized) .call-window-header { margin: -24px -18px 14px; }
  .call-avatar { width: 64px; height: 64px; font-size: 1.5rem; }
  .group-call-room.is-minimized { width: calc(100vw - 24px); max-width: 260px; right: 12px; bottom: 12px; }
  /* Text label would overflow the topbar alongside search/theme/notification icons at this
     width — icon-only, same degrade pattern .topbar-search-trigger's own placeholder text
     already uses (d-none d-md-inline). */
  .master-access-toggle-btn span { display: none; }
  .master-access-banner { flex-wrap: wrap; row-gap: 6px; }
}

/* §Task Hierarchy — the Tasks-tab tree (project.php), the task-detail Subtasks list, and the
   Parent Task autocomplete. Reuses existing badge/progress/table styles for everything except
   the indentation/expand chevron/breadcrumb shell itself. */
.task-tree-cell { display: flex; align-items: center; gap: 4px; }
.task-tree-toggle {
  flex-shrink: 0; width: 20px; height: 20px; padding: 0; border: 0; background: transparent;
  color: var(--text-muted); display: inline-flex; align-items: center; justify-content: center;
  border-radius: 4px; cursor: pointer;
}
.task-tree-toggle:hover { background: var(--surface-sunken); color: var(--text-body); }
.task-tree-toggle i { transition: transform 0.12s ease; font-size: 0.8rem; }
.task-tree-toggle-spacer { flex-shrink: 0; width: 20px; height: 20px; display: inline-block; }

.task-parent-autocomplete {
  position: absolute; left: 0; right: 0; top: 100%; margin-top: 2px; z-index: 20;
  background: var(--surface-card); border: 1px solid var(--border-color); border-radius: 8px;
  box-shadow: var(--shadow-card); max-height: 220px; overflow-y: auto; padding: 4px;
}
.task-parent-option {
  display: block; width: 100%; text-align: left; padding: 6px 10px; border: 0; background: transparent;
  color: var(--text-body); font-size: 0.85rem; border-radius: 6px; cursor: pointer;
}
.task-parent-option:hover, .task-parent-option:focus { background: var(--surface-sunken); }

.task-breadcrumb { font-size: 0.8rem; color: var(--text-muted); margin-bottom: 8px; }
.task-breadcrumb a { color: var(--text-muted); text-decoration: none; }
.task-breadcrumb a:hover { color: var(--brand-primary); text-decoration: underline; }
.task-breadcrumb .bi-chevron-right { font-size: 0.65rem; margin: 0 4px; vertical-align: middle; }

.subtask-row { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid var(--border-color); }
.subtask-row:last-child { border-bottom: 0; }
.subtask-row-title { flex: 1; min-width: 0; }
.subtask-row-title a { color: var(--text-body); text-decoration: none; }
.subtask-row-title a:hover { color: var(--brand-primary); }

/* §Mobile table/tab polish — pages/project.php's Channels/Tasks/Calendar/Milestones/Files/
   Members/Review tabs were wrapping into a cramped two-row block on a phone-width screen
   (plain Bootstrap .nav-tabs flex-wraps by default); below tablet width this turns it into a
   single-row, horizontally-scrollable strip instead — the standard native-app tab-bar feel,
   and scrollbar-hidden so it doesn't look like a broken/stray scroll area. Scoped to .nav-tabs
   generally (not just project.php) since any tab bar in the app wrapping to two rows on mobile
   is the same bug. */
@media (max-width: 767.98px) {
  .nav-tabs { flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
  .nav-tabs::-webkit-scrollbar { display: none; }
  .nav-tabs .nav-item { flex-shrink: 0; }
  .nav-tabs .nav-link { white-space: nowrap; padding: 8px 12px; }
}

/* §Mobile table/tab polish — a wide data table (My Tasks: 10 columns; project.php's Tasks tab:
   8; Members: 5) squeezed into .table-responsive's horizontal scroll reads as a broken desktop
   leftover on a phone — every cell in a <tr> shares that row's height, so one tall cell (e.g.
   Members' "Assign Role" column, 4 stacked buttons) stretches every other cell in that row into
   a large block of mostly-empty space too. Below tablet width, opt-in tables (.table-card-mobile
   — added only to the specific tables this was reported against, not every table in the app)
   instead render as a stack of card-like rows: thead hidden, each <tr> becomes its own bordered
   card, each <td> becomes a labeled line using its own data-label attribute (added at the PHP/JS
   call sites, not inferred from <th> text, since a cell's real content can be arbitrary markup
   — badges, avatars, buttons — not just text). */
@media (max-width: 767.98px) {
  .table-card-mobile thead { display: none; }
  .table-card-mobile, .table-card-mobile tbody, .table-card-mobile tr, .table-card-mobile td { display: block; width: 100%; }
  .table-card-mobile tr {
    background: var(--surface-card); border: 1px solid var(--border-color); border-radius: var(--radius-lg);
    margin-bottom: 12px; padding: 10px 12px;
  }
  .table-card-mobile td {
    display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
    padding: 6px 0; border: none; text-align: right;
  }
  .table-card-mobile td:not(:last-child) { border-bottom: 1px solid var(--border-color); }
  .table-card-mobile td[data-label]::before {
    content: attr(data-label); font-weight: 600; font-size: 0.78rem; color: var(--text-muted);
    text-align: left; flex-shrink: 0;
  }
  /* The action-only trailing cell (View/Remove button, no data-label) doesn't need the
     label/value split layout — just a plain full-width row for the button(s). */
  .table-card-mobile td:not([data-label]) { justify-content: flex-end; }
}
