﻿

[hidden] { display: none !important; }

:root {

  --color-primary: #db0f6d;
  --color-primary-hover: #b50c5a;
  --color-primary-tint: rgba(219, 15, 109, 0.12);
  --color-primary-ring: rgba(219, 15, 109, 0.35);

  --glass-bg: rgba(15, 23, 42, 0.92);
  --glass-bg-soft: rgba(15, 23, 42, 0.85);
  --glass-border: rgba(255, 255, 255, 0.09);
  --glass-inner: rgba(255, 255, 255, 0.05);
  --glass-inner-hover: rgba(255, 255, 255, 0.10);
  --glass-divider: rgba(255, 255, 255, 0.07);

  --content-bg: #f8fafc;
  --card-bg: #ffffff;
  --card-border: #e2e8f0;
  --surface-2: #f8fafc;
  --border-soft: #f1f5f9;
  --qline-section-bg: #eef2f6;

  --text-on-dark: #e2e8f0;
  --text-on-dark-muted: #94a3b8;
  --text-on-dark-faint: #64748b;
  --text-main: #0f172a;
  --text-muted: #64748b;

  --status-info: #2563eb;     --status-info-bg: #dbeafe;
  --status-warning: #b45309;  --status-warning-bg: #fef3c7;
  --status-danger: #dc2626;   --status-danger-bg: #fee2e2;
  --status-success: #15803d;  --status-success-bg: #dcfce7;

  --radius-panel: 0.5rem;
  --radius-card: 0.5rem;
  --radius-control: 0.5rem;
  --radius-pill: 9999px;

  --shadow-panel: 0 20px 50px -12px rgba(15, 23, 42, 0.45);
  --shadow-card: 0 1px 3px rgba(15, 23, 42, 0.06), 0 1px 2px rgba(15, 23, 42, 0.04);
  --shadow-pop: 0 12px 32px -8px rgba(15, 23, 42, 0.25);

  --sidebar-w: 280px;
  --sidebar-gap: 14px;
}

[data-theme="dark"] {
  --content-bg: #1e293b;
  --card-bg: #253247;
  --card-border: rgba(255, 255, 255, 0.10);
  --surface-2: rgba(255, 255, 255, 0.05);
  --border-soft: rgba(255, 255, 255, 0.07);
  --qline-section-bg: rgba(255, 255, 255, 0.09);
  --text-main: #e2e8f0;
  --text-muted: #94a3b8;
}

/* The dark slate text utilities have to be flipped, not merely dimmed: a
   `text-slate-600` left at #475569 on a #253247 card reads as DISABLED, which is
   what made the Settings nav look like placeholder text. Anything 600 and darker
   becomes the light end of the same scale. */
[data-theme="dark"] .text-slate-900,
[data-theme="dark"] .text-slate-800 { color: #e2e8f0; }
[data-theme="dark"] .text-slate-700,
[data-theme="dark"] .text-slate-600 { color: #cbd5e1; }
[data-theme="dark"] .text-slate-500,
[data-theme="dark"] .text-slate-400 { color: #94a3b8; }
[data-theme="dark"] .border-slate-200 { border-color: var(--card-border); }
[data-theme="dark"] .border-slate-100 { border-color: var(--border-soft); }
[data-theme="dark"] .bg-white { background-color: var(--card-bg); }
[data-theme="dark"] .bg-slate-50 { background-color: var(--surface-2); }
[data-theme="dark"] .bg-slate-100 { background-color: rgba(255, 255, 255, 0.08); }
[data-theme="dark"] .hover\:bg-slate-50:hover,
[data-theme="dark"] .hover\:bg-slate-100:hover { background-color: rgba(255, 255, 255, 0.07); }
/* QR codes must stay on white to scan reliably */
[data-theme="dark"] #totpQr { background-color: #ffffff; }

[data-theme="dark"] .badge-info    { background: rgba(37, 99, 235, 0.22);  color: #93c5fd; }
[data-theme="dark"] .badge-warning { background: rgba(217, 119, 6, 0.22);  color: #fcd34d; }
[data-theme="dark"] .badge-danger  { background: rgba(220, 38, 38, 0.22);  color: #fca5a5; }
[data-theme="dark"] .badge-success { background: rgba(21, 128, 61, 0.25);  color: #86efac; }
[data-theme="dark"] .badge-neutral { background: rgba(255, 255, 255, 0.08); color: #cbd5e1; }

html { scroll-behavior: smooth; }

/* Thin scrollbars everywhere (page + every scrollable container). Firefox uses
   scrollbar-width/color; WebKit gets the equivalent sizes. Opt-outs that hide
   the bar entirely (.tabs-scroll, [data-sticky-hscroll]) stay more specific. */
* {
  scrollbar-width: thin;
  scrollbar-color: rgba(100, 116, 139, 0.45) transparent;
}
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background-color: rgba(100, 116, 139, 0.45);
  border-radius: 9999px;
}
*::-webkit-scrollbar-thumb:hover { background-color: rgba(100, 116, 139, 0.7); }
[data-theme="dark"] * { scrollbar-color: rgba(148, 163, 184, 0.35) transparent; }
[data-theme="dark"] *::-webkit-scrollbar-thumb { background-color: rgba(148, 163, 184, 0.35); }
[data-theme="dark"] *::-webkit-scrollbar-thumb:hover { background-color: rgba(148, 163, 184, 0.6); }
body {
  background: var(--content-bg);
  color: var(--text-main);
  font-family: 'Roboto', ui-sans-serif, system-ui, sans-serif;
}

:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
  border-radius: var(--radius-control);
}

.icon { width: 1.25rem; height: 1.25rem; flex: none; }
.icon-sm { width: 1rem; height: 1rem; flex: none; }

.app-preloader {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--content-bg);
  opacity: 1;
  transition: opacity 0.35s ease;
}
.app-preloader.is-hiding { opacity: 0; pointer-events: none; }
.preloader-layers {
  width: 64px;
  height: 64px;
  color: var(--color-primary);
}
.preloader-layers path {
  transform-box: fill-box;
  transform-origin: center;
  animation: preloaderLayer 1.4s ease-in-out infinite;
}
.preloader-layers .pl-3 { animation-delay: 0s; }
.preloader-layers .pl-2 { animation-delay: 0.16s; }
.preloader-layers .pl-1 { animation-delay: 0.32s; }
@keyframes preloaderLayer {
  0%, 100% { opacity: 0.25; transform: translateY(3px); }
  45%      { opacity: 1;    transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .preloader-layers path { animation: none; opacity: 1; transform: none; }
}

.switch { position: relative; display: inline-flex; align-items: center; flex: none; cursor: pointer; }
.switch:has(input:disabled) { cursor: not-allowed; }
.switch input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.switch .switch-track {
  width: 2.5rem; height: 1.4rem; border-radius: var(--radius-pill);
  background: var(--card-border); transition: background 0.2s ease;
}
.switch .switch-thumb {
  position: absolute; top: 2px; inset-inline-start: 2px;
  width: 1rem; height: 1rem; border-radius: var(--radius-pill);
  background: #fff; box-shadow: 0 1px 2px rgba(15, 23, 42, 0.25);
  transition: transform 0.2s ease;
}
.switch input:checked + .switch-track { background: var(--color-primary); }
.switch input:checked + .switch-track + .switch-thumb { transform: translateX(1.1rem); }
.switch input:focus-visible + .switch-track { outline: 2px solid var(--color-primary); outline-offset: 2px; }

.sidebar {
  position: fixed;
  inset-block: var(--sidebar-gap);
  inset-inline-start: var(--sidebar-gap);
  width: var(--sidebar-w);
  z-index: 40;
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-panel);
  background: var(--glass-bg);
  -webkit-backdrop-filter: blur(18px);
  backdrop-filter: blur(18px);
  border: 1px solid var(--glass-border);
  box-shadow: var(--shadow-panel);
  color: var(--text-on-dark);
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  overflow: hidden;
}

.sidebar-scroll {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,0.15) transparent;
}

.sb-label,
.sb-trailing { white-space: nowrap; }

.sidebar-logo {
  display: flex; align-items: center; gap: 0.6rem;
  padding: 0.25rem 0.5rem 0.85rem;
  border-block-end: 1px solid var(--glass-divider);
  text-decoration: none;
}

.sidebar-user {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.25rem 0.5rem;
}

.sidebar-bottom {
  border-block-start: 1px solid var(--glass-divider);
  padding-block-start: 0.75rem;
}
.avatar {
  width: 2.75rem; height: 2.75rem; flex: none;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(135deg, var(--color-primary), #7c3aed);
  color: #fff; font-weight: 600; font-size: 0.9rem;
}
.avatar-sm { width: 1.75rem; height: 1.75rem; font-size: 0.65rem; }

.sidebar-section-title {
  display: flex; align-items: center; gap: 0.4rem;
  font-size: 0.72rem; font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-on-dark-faint);
  padding-inline: 0.5rem;
}

.sidebar-item {
  position: relative;
  display: flex; align-items: center; gap: 0.75rem;
  width: 100%;
  padding: 0.65rem 0.85rem;
  border-radius: var(--radius-control);
  color: var(--text-on-dark);
  font-size: 0.9rem; font-weight: 500;
  text-align: start; /* it is a <button> (defaults to center) — a flex-1 label
                        would otherwise centre its text (portal nav misalignment) */
  text-decoration: none;
  transition: background 0.15s ease, color 0.15s ease;
}
.sidebar-item:hover { background: var(--glass-inner-hover); }
.sidebar-item.active {
  background: var(--color-primary);
  color: #fff;
  box-shadow: 0 8px 20px -6px var(--color-primary-ring);
}
.sidebar-item .sb-trailing { margin-inline-start: auto; display: flex; align-items: center; gap: 0.25rem; }

.sidebar-group .submenu-chevron { transition: transform 0.2s ease; color: var(--text-on-dark-faint); }
.sidebar-item.active .sb-trailing .submenu-chevron { color: var(--card-bg); }
.sidebar-group.open .submenu-chevron { transform: rotate(180deg); }
.sidebar-submenu {
  display: none;
  flex-direction: column;
  gap: 2px;
  margin-block-start: 2px;
  margin-inline-start: 1.45rem;
  padding-inline-start: 0.75rem;
  border-inline-start: 1px solid var(--glass-divider);
}
.sidebar-group.open .sidebar-submenu { display: flex; }
.sidebar-subitem {
  display: flex; align-items: center; gap: 0.6rem;
  padding: 0.45rem 0.7rem;
  border-radius: var(--radius-control);
  color: var(--text-on-dark-muted);
  font-size: 0.85rem; font-weight: 500;
  text-decoration: none;
  transition: background 0.15s, color 0.15s;
}
.sidebar-subitem:hover { background: var(--glass-inner-hover); color: #fff; }
.sidebar-subitem.active { color: var(--color-primary); font-weight: 600; }

.sb-badge {
  min-width: 1.35rem; height: 1.35rem;
  padding-inline: 0.35rem;
  border-radius: var(--radius-pill);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--color-primary);
  color: #fff; font-size: 0.68rem; font-weight: 700;
}
.sidebar-item.active .sb-badge { background: rgba(255,255,255,0.25); }

.sb-plus {
  width: 1.5rem; height: 1.5rem;
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--glass-inner-hover);
  color: var(--text-on-dark);
  transition: background 0.15s;
}
.sb-plus:hover { background: var(--color-primary); color: #fff; }

.avatar-stack { display: flex; }
.avatar-stack .avatar-sm { border: 2px solid #1e293b; }
.avatar-stack .avatar-sm + .avatar-sm { margin-inline-start: -0.5rem; }

.sidebar-panel {
  background: var(--glass-inner);
  border: 1px solid var(--glass-divider);
  border-radius: var(--radius-card);
  padding: 0.1rem;
  display: flex; flex-direction: column; gap: 0.125rem;
}

.plugin-item {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.55rem 0.65rem;
  border-radius: var(--radius-control);
  color: var(--text-on-dark);
  font-size: 0.875rem; font-weight: 500;
  text-decoration: none;
  transition: background 0.15s;
}
.plugin-item:hover { background: var(--glass-inner-hover); }
.plugin-icon {
  width: 1.9rem; height: 1.9rem; flex: none;
  border-radius: 0.5rem;
  display: flex; align-items: center; justify-content: center;
  font-size: 0.8rem; color: #fff;
}

.sidebar-iconrow {
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.25rem;
  padding: 0.4rem 0.5rem;
}
.iconrow-btn {
  position: relative;
  width: 2rem; height: 2rem;
  border-radius: var(--radius-control);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-on-dark-muted);
  transition: background 0.15s, color 0.15s;
}
.iconrow-btn:hover { background: var(--glass-inner-hover); color: #fff; }
.iconrow-btn.active { background: var(--color-primary); color: #fff; }

/* Quick-create "+" button in the logo row (opens the create dropdown). */
.sidebar-add {
  width: 2rem; height: 2rem; flex: none;
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  /* Glass effect — translucent, matching the dark glass sidebar (uses the
     shared glass tokens); a top inner sheen + soft drop shadow give depth. */
  background: var(--glass-inner);
  color: var(--text-on-dark);
  border: 1px solid var(--glass-border);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 4px 12px -6px rgba(0, 0, 0, 0.5);
  transition: background 0.15s, transform 0.15s, box-shadow 0.15s, color 0.15s;
}
.sidebar-add:hover {
  background: var(--glass-inner-hover);
  color: #fff;
  transform: scale(1.05);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 6px 16px -6px rgba(0, 0, 0, 0.55);
}

.sidebar:not(.sorting) [data-sid].sb-hidden,
.sidebar:not(.sorting) [data-bid].sb-hidden { display: none; }
.sidebar.sorting [data-sid],
.sidebar.sorting [data-bid] {
  border: 1px dashed rgba(255, 255, 255, 0.18);
  border-radius: var(--radius-control);
}
.sidebar.sorting [data-sid].sb-hidden,
.sidebar.sorting [data-bid].sb-hidden { opacity: 0.45; }
.sidebar.sorting [data-sid].dragging,
.sidebar.sorting [data-bid].dragging { opacity: 0.5; }

.sort-block-bar {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.3rem 0.5rem;
  margin-block-end: 0.35rem;
  border-radius: 0.5rem;
  background: var(--glass-inner);
  font-size: 0.7rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--text-on-dark-muted);
}
.sidebar.sorting .sidebar-submenu { display: none !important; }
.sort-grip {
  display: inline-flex; flex: none;
  cursor: grab;
  color: var(--text-on-dark-faint);

  touch-action: none;
}
.sort-grip:active { cursor: grabbing; }

.sidebar.sorting { user-select: none; -webkit-user-select: none; }

.sort-controls {
  margin-inline-start: auto;
  display: inline-flex; align-items: center; gap: 2px;
  flex: none;
}
.sort-eye,
.sort-move {
  width: 1.6rem; height: 1.6rem; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 0.45rem;
  color: var(--text-on-dark-muted);
  transition: background 0.15s, color 0.15s;
}
.sort-eye:hover,
.sort-move:hover { background: var(--glass-inner-hover); color: #fff; }

.sidebar.sorting .sb-trailing { margin-inline-start: auto; }
.sidebar.sorting .sb-trailing + .sort-controls { margin-inline-start: 0.25rem; }

[data-sort-blocks].sorting-blocks { user-select: none; -webkit-user-select: none; }
[data-sort-blocks].sorting-blocks > [data-block] {
  outline: 1px dashed var(--card-border);
  outline-offset: 2px;
}
[data-sort-blocks].sorting-blocks > [data-block].dragging { opacity: 0.5; }
/* Hidden content blocks: gone outside sort mode, dimmed (still arrangeable) inside it */
[data-sort-blocks]:not(.sorting-blocks) > [data-block].blk-hidden { display: none; }
[data-sort-blocks].sorting-blocks > [data-block].blk-hidden { opacity: 0.45; }
.block-sort-bar {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.35rem 0.6rem;
  border-block-end: 1px solid var(--border-soft);
  background: var(--surface-2);
  border-start-start-radius: var(--radius-card);
  border-start-end-radius: var(--radius-card);
}
.block-sort-grip {
  display: inline-flex; flex: none;
  cursor: grab; color: var(--text-muted);
  touch-action: none;
}
.block-sort-grip:active { cursor: grabbing; }
.block-sort-controls {
  margin-inline-start: auto;
  display: inline-flex; align-items: center; gap: 2px; flex: none;
}
.block-move {
  width: 1.6rem; height: 1.6rem; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 0.45rem; color: var(--text-muted);
  transition: background 0.15s, color 0.15s;
}
.block-move:hover { background: var(--card-border); color: var(--text-main); }

.sidebar-overlay {
  position: fixed; inset: 0; z-index: 35;
  background: rgba(15, 23, 42, 0.5);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  opacity: 0; pointer-events: none;
  transition: opacity 0.25s ease;
}
@media (max-width: 1023px) {

  .sidebar { width: calc(100% - var(--sidebar-gap) * 2); }
  .sidebar { transform: translateX(calc(-100% - var(--sidebar-gap) - 4px)); }
  [dir="rtl"] .sidebar { transform: translateX(calc(100% + var(--sidebar-gap) + 4px)); }
  .sidebar.mobile-open { transform: translateX(0); }
  .sidebar.mobile-open ~ .sidebar-overlay,
  .sidebar-overlay.show { opacity: 1; pointer-events: auto; }
}

.main-area {
  padding-inline-start: calc(var(--sidebar-w) + var(--sidebar-gap) * 2);
}
@media (max-width: 1023px) {
  .main-area { padding-inline-start: 0; }
}

.card {
  background: var(--card-bg);
  border: 1px solid var(--card-border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
}
.card-header {
  display: flex; align-items: center; gap: 0.6rem;
  padding: 1rem 1.25rem;
  border-block-end: 1px solid var(--card-border);
  font-weight: 600; font-size: 0.95rem;
}

/* Form action row — the ONE place a form's Save lives (owner's rule 2026-08-07:
   Save belongs at the BOTTOM of the form, not in the topbar). Sits after the
   last card, right-aligned, Cancel/Back first and the primary action last.
   items-stretch so buttons in the row keep one height (button-row-height rule).
   Use this class on every form page instead of hand-rolling the row. */
.form-actions {
  margin-block-start: 1.5rem;
  /* Room under the row for the AI assistant button (owner, 2026-08-11: Save was
     hidden behind it). .ai-fab is a 3.25rem circle fixed 1.5rem off the bottom
     of the VIEWPORT, so it owns the lowest 4.75rem of the window whatever the
     scroll position; this margin (plus the page wrapper's own bottom padding)
     keeps Save above that band even when the page is scrolled all the way down.
     The row itself stays right-aligned - Save does not move. Keep >= 4.75rem if
     the FAB is ever resized. */
  margin-block-end: 6rem;
  display: flex; flex-wrap: wrap; align-items: stretch;
  justify-content: flex-end; gap: 0.5rem;
}
/* On phones the row goes full width so the primary action is easy to hit. */
@media (max-width: 640px) {
  .form-actions > .btn { flex: 1 1 auto; justify-content: center; }
}

.badge {
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: 0.2rem 0.6rem;
  border-radius: var(--radius-pill);
  font-size: 0.72rem; font-weight: 600;
  white-space: nowrap;
}
.badge-info    { background: var(--status-info-bg);    color: var(--status-info); }
.badge-warning { background: var(--status-warning-bg); color: var(--status-warning); }
.badge-danger  { background: var(--status-danger-bg);  color: var(--status-danger); }
.badge-success { background: var(--status-success-bg); color: var(--status-success); }
.badge-neutral { background: var(--surface-2); color: var(--text-muted); }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
  padding: 0.55rem 1.1rem;
  border-radius: var(--radius-control);
  font-size: 0.875rem; font-weight: 600;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, box-shadow 0.15s;
  border: 1px solid transparent;
}
.btn-primary { background: var(--color-primary); color: #fff; }
.btn-primary:hover { background: var(--color-primary-hover); }
.btn-secondary { background: var(--card-bg); color: var(--text-main); border-color: var(--card-border); }
.btn-secondary:hover { background: var(--surface-2); }
.btn-danger { background: var(--status-danger); color: #fff; }
.btn-danger:hover { background: #b91c1c; }
.btn-ghost { color: var(--text-muted); }
.btn-ghost:hover { background: var(--surface-2); color: var(--text-main); }
.btn:disabled { opacity: 0.65; cursor: default; }
.btn[aria-busy="true"] { cursor: progress; }

.btn-spinner {
  display: inline-block;
  width: 1.05em; height: 1.05em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 9999px;
  animation: btn-spin 0.6s linear infinite;
}
.spinner {
  display: inline-block;
  width: 1.25rem; height: 1.25rem;
  border: 2px solid var(--color-primary);
  border-right-color: transparent;
  border-radius: 9999px;
  animation: btn-spin 0.6s linear infinite;
}
@keyframes btn-spin { to { transform: rotate(360deg); } }

.input {
  width: 100%;
  padding: 0.55rem 0.85rem;
  border: 1px solid var(--card-border);
  border-radius: var(--radius-control);
  font-size: 0.875rem;
  background: var(--card-bg);
  transition: border-color 0.15s, box-shadow 0.15s;
}
.input:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--color-primary-ring);
}
.input.input-error { border-color: var(--status-danger); }
.input.input-error:focus { box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.25); }
.field-error { color: var(--status-danger); font-size: 0.75rem; margin-block-start: 0.3rem; display: none; }
.field.has-error .field-error { display: block; }

.input-icon-wrap { position: relative; }

.input-icon-wrap > .icon-sm {
  position: absolute; inset-inline-start: 0.8rem; top: 50%;
  transform: translateY(-50%);
  color: var(--text-muted); pointer-events: none;
}
.input-icon-wrap .input { padding-inline-start: 2.5rem; }

/* Tabs — pill style (CRM-wide, owner's redesign 2026-07-13): no underline
   under the strip; the active tab is a filled brand-primary pill. Tabs are
   content-sized and left-aligned; the strip carries no padding of its own
   (in-card strips add a px-* utility to align with the card's content). */
.tabs { display: flex; align-items: center; gap: 0.375rem; }
.tab {
  padding: 0.5rem 1.15rem;
  font-size: 0.875rem; font-weight: 500;
  color: var(--text-muted);
  border-radius: var(--radius-control);
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.tab:hover { background: var(--surface-2); color: var(--text-main); }
.tab.active { background: var(--color-primary); color: #fff; font-weight: 600; }
.tab.active:hover { background: var(--color-primary-hover); color: #fff; }
/* Scrollable tab strip (multi-tab pages): on phones the pills swipe/scroll
   horizontally instead of cramming into the narrow width. */
.tabs-scroll { overflow-x: auto; scrollbar-width: none; }
.tabs-scroll::-webkit-scrollbar { display: none; }

.table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
.table th {
  text-align: start;
  padding: 0.75rem 1.25rem;
  font-size: 0.72rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--text-muted);
  background: var(--surface-2);
  border-block-end: 1px solid var(--card-border);
}
/* `.table th` sets text-align:start, which (being more specific) beats the
   Tailwind text-* utilities — so a `<th class="text-end">` header would stay
   left-aligned while its `<td>` cells are right-aligned. Let the alignment
   utilities win on headers too, so numeric columns line up header-to-cell. */
.table th.text-end { text-align: end; }
.table th.text-center { text-align: center; }
.table th.text-start { text-align: start; }
.table td { padding: 0.85rem 1.25rem; border-block-end: 1px solid var(--border-soft); }
.table tbody tr { transition: background 0.12s; }
.table tbody tr:hover { background: var(--surface-2); }

.table th.sticky-action, .table td.sticky-action { position: sticky; inset-inline-end: 0; }
.table td.sticky-action { z-index: 2; vertical-align: top; background: var(--card-bg); box-shadow: -8px 0 14px -12px rgba(15, 23, 42, 0.3); }
/* A STICKY CELL MUST BE OPAQUE. --surface-2 is a translucent white in dark mode -
   right as a tint on a card, fatal here, because the columns sliding underneath
   showed straight through it ("ACTIONS" printed over "WEBSITE"). So the tint is
   LAYERED over an opaque --card-bg instead of used as the background: same shade
   as the other cells on any card colour, but nothing can be read through it. */
.table thead th.sticky-action,
.table tbody tr:hover td.sticky-action {
  background-color: var(--card-bg);
  background-image: linear-gradient(var(--surface-2), var(--surface-2));
}
.table thead th.sticky-action { z-index: 3; }

.table th.th-sortable { cursor: pointer; user-select: none; }
.table th.th-sortable:hover { color: var(--text-main); }
.th-sort { display: inline-flex; align-items: center; gap: 0.3rem; }
.th-sort-ico { width: 0.85rem; height: 0.85rem; flex: none; opacity: 0.35; }
.table th.th-sortable:hover .th-sort-ico { opacity: 0.7; }
.th-sort.is-active .th-sort-ico { opacity: 1; color: var(--color-primary); }

/* E-mailsjablonen → the System card groups its 30-odd transactional templates per
   module: one full-width header row per module (icon + label + count) that folds
   its own rows. A row, not a <th>, so the table keeps its rhythm; every colour is
   a variable, so light and dark both follow. */
.tpl-subhead > td {
  padding: 0;
  background: var(--surface-2);
  border-block-end: 1px solid var(--card-border);
}
.table tbody tr.tpl-subhead:hover { background: transparent; }
.tpl-subhead-btn {
  display: flex; align-items: center; gap: 0.6rem;
  width: 100%; padding: 0.55rem 1.25rem;
  background: none; border: 0; cursor: pointer;
  color: var(--text-main); text-align: start;
}
.tpl-subhead-btn:hover { background: var(--border-soft); }
.tpl-subhead-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.6rem; height: 1.6rem; border-radius: 999px; flex: none;
}
.tpl-subhead-label { font-size: 0.8rem; font-weight: 600; }
.tpl-subhead-count {
  font-size: 0.7rem; font-weight: 600; color: var(--text-muted);
  background: var(--card-bg); border: 1px solid var(--card-border);
  border-radius: 999px; padding: 0.05rem 0.45rem;
}
/* The chevron must not swallow the click that `closest('[data-toggle-sub]')` needs. */
.tpl-subhead-btn svg { pointer-events: none; color: var(--text-muted); flex: none; }

/* Quotes → Items grid: tighter cell padding so the input fields sit closer
   together (the standard list padding is too airy for an editable data grid). */
#qLinesTable th, #qLinesTable td { padding: 3px; }
/* Required line-item cell left empty on a manual save. */
#qLinesTable .qline-invalid { border-color: var(--status-danger); box-shadow: 0 0 0 1px var(--status-danger); }
/* Quantity driven by the measurement calculator — read-only. */
#qLinesTable .qline-ro { background: var(--surface-2); cursor: default; }
/* Optional line: shown with its price but excluded from the totals. */
#qLinesTable tr.qline-option > td { opacity: .72; }
/* Page-break marker row: a slim dashed divider (forces a new PDF page). */
#qLinesTable tr.qline-pagebreak > td { background: var(--qline-section-bg); padding-block: .4rem; }
.qline-pb-label { display: flex; align-items: center; gap: .6rem; }
.qline-pb-line { flex: 1; border-top: 1px dashed var(--card-border); }
.qline-pb-badge { display: inline-flex; align-items: center; gap: .35rem; font-size: .7rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); white-space: nowrap; }
/* Inline discount / quantity-detail tag under a line's description. */
.qline-tag {
  display: inline-flex; align-items: center; gap: .3rem;
  padding: .15rem .5rem; border-radius: 999px; font-size: .75rem; line-height: 1.2;
  background: var(--surface-2); color: var(--text-soft); border: 1px solid var(--card-border);
}
.qline-tag:hover { border-color: var(--color-primary); color: var(--color-primary); }

/* A cell's own text acting as the row's primary action - looks like the text
   around it until you hover, so the table stays calm but the title is clickable. */
.link-plain {
  display: inline; max-width: 100%;
  padding: 0; background: none; border: 0;
  font: inherit; color: inherit; text-align: inherit;
  cursor: pointer;
}
.link-plain:hover { color: var(--color-primary); text-decoration: underline; }

.row-action {
  position: relative;
  width: 2rem; height: 2rem;
  border-radius: 0.5rem;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-muted);
  transition: background 0.15s, color 0.15s;
}
.row-action:hover:not(:disabled) { background: var(--surface-2); color: var(--text-main); }
.row-action.danger:hover:not(:disabled) { background: var(--status-danger-bg); color: var(--status-danger); }
/* A row action that is a DECISION rather than a way to look at something (the
   Leads pool's Take / Reject) wears the same clothes as its labelled twin on the
   kanban card, so one choice looks like one choice in both views: `filled` is
   .btn-primary, `outlined` is .btn-secondary. Same theme variables as the
   buttons, so both follow dark mode with everything else. `box-sizing` is
   border-box, so the outline does not change the 2rem square. */
.row-action.filled { background: var(--color-primary); color: #fff; }
.row-action.filled:hover:not(:disabled) { background: var(--color-primary-hover); color: #fff; }
.row-action.outlined { background: var(--card-bg); color: var(--text-main); border: 1px solid var(--card-border); }
.row-action.outlined:hover:not(:disabled) { background: var(--surface-2); color: var(--text-main); }
/* Icon-only pagers (e.g. the Dashboard agenda arrows) use .row-action and can be
   at the end of their range - a disabled one must read as unavailable, not idle. */
.row-action:disabled { opacity: 0.4; cursor: not-allowed; }
/* The icon must not swallow clicks: a click on the glyph (svg/circle) would
   make it the event target, and delegated handlers using closest('[data-…]')
   could miss the button. Make the icon transparent to pointer events so every
   click inside the button registers as the button itself. */
.row-action svg, .row-action i { pointer-events: none; }

/* Sticky horizontal scrollbar (js/sticky-hscroll.js). The upgraded container's
   own bar is hidden; the proxy below it sticks to the viewport bottom so the
   "scrolls sideways" cue is visible from anywhere in a long table. */
[data-sticky-hscroll] { scrollbar-width: none; }
[data-sticky-hscroll]::-webkit-scrollbar { display: none; }
.sticky-hscroll {
  position: sticky; bottom: 0; z-index: 4;
  overflow-x: auto; overflow-y: hidden;
  background: var(--card-bg);
}
.sticky-hscroll > div { height: 1px; }
.sticky-hscroll { scrollbar-width: thin; scrollbar-color: rgba(100, 116, 139, 0.55) transparent; }
.sticky-hscroll::-webkit-scrollbar { height: 10px; }
.sticky-hscroll::-webkit-scrollbar-track { background: transparent; }
.sticky-hscroll::-webkit-scrollbar-thumb {
  background: rgba(100, 116, 139, 0.55);
  border-radius: 5px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
.sticky-hscroll::-webkit-scrollbar-thumb:hover { background-color: rgba(100, 116, 139, 0.8); }

.tip-floating {
  position: fixed; z-index: 9999; display: none;
  background: #0f172a; color: #fff;
  font-size: 0.7rem; font-weight: 500;
  padding: 0.25rem 0.55rem; border-radius: 0.4rem;
  white-space: nowrap; pointer-events: none;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}

.dropdown { position: relative; }
.dropdown-menu {
  position: absolute;
  inset-inline-end: 0;
  top: calc(100% + 6px);
  min-width: 11rem;
  background: var(--card-bg);
  border: 1px solid var(--card-border);
  border-radius: var(--radius-control);
  box-shadow: var(--shadow-pop);
  padding: 0.35rem;
  z-index: 30;
  display: none;
}
.dropdown.open .dropdown-menu { display: block; }
.dropdown-item {
  display: flex; align-items: center; gap: 0.6rem;
  width: 100%;
  padding: 0.5rem 0.7rem;
  border-radius: 0.5rem;
  font-size: 0.85rem;
  color: var(--text-main);
  cursor: pointer;
  text-align: start;
}
.dropdown-item:hover { background: var(--surface-2); }
.dropdown-item.danger { color: var(--status-danger); }
.dropdown-item.danger:hover { background: var(--status-danger-bg); }
.dropdown-item:disabled { opacity: 0.4; cursor: not-allowed; }
.dropdown-item:disabled:hover { background: transparent; }

/* FlagSelect, compact variant (`mount(..., { compact: true })`): a language/country
   picker that is a PAGE control, not a form field - a small pill showing just the
   flag. Used by the public booking form's language switch. The active option in its
   menu is ticked, like the owner filter below. */
.flag-pill {
  display: inline-flex; align-items: center; gap: 0.4rem;
  padding: 0.3rem 0.5rem 0.3rem 0.45rem;
  border: 1px solid var(--card-border);
  border-radius: 9999px;
  background: var(--card-bg);
  color: var(--text-muted);
  cursor: pointer;
  transition: border-color 0.15s, box-shadow 0.15s, color 0.15s;
}
.flag-pill:hover { border-color: var(--color-primary); color: var(--text-main); }
.flag-pill:focus-visible { outline: none; border-color: var(--color-primary); box-shadow: 0 0 0 3px var(--color-primary-tint); }
.flag-pill .icon-sm { transition: transform 0.15s; }
.dropdown.open .flag-pill { border-color: var(--color-primary); color: var(--text-main); }
.dropdown.open .flag-pill .icon-sm { transform: rotate(180deg); }
.dropdown-item[data-flag-opt].is-active { background: var(--color-primary-tint); color: var(--text-main); font-weight: 600; }

/* Owner-filter (Clients/Quotes) single-select: the trailing check shows ONLY on
   the active option. visibility (not display) keeps every row's label aligned. */
.owner-opt-check { visibility: hidden; margin-inline-start: auto; color: var(--color-primary); }
.owner-opt.is-active .owner-opt-check { visibility: visible; }

.modal-backdrop {
  position: fixed; inset: 0; z-index: 50;
  background: rgba(15, 23, 42, 0.5);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  display: none;
  align-items: center; justify-content: center;
  padding: 1rem;
}
.modal-backdrop.open { display: flex; animation: modal-backdrop-in 0.25s ease-out; }
.modal {
  width: 100%; max-width: 32rem;
  background: var(--card-bg);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-pop);
  animation: modal-in 0.28s cubic-bezier(0.16, 1, 0.3, 1);
  max-height: 90vh;
  overflow-y: auto;
}
.modal-sm { max-width: 26rem; }
@keyframes modal-backdrop-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes modal-in {
  from { opacity: 0; transform: translateY(16px) scale(0.95); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .modal-backdrop.open, .modal { animation: none; }
}

/* E-mail change stepper (profile modal). Dot states: todo / current / done. */
.ec-step .ec-dot { background: var(--surface-2); color: var(--text-muted); border: 1px solid var(--border); transition: background .2s, color .2s; }
.ec-step.is-current .ec-dot { background: var(--color-primary); color: #fff; border-color: transparent; }
.ec-step.is-done .ec-dot { background: var(--status-success); color: #fff; border-color: transparent; }
.ec-step.is-todo { opacity: .5; }

.choice-card {
  display: flex; align-items: center; gap: 0.9rem;
  width: 100%;
  padding: 1rem;
  border: 1px solid var(--card-border);
  border-radius: var(--radius-card);
  text-decoration: none;
  color: var(--text-main);
  transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
}
.choice-card:hover {
  border-color: var(--color-primary);
  background: var(--color-primary-tint);
  box-shadow: 0 0 0 1px var(--color-primary);
}
.choice-icon {
  width: 2.75rem; height: 2.75rem; flex: none;
  border-radius: 0.75rem;
  display: flex; align-items: center; justify-content: center;
  background: var(--color-primary-tint);
  color: var(--color-primary);
}

.toast-region {
  position: fixed;
  inset-block-end: 1.25rem; inset-inline-end: 1.25rem;
  z-index: 60;
  display: flex; flex-direction: column; gap: 0.6rem;
  max-width: 22rem;
}
.toast {
  display: flex; align-items: flex-start; gap: 0.6rem;
  background: var(--card-bg);
  border: 1px solid var(--card-border);
  border-inline-start: 4px solid var(--status-info);
  border-radius: var(--radius-control);
  box-shadow: var(--shadow-pop);
  padding: 0.8rem 1rem;
  font-size: 0.85rem;
  animation: toast-in 0.25s ease;
}
.toast.success { border-inline-start-color: var(--status-success); }
.toast.error   { border-inline-start-color: var(--status-danger); }
.toast.leaving { opacity: 0; transform: translateX(20px); transition: all 0.25s ease; }
@keyframes toast-in {
  from { opacity: 0; transform: translateX(20px); }
  to   { opacity: 1; transform: none; }
}

.empty-state {
  display: none;
  flex-direction: column; align-items: center; justify-content: center;
  text-align: center;
  padding: 3rem 1.5rem;
  color: var(--text-muted);
}
.empty-state.show { display: flex; }

.notif-item {
  display: flex; align-items: flex-start; gap: 0.7rem;
  padding: 0.85rem 1.25rem;
  border-block-end: 1px solid var(--border-soft);
  transition: background 0.12s;
}
.notif-item:hover { background: var(--surface-2); }
.notif-dot {
  width: 0.5rem; height: 0.5rem; flex: none;
  border-radius: 50%;
  background: var(--color-primary);
  margin-block-start: 0.45rem;
}
.notif-item.read .notif-dot { background: transparent; }
.notif-item.read .notif-text { color: var(--text-muted); font-weight: 400; }

.login-page {
  background: linear-gradient(160deg, #1e293b 0%, #0f172a 70%);
  min-height: 100vh;
  min-height: 100dvh;
  position: relative;
  overflow: hidden;
}

.login-orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(90px);
  opacity: 0.5;
  /* Promote each orb to its own compositor layer so the heavy blur is
     rasterized once and only the layer is moved — without this the blurred
     pixels are recomputed every frame, which (under the card's backdrop-filter)
     flickers badly on mobile. translate3d in the keyframe keeps the GPU layer. */
  will-change: transform;
  transform: translateZ(0);
  backface-visibility: hidden;
  animation: orb-float 14s ease-in-out infinite alternate;
}
@keyframes orb-float {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(40px, -30px, 0) scale(1.12); }
}
/* On phones the moving blur under the card's backdrop-filter is the worst case
   for flicker, and the GPU layer alone isn't always enough — hold the orbs
   still there (the gradient + static glow still looks the part). */
@media (max-width: 640px) {
  .login-orb { animation: none; filter: blur(70px); }
}
/* Respect users who asked for less motion. */
@media (prefers-reduced-motion: reduce) {
  .login-orb { animation: none; }
}

.login-card {
  position: relative;
  z-index: 10;
  background: linear-gradient(145deg, rgba(255,255,255,0.12) 0%, rgba(255,255,255,0.04) 100%);
  -webkit-backdrop-filter: blur(22px) saturate(140%);
  backdrop-filter: blur(22px) saturate(140%);
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: var(--radius-panel);
  box-shadow:
    var(--shadow-panel),
    inset 0 1px 0 rgba(255, 255, 255, 0.18);
  color: var(--text-on-dark);
}
.login-card .login-heading,
.login-card .logo-text,
.login-card label { color: #fff; }
.login-card .login-sub,
.login-card .login-foot { color: var(--text-on-dark-muted); }
.login-card .input {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.15);
  color: #fff;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
.login-card .input:focus { background: rgba(255, 255, 255, 0.12); }
.login-card .input::placeholder { color: var(--text-on-dark-faint); }

/* ALTCHA widget (v3). Inline displays stretch to the full card width (the
   library caps itself via --altcha-max-width, default 320px); the floating
   popover keeps its compact size. */
altcha-widget { display: block; width: 100%; }
altcha-widget:not([display='floating']) { --altcha-max-width: 100%; }

/* Widget themes are the library's own skins (vendor/altcha/themes/*.css,
   hooked on altcha-widget[theme=...]); every skin adapts to light/dark
   through light-dark() following the page color scheme. */

/* The login card paints all labels white — inside the widget the text color
   must follow the widget's own (light-dark aware) foreground instead. */
.login-card altcha-widget label { color: var(--altcha-color-base-content, inherit); }

/* Captcha mount points render nothing when the provider is off (or still
   loading) — collapse them so they don't eat an extra space-y gap in the
   auth forms and the Settings -> CAPTCHA card. */
#loginCaptcha:empty, #forgotCaptcha:empty, #resetCaptcha:empty,
#portalCaptcha:empty, #captchaProviderFields:empty { display: none; }

.text-primary { color: var(--color-primary); }
.text-primary:hover { color: var(--color-primary-hover); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}

.iconrow-btn.is-locked {
  opacity: 0.3;
  pointer-events: none;
  cursor: default;
}

.login-lang-toggle {
  display: inline-flex; align-items: center; gap: 0.45rem;
  padding: 0.4rem 0.6rem;
  border-radius: var(--radius-control);
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: #e2e8f0;
  font-size: 0.8rem; font-weight: 600;
  transition: background 0.15s;
}
.login-lang-toggle:hover { background: rgba(255, 255, 255, 0.12); }
.login-lang-flag {
  width: 20px; height: 15px; flex: none;
  border-radius: 2px; object-fit: cover;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.15);
}
.login-lang-menu {
  background: #0f172a;
  border-color: rgba(255, 255, 255, 0.14);
  min-width: 12rem;
}
.login-lang-item {
  display: flex; align-items: center; gap: 0.6rem;
  width: 100%;
  padding: 0.5rem 0.7rem;
  border-radius: 0.5rem;
  font-size: 0.85rem;
  color: #e2e8f0;
  cursor: pointer; text-align: start;
}
.login-lang-item:hover { background: rgba(255, 255, 255, 0.1); }
.login-lang-item.active { background: var(--color-primary); color: #fff; }

.cp-swatch {
  width: 2.5rem; height: 1.75rem; flex: none; padding: 0;
  border: 1px solid var(--border-soft, #e2e8f0); border-radius: 0.4rem;
  background: var(--cp-color, #000); cursor: pointer;
  box-shadow: inset 0 0 0 2px var(--card-bg, #fff);
}
.cp-swatch:hover { border-color: var(--color-primary); }
.pcr-app { z-index: 1000; }

/* Customize Layout — preset palette cards (Settings > Customize Layout). */
.uitheme-preset {
  display: flex; flex-direction: column; align-items: center; gap: 0.4rem;
  padding: 0.6rem 0.4rem; border-radius: var(--radius-control);
  border: 1px solid var(--border-soft, #e2e8f0); background: var(--card-bg);
  cursor: pointer; transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.uitheme-preset:hover { border-color: var(--color-primary); }
.uitheme-preset.active { border-color: var(--color-primary); box-shadow: 0 0 0 2px var(--color-primary-tint); }
.uitheme-preset-swatches { display: flex; }
.uitheme-preset-swatches i {
  display: block; width: 1rem; height: 1.35rem; margin-inline-start: -0.35rem;
  border: 1px solid rgba(255, 255, 255, 0.7); border-radius: 0.2rem; box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.08);
}
.uitheme-preset-swatches i:first-child { margin-inline-start: 0; }

/* ============================================================================
   Client Portal — single-quote landing page ("Accept & sign" deep link).
   Dark hero (mirrors .login-page) + light content cards below. The hero's
   inner content shares the SAME max-width as .qv-body, so the dark band
   reads as a header on top of one container instead of a wider, separate
   strip — kept short (compact vertical rhythm) rather than a tall banner.
   ============================================================================ */
.qv { min-height: 100vh; min-height: 100dvh; background: var(--body-bg, #f8fafc); }

.qv-hero {
  background: linear-gradient(160deg, #1e293b 0%, #0f172a 70%);
  padding-block: 1.25rem clamp(1.75rem, 4vw, 2.5rem);
  color: #fff;
}
/* Same box model as .qv-body (max-width INCLUDES this padding) so the hero's
   content lines up edge-to-edge with the cards below, instead of the hero
   bleeding to the container edge while the cards sit inset by .qv-body's own
   padding. */
.qv-hero-inner { max-width: 72rem; margin: 0 auto; padding-inline: clamp(1rem, 4vw, 3rem); }
.qv-hero-topbar { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding-block-end: 0.85rem; margin-block-end: 1rem; border-block-end: 1px solid rgba(255,255,255,0.12); }
.qv-brand { display: flex; align-items: center; min-height: 2rem; }
.qv-back-btn {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 2.1rem; height: 2.1rem; margin-inline-end: 0.85rem; border-radius: 999px;
  background: rgba(255,255,255,0.08); color: #fff; border: 1px solid rgba(255,255,255,0.15);
  cursor: pointer; -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  transition: background 0.15s ease, transform 0.15s ease;
}
.qv-back-btn:hover { background: rgba(255,255,255,0.18); }
.qv-back-btn:active { transform: scale(0.94); }
.qv-brand img { height: 2rem; width: auto; max-width: 11rem; object-fit: contain; display: block; }
.qv-brand-name { font-weight: 800; font-size: 1.05rem; letter-spacing: -0.01em; }
.qv-contact { display: flex; align-items: center; gap: 1rem; }
.qv-contact-link { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.85rem; color: rgba(255,255,255,0.75); text-decoration: none; }
.qv-contact-link:hover { color: #fff; }
.qv-lang-btn { display: inline-flex; align-items: center; justify-content: center; width: 2.1rem; height: 2.1rem; border-radius: 999px; background: rgba(255,255,255,0.08); color: #fff; border: 1px solid rgba(255,255,255,0.15); cursor: pointer; }
.qv-lang-btn:hover { background: rgba(255,255,255,0.16); }
.qv-lang-menu { min-width: 10rem; }

/* Two columns from tablet up (name+meta | price+CTA), one column on phone —
   the original hero layout, with 50px of breathing room top/bottom. The name
   block sits vertically centered against the (taller) price card. */
.qv-hero-body { display: flex; flex-wrap: wrap; gap: 1.5rem; align-items: center; justify-content: space-between; margin-block: 50px; }
.qv-hero-main { flex: 1 1 22rem; min-width: 0; }
.qv-kicker {
  display: inline-flex; align-items: center; padding: 0.3rem 0.75rem; border-radius: var(--radius-pill);
  background: var(--color-primary); color: #fff; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
}
.qv-title { margin-block-start: 0.7rem; font-size: clamp(1.5rem, 2.8vw, 2.2rem); font-weight: 800; letter-spacing: -0.02em; line-height: 1.15; }
.qv-subtitle { margin-block-start: 0.4rem; color: rgba(255,255,255,0.65); font-size: 0.9rem; max-width: 34rem; }
.qv-meta { display: flex; flex-wrap: wrap; gap: 1.75rem; margin-block-start: 1.1rem; }
.qv-meta-label { display: block; font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.05em; color: rgba(255,255,255,0.45); font-weight: 700; }
.qv-meta-value { display: block; margin-block-start: 0.2rem; font-weight: 700; font-size: 0.95rem; }

.qv-price-card {
  flex: 0 1 29rem; min-width: 24rem;
  background: rgba(255,255,255,0.06);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 1.1rem;
  padding: 1.62rem 1.5rem; /* +20% block padding → a visibly taller card */
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
}
.qv-price-label { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: rgba(255,255,255,0.5); font-weight: 700; }
.qv-price { margin-block-start: 0.3rem; font-size: 2rem; font-weight: 800; letter-spacing: -0.02em; }
.qv-cta-area { margin-block-start: 1.15rem; display: flex; flex-direction: column; gap: 0.6rem; }
.qv-cta-area .btn { width: 100%; }
/* Sign + Decline share one row: Sign dominant (~65%), Decline takes the rest. */
.qv-cta-row { display: flex; gap: 0.6rem; }
.qv-cta-row .btn { width: auto; }
.qv-cta-row .qv-cta-sign { flex: 0 0 65%; }
.qv-cta-row .qv-cta-decline { flex: 1 1 0; }
.qv-cta-hint { font-size: 0.72rem; color: rgba(255,255,255,0.45); text-align: center; margin-block-start: 0.2rem; }

/* Modern quote-view CTA buttons (rendered on the dark glass price card):
   a translucent "download" and a solid brand "sign" that anchors the action. */
.qv-cta-btn {
  justify-content: center;
  gap: 0.55rem;
  padding-block: 0.8rem;
  border-radius: 0.85rem;
  font-weight: 600;
  font-size: 0.95rem;
  transition: transform .12s ease, box-shadow .16s ease, background .16s ease, border-color .16s ease, filter .16s ease;
}
.qv-cta-btn:hover { transform: translateY(-1px); }
.qv-cta-btn:active { transform: translateY(0); }
.qv-cta-download {
  background: rgba(255,255,255,0.09);
  color: #fff;
  border: 1px solid rgba(255,255,255,0.28);
}
.qv-cta-download:hover { background: rgba(255,255,255,0.17); border-color: rgba(255,255,255,0.45); }
.qv-cta-sign {
  background: var(--color-primary);
  color: #fff;
  border: 1px solid transparent;
  box-shadow: 0 12px 26px -10px rgba(0,0,0,0.55);
}
.qv-cta-sign:hover { filter: brightness(1.06); box-shadow: 0 16px 32px -10px rgba(0,0,0,0.6); }
/* Decline: deliberately NOT a solid brand button — an outlined danger action so
   it reads as the opposite of Sign and can never be mistaken for it. */
.qv-cta-decline {
  background: transparent;
  color: #fff;
  border: 1px solid rgba(255,255,255,0.45);
}
.qv-cta-decline:hover { background: rgba(220,38,38,0.22); border-color: rgba(248,113,113,0.8); }
.qv-signed-note { display: flex; align-items: center; gap: 0.5rem; padding: 0.7rem 0.9rem; border-radius: 0.7rem; background: var(--status-success-bg); color: var(--status-success); font-size: 0.85rem; font-weight: 600; }
.qv-declined-note { display: flex; align-items: center; gap: 0.5rem; padding: 0.7rem 0.9rem; border-radius: 0.7rem; background: var(--status-danger-bg); color: var(--status-danger); font-size: 0.85rem; font-weight: 600; }

/* "This quote has been replaced" — the whole page is one centred card, because
   there is nothing else on it to look at. */
.qv-gone {
  max-width: 34rem; margin: 0 auto; padding: clamp(2.5rem, 10vh, 6rem) 1.5rem;
  display: flex; flex-direction: column; align-items: center; gap: 1rem; text-align: center;
}
.qv-gone-icon { width: 2.75rem; height: 2.75rem; color: var(--color-primary); }
.qv-gone h1 { font-size: 1.5rem; font-weight: 600; color: var(--text-main); }
.qv-gone p { color: var(--text-muted); line-height: 1.6; }
.qv-gone .btn { margin-block-start: 0.5rem; }

.qv-body { max-width: 72rem; margin: -1.25rem auto 0; padding: 0 clamp(1rem, 4vw, 3rem) 3rem; display: flex; flex-direction: column; gap: 1.25rem; position: relative; z-index: 5; }
.qv-card { background: var(--card-bg); border: 1px solid var(--card-border); border-radius: var(--radius-card); box-shadow: var(--shadow-card); overflow: hidden; }
.qv-card-header { display: flex; align-items: center; gap: 0.6rem; padding: 1rem 1.25rem; border-block-end: 1px solid var(--card-border); font-weight: 600; font-size: 0.95rem; }

.qv-group { border-block-end: 1px solid var(--card-border); }
.qv-group:last-child { border-block-end: 0; }
.qv-group-head { width: 100%; display: flex; align-items: center; gap: 0.85rem; padding: 1rem 1.25rem; background: none; border: 0; cursor: pointer; text-align: start; }
.qv-group-icon { display: flex; flex-none; align-items: center; justify-content: center; width: 2.3rem; height: 2.3rem; border-radius: 0.65rem; background: var(--surface-2); color: var(--text-muted); }
.qv-group-title { flex: 1; min-width: 0; }
.qv-group-name { font-weight: 700; font-size: 0.92rem; }
.qv-group-sub { display: flex; align-items: center; gap: 0.5rem; margin-block-start: 0.2rem; font-size: 0.78rem; color: var(--text-muted); }
.qv-group-total { flex: none; text-align: end; font-weight: 700; }
.qv-group-chevron { flex: none; color: var(--text-muted); transition: transform 0.15s; }
.qv-group.open .qv-group-chevron { transform: rotate(180deg); }
.qv-group-items { display: none; }
.qv-group.open .qv-group-items { display: block; }
.qv-item { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; padding: 1.05rem 1.25rem 1.05rem 4rem; font-size: 0.85rem; line-height: 1.5; }
.qv-item + .qv-item { border-block-start: 1px dashed var(--card-border); }
.qv-item-desc { flex: 1; min-width: 0; }
.qv-item-qty { flex: none; width: 6rem; text-align: end; color: var(--text-muted); font-size: 0.78rem; white-space: nowrap; }
/* Discount badge column — its own fixed slot after the quantity so the badges
   line up and the (fixed) price column always stays flush right. */
.qv-item-disc { flex: none; width: 4rem; text-align: end; }
.qv-item-total { flex: none; width: 8.5rem; font-weight: 600; white-space: nowrap; text-align: end; }
/* Discounted line: original price struck-through above the discounted total. */
.qv-item-gross { display: block; font-weight: 400; font-size: 0.78rem; color: var(--text-muted); text-decoration: line-through; }
.qv-item-discount { display: inline-block; padding: 0.05rem 0.4rem; border-radius: var(--radius-pill); background: var(--status-success-bg); color: var(--status-success); font-size: 0.72rem; font-weight: 700; }

.qv-attachment { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 1.05rem 1.25rem 1.05rem 4rem; border-block-start: 1px dashed var(--card-border); font-size: 0.88rem; }
.qv-attachment:first-child { border-block-start: 0; }

.qv-attachment-name { display: flex; align-items: center; gap: 0.6rem; min-width: 0; }

/* Dark footer — mirrors the hero so the page is bookended in the brand's dark
   tone, with light text. Desktop: logo + company block on the start edge,
   contact links on the end edge; a full-width divider then copyright (start) +
   legal links (end). Mobile: everything stacks, logo on top. */
.qv-footer {
  margin-block-start: 2.5rem;
  background: linear-gradient(200deg, #1e293b 0%, #0f172a 75%);
  color: rgba(255, 255, 255, 0.72);
}
/* Extra bottom room so the floating Feedback button (fixed, bottom-end) never
   sits on top of the copyright / legal links when scrolled to the very end. */
.qv-footer-inner { max-width: 72rem; margin: 0 auto; padding: 2.75rem clamp(1rem, 4vw, 3rem) 5rem; }
.qv-footer p { margin: 0; }
/* Three equal columns on desktop: company info (start) / logo (center) /
   contact links (end). On phones it stacks with the logo on top (see media). */
.qv-footer-top { display: flex; gap: 1.75rem 3rem; align-items: flex-start; }
.qv-footer-brandcol { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.6rem; align-items: flex-start; }
.qv-footer-logocol { flex: 1; display: flex; justify-content: center; align-self: center; }
.qv-footer-logo { height: 2.1rem; width: auto; max-width: 12rem; object-fit: contain; }
.qv-footer-brand { font-weight: 700; font-size: 0.95rem; color: #fff; }
.qv-footer-line { font-size: 0.82rem; line-height: 1.55; }
.qv-footer-muted { color: rgba(255, 255, 255, 0.45); }
.qv-footer-contact { flex: 1; display: flex; flex-direction: column; gap: 0.6rem; align-items: flex-end; }
.qv-footer-item { display: inline-flex; align-items: center; gap: 0.55rem; font-size: 0.85rem; color: rgba(255, 255, 255, 0.72); text-decoration: none; transition: color 0.15s; }
/* Company info lines (address, VAT) — same icon+text look as the contact
   links column; vertical rhythm comes from the column's own gap. */
.qv-footer-infoline { display: flex; }
.qv-footer-item:hover { color: #fff; }
.qv-footer-item svg { color: var(--color-primary); flex: none; }
.qv-footer-baseline {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.5rem 1.5rem;
  margin-block-start: 1.75rem; padding-block-start: 1.25rem; border-block-start: 1px solid rgba(255, 255, 255, 0.1);
}
.qv-footer-copyright { font-size: 0.72rem; color: rgba(255, 255, 255, 0.4); }
.qv-footer-legal { display: flex; flex-wrap: wrap; gap: 0.4rem 1rem; }
.qv-footer-legal a { font-size: 0.72rem; color: rgba(255, 255, 255, 0.55); text-decoration: none; cursor: pointer; }
.qv-footer-legal a:hover { color: #fff; text-decoration: underline; }

/* Rendered content-page body (portal viewer modal + reused elsewhere). */
.doc-body { color: var(--text-main); line-height: 1.6; font-size: 0.9rem; }
.doc-body h2 { font-size: 1.2rem; font-weight: 700; margin: 1.2rem 0 0.4rem; }
.doc-body h3 { font-size: 1.05rem; font-weight: 700; margin: 0.9rem 0 0.3rem; }
.doc-body p { margin: 0.55rem 0; }
.doc-body ul { list-style: disc; padding-inline-start: 1.5rem; margin: 0.55rem 0; }
.doc-body ol { list-style: decimal; padding-inline-start: 1.5rem; margin: 0.55rem 0; }
.doc-body a { color: var(--color-primary); text-decoration: underline; }
.doc-body:empty:before { content: attr(data-empty); color: var(--text-muted); }

@media (max-width: 640px) {
  .qv-hero-inner { padding-inline: 1rem; }
  .qv-hero-topbar { flex-wrap: wrap; row-gap: 0.6rem; }
  .qv-contact { flex-wrap: wrap; gap: 0.6rem 0.9rem; }
  .qv-contact-link span { display: none; }
  .qv-price-card { flex-basis: 100%; }
  .qv-item, .qv-attachment { padding-inline-start: 1.25rem; }
  /* Narrow screens can't afford fixed columns — let them size to content so the
     description keeps room; alignment across rows relaxes here. */
  .qv-item { gap: 0.6rem; }
  .qv-item-qty, .qv-item-disc, .qv-item-total { width: auto; }
  /* Footer stacks: logo on top, then company info and contacts as a list. */
  .qv-footer-top { flex-direction: column; gap: 1.5rem; }
  .qv-footer-logocol { order: -1; justify-content: flex-start; }
  .qv-footer-contact { align-items: flex-start; margin-block-start: 0.25rem; }
  .qv-footer-baseline { flex-direction: column; align-items: flex-start; gap: 0.6rem; }
}

/* Layered price summary — an invoice-style total block that sits at the end
   edge of the card. Rows: label + amount; discounts render as negatives, the
   final total is emphasised in the brand colour. */
.qv-summary-head { display: flex; align-items: center; gap: 0.6rem; padding: 1rem 1.25rem; border-block-end: 1px solid var(--card-border); font-weight: 600; font-size: 0.95rem; }
.qv-summary-head svg { color: var(--text-muted); }
.qv-summary-body { padding: 0.9rem 1.5rem 1.25rem; }
/* Collapsible "View full breakdown" toggle + the layered detail it reveals. */
.qv-summary-toggle { display: inline-flex; align-items: center; gap: 0.4rem; margin-block-end: 0.6rem; padding: 0.3rem 0; background: none; border: 0; cursor: pointer; font-size: 0.8rem; font-weight: 600; color: var(--color-primary); }
.qv-summary-toggle-chevron { transition: transform 0.15s; }
.qv-summary-toggle.is-open .qv-summary-toggle-chevron { transform: rotate(90deg); }
.qv-summary-detail { border-inline-start: 2px solid var(--card-border); padding-inline-start: 0.9rem; margin-block-end: 0.3rem; }
.qv-sum-row { display: flex; align-items: baseline; justify-content: space-between; gap: 1.5rem; padding: 0.4rem 0; font-size: 0.9rem; }
.qv-sum-label { color: var(--text-muted); }
.qv-sum-value { font-weight: 600; white-space: nowrap; color: var(--text-main); }
.qv-sum-row.is-subtotal .qv-sum-label { color: var(--text-main); font-weight: 600; }
.qv-sum-row.is-neg .qv-sum-value { color: var(--status-success); }
.qv-sum-row.is-muted { padding: 0.2rem 0; font-size: 0.8rem; }
.qv-sum-row.is-muted .qv-sum-value { font-weight: 500; color: var(--text-muted); }
.qv-sum-divider { height: 1px; background: var(--card-border); margin: 0.55rem 0; }
.qv-sum-row.is-total { margin-block-start: 0.35rem; padding-block-start: 0.75rem; border-block-start: 2px solid var(--card-border); }
.qv-sum-row.is-total .qv-sum-label { font-weight: 700; color: var(--text-main); font-size: 1rem; }
.qv-sum-row.is-total .qv-sum-value { font-weight: 800; font-size: 1.2rem; color: var(--color-primary); }
.qv-sum-row.is-info { padding: 0.2rem 0; font-size: 0.8rem; }
.qv-sum-row.is-info .qv-sum-label { color: var(--text-muted); }
.qv-sum-row.is-info .qv-sum-value { font-weight: 600; color: var(--text-muted); }
/* Deposit/subsidy are informational — set them off from the payable block. */
.qv-sum-row.is-info:first-of-type { margin-block-start: 0.5rem; }

/* Feedback drawer — floating trigger + slide-in panel, both fixed to the
   viewport (independent of scroll position on the quote page). */
.qv-feedback-trigger {
  position: fixed; z-index: 40; inset-block-end: 1.5rem; inset-inline-end: 1.5rem;
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.75rem 1.25rem;
  background: var(--color-primary); color: #fff;
  border: 0; border-radius: var(--radius-pill);
  font-size: 0.85rem; font-weight: 700;
  box-shadow: 0 10px 24px -6px rgba(15, 23, 42, 0.35);
  cursor: pointer;
}
.qv-feedback-trigger:hover { background: var(--color-primary-hover); }
/* Unread-replies count bubble on the floating Feedback button. */
.qv-fb-badge {
  position: absolute; inset-block-start: -0.4rem; inset-inline-end: -0.4rem;
  min-width: 1.25rem; height: 1.25rem; padding: 0 0.35rem;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-pill);
  background: var(--status-danger, #dc2626); color: #fff;
  font-size: 0.7rem; font-weight: 700; line-height: 1;
  box-shadow: 0 0 0 2px var(--color-primary);
}
/* Inline unread-replies pill on quote list / dashboard rows. */
.qv-fb-pill {
  display: inline-flex; align-items: center; gap: 0.25rem;
  padding: 0.15rem 0.5rem; border-radius: var(--radius-pill);
  background: var(--status-danger-bg, #fee2e2); color: var(--status-danger, #dc2626);
  font-size: 0.72rem; font-weight: 700; line-height: 1.1; white-space: nowrap;
}
.qv-fb-pill .icon-xs { width: 0.85rem; height: 0.85rem; }

/* Invoice payment — method chooser (segmented pills) + panels. */
.iv-pay-tabs { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-block-end: 1.1rem; }
.iv-pay-tab {
  display: inline-flex; align-items: center; gap: 0.4rem;
  padding: 0.45rem 0.9rem; border-radius: var(--radius-pill);
  border: 1px solid var(--card-border); background: var(--card-bg);
  font-size: 0.82rem; font-weight: 600; color: var(--text-muted);
  cursor: pointer; transition: all 0.15s;
}
.iv-pay-tab:hover:not(:disabled) { border-color: var(--color-primary); color: var(--text-main); }
.iv-pay-tab.is-active { background: var(--color-primary); border-color: var(--color-primary); color: #fff; }
.iv-pay-tab:disabled { opacity: 0.55; cursor: not-allowed; }
.iv-pay-soon {
  padding: 0.05rem 0.4rem; border-radius: var(--radius-pill);
  background: var(--surface-2); color: var(--text-muted);
  font-size: 0.62rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
}
.iv-copy-btn { flex: none; color: var(--text-muted); background: none; border: 0; cursor: pointer; padding: 0.15rem; border-radius: var(--radius-control); }
.iv-copy-btn:hover { color: var(--color-primary); }
.iv-copy-btn .icon-xs { width: 0.9rem; height: 0.9rem; }

/* Inline thread section — a normal card in the quote body (like Price summary),
   collapsed by default and toggled open by the floating feedback button. */
.qv-feedback-inline { display: flex; flex-direction: column; gap: 0.85rem; scroll-margin-block-start: 1rem; }
.qv-feedback-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.qv-feedback-head h2 { font-size: 1.05rem; font-weight: 700; }
.qv-feedback-head button { flex: none; color: var(--text-muted); background: none; border: 0; cursor: pointer; padding: 0.25rem; }
.qv-feedback-sub { font-size: 0.85rem; color: var(--text-muted); }
.qv-feedback-textarea {
  min-height: 5rem; resize: vertical;
  padding: 0.75rem; border: 1px solid var(--card-border); border-radius: var(--radius-control);
  font-size: 0.9rem; font-family: inherit; background: var(--card-bg); color: var(--text-main);
}
.qv-feedback-textarea:focus { outline: none; border-color: var(--color-primary); box-shadow: 0 0 0 3px var(--color-primary-ring); }

/* Conversation thread — chat bubbles: client messages on the end edge, staff
   replies on the start edge. Scrolls; the composer below stays put. */
.qv-feedback-thread { max-height: 24rem; overflow-y: auto; display: flex; flex-direction: column; gap: 0.6rem; padding-inline-end: 0.25rem; }
.qv-feedback-thread:empty { display: none; }
.qv-fb-empty { margin: auto 0; text-align: center; font-size: 0.85rem; color: var(--text-muted); padding: 1.5rem 0.5rem; }
.qv-fb-bubble { max-width: 88%; padding: 0.6rem 0.8rem; border-radius: 1rem; }
.qv-fb-bubble.is-mine { align-self: flex-end; background: var(--color-primary-tint); border-end-end-radius: 0.3rem; }
.qv-fb-bubble.is-team { align-self: flex-start; background: var(--surface-2); border-end-start-radius: 0.3rem; }
.qv-fb-meta { display: flex; align-items: center; gap: 0.5rem; margin-block-end: 0.2rem; font-size: 0.68rem; color: var(--text-muted); }
.qv-fb-author { font-weight: 700; }
.qv-fb-text { white-space: pre-wrap; font-size: 0.85rem; line-height: 1.45; color: var(--text-main); }
.qv-fb-status { display: block; margin-block-start: 0.25rem; font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--text-muted); text-align: end; }

/* Composer — kind chooser + textarea + send, pinned to the drawer bottom. */
.qv-feedback-composer { flex: none; display: flex; flex-direction: column; gap: 0.6rem; border-block-start: 1px solid var(--card-border); padding-block-start: 0.85rem; }
.qv-feedback-kinds { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.qv-feedback-kind { padding: 0.35rem 0.75rem; border-radius: var(--radius-pill); border: 1px solid var(--card-border); background: var(--card-bg); font-size: 0.78rem; font-weight: 600; color: var(--text-muted); cursor: pointer; transition: all 0.15s; }
.qv-feedback-kind:hover { border-color: var(--color-primary); color: var(--text-main); }
.qv-feedback-kind.is-active { background: var(--color-primary); border-color: var(--color-primary); color: #fff; }

@media (max-width: 480px) {
  .qv-feedback-trigger span { display: none; }
  .qv-feedback-trigger { padding: 0.85rem; }
}
.uitheme-preset-name { font-size: 0.7rem; color: var(--text-muted); text-align: center; line-height: 1.15; }

/* ==========================================================================
   In-app documentation (/docs) — nav, index cards and article typography.
   Everything uses the theme variables so dark mode works automatically.
   ========================================================================== */
.docs-nav-group {
  margin: 0.75rem 0 0.25rem; padding-inline: 0.5rem;
  font-size: 0.68rem; font-weight: 600; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--text-muted);
}
.docs-nav-group:first-child { margin-top: 0.25rem; }
.docs-navlink {
  display: flex; align-items: center; gap: 0.55rem;
  padding: 0.45rem 0.6rem; border-radius: 0.55rem;
  font-size: 0.85rem; font-weight: 500; color: var(--text-main);
}
.docs-navlink:hover { background: var(--surface-2); }
.docs-navlink.active { background: var(--color-primary-tint); color: var(--color-primary); }
.docs-navlink .icon-sm { color: var(--text-muted); }
.docs-navlink.active .icon-sm { color: var(--color-primary); }

.docs-indexcard {
  display: flex; align-items: flex-start; gap: 0.75rem;
  padding: 1rem; transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.docs-indexcard:hover { border-color: var(--color-primary); }
.docs-indexcard-sum {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

.docs-prose { font-size: 0.92rem; line-height: 1.65; color: var(--text-main); }
.docs-prose h1 { font-size: 1.45rem; font-weight: 700; line-height: 1.3; margin-bottom: 0.75rem; }
.docs-prose h2 {
  font-size: 1.12rem; font-weight: 700; margin: 1.75rem 0 0.6rem;
  padding-top: 1.1rem; border-top: 1px solid var(--border-soft);
}
.docs-prose h3 { font-size: 0.98rem; font-weight: 600; margin: 1.25rem 0 0.4rem; }
.docs-prose h4 { font-size: 0.9rem; font-weight: 600; margin: 1rem 0 0.3rem; }
.docs-prose p { margin: 0.55rem 0; }
.docs-prose a { color: var(--color-primary); font-weight: 500; text-decoration: none; }
.docs-prose a:hover { text-decoration: underline; }
.docs-prose a .icon-xs { width: 0.8rem; height: 0.8rem; display: inline-block; vertical-align: -0.05em; }
.docs-prose ul, .docs-prose ol { margin: 0.55rem 0; padding-inline-start: 1.35rem; }
.docs-prose ul { list-style: disc; }
.docs-prose ol { list-style: decimal; }
.docs-prose li { margin: 0.25rem 0; }
.docs-prose li > ul { margin: 0.2rem 0 0.2rem; list-style: circle; }
.docs-prose code {
  background: var(--surface-2); border: 1px solid var(--border-soft);
  border-radius: 0.35rem; padding: 0.08rem 0.35rem;
  font-size: 0.82em; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.docs-prose pre {
  background: var(--surface-2); border: 1px solid var(--border-soft);
  border-radius: 0.6rem; padding: 0.85rem 1rem; margin: 0.75rem 0;
  overflow-x: auto;
}
.docs-prose pre code { background: none; border: 0; padding: 0; font-size: 0.8rem; }
.docs-prose img {
  max-width: 100%; border-radius: 0.6rem; margin: 0.75rem 0;
  border: 1px solid var(--border-soft); box-shadow: var(--shadow-soft, 0 1px 3px rgba(0,0,0,0.08));
}
.docs-prose blockquote {
  margin: 0.75rem 0; padding: 0.5rem 1rem;
  border-inline-start: 3px solid var(--color-primary);
  background: var(--surface-2); border-radius: 0 0.5rem 0.5rem 0;
  color: var(--text-muted);
}
.docs-prose hr { border: 0; border-top: 1px solid var(--border-soft); margin: 1.5rem 0; }
.docs-tablewrap { overflow-x: auto; margin: 0.75rem 0; border: 1px solid var(--border-soft); border-radius: 0.6rem; }
.docs-tablewrap table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
.docs-tablewrap th {
  text-align: start; font-weight: 600; background: var(--surface-2);
  padding: 0.55rem 0.8rem; border-bottom: 1px solid var(--border-soft); white-space: nowrap;
}
.docs-tablewrap td { padding: 0.5rem 0.8rem; border-bottom: 1px solid var(--border-soft); vertical-align: top; }
.docs-tablewrap tr:last-child td { border-bottom: 0; }

.docs-callout {
  display: flex; flex-direction: column; gap: 0.15rem;
  margin: 0.85rem 0; padding: 0.7rem 0.95rem;
  border-radius: 0.6rem; border: 1px solid;
}
.docs-callout p { margin: 0; font-size: 0.87rem; }
.docs-callout-head {
  display: flex; align-items: center; gap: 0.4rem;
  font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
}
.docs-callout.is-tip { background: var(--status-success-bg); border-color: transparent; }
.docs-callout.is-tip .docs-callout-head { color: var(--status-success); }
.docs-callout.is-note { background: var(--status-info-bg); border-color: transparent; }
.docs-callout.is-note .docs-callout-head { color: var(--status-info); }
.docs-callout.is-warn { background: var(--status-warning-bg); border-color: transparent; }
.docs-callout.is-warn .docs-callout-head { color: var(--status-warning); }

.docs-toc {
  margin: 0.75rem 0 1rem; padding: 0.75rem 1rem;
  background: var(--surface-2); border: 1px solid var(--border-soft); border-radius: 0.6rem;
}
.docs-toc-title {
  display: block; font-size: 0.7rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted);
  margin-bottom: 0.35rem;
}
.docs-toc ul { list-style: none; margin: 0; padding: 0; columns: 2; column-gap: 1.5rem; }
.docs-toc li { margin: 0.15rem 0; break-inside: avoid; }
.docs-toc a { color: var(--text-main); font-size: 0.85rem; font-weight: 500; }
.docs-toc a:hover { color: var(--color-primary); }
@media (max-width: 640px) { .docs-toc ul { columns: 1; } }

/* Docs dark theme: /api/theme.css re-declares :root surface/border/status vars
   AFTER the [data-theme="dark"] block, so (like the badges above) the docs
   pieces that lean on those vars get explicit dark values. */
[data-theme="dark"] .docs-prose code,
[data-theme="dark"] .docs-prose pre,
[data-theme="dark"] .docs-prose blockquote,
[data-theme="dark"] .docs-toc,
[data-theme="dark"] .docs-navlink:hover {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.09);
}
[data-theme="dark"] .docs-tablewrap,
[data-theme="dark"] .docs-prose hr,
[data-theme="dark"] .docs-prose h2,
[data-theme="dark"] .docs-prose img { border-color: rgba(255, 255, 255, 0.09); }
[data-theme="dark"] .docs-tablewrap th {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.09);
}
[data-theme="dark"] .docs-tablewrap td { border-color: rgba(255, 255, 255, 0.07); }
[data-theme="dark"] .docs-callout.is-tip { background: rgba(21, 128, 61, 0.2); }
[data-theme="dark"] .docs-callout.is-tip .docs-callout-head { color: #86efac; }
[data-theme="dark"] .docs-callout.is-note { background: rgba(37, 99, 235, 0.18); }
[data-theme="dark"] .docs-callout.is-note .docs-callout-head { color: #93c5fd; }
[data-theme="dark"] .docs-callout.is-warn { background: rgba(217, 119, 6, 0.18); }
[data-theme="dark"] .docs-callout.is-warn .docs-callout-head { color: #fcd34d; }

/* ---------------------------------------------------------------------------
   Lightweight rich-text editor (RTE) — shared skin used by the quote send page
   and the lead "Send email" modal. All colours via tokens, so dark mode is
   automatic. (Kept in sync with the inline copy in quote-send.html.)
   --------------------------------------------------------------------------- */
.rte { border: 1px solid var(--card-border); border-radius: var(--radius-control); overflow: hidden; background: var(--card-bg); }
.rte:focus-within { border-color: var(--color-primary); }
.rte-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.15rem; padding: 0.35rem; border-block-end: 1px solid var(--card-border); background: var(--surface-2); }
.rte-btn { display: inline-flex; align-items: center; justify-content: center; height: 1.85rem; min-width: 1.85rem; padding: 0 0.35rem; border-radius: 0.4rem; color: var(--text-muted); cursor: pointer; }
.rte-btn:hover { background: var(--card-bg); color: var(--text-main); }
.rte-sep { width: 1px; align-self: stretch; margin: 0.2rem 0.25rem; background: var(--card-border); }
.rte-editor { min-height: 10rem; max-height: 22rem; overflow-y: auto; padding: 0.6rem 0.75rem; font-size: 0.875rem; line-height: 1.5; color: var(--text-main); outline: none; }
.rte-editor:empty:before { content: attr(data-placeholder); color: var(--text-muted); }
.rte-editor ul { list-style: disc; padding-inline-start: 1.4rem; }
.rte-editor ol { list-style: decimal; padding-inline-start: 1.4rem; }
.rte-editor p { margin: 1em 0; }
.rte-editor ul, .rte-editor ol { margin: 1em 0; }
.rte-editor h1, .rte-editor h2, .rte-editor h3 { margin: .5em 0; }
.rte-editor a { color: #2563eb; }

/* ---------------------------------------------------------------------------
   Lead pipeline stepper — the graphical status track in the lead info modal.
   One node per open stage (new..qualified), plus a converted/lost cap. States:
   done (filled brand), current (ringed), future/muted (outlined).
   --------------------------------------------------------------------------- */
.lead-pipe { display: flex; align-items: flex-start; gap: 0; overflow-x: auto; padding: 0.25rem 0 0.25rem; }
.lead-pipe-step { position: relative; display: flex; flex: 1 1 0; min-width: 4.25rem; flex-direction: column; align-items: center; }
.lead-pipe-step:not(:first-child)::before {
  content: ''; position: absolute; top: 0.85rem; inset-inline-end: 50%; width: 100%; height: 2px;
  background: var(--card-border); z-index: 0;
}
.lead-pipe-dot {
  position: relative; z-index: 1; display: flex; align-items: center; justify-content: center;
  width: 1.75rem; height: 1.75rem; border-radius: 9999px; border: 2px solid var(--card-border);
  background: var(--card-bg); color: var(--text-muted); font-size: 0.75rem; font-weight: 700;
}
.lead-pipe-label { margin-top: 0.35rem; text-align: center; font-size: 0.7rem; line-height: 1.15; color: var(--text-muted); }
.lead-pipe-step.is-done .lead-pipe-dot { background: var(--color-primary); border-color: var(--color-primary); color: #fff; }
.lead-pipe-step.is-done::before { background: var(--color-primary); }
.lead-pipe-step.is-current .lead-pipe-dot { border-color: var(--color-primary); color: var(--color-primary); box-shadow: 0 0 0 4px var(--color-primary-ring); }
.lead-pipe-step.is-current::before { background: var(--color-primary); }
.lead-pipe-step.is-done .lead-pipe-label,
.lead-pipe-step.is-current .lead-pipe-label { color: var(--text-main); font-weight: 600; }
.lead-pipe-step.is-converted .lead-pipe-dot { background: var(--status-success); border-color: var(--status-success); color: #fff; }
.lead-pipe-step.is-converted::before { background: var(--status-success); }
.lead-pipe-step.is-lost .lead-pipe-dot { background: var(--status-danger); border-color: var(--status-danger); color: #fff; }
.lead-pipe-step.is-lost::before { background: var(--status-danger); }
/* on_hold side-lane cap — a parked (not decided) lead. */
.lead-pipe-step.is-hold .lead-pipe-dot { background: var(--status-warning, #b45309); border-color: var(--status-warning, #b45309); color: #fff; }
.lead-pipe-step.is-hold .lead-pipe-label { color: var(--text-main); font-weight: 600; }

/* ---------------------------------------------------------------------------
   Vehicle license-plate component (country-aware, physical look).
   Colours are fixed on purpose — a plate looks the same in light & dark.
   Rendered by public/js/plate.js (Plate.html). Sizes: .plate-sm / .plate-lg.
--------------------------------------------------------------------------- */
.plate {
  display: inline-flex; align-items: stretch; vertical-align: middle; max-width: 100%;
  height: var(--plate-h, 44px); border-radius: 8px; overflow: hidden;
  background: var(--plate-bg, #fff); border: 2px solid var(--plate-border, #111);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .18);
  font-family: 'Arial Narrow', 'Helvetica Neue', Arial, sans-serif; user-select: none;
}
.plate-band {
  flex: none; width: calc(var(--plate-h, 44px) * .56); background: #003399; color: #fff;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1px; padding: 3px 0;
}
.plate-stars { width: calc(var(--plate-h, 44px) * .34); height: calc(var(--plate-h, 44px) * .34); display: block; }
.plate-cc { font-size: calc(var(--plate-h, 44px) * .22); font-weight: 800; line-height: 1; letter-spacing: .5px; }
.plate-num {
  display: flex; align-items: center; padding: 0 calc(var(--plate-h, 44px) * .3);
  font-weight: 800; letter-spacing: .09em; color: var(--plate-ink, #111);
  font-size: calc(var(--plate-h, 44px) * .5); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.plate-empty { opacity: .4; font-weight: 700; }
.plate-sm { --plate-h: 30px; }
.plate-lg { --plate-h: 64px; }
.plate-be { --plate-bg: #fff;     --plate-ink: #b01c2e; --plate-border: #b01c2e; }
.plate-nl { --plate-bg: #f4c500;  --plate-ink: #111;    --plate-border: #111; }
.plate-fr { --plate-bg: #fff;     --plate-ink: #111;    --plate-border: #111; }
.plate-de { --plate-bg: #fff;     --plate-ink: #111;    --plate-border: #111; }
.plate-lu { --plate-bg: #f4c500;  --plate-ink: #111;    --plate-border: #111; }
.plate-eu { --plate-bg: #fff;     --plate-ink: #111;    --plate-border: #94a3b8; }

/* ============================================================
   CALENDAR / AGENDA (FullCalendar 6, vendored)
   The library is themed entirely through its own CSS variables,
   mapped here onto the CRM design tokens - so light/dark and any
   rebranding follow automatically and nothing is hard-coded.
   ============================================================ */
.fc {
  --fc-border-color: var(--border-soft);
  --fc-page-bg-color: var(--card-bg);
  --fc-neutral-bg-color: var(--surface-2);
  --fc-neutral-text-color: var(--text-muted);
  --fc-today-bg-color: var(--color-primary-tint);
  --fc-highlight-color: var(--color-primary-tint);
  --fc-non-business-color: var(--surface-2);
  --fc-event-bg-color: var(--color-primary);
  --fc-event-border-color: var(--color-primary);
  --fc-event-text-color: #fff;
  --fc-list-event-hover-bg-color: var(--surface-2);
  --fc-small-font-size: .8125rem;
  font-family: inherit;
  color: var(--text-main);
}
.fc a { color: inherit; text-decoration: none; }
.fc .fc-col-header-cell-cushion,
.fc .fc-daygrid-day-number { padding: .375rem .5rem; font-weight: 600; }
.fc .fc-col-header-cell { background: var(--surface-2); }
.fc .fc-timegrid-slot-label-cushion,
.fc .fc-list-day-text,
.fc .fc-list-day-side-text { color: var(--text-muted); }
.fc .fc-scrollgrid { border-radius: .75rem; overflow: hidden; }
.fc .fc-event { border-radius: .375rem; padding: 1px 2px; cursor: pointer; }
.fc .fc-event:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
.fc .fc-event-main { font-weight: 500; }
/* A moderated request that still needs a yes/no reads as an outline, not a solid
   block, so a full week shows at a glance what is not confirmed yet. */
.fc .fc-event.cal-pending { background: transparent !important; color: var(--text-main); border-style: dashed; }
.fc .fc-timegrid-now-indicator-line { border-color: var(--color-primary); }
.fc .fc-timegrid-now-indicator-arrow { border-color: var(--color-primary); color: var(--color-primary); }
.fc .fc-list-empty { background: var(--surface-2); color: var(--text-muted); }
/* The view pills and navigation live in the CRM toolbar, so the library's own
   header is switched off (headerToolbar: false) - nothing to style here. */
.cal-shell { min-height: 32rem; }
@media (max-width: 640px) {
  .cal-shell { min-height: 24rem; }
  .fc .fc-toolbar-title { font-size: 1rem; }
}
/* Lunch breaks from calendar_availability: drawn as hatched background events so
   they read as "not bookable" without competing with real appointments. */
.fc .cal-break {
  background-color: transparent !important;
  background-image: repeating-linear-gradient(
    45deg,
    var(--text-muted) 0, var(--text-muted) 1px,
    transparent 1px, transparent 7px
  );
  opacity: .35;
}
/* Public holidays as all-day chips: solid when they block bookings, outlined when
   the user works anyway (decision Q9). Own time off is a soft band behind the day. */
.fc .cal-holiday {
  background-color: var(--surface-2) !important;
  border-color: var(--border-soft) !important;
  font-weight: 600;
}
/* The library paints its event text white on the INNER element, so the override
   has to reach the children - the outer colour alone leaves it unreadable. */
.fc .cal-holiday,
.fc .cal-holiday .fc-event-main,
.fc .cal-holiday .fc-event-title,
.fc .cal-holiday .fc-event-time { color: var(--text-main) !important; }
.fc .cal-holiday.cal-holiday-open { border-style: dashed !important; opacity: .8; }
.fc .cal-timeoff {
  background-color: var(--color-primary) !important;
  opacity: .12;
}

/* ---- Print (Stage 10b: the day sheet) -----------------------------------
   The day sheet is the same page on screen and on paper, so printing is a
   stylesheet job: drop the chrome, drop the colours a laser printer turns into
   grey mud, and keep a visit from being split across two pages. */
.print-only { display: none; }

@media print {
  .no-print, #sidebarMount, .sidebar, .toast-wrap { display: none !important; }
  .print-only { display: block; }
  .main-area { margin: 0 !important; }
  body { background: #fff !important; }
  .card {
    border: 1px solid #cbd5e1 !important;
    box-shadow: none !important;
    background: #fff !important;
    /* A visit is one block: never break an address away from its time. */
    break-inside: avoid;
    page-break-inside: avoid;
  }
  .badge { border: 1px solid #94a3b8 !important; background: #fff !important; color: #0f172a !important; }
  a { text-decoration: none; color: inherit; }
}

/* An imported Google event (Stage 9b): time that is taken, but not by us. Grey
   on purpose - the brand tint means "our own agenda decided this". */
.fc .cal-busy {
  background-color: var(--text-muted, #64748b) !important;
  opacity: .16;
}

/* ---- Multi-user overlay (decision Q1b) ----------------------------------
   The free FullCalendar core has no column-per-user view, so several agendas
   are OVERLAID on one grid: each event takes its owner's calendar colour and
   carries an initials chip, and parallel events sit next to each other in the
   same time column instead of hiding one another. */
.cal-legend-dot {
  width: .75rem; height: .75rem; flex: none;
  border-radius: 9999px; display: inline-block;
}
.fc .cal-owned-by { border-inline-start-width: 4px !important; }
.cal-who {
  display: inline-block;
  margin-inline-end: .25rem;
  padding: 0 .25rem;
  border-radius: .25rem;
  background: rgba(0, 0, 0, .22);
  font-size: .6875rem;
  font-weight: 700;
  letter-spacing: .02em;
}
/* Three people in a week view leave ~37 px per event, so the start time - which
   the position in the grid already tells you - gives way to the initials chip
   and the title. In Day view there is room for everything anyway. */
.fc .fc-timegrid-event.cal-owned-by .fc-event-time { display: none; }
/* An appointment the reader may see but not change: no drag handles, no pointer. */
.fc .fc-event.cal-readonly { cursor: default; }
/* Company events read as a distinct, calmer block - they are nobody's personal
   appointment and must not look like one. */
.fc .fc-event.cal-company { border-style: dotted !important; border-width: 2px !important; }

/* ==========================================================================
   AI assistant panel ("Mumu") - the floating button and the slide-over chat.

   Built entirely on the theme variables, so the dark theme needs no rules of
   its own: the panel is a .card-coloured surface on a --border-soft edge.
   Logical properties throughout (inset-inline-end, padding-inline), so an RTL
   language flips it for free.
   ========================================================================== */
.ai-fab {
  position: fixed;
  inset-block-end: 1.5rem;
  inset-inline-end: 1.5rem;
  z-index: 40;
  width: 3.25rem; height: 3.25rem;
  display: flex; align-items: center; justify-content: center;
  border-radius: 9999px;
  background: var(--color-primary);
  color: #fff;
  box-shadow: var(--shadow-pop);
  transition: transform .18s ease, filter .18s ease;
}
.ai-fab:hover { transform: translateY(-2px); filter: brightness(1.06); }
.ai-fab:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }
/* Out of the way while the panel is open: the panel now floats with a margin, so
   the button would otherwise peek out from behind its rounded corner. */
.ai-fab.is-away { opacity: 0; transform: scale(.9); pointer-events: none; }

/* A floating card, not a drawer glued to the window edge: it keeps a margin on
   every side (--ai-gap), so the page stays visible around it and the panel reads
   as one more card of this CRM rather than a second window. */
.ai-panel {
  --ai-gap: 1.25rem;
  position: fixed;
  inset-block: var(--ai-gap);
  inset-inline-end: var(--ai-gap);
  z-index: 45;
  width: min(27rem, calc(100vw - var(--ai-gap) * 2));
  display: flex; flex-direction: column;
  background: var(--card-bg);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-panel);
  /* The children carry the corners of the card, not their own. */
  overflow: hidden;
  /* Slides in from the inline end; `translate` keeps it correct under RTL, and
     the gap is added so it starts fully off-screen despite the margin. */
  transform: translateX(calc(100% + var(--ai-gap)));
  opacity: 0;
  transition: transform .24s cubic-bezier(0.16, 1, 0.3, 1), opacity .24s ease;
}
[dir="rtl"] .ai-panel { transform: translateX(calc(-100% - var(--ai-gap))); }
.ai-panel.open { transform: none; opacity: 1; }

.ai-panel-head {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  padding: .875rem 1rem;
  border-block-end: 1px solid var(--border-soft);
  background: var(--surface-2);
}
.ai-avatar {
  width: 2rem; height: 2rem; flex: none;
  display: flex; align-items: center; justify-content: center;
  border-radius: 9999px;
  background: color-mix(in srgb, var(--color-primary) 16%, transparent);
  color: var(--color-primary);
}

/* The mark in the HEADER is alive while the panel is open - the assistant is
   listening, and a completely static icon in an open chat looks switched off. Two
   layers, both slow on purpose: a sparkle that tilts and breathes, and one halo
   ring leaving the circle every few seconds. The avatars in the message rows stay
   still: twenty animated icons in a scrolling log would be a fairground. */
.ai-panel.open .ai-panel-head .ai-avatar > svg {
  animation: ai-twinkle 3.6s ease-in-out infinite;
  transform-origin: 55% 55%;
}
.ai-panel-head .ai-avatar { position: relative; }
.ai-panel.open .ai-panel-head .ai-avatar::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 9999px;
  border: 1px solid var(--color-primary);
  animation: ai-halo 3.6s ease-out infinite;
}
/* While an answer is being written the same mark simply hurries: the panel gets
   `is-busy` from setBusy(), so "it is working" is said in the place a reader is
   already looking instead of by a second spinner somewhere else. */
.ai-panel.open.is-busy .ai-panel-head .ai-avatar > svg { animation-duration: 1.1s; }
.ai-panel.open.is-busy .ai-panel-head .ai-avatar::after { animation-duration: 1.1s; }
@keyframes ai-twinkle {
  0%, 100% { transform: rotate(0deg) scale(1); opacity: .85; }
  35%      { transform: rotate(14deg) scale(1.12); opacity: 1; }
  70%      { transform: rotate(-8deg) scale(1.04); opacity: .95; }
}
@keyframes ai-halo {
  0%       { opacity: .5; transform: scale(1); }
  70%, 100% { opacity: 0; transform: scale(1.55); }
}

.ai-log {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 1.25rem 1rem;
  display: flex; flex-direction: column; gap: .875rem;
}

/* A message is an avatar beside a bubble: the assistant's mark on the reading
   start, the colleague's initials on the end. The markup order is always
   "avatar, then text" (a screen reader announces who is speaking first) and
   `row-reverse` does the side-swapping, which is also why it needs no RTL rule
   of its own. */
/* Avatars sit at the TOP of the row: an explanation of six lines would otherwise
   leave its icon floating beside the last one, pointing at nothing. */
.ai-row { display: flex; align-items: flex-start; gap: .5rem; }
.ai-row-me { flex-direction: row-reverse; }
/* One size for both marks: `.ai-avatar` and `.avatar-sm` come from different
   components and would otherwise sit beside each other a few pixels apart. */
.ai-row-avatar { width: 2rem; height: 2rem; font-size: .7rem; }
/* The bubble stops where the avatar and the gap begin, so a wide answer can
   never push the row past the panel. */
.ai-msg {
  max-width: calc(100% - 3rem);
  padding: .625rem .875rem;
  border-radius: 1.1rem;
  font-size: .875rem;
  line-height: 1.55;
  white-space: pre-wrap;      /* the model writes plain text with real newlines */
  overflow-wrap: anywhere;    /* a long id must never widen the panel */
}
/* The one square-ish corner points at the speaker's avatar - the detail that
   makes a stack of bubbles read as a conversation. */
.ai-msg-bot {
  background: var(--surface-2);
  border: 1px solid var(--border-soft);
  border-start-start-radius: .3rem;
}
.ai-msg-me {
  background: var(--color-primary);
  color: #fff;
  border-start-end-radius: .3rem;
  box-shadow: var(--shadow-card);
}
/* The four things formatAnswer() may produce (ai-assistant.js). `code` needs its
   own surface in both bubbles - inside the primary one, a tinted white. */
.ai-msg strong { font-weight: 600; }
.ai-msg code {
  padding: .1em .35em;
  border-radius: .3rem;
  background: color-mix(in srgb, var(--text-main) 8%, transparent);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .85em;
}
.ai-msg-me code { background: rgba(255, 255, 255, .22); }
/* A fenced block: its own surface, and it scrolls rather than widening the panel. */
.ai-msg pre {
  margin-block: .5rem;
  padding: .5rem .625rem;
  border-radius: .4rem;
  background: color-mix(in srgb, var(--text-main) 8%, transparent);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .8em;
  white-space: pre;
  overflow-x: auto;
}
.ai-msg-me pre { background: rgba(255, 255, 255, .2); }

/* While the session is being opened. Revealed only after a short delay (see
   ai-assistant.js): on a fast connection there is nothing to see, and a spinner
   that flashes for 80 ms reads as a glitch. */
.ai-loading {
  margin: auto;
  display: flex; flex-direction: column; align-items: center; gap: .75rem;
  color: var(--text-muted);
  font-size: .8125rem;
  opacity: 0;
  transition: opacity .2s ease;
}
.ai-loading.is-on { opacity: 1; }
.ai-loading .preloader-layers { width: 40px; height: 40px; }
/* The answer that has not started arriving yet: a caret, not a spinner - the
   text appears in place, so a spinner would only be replaced a moment later. */
.ai-msg.is-typing::after {
  content: '';
  display: inline-block;
  width: .5rem; height: 1em;
  vertical-align: text-bottom;
  background: currentColor;
  opacity: .35;
  animation: ai-caret 1s steps(2) infinite;
}
@keyframes ai-caret { 50% { opacity: 0; } }
/* Stopped by the reader: the answer stands, visibly unfinished. */
.ai-msg.is-stopped::after { content: ' […]'; opacity: .5; }
.ai-note {
  align-self: center;
  font-size: .75rem;
  color: var(--text-muted);
  text-align: center;
}

/* One SITTING inside a transcript: the dated heading where the reader came back
   after a break. Distinct from .ai-note (a quiet aside about the screen) because
   the two appear next to each other and must not read as the same thing. */
.transcript-sitting {
  position: sticky; top: 0; z-index: 1;
  margin-block: .75rem 0;
  padding: .3rem .625rem;
  border-radius: 9999px;
  align-self: center;
  width: fit-content;
  /* Sticky: messages scroll BEHIND it, so the tint needs an opaque layer under
     it - see the sticky action column for the same trick. */
  background-color: var(--card-bg);
  background-image: linear-gradient(var(--surface-2), var(--surface-2));
  border: 1px solid var(--border-soft);
  font-size: .6875rem; font-weight: 600;
  color: var(--text-muted);
}
.transcript-sitting:first-child { margin-block-start: 0; }
/* The sitting the reader clicked in the list, so the jump is visible. */
.transcript-sitting.is-target {
  color: var(--color-primary);
  border-color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 12%, transparent);
}

/* What the assistant remembers about this colleague (stage 3b). It takes the
   log's place inside the panel instead of opening a dialog on top of it, so it
   inherits .ai-log's scrolling and padding and only adds the rows. */
.ai-notes { gap: .5rem; }
.ai-notes-list { flex: 1 1 auto; display: flex; flex-direction: column; gap: .5rem; }
.ai-note-row {
  display: flex; align-items: flex-start; gap: .5rem;
  padding: .5rem .625rem;
  border: 1px solid var(--border-soft);
  border-radius: .625rem;
  background: var(--surface-2);
  font-size: .8125rem;
  line-height: 1.45;
}
.ai-note-row > span { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.ai-note-row .row-action { flex: none; }
/* The add field sticks to the bottom of the list: the composer is hidden while
   this view is open, so this is the only input on screen and it should sit where
   the composer was. */
.ai-note-add {
  position: sticky; bottom: 0;
  display: flex; align-items: stretch; gap: .5rem;
  margin-block-start: .25rem;
  padding-block-start: .5rem;
  background: var(--card-bg);   /* --surface-1 never existed: the row was transparent
                                   and the notes scrolled through the input */
}
.ai-note-add .input { flex: 1 1 auto; min-width: 0; }
.ai-fab.is-on, .ai-panel-head .row-action.is-on { color: var(--color-primary); }

/* The assistant closed for this colleague (stage C): switched off by hand, an
   unpaid charge past its term, or a debt over the ceiling. It replaces the
   conversation rather than sitting beside it - there is nothing else in the log
   at that moment - and it is a WARNING, not an error: the money is owed, not
   lost, and a red panel would read as "something is broken with the software". */
.ai-blocked {
  margin: auto 0;
  padding: .875rem 1rem;
  border: 1px solid var(--border-soft);
  border-inline-start: 3px solid var(--status-warning);
  border-radius: .75rem;
  background: var(--surface-2);
  font-size: .8125rem;
  line-height: 1.5;
}
.ai-blocked p { margin: 0; }
.ai-blocked p + p { margin-block-start: .35rem; }
.ai-blocked-title { font-weight: 600; font-size: .875rem; }
.ai-blocked-hint { color: var(--text-muted); }

/* An automation draft in the chat (stage 4). A card rather than a bubble: it is
   not something the assistant said, it is a document to read and decide on, and
   the JSON inside needs the full width the bubbles do not have. */
.ai-draft {
  margin: .5rem 0;
  padding: .75rem .875rem;
  border: 1px solid var(--border-soft);
  border-inline-start: 3px solid var(--color-primary);
  border-radius: .75rem;
  background: var(--surface-2);
  font-size: .8125rem;
  line-height: 1.5;
}
.ai-draft p { margin: 0 0 .35rem; }
.ai-draft-title { font-weight: 600; font-size: .875rem; }
/* The one-line reading of the same document, built server-side from the catalog
   so it can never describe something the JSON does not say. */
.ai-draft-flow { color: var(--text-muted); }
.ai-draft-json {
  max-height: 14rem;
  overflow: auto;
  margin: .5rem 0;
  padding: .5rem .625rem;
  border-radius: .5rem;
  background: var(--card-bg);
  font-size: .6875rem;
  line-height: 1.45;
  white-space: pre;
}
.ai-draft-actions { display: flex; flex-wrap: wrap; gap: .5rem; }

/* The proposed quote lines (stage 7). A real table, because three columns of
   numbers read as a table and as nothing else - and it scrolls inside the card
   rather than widening the panel. */
.ai-lines-wrap { max-height: 16rem; overflow: auto; margin: .5rem 0; }
.ai-lines { width: 100%; border-collapse: collapse; font-size: .75rem; }
.ai-lines td { padding: .3rem .375rem; border-block-end: 1px solid var(--border-soft); vertical-align: top; }
.ai-lines tr:last-child td { border-block-end: 0; }
.ai-lines-title td { font-weight: 600; background: var(--card-bg); }
/* A line the catalogue could not price says so in words. It must never render
   as "0,00", which reads as free rather than as unfinished. */
.ai-lines-noprice { color: var(--status-warning); white-space: nowrap; }

/* What the assistant is costing THIS colleague (stage E). Deliberately the
   quietest thing in the panel: no icon, no border, muted text, one line above
   the composer. It is there to be checked, not to be read - a running euro
   counter shouting in a chat would make people think twice before asking a
   question, which is the opposite of what this module is for. */
.ai-spend {
  display: block;
  padding: .375rem 1rem 0;
  font-size: .6875rem;
  line-height: 1.4;
  color: var(--text-muted);
  text-decoration: none;
  text-align: end;
}
.ai-spend:hover { text-decoration: underline; }
/* At 80 % of the ceiling it stops being trivia and takes the same warning colour
   as the blocked notice - the one place the two are meant to look related. */
.ai-spend.is-warning { color: var(--status-warning); font-weight: 500; }

.ai-compose {
  padding: .75rem 1rem 1rem;
  border-block-start: 1px solid var(--border-soft);
}
/* ONE control, not a field plus a button that happen to line up: the box owns
   the border and the focus ring (same as .input, so it belongs to the template),
   the textarea inside is borderless, and `align-items: stretch` makes the button
   exactly as tall as the input - by construction, not by two matching numbers
   that drift apart the day the padding changes. */
.ai-compose-box {
  display: flex; align-items: stretch; gap: .375rem;
  padding: .3125rem;
  padding-inline-start: .5rem;
  background: var(--card-bg);
  border: 1px solid var(--card-border);
  border-radius: var(--radius-control);
  transition: border-color .15s ease, box-shadow .15s ease;
}
.ai-compose-box:focus-within {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--color-primary-ring);
}
.ai-input {
  flex: 1 1 auto;
  min-width: 0;
  align-self: center;
  border: 0; outline: none;
  background: transparent;
  color: var(--text-main);
  font: inherit;
  font-size: .875rem;
  line-height: 1.5;
  padding: .375rem .25rem;
  resize: none;
  max-height: 8rem;
}
.ai-input::placeholder { color: var(--text-muted); }
/* The ring belongs to the BOX, never to the textarea inside it: a second ring
   drawn around the input (the browser's own blue one, or the global
   :focus-visible rule) sits inside the pink one and reads as a bug. Stated for
   both :focus and :focus-visible, and scoped to the box so it outranks anything
   that comes later. Accessibility is unaffected - the box still shows the focus. */
.ai-compose-box .ai-input:focus,
.ai-compose-box .ai-input:focus-visible {
  outline: none;
  box-shadow: none;
  border: 0;
}
.ai-send {
  flex: none;
  width: 2.25rem;
  display: flex; align-items: center; justify-content: center;
  border-radius: calc(var(--radius-control) - .1875rem);
  background: var(--color-primary);
  color: #fff;
  transition: filter .15s ease, opacity .15s ease;
}
.ai-send:hover { filter: brightness(1.06); }
.ai-send:disabled { opacity: .6; }
.ai-send:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
/* Stop is the same shape in a quieter colour: it interrupts, it does not send. */
.ai-send-stop {
  background: var(--surface-2);
  color: var(--text-main);
  border: 1px solid var(--border-soft);
}

@media (max-width: 640px) {
  .ai-panel { --ai-gap: .625rem; }
  .ai-fab { inset-block-end: 1rem; inset-inline-end: 1rem; }
}
@media (prefers-reduced-motion: reduce) {
  .ai-panel { transition: none; }
  .ai-msg.is-typing::after { animation: none; }
  /* The header mark keeps its colour and its place, and stops moving. */
  .ai-panel.open .ai-panel-head .ai-avatar > svg { animation: none; opacity: 1; transform: none; }
  .ai-panel.open .ai-panel-head .ai-avatar::after { animation: none; opacity: 0; }
}

/* The dashboard's use-per-day chart. Pure CSS on purpose: one bar per day does
   not justify a charting library, and the E-mails module set the precedent. */
.ai-chart {
  display: flex;
  align-items: flex-end;
  justify-content: flex-start;   /* two days must not stretch across the card */
  gap: .25rem;
  height: 9rem;
  overflow-x: auto;
}
.ai-chart-col {
  flex: 1 1 1.25rem;
  min-width: 1.25rem;
  max-width: 3rem;               /* a single day is a bar, not a filled panel */
  height: 100%;
  display: flex; flex-direction: column; justify-content: flex-end; align-items: center;
  gap: .35rem;
}
.ai-chart-bar {
  width: 100%;
  border-radius: .25rem .25rem 0 0;
  background: var(--color-primary);
  opacity: .85;
  transition: opacity .15s ease;
}
.ai-chart-col:hover .ai-chart-bar { opacity: 1; }
.ai-chart-label {
  font-size: .625rem;
  color: var(--text-muted);
  white-space: nowrap;
}

/* ==========================================================================
   Help drawer (js/help-drawer.js) - one section of the in-app documentation,
   slid in from the inline end next to the screen it explains.

   Built on the theme variables only, so the dark theme needs no rules of its
   own; the body carries `.docs-prose`, which already has its dark overrides.
   Logical properties throughout (inset-inline-end, border-inline-start), so an
   RTL language flips the whole drawer for free.
   ========================================================================== */
.help-drawer-overlay {
  position: fixed; inset: 0; z-index: 50;
  background: rgba(15, 23, 42, 0.35);
}
.help-drawer {
  position: fixed;
  inset-block: 0;
  inset-inline-end: 0;
  z-index: 55;
  width: min(30rem, 100vw);
  display: flex; flex-direction: column;
  background: var(--card-bg);
  border-inline-start: 1px solid var(--border-soft);
  box-shadow: var(--shadow-panel);
  /* Off-screen until .is-open, which is added one frame later so the slide runs
     instead of the panel simply appearing. */
  transform: translateX(100%);
  transition: transform .22s ease;
}
/* translateX is direction-blind, so RTL needs the mirror of it. */
[dir="rtl"] .help-drawer { transform: translateX(-100%); }
.help-drawer.is-open { transform: translateX(0); }
.help-drawer-head {
  display: flex; align-items: center; gap: 0.6rem;
  padding: 1rem 1.25rem;
  border-block-end: 1px solid var(--border-soft);
  color: var(--text-main);
}
.help-drawer-close {
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem;
  border-radius: 0.5rem;
  color: var(--text-muted);
}
.help-drawer-close:hover { background: var(--surface-2); color: var(--text-main); }
.help-drawer-close:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
/* The only scrolling region: the header and footer stay put while a long
   instruction scrolls, which is why the footer button is always reachable. */
.help-drawer-body { flex: 1 1 auto; overflow-y: auto; padding: 1.25rem; }
.help-drawer-body > :first-child { margin-block-start: 0; }
.help-drawer-loading { color: var(--text-muted); font-size: 0.87rem; }
.help-drawer-foot {
  flex: none;
  padding: 0.9rem 1.25rem;
  border-block-start: 1px solid var(--border-soft);
}
/* Page scroll is locked while the drawer is open, so the drawer's own scroll is
   the one that answers the wheel. */
body.help-drawer-open { overflow: hidden; }
