/* ====================================================================
   Clean glass design system — flat, minimal, frosted-glass surfaces,
   one calm indigo accent. Sans-serif throughout. Theme toggle swaps
   these tokens and the whole UI follows.
   ==================================================================== */
:root,
:root[data-theme="dark"] {
  --bg: #0a0d14;                          /* deep cool slate */
  --bg-grad-1: rgba(99, 110, 247, 0.16);  /* faint indigo glow */
  --bg-grad-2: rgba(56, 189, 248, 0.08);  /* faint sky glow */
  --panel: rgba(22, 27, 40, 0.55);        /* glass surface */
  --panel-2: rgba(34, 40, 56, 0.55);      /* glass — inputs / secondary */
  --solid: #141927;                       /* opaque fallback (selects, etc.) */
  --line: rgba(255, 255, 255, 0.09);      /* hairline */
  --glass-border: rgba(255, 255, 255, 0.10);
  --text: #e8ebf3;
  --muted: #969db0;
  --accent: #6b8afd;
  --accent-strong: #88a0ff;
  --accent-soft: rgba(107, 138, 253, 0.16);
  --accent-fill: #1e2742;                  /* opaque match of the highlighted (--accent-soft) bg */
  --accent-line: rgba(107, 138, 253, 0.40);
  --brass: #e0a955;                        /* amber — building/voting accents, demo */
  --brass-soft: rgba(224, 169, 85, 0.14);
  --brass-line: rgba(224, 169, 85, 0.38);
  --gold: var(--brass);                    /* legacy alias */
  --violet: #b491f5;                       /* report status: New */
  --violet-soft: rgba(180, 145, 245, 0.16);
  --violet-line: rgba(180, 145, 245, 0.40);
  --cyan: #38d6e0;                         /* report status: Accepted */
  --cyan-soft: rgba(56, 214, 224, 0.16);
  --cyan-line: rgba(56, 214, 224, 0.40);
  --danger: #f0746b;
  --danger-soft: rgba(240, 116, 107, 0.12);
  --ok: #46d39a;
  --ok-soft: rgba(70, 211, 154, 0.12);
  --ok-line: rgba(70, 211, 154, 0.40);
  --shadow: 0 10px 34px -20px rgba(0, 0, 0, 0.7);
  --shadow-lift: 0 18px 50px -24px rgba(0, 0, 0, 0.75);
  --radius: 16px;
  --radius-sm: 11px;
  --sidebar-w: 252px;                      /* desktop sidebar width; 0 on mobile */
  --blur: blur(16px) saturate(1.3);
  --display: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
}

/* Light mode — cool near-white, soft tinted glow so the glass reads. */
:root[data-theme="light"] {
  --bg: #eef1f8;
  --bg-grad-1: rgba(99, 110, 247, 0.12);
  --bg-grad-2: rgba(56, 189, 248, 0.10);
  --panel: rgba(255, 255, 255, 0.68);
  --panel-2: rgba(255, 255, 255, 0.52);
  --solid: #ffffff;
  --line: rgba(18, 24, 40, 0.10);
  --glass-border: rgba(255, 255, 255, 0.75);
  --text: #161a26;
  --muted: #5a6376;
  --accent: #4f6ef7;
  --accent-strong: #3f5de0;
  --accent-soft: rgba(79, 110, 247, 0.10);
  --accent-fill: #e7ecfd;                  /* opaque match of the highlighted (--accent-soft) bg */
  --accent-line: rgba(79, 110, 247, 0.28);
  --brass: #b9822a;
  --brass-soft: rgba(185, 130, 42, 0.10);
  --brass-line: rgba(185, 130, 42, 0.30);
  --violet: #8b5cf6;
  --violet-soft: rgba(139, 92, 246, 0.10);
  --violet-line: rgba(139, 92, 246, 0.30);
  --cyan: #0891b2;
  --cyan-soft: rgba(8, 145, 178, 0.10);
  --cyan-line: rgba(8, 145, 178, 0.30);
  --danger: #dc4f47;
  --danger-soft: rgba(220, 79, 71, 0.08);
  --ok: #16a34a;
  --ok-soft: rgba(22, 163, 74, 0.08);
  --ok-line: rgba(22, 163, 74, 0.40);
  --shadow: 0 10px 30px -18px rgba(30, 41, 80, 0.20);
  --shadow-lift: 0 18px 46px -22px rgba(30, 41, 80, 0.26);
}
* { box-sizing: border-box; }
body {
  margin: 0;
  font: 15px/1.6 var(--sans);
  background: var(--bg);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
/* Soft tinted glow behind everything — gives the frosted glass something to
   blur, and keeps the page feeling clean and airy. */
body::before {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(58% 48% at 12% -5%, var(--bg-grad-1), transparent 60%),
    radial-gradient(52% 44% at 102% 104%, var(--bg-grad-2), transparent 58%);
}
h1, h2, h3, h4 { letter-spacing: -0.02em; }
/* Glass surfaces — translucent + frosted. Applied to every panel-like element
   so the whole UI reads as clean stacked glass. */
.card, .sidebar, .login-card, .invoice, .issue-card, .lf-card, .notif, .feed-item,
.post, .offer, .proposal, .topic, .doc-row, .stat-card, .toast, .demo-banner,
.mobile-topbar, .info-box, .dup-card, .matches,
.admin-panel, .msg-bubble, .empty-icon, select, input, textarea {
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
}

/* ===== Login screen: manifesto hero + sign-in card ===== */
.login-screen { display: grid; grid-template-columns: 1.25fr 1fr; min-height: 100vh; }
.login-screen.hidden { display: none; }
/* Standalone centered card (onboarding, "no association yet") */
.center-screen { display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 24px; }
.center-screen.hidden { display: none; }
.center-screen .login-card { border: 1px solid var(--line); border-radius: 16px; padding: 28px; }

/* Post-onboarding welcome: kinetic typography rises from behind masks and a
   hairline rule sweeps under the name, then the stage dissolves into the
   dashboard. */
.welcome-screen { display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 24px; text-align: center; }
.welcome-screen.hidden { display: none; }
.welcome-stage { max-width: 460px; padding: 40px 0; }

/* Masked line reveals — the inner span slides up from behind the clip edge. The
   mask wraps ONLY the text so the hairline rule (::after) stays outside it and
   can't enlarge the clip box (which would let the letters peek through at rest). */
.welcome-eyebrow { overflow: hidden; font-family: var(--mono); font-size: 12px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--muted); margin: 0 0 14px; }
.welcome-title { font-family: var(--display); font-weight: 800; font-size: 38px; line-height: 1.2; letter-spacing: -0.025em; margin: 0 0 4px; }
.line-mask { display: block; overflow: hidden; padding-bottom: 0.14em; }
.reveal-line { display: inline-block; transform: translateY(120%); will-change: transform; }
.welcome-eyebrow .reveal-line { animation: revealLine 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.3s forwards; }
.welcome-title .reveal-line { animation: revealLine 0.9s cubic-bezier(0.16, 1, 0.3, 1) 0.55s forwards; }
@keyframes revealLine { to { transform: translateY(0); } }

/* A hairline rule sweeps in under the name. */
.welcome-title::after { content: ""; display: block; width: 64px; height: 2px; margin: 16px auto 0; background: linear-gradient(90deg, transparent, var(--accent-line), transparent); transform: scaleX(0); animation: welcomeRule 0.9s cubic-bezier(0.16, 1, 0.3, 1) 1s forwards; }
@keyframes welcomeRule { to { transform: scaleX(1); } }

.welcome-sub { color: var(--muted); font-size: 15px; margin: 22px 0 30px; opacity: 0; animation: welcomeUp 0.7s cubic-bezier(0.16, 1, 0.3, 1) 1.15s forwards; }
.welcome-sub strong { color: var(--text); font-weight: 600; }
.welcome-go { opacity: 0; animation: welcomeUp 0.7s ease 1.7s forwards; }
@keyframes welcomeUp { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .reveal-line { transform: none; animation: none; }
  .welcome-title::after { transform: scaleX(1); animation: none; }
  .welcome-eyebrow, .welcome-sub, .welcome-go { animation: none; opacity: 1; }
}

.hero {
  position: relative;
  overflow: hidden;
  padding: 7vh 6vw;
  display: flex;
  align-items: center;
  /* The hero is ALWAYS dark, so pin light text colours here regardless of the
     active theme (otherwise light mode renders dark text on this dark panel). */
  --text: #eef1f8;
  --muted: #aab3c6;
  color: var(--text);
  background:
    radial-gradient(110% 90% at 12% 4%, rgba(99, 110, 247, 0.24), transparent 56%),
    radial-gradient(90% 90% at 96% 102%, rgba(56, 189, 248, 0.14), transparent 56%),
    #0a0d14;
}
/* faint grain/texture overlay */
.hero::after {
  content: "";
  position: absolute; inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.025'/%3E%3C/svg%3E");
  pointer-events: none;
}
.hero-inner { position: relative; max-width: 600px; }
.eyebrow {
  display: inline-block;
  font-size: 12px; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--accent); border: 1px solid var(--accent-line);
  border-radius: 999px; padding: 5px 14px; margin-bottom: 28px;
  animation: rise 0.7s ease both;
}
.hero-title {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(2.5rem, 5vw, 4.2rem);
  line-height: 1.05;
  letter-spacing: -0.035em;
  margin: 0 0 26px;
  animation: rise 0.7s ease 0.08s both;
}
.hero-title em { font-style: normal; font-weight: 800; color: var(--accent); }
.hero-lead {
  font-size: 1.06rem; color: var(--muted); max-width: 52ch; margin: 0 0 16px;
  animation: rise 0.7s ease 0.16s both;
}
.hero-points {
  list-style: none; padding: 0; margin: 30px 0 0;
  display: flex; flex-direction: column; gap: 12px;
  animation: rise 0.7s ease 0.24s both;
}
.hero-points li {
  position: relative; padding-left: 26px; color: var(--text); font-weight: 500;
}
.hero-points li::before {
  content: ""; position: absolute; left: 2px; top: 8px;
  width: 8px; height: 8px; border-radius: 999px;
  background: var(--accent-soft); border: 1.5px solid var(--accent);
}
@keyframes rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }

.login-panel {
  display: flex; align-items: center; justify-content: center;
  padding: 40px; background: var(--bg);
  border-left: 1px solid var(--line);
}
.login-card { width: 100%; max-width: 340px; text-align: center; position: relative; }
.login-card label { text-align: left; }
.login-card .brand {
  font-family: var(--display); font-weight: 600; font-size: 26px;
  letter-spacing: 0.02em; margin-bottom: 4px;
}
.login-card h2 { margin: 0 0 20px; font-weight: 600; color: var(--muted); font-size: 15px; }

/* Back to the method picker (or, from register, back to the email form) —
   one small top-left arrow shared by every expanded panel, replacing what
   used to be a separate full-width "← ..." button per panel. */
.method-back {
  position: absolute; top: 0; left: 0; width: 32px; height: 32px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: transparent; border: 1px solid var(--line); border-radius: 999px;
  color: var(--muted);
}
.method-back:hover { background: var(--panel-2); color: var(--text); border-color: var(--accent-line); }

/* Logout + language switch, pinned top-right, available on every pre-app
   screen (verify-email, onboarding, no-org) — see route() in app.js. */
.pre-app-bar, .login-lang-bar { position: fixed; top: 16px; right: 16px; z-index: 500; display: flex; gap: 8px; align-items: center; }
.pre-app-bar button, .pre-app-bar select, .login-lang-bar select { margin: 0; }

/* Method picker buttons */
.login-methods { display: flex; flex-direction: column; gap: 9px; transition: opacity 0.2s ease; }
.login-methods.methods-collapsed { opacity: 0; pointer-events: none; height: 0; overflow: hidden; }
.login-method {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 13px 16px; background: var(--panel-2);
  border: 1px solid var(--line); border-radius: 12px;
  color: var(--text); font-size: 15px; font-weight: 500;
  text-decoration: none; cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}
.login-method:hover { border-color: var(--accent); background: var(--accent-soft); }
.login-method svg:first-child { flex-shrink: 0; opacity: 0.65; }
.login-method span { flex: 1; text-align: left; }
.login-method .chevron { opacity: 0.35; margin-left: auto; }

/* Expandable form panels */
.method-expand {
  display: grid; grid-template-rows: 0fr;
  opacity: 0; transition: grid-template-rows 0.3s ease, opacity 0.25s ease;
}
.method-expand.open { grid-template-rows: 1fr; opacity: 1; }
.expand-inner { overflow: hidden; }
.expand-inner label:first-child { margin-top: 16px; display: block; }
.expand-inner button { width: 100%; margin-top: 8px; }

/* PIN overlay */
.pin-overlay {
  position: fixed; inset: 0; z-index: 9000;
  background: var(--bg);
  display: flex; align-items: center; justify-content: center;
  opacity: 0; pointer-events: none;
  transition: opacity 0.35s ease;
}
.pin-overlay.visible { opacity: 1; pointer-events: all; }
.pin-card {
  text-align: center; max-width: 380px; width: 100%; padding: 0 28px;
  transform: translateY(28px); transition: transform 0.35s ease;
}
.pin-overlay.visible .pin-card { transform: none; }
.pin-brand { font-weight: 600; font-size: 22px; margin-bottom: 40px; letter-spacing: 0.02em; }
.pin-title { font-size: 24px; font-weight: 700; margin: 0 0 10px; }
.pin-hint { color: var(--muted); font-size: 15px; margin: 0 0 32px; line-height: 1.7; }
.pin-hint strong { color: var(--text); }
.pin-digits { display: flex; gap: 10px; justify-content: center; margin-bottom: 20px; }
.pin-digit {
  width: 50px; height: 62px; text-align: center;
  font-size: 26px; font-weight: 700; font-family: var(--mono);
  background: var(--panel); border: 2px solid var(--line);
  border-radius: 14px; color: var(--text); caret-color: transparent;
  transition: border-color 0.15s, background 0.15s;
  -webkit-appearance: none; appearance: none;
}
.pin-digit:focus { outline: none; border-color: var(--accent); background: var(--accent-soft); }
.pin-digit.filled { border-color: var(--accent-line); }
.pin-digit:disabled { opacity: 0.5; }
#pin-back {
  background: none; border: none; color: var(--muted);
  font-size: 14px; cursor: pointer; margin-top: 8px; padding: 8px 0;
  transition: color 0.15s; width: auto;
}
#pin-back:hover { color: var(--text); }

@media (max-width: 860px) {
  .login-screen { grid-template-columns: 1fr; }
  .hero { padding: 6vh 8vw 4vh; }
  .login-panel { border-left: 0; border-top: 1px solid var(--line); }
  .hero-building { display: none; } /* decorative — would crowd the manifesto */
}
header {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 20px;
  border-bottom: 1px solid var(--line);
}
header h1 { font-family: var(--display); font-weight: 600; font-size: 20px; margin: 0; letter-spacing: 0.02em; }
.card h2 { font-family: var(--display); font-weight: 600; }
.env {
  font-size: 12px;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 2px 10px;
}
.who { margin-left: auto; color: var(--muted); font-size: 13px; }
button {
  background: var(--accent);
  color: #fff;
  border: 0;
  border-radius: 10px;
  padding: 10px 17px;
  font-size: 14px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition: background 0.15s ease, transform 0.08s ease, box-shadow 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
/* Brighten on hover (works for the accent CTA). Card/chip-style buttons opt
   out with `filter: none` in their own :hover, so the accent never leaks onto
   their translucent backgrounds. */
button:hover { filter: brightness(1.08); }
button:active { transform: translateY(1px); }
button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
button.ghost { background: transparent; border: 1px solid var(--line); color: var(--muted); }
button.ghost:hover { background: var(--panel-2); color: var(--text); border-color: var(--accent-line); }
button:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
.card {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 22px 24px; max-width: 520px; margin: 0 auto 16px; box-shadow: var(--shadow);
}
.auth { background: var(--panel); border: 1px solid var(--line); border-radius: 14px; padding: 28px; }
.auth h2 { margin-top: 0; }
.hint { color: var(--muted); font-size: 13px; }
label { display: block; margin: 14px 0; font-size: 13px; color: var(--muted); }
input, textarea, select, .dd-trigger {
  width: 100%;
  margin-top: 6px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 10px;
  color: var(--text);
  padding: 10px 12px;
  font: inherit;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
input:focus, textarea:focus, select:focus, .dd-trigger:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
input::placeholder, textarea::placeholder { color: var(--muted); opacity: 0.7; }
.error { color: var(--danger); font-size: 13px; white-space: pre-wrap; }
.hidden { display: none !important; }

.filters {
  display: flex;
  gap: 14px;
  align-items: flex-end;
  flex-wrap: wrap;
  padding: 16px 20px;
  border-bottom: 1px solid var(--line);
}
.filters label { margin: 0; }
.filters input, .filters select { width: auto; }

.split { display: grid; grid-template-columns: 1fr 1fr; gap: 0; height: calc(100vh - 64px); }
.list-pane { overflow: auto; border-right: 1px solid var(--line); }
.preview-pane { overflow: auto; padding: 20px; }

table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 10px 14px; border-bottom: 1px solid var(--line); font-size: 13px; }
th { position: sticky; top: 0; background: var(--panel); color: var(--muted); font-weight: 600; }
tbody tr { cursor: pointer; }
tbody tr:hover { background: var(--panel); }
tbody tr.active { background: var(--panel-2); }
.tag { font-size: 11px; color: var(--muted); border: 1px solid var(--line); border-radius: 6px; padding: 1px 6px; }

.invoice { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 20px; }
.invoice h2 { margin: 0 0 4px; }
.parties { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin: 16px 0; }
.parties h4 { margin: 0 0 4px; color: var(--muted); font-size: 12px; text-transform: uppercase; }
.lines { width: 100%; border-collapse: collapse; margin-top: 8px; }
.lines th, .lines td { font-size: 12px; padding: 6px 8px; }
.totals { display: flex; gap: 24px; justify-content: flex-end; margin-top: 16px; font-size: 14px; }
.totals .gross { font-weight: 700; font-size: 16px; }
.xml-link { display: inline-block; margin-top: 14px; color: var(--accent); font-size: 13px; }

/* Org picker */
.org-list { list-style: none; padding: 0; margin: 8px 0 0; }
.org-list li { margin: 8px 0; }
.org-btn {
  width: 100%; text-align: left; display: flex; flex-direction: column; gap: 2px;
  background: var(--panel-2); border: 1px solid var(--line); color: var(--text);
}
.org-btn:hover { border-color: var(--accent); filter: none; }

/* Admin */
.admin { max-width: 760px; margin: 28px auto; }
.admin-orgs { width: 100%; border-collapse: collapse; margin-top: 8px; }
.admin-orgs th, .admin-orgs td { padding: 8px 10px; border-bottom: 1px solid var(--line); font-size: 13px; text-align: left; }
.admin-orgs th { color: var(--muted); font-weight: 600; }
.admin-orgs .token-in, .admin-orgs .env-sel { margin: 0; }
.add-org { display: flex; gap: 8px; align-items: center; margin-top: 10px; flex-wrap: wrap; }
.add-org input, .add-org select { width: auto; margin: 0; }
.admin h3 { margin: 22px 0 0; font-size: 14px; color: var(--muted); }
.admin .section-top { margin-top: 30px; border-top: 1px solid var(--line); padding-top: 20px; }
.admin-users { width: 100%; border-collapse: collapse; margin-top: 8px; }
.admin-users th, .admin-users td { padding: 8px 10px; border-bottom: 1px solid var(--line); font-size: 13px; text-align: left; vertical-align: top; }
.admin-users th { color: var(--muted); font-weight: 600; }
.admin-users .pw-in { margin: 0; width: 150px; display: inline-block; }
.org-checks { display: inline-flex; flex-wrap: wrap; gap: 10px; margin-right: 8px; }
label.inline { display: inline-flex; align-items: center; gap: 5px; margin: 0; color: var(--text); }
label.inline input { width: auto; margin: 0; }

/* Horizontal scroll fallback for wide admin tables on narrow screens */
.table-wrap { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ===== Mobile / responsive ===== */
@media (max-width: 760px) {
  /* Header: let it wrap, stay compact; org name takes its own line */
  header { flex-wrap: wrap; gap: 8px 10px; padding: 12px 16px; }
  header h1 { font-size: 18px; }
  .who { margin-left: 0; flex-basis: 100%; order: 5; }
  header .ghost { padding: 7px 12px; font-size: 13px; }

  /* Cards never touch the screen edges — unless the view already pads
     itself (--card-edge-margin: 0 there), in which case adding this too
     would double the margin. */
  .card { margin: 20px var(--card-edge-margin, 14px); }
  .auth { padding: 22px; }
  .admin { max-width: none; }

  /* Invoices: stack list above preview, natural page scroll (no fixed-height panes) */
  .split { grid-template-columns: 1fr; height: auto; }
  .list-pane { overflow: visible; border-right: 0; border-bottom: 1px solid var(--line); max-height: 55vh; overflow-y: auto; }
  .preview-pane { overflow: visible; padding: 16px; }

  /* Filters stack full-width and become easy tap targets */
  .filters { padding: 14px 16px; gap: 10px; }
  .filters label { flex: 1 1 130px; }
  .filters input, .filters select { width: 100%; }
  .filters button { flex-basis: 100%; }

  /* Invoice preview: stack parties + totals */
  .parties { grid-template-columns: 1fr; gap: 12px; }
  .totals { flex-wrap: wrap; gap: 10px 20px; justify-content: flex-start; }

  /* 16px inputs prevent iOS Safari from auto-zooming on focus */
  input, textarea, select { font-size: 16px; }
}

/* ===== Community modules ===== */
.modnav { display: flex; gap: 4px; margin-left: 8px; flex-wrap: wrap; }
.modbtn {
  background: transparent; border: 1px solid transparent; color: var(--muted);
  border-radius: 8px; padding: 7px 12px; font-size: 13px; font-weight: 600;
}
.modbtn:hover { color: var(--text); filter: none; }
.modbtn.active { background: var(--panel-2); color: var(--text); border-color: var(--line); }
.badge {
  display: inline-block; min-width: 16px; margin-left: 6px; padding: 0 5px;
  background: var(--danger); color: #fff; border-radius: 999px; font-size: 11px; line-height: 16px; text-align: center;
}

#dashboard-view, #feedback-view, #assistant-info-view, #reports-view, #report-form-view, #report-detail-view,
#lostfound-view, #lostfound-form-view, #lostfound-detail-view, #notifications-view, #debts-view,
#residents-view, #knowledge-view, #providers-view, #roadmap-view, #provider-portal-view,
#board-view, #chat-view, #parking-view, #voting-view, #payments-view, #documents-view, #purchases-view,
#community-settings-view {
  /* var(--view-max, 880px), not a flat 880px: .thread-mode below sets
     --view-max: 760px on the element itself, so a thread narrows without
     needing an id selector to out-specificity this id-group rule (this
     group's own specificity would otherwise always beat a bare .thread-mode). */
  max-width: var(--view-max, 880px); margin: 0 auto; padding: 24px var(--view-pad-x, 20px) 60px;
  /* These views already carry the padding above — a .card inside one
     shouldn't add its own horizontal edge margin on top of it (see the
     mobile ".card" rule, which falls back to 14px for standalone cards
     like #settings-view that have no padding of their own). */
  --card-edge-margin: 0;
}
/* Action buttons inside the assistant's reply bubble */
.msg-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 13px; }
.msg-act { display: inline-flex; align-items: center; gap: 9px; width: auto; font-size: 13px; font-weight: 600; padding: 7px 7px 7px 16px; border-radius: 999px; box-shadow: var(--shadow); transition: transform 0.14s ease, box-shadow 0.14s ease, filter 0.14s ease; }
.msg-act:hover { transform: translateY(-1px); box-shadow: var(--shadow-lift); filter: brightness(1.05); }
.msg-act:active { transform: translateY(0); }
.msg-act .act-chev { width: 15px; height: 15px; flex-shrink: 0; padding: 4px; box-sizing: content-box; border-radius: 999px; background: rgba(255, 255, 255, 0.18); opacity: 1; transition: transform 0.16s ease; }
.msg-act:hover .act-chev { transform: translateX(2px); }
.msg-act.ghost { box-shadow: none; }
.msg-act.ghost:hover { transform: none; box-shadow: none; filter: none; border-color: var(--accent-line); color: var(--text); }
.msg-act.ghost .act-chev { background: var(--panel-2); }
@media (prefers-reduced-motion: reduce) { .msg-act, .msg-act .act-chev { transition: none; } }
.mod-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 18px; }
/* h3 covered too: "Moje rezerwacje" / "Moje udostępnienia" use h3 (a smaller
   heading felt right for a sub-section, not the page-level h2 everywhere else
   uses), but without this the browser's own ~1em h3 margin stacks on top of
   this rule's margin-bottom, which is what read as too much space both above
   the title and before the tile below it. */
.mod-head h2, .mod-head h3 { font-family: var(--display); font-weight: 600; margin: 0; }
/* A count, not an alert — soft accent rather than the solid-fill treatment
   .topic-unread uses for unread messages, since there's nothing urgent about
   how many of your own reservations or shares exist. */
.mod-head-count { color: var(--muted); font-weight: 400; }

.ref { font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 0.92em; color: var(--muted); letter-spacing: -0.02em; }
.multi { color: var(--gold); font-weight: 700; }

/* One status→color mapping, consumed by three contexts (badge pill, admin
   status picker, timeline dot below) via custom properties instead of
   repeating the same 5 color triplets in each place. */
.s-new { --status-c: var(--violet); --status-soft: var(--violet-soft); --status-line: var(--violet-line); }
.s-accepted { --status-c: var(--cyan); --status-soft: var(--cyan-soft); --status-line: var(--cyan-line); }
.s-in_progress { --status-c: var(--accent); --status-soft: var(--accent-soft); --status-line: var(--accent-line); }
.s-resolved { --status-c: var(--ok); --status-soft: var(--ok-soft); --status-line: rgba(111,207,151,0.4); }
.s-rejected { --status-c: var(--danger); --status-soft: var(--danger-soft); --status-line: rgba(232,115,107,0.4); }
.status-badge {
  display: inline-block; font-size: 12px; font-weight: 600; border-radius: 6px; padding: 2px 9px; border: 1px solid;
  color: var(--status-c, var(--muted)); border-color: var(--status-line, var(--line)); background: var(--status-soft, transparent);
}
.choice.sel { color: var(--status-c, var(--accent)); border-color: var(--status-line, var(--accent-line)); background: var(--status-soft, var(--accent-soft)); }
/* Building badge (reporter's block) + badge group on cards/detail */
.bldg-badge { display: inline-flex; align-items: center; gap: 4px; font-size: 11.5px; font-weight: 600; color: var(--muted); background: var(--panel-2); border: 1px solid var(--line); border-radius: 999px; padding: 2px 9px; white-space: nowrap; }
.bldg-badge svg { width: 13px; height: 13px; flex-shrink: 0; opacity: 0.85; }
.card-badges, .detail-badges { display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0; flex-wrap: wrap; }
/* Card badges sit on their own row below the tile (see .issue-body), left-
   aligned so they never compete with the title for width; detail badges stay
   next to the page heading, right-aligned. */
.card-badges { justify-content: flex-start; }
.detail-badges { justify-content: flex-end; }
/* Feedback detail: structured details + collapsible transcript (.desc already
   wraps; .thread-scroll is fixed-height for chat, so the transcript needs its
   own scroll cap). */
.fb-h { font-family: var(--display); font-weight: 600; font-size: 1.02rem; margin: 18px 0 6px; }
.fb-transcript { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; max-height: 340px; overflow-y: auto; }

.wide { display: block; width: 100%; margin: 14px 0; }
.lbl { font-size: 13px; color: var(--muted); margin: 16px 0 8px; }

/* stepper */
.stepper { margin: 12px 0 22px; }
.stepper span { font-size: 13px; font-weight: 600; color: var(--accent); }
.stepper .bar { height: 8px; background: var(--panel-2); border-radius: 999px; margin-top: 8px; overflow: hidden; }
.stepper .bar div { height: 100%; background: var(--accent); border-radius: 999px; transition: width 0.3s; }

/* choice buttons */
.choice-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.choice-grid.sm { grid-template-columns: repeat(3, 1fr); }
.choice {
  background: var(--panel-2); border: 1px solid var(--line); color: var(--text);
  border-radius: 10px; padding: 14px; text-align: left; font-size: 15px; font-weight: 500;
}
.choice:hover { border-color: var(--accent); filter: none; }
.wiz-nav { display: flex; gap: 10px; margin-top: 22px; }
.wiz-nav .next { flex: 2; }
.wiz-nav .ghost { flex: 1; }
.wizard h2 { margin-top: 6px; }

/* dedup cards */
.dup-card { background: var(--panel-2); border: 1px solid var(--accent-line); border-radius: 12px; padding: 16px; margin: 12px 0; }
.dup-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
.dup-card .join { margin-top: 12px; width: 100%; }

/* review table */
.kv { width: 100%; border-collapse: collapse; margin-top: 8px; }
.kv th { text-align: left; color: var(--muted); font-weight: 600; width: 130px; vertical-align: top; padding: 8px 10px; border-bottom: 1px solid var(--line); }
.kv td { padding: 8px 10px; border-bottom: 1px solid var(--line); }

/* photos */
.photos { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 8px; }
.thumb { width: 96px; height: 96px; object-fit: cover; border-radius: 10px; border: 1px solid var(--line); }
.thumb.sm { width: 72px; height: 72px; }
.photo-add {
  width: 96px; height: 96px; border: 1px dashed var(--line); background: var(--panel-2);
  color: var(--muted); border-radius: 10px; font-size: 13px;
}
.photo-add:hover { border-color: var(--accent); color: var(--accent); filter: none; }

/* detail */
.detail-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.detail-top h2 { margin: 0; font-family: var(--display); font-weight: 600; }
/* Byline: who reported it, like a post's author line — the avatar makes it
   a person, not another statistic, so it doesn't belong crammed into .meta
   next to plain counters. */
.report-byline { display: flex; align-items: center; gap: 10px; margin-top: 12px; }
.report-byline-when { font-size: 12.5px; color: var(--muted); }
.desc { white-space: pre-wrap; margin: 12px 0; }
/* A hairline footer, not a row floating left with empty space beside it —
   anchors the plain counters (Type, Reporters) to the card instead of just
   trailing off after the description. */
.meta { display: flex; flex-wrap: wrap; gap: 28px; margin: 16px 0 0; padding-top: 14px; border-top: 1px solid var(--line); }
.meta dt { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); }
.meta dd { margin: 2px 0 0; font-weight: 600; }

/* status timeline */
.timeline { list-style: none; padding: 0; margin: 14px 0 0; }
.timeline li { position: relative; padding: 0 0 18px 28px; color: var(--muted); font-weight: 600; }
.timeline li:last-child { padding-bottom: 0; }
.timeline li::before { content: ""; position: absolute; left: 9px; top: 18px; bottom: 0; width: 2px; background: var(--line); }
.timeline li:last-child::before { display: none; }
.timeline .dot { position: absolute; left: 0; top: 2px; width: 20px; height: 20px; border-radius: 999px; border: 3px solid var(--line); background: var(--bg); }
.timeline li.done { color: var(--text); }
/* Same --status-c/--status-soft custom properties as the badge/choice
   rules above — each step lights up in its own status color once reached
   instead of a uniform blue for every step ("Resolved" reads as
   done-and-good/green, not just done-and-blue like "Accepted"). */
.timeline li.done .dot { border-color: var(--status-c, var(--accent)); background: var(--status-c, var(--accent)); }
.timeline li.current .dot { box-shadow: 0 0 0 4px var(--status-soft, var(--accent-soft)); animation: dot-pulse 1.8s ease-in-out infinite; }
@keyframes dot-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.15); } }
@media (prefers-reduced-motion: reduce) { .timeline li.current .dot { animation: none; } }
.timeline .now { color: var(--status-c, var(--accent)); font-weight: 500; }
/* The latest thing the handler said, shown under the step the case is on.
   Only there: repeated under every step it would read as if each one had
   been commented on. The li's own 28px left padding aligns it under the
   label; the weight/colour reset undoes `.timeline li`'s bold. */
.timeline .tl-note { margin-top: 5px; font-weight: 400; font-size: 13px; line-height: 1.5; color: var(--muted); }
/* Sits directly under the "Status zgłoszenia" heading, before the ladder —
   a fact about the report as a whole, not about whichever status it is on,
   so it does not live inside any one <li> and never moves as status changes. */
.tl-seen {
  display: inline-flex; align-items: flex-start; gap: 5px; margin: 4px 0 16px;
  font-size: 12px; font-weight: 600; line-height: 1.4; color: var(--ok);
  background: var(--ok-soft); border-radius: 999px; padding: 4px 10px 4px 7px;
}
.tl-seen svg { width: 11px; height: 11px; flex: none; margin-top: 2px; }
.rejected-note { background: var(--danger-soft); border: 1px solid rgba(232,115,107,0.3); color: var(--danger); border-radius: 10px; padding: 12px 14px; }
.history { list-style: none; padding: 0; margin: 10px 0 0; }
.history li { padding: 4px 0; }
details summary { cursor: pointer; color: var(--accent); font-size: 13px; margin-top: 12px; }

.admin-panel h3 { margin-top: 0; }

/* report thread */
#report-thread-card h3 { margin-top: 0; }
.thread-list { list-style: none; padding: 0; margin: 0 0 14px; }
.thread-msg { padding: 10px 0; border-top: 1px solid var(--line); }
.thread-msg:first-child { border-top: 0; padding-top: 0; }
.thread-msg-head { display: flex; align-items: center; gap: 8px; margin-bottom: 2px; }
.thread-msg-head .hint { margin-left: auto; }
.thread-msg p { margin: 0; white-space: pre-wrap; }
/* The managing agent's reply reads as a distinct voice in the thread — same
   soft-accent treatment the timeline gives the current status step. */
.thread-msg-agent { background: var(--accent-soft); border-radius: 10px; padding: 10px 12px; border-top: 0; margin: 4px 0; }
#report-thread-card textarea { margin: 0 0 8px; }

/* tabs (lost & found) */
.tabs { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; background: var(--panel-2); border-radius: 10px; padding: 5px; margin-bottom: 14px; }
.tab { background: transparent; color: var(--muted); border: 0; border-radius: 7px; padding: 10px; font-weight: 600; }
.tab:hover { filter: none; color: var(--text); }
.tab.sel { background: var(--panel); color: var(--text); }
.lf-list { display: flex; flex-direction: column; gap: 10px; }
.lf-card { display: flex; align-items: center; gap: 14px; color: var(--text); background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 12px; cursor: pointer; }
.lf-card:hover { border-color: var(--accent); }
.lf-thumb { width: 60px; height: 60px; border-radius: 9px; object-fit: cover; }
.lf-thumb.ph { display: flex; align-items: center; justify-content: center; background: var(--panel-2); color: var(--muted); font-size: 22px; }
.matches { background: var(--brass-soft); border: 1px solid var(--brass-line); border-radius: 10px; padding: 12px; margin: 8px 0; }
.matches .match { display: block; width: 100%; text-align: left; background: var(--panel); border: 1px solid var(--line); color: var(--text); margin-top: 8px; }
.matches .match:hover { border-color: var(--gold); filter: none; }
.info-box { background: var(--panel-2); border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; color: var(--muted); }
.info-box.ok { color: var(--ok); border-color: rgba(111,207,151,0.3); background: var(--ok-soft); }

/* notifications */
.notif-list { display: flex; flex-direction: column; gap: 8px; }
.notif-row { position: relative; overflow: hidden; border-radius: 10px; }
.notif { display: flex; align-items: flex-start; gap: 10px; width: 100%; text-align: left; background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; color: var(--text); position: relative; z-index: 1; touch-action: pan-y; }
.notif:hover { border-color: var(--accent); filter: none; }
.notif.unread { border-left: 3px solid var(--accent); }
/* Revealed behind .notif as it's dragged left (swipe-to-delete). opacity: 0
   by default rather than relying on z-index/overflow alone — .notif's own
   background (--panel) is translucent by design, so a hidden-only-by-stacking
   red layer would still bleed through and tint the whole card; JS toggles
   this to 1 (and pointer-events back on) once a horizontal drag starts. */
.notif-swipe-action { position: absolute; inset: 0; width: 100%; display: flex; align-items: center; justify-content: flex-end; padding-right: 22px; background: var(--danger-soft); color: var(--danger); border: 0; cursor: pointer; opacity: 0; pointer-events: none; }
.notif-swipe-action svg { width: 18px; height: 18px; }
.notif-icon { flex-shrink: 0; }
.notif-icon-type svg { width: 18px; height: 18px; }
.notif-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.notif-top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.notif-top strong { min-width: 0; }
/* Italic on purpose — reads as "just a timestamp", not more content. */
.notif-when { flex-shrink: 0; font-style: italic; font-size: 12px; color: var(--muted); font-weight: 400; }
.notif-body { color: var(--muted); font-size: 13px; font-weight: 400; }

/* toast */
/* Centred over the main content area (right of the sidebar), not the viewport. */
#toast-host { position: fixed; left: var(--sidebar-w); right: 0; bottom: 24px; display: flex; flex-direction: column; align-items: center; gap: 8px; z-index: 100; pointer-events: none; }
.toast { pointer-events: auto; background: #6fcf97; color: #06251a; font-weight: 600; padding: 12px 18px; border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,0.4); max-width: 90vw; }
.toast-error { background: var(--danger); color: #2a0707; }

@media (max-width: 760px) {
  .choice-grid, .choice-grid.sm { grid-template-columns: 1fr 1fr; }
  .modnav { width: 100%; order: 6; }
}

/* Issue cards (usterki / sprzątanie list) — photo + title + short description.
   Two CSS-Grid attempts at "column width = the list's longest status pill"
   (max-content column, then a subgrid) both broke: nesting a nested/subgrid
   intrinsic-sizing pass inside a max-content track resolved a descendant's
   width against some large ancestor instead of its own content, blowing the
   column out past 1000px. Plain flex (below) doesn't have that measurement
   pass at all — the shared width instead comes from --issue-thumb-w, a CSS
   custom property JS sets once per list (see syncThumbWidths in
   community.js) after measuring every pill's natural, unconstrained width. */
.card-list { display: flex; flex-direction: column; gap: 10px; }
.issue-card {
  display: flex; gap: 14px; width: 100%; text-align: left; cursor: pointer; color: var(--text);
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 12px;
}
.issue-card:hover { border-color: var(--accent); filter: none; }
/* Thumb + status pill, stacked. Both take --issue-thumb-w (set by JS to the
   list's widest pill; 84px default before JS runs) — the thumb square via
   aspect-ratio off that width, the pill via width:100% of the same column —
   so they can never disagree, and every card in the list matches the one
   whose status text was longest. */
.issue-thumb-col { display: flex; flex-direction: column; align-items: stretch; gap: 6px; flex-shrink: 0; width: var(--issue-thumb-w, 84px); }
.issue-thumb { width: 100%; aspect-ratio: 1 / 1; border-radius: 9px; object-fit: cover; }
/* No-photo tile = a category token: tinted fill, matching ring, category icon.
   Colour code mirrors .feed-ico so the list and the activity feed agree. */
.issue-thumb.ph {
  display: flex; align-items: center; justify-content: center;
  background: var(--accent-soft); color: var(--accent);
  border: 1px solid color-mix(in srgb, currentColor 24%, transparent);
  font-family: var(--display); font-size: 26px; font-weight: 700; text-transform: uppercase;
}
.issue-thumb.ph svg { width: 26px; height: 26px; }
.issue-thumb.ph.cleaning { background: var(--ok-soft); color: var(--ok); }
.issue-thumb.ph.lost, .issue-thumb.ph.found { background: var(--brass-soft); color: var(--brass); }
/* The status pill under the thumb: full column width, centered text. */
.issue-thumb-col .status-badge { display: block; width: 100%; box-sizing: border-box; text-align: center; }
.issue-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.issue-top { display: flex; align-items: flex-start; }
.issue-top strong { font-size: 15px; }
.issue-desc { margin: 0; color: var(--muted); font-size: 13px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.issue-meta { margin: auto 0 0; font-size: 12px; color: var(--muted); }

/* Dedup dialog: candidate photo + AI reason + checking spinner */
.dup-body { display: flex; gap: 12px; margin: 6px 0; }
.dup-thumb { width: 60px; height: 60px; border-radius: 8px; object-fit: cover; flex-shrink: 0; }
.dup-reason { font-style: italic; }
.ai-checking { display: flex; justify-content: center; padding: 32px 0; }
.spinner { width: 30px; height: 30px; border: 3px solid var(--line); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 2s; } }

/* ===== App shell: left sidebar + content ===== */
.app-shell { display: flex; min-height: 100vh; }
.app-shell.hidden { display: none; }
.sidebar {
  position: sticky; top: 0; height: 100vh; width: var(--sidebar-w); flex-shrink: 0;
  display: flex; flex-direction: column; gap: 6px;
  padding: 18px 14px; background: var(--panel); border-right: 1px solid var(--line);
}
.side-brand { font-family: var(--display); font-weight: 600; font-size: 21px; padding: 2px 8px 8px; }
.org-switch-wrap { display: flex; flex-direction: column; gap: 5px; margin: 2px 0 14px; }
.side-cap { font-size: 11px; text-transform: uppercase; letter-spacing: 0.09em; color: var(--muted); padding-left: 4px; }
.org-switch { width: 100%; margin: 0; font-weight: 600; }
/* Single-community residents see the name as plain text, not a dropdown. */
.org-name { display: none; padding: 4px 4px; font-weight: 600; font-size: 14px; color: var(--text); }
.org-switch-wrap.single .org-name { display: block; }
.org-switch-wrap.single .dd-trigger, .org-switch-wrap.single .org-switch { display: none; }
.side-nav { display: flex; flex-direction: column; gap: 1px; overflow-y: auto; margin: 0 -4px; padding: 0 4px; scrollbar-width: none; }
.side-nav::-webkit-scrollbar { display: none; }
.side-group-label {
  font-size: 10px; letter-spacing: 0.13em; text-transform: uppercase; color: var(--muted);
  opacity: 0.7; padding: 14px 12px 5px; font-weight: 700;
}
.side-link {
  position: relative; background: transparent; border: 0; color: var(--muted); text-align: left;
  border-radius: 9px; padding: 9px 12px; font-size: 14px; font-weight: 600; width: 100%;
  display: flex; align-items: center; gap: 10px;
  transition: background 0.14s ease, color 0.14s ease;
}
.side-link svg { width: 17px; height: 17px; flex-shrink: 0; opacity: 0.8; }
.side-link .badge { margin-left: auto; }
.side-link .mock-dot { margin-left: auto; width: 6px; height: 6px; border-radius: 999px; background: var(--brass); opacity: 0.6; }
.side-link:hover { background: var(--panel-2); color: var(--text); filter: none; }
.side-link.active { background: var(--accent-soft); color: var(--text); }
.side-link.active svg { opacity: 1; color: var(--accent); }
.side-link.active::before {
  content: ""; position: absolute; left: -4px; top: 8px; bottom: 8px; width: 3px;
  border-radius: 0 3px 3px 0; background: var(--accent);
}
.side-foot { margin-top: auto; border-top: 1px solid var(--line); padding-top: 12px; display: flex; flex-direction: column; gap: 8px; }
.side-foot .who { color: var(--muted); font-size: 12px; padding-left: 4px; word-break: break-all; }
.content { flex: 1; min-width: 0; height: 100vh; overflow-y: auto; }
/* Square icon button — no label text, the mode name lives in title/aria-label. */
.theme-toggle { flex: 0 0 38px; width: 38px; height: 38px; padding: 0; display: inline-flex; align-items: center; justify-content: center; }
.theme-toggle svg { flex-shrink: 0; }

/* Mobile top bar + drawer scrim — hidden on desktop, shown in the media query. */
.mobile-topbar {
  display: none; position: sticky; top: 0; z-index: 50; align-items: center; gap: 12px;
  padding: 11px 14px; background: var(--panel); border-bottom: 1px solid var(--line);
}
.mobile-topbar .mobile-brand { display: flex; align-items: center; gap: 9px; font-family: var(--display); font-weight: 600; font-size: 18px; }
.mobile-topbar .mobile-brand .brand-glyph { width: 18px; height: 18px; color: var(--accent); }
.mobile-topbar .mobile-brand .brand-glyph .gw { fill: var(--brass); }
.nav-toggle { background: transparent; border: 1px solid var(--line); color: var(--text); padding: 7px; border-radius: 9px; display: inline-flex; }
.nav-toggle:hover { background: var(--panel-2); }
.nav-toggle svg { width: 20px; height: 20px; display: block; }
.sidebar-backdrop {
  display: none; position: fixed; inset: 0; z-index: 55; background: transparent;
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  opacity: 0; pointer-events: none; transition: opacity 0.28s ease;
}
.sidebar-backdrop.open { opacity: 1; pointer-events: auto; }

@media (max-width: 860px) {
  /* Match .mobile-topbar's own horizontal padding (11px 14px) so page
     content sits at the same distance from the edge as the hamburger. */
  :root { --sidebar-w: 0px; --view-pad-x: 14px; } /* content (and toasts) span full width */
  .app-shell { display: block; }
  .mobile-topbar { display: flex; }
  .sidebar-backdrop { display: block; }
  /* Sidebar becomes an off-canvas drawer sliding in from the left. */
  .sidebar {
    position: fixed; top: 0; left: 0; height: 100vh; width: min(84vw, 300px);
    transform: translateX(-100%); transition: transform 0.28s cubic-bezier(0.2, 0.7, 0.2, 1);
    z-index: 60; border-right: 1px solid var(--line);
  }
  .sidebar.open { transform: none; box-shadow: 0 0 60px rgba(0, 0, 0, 0.5); }
  .content { height: auto; }
  .split { height: auto; }
  /* On mobile the page scrolls normally; give the thread a fixed height again. */
  #feedback-view.chat-mode, #assistant-info-view.chat-mode, .thread-mode { height: auto; overflow: visible; }
  .thread { flex: none; height: calc(100vh - 200px); }
}

/* Big "not connected" placeholder (e.g. invoices without KSeF access), and
   the owner-gate's fake background table — #list-view has no max-width of
   its own (its real content is the two-pane invoices/preview split, meant to
   use the full width), so without this the fake table stretches edge to
   edge instead of sitting in the same centered column every other view uses. */
#inv-placeholder { max-width: 880px; margin: 0 auto; }
.placeholder { display: flex; align-items: center; justify-content: center; min-height: 60vh; padding: 24px; }
.placeholder-card { max-width: 460px; text-align: center; color: var(--muted); }
.placeholder-card svg { color: var(--muted); opacity: 0.7; }
.placeholder-card h2 { font-family: var(--display); font-weight: 600; color: var(--text); margin: 12px 0 8px; }
.placeholder-card p { margin: 6px 0; }
.placeholder-card .ph-strong { color: var(--text); font-weight: 600; margin-top: 12px; }
.placeholder-card button { margin-top: 20px; }

/* "Request sent" confirmation animation */
@keyframes ph-pop { from { opacity: 0; transform: translateY(10px) scale(0.97); } to { opacity: 1; transform: none; } }
@keyframes ph-draw { to { stroke-dashoffset: 0; } }
.access-sent { animation: ph-pop 0.4s cubic-bezier(0.2, 0.7, 0.2, 1) both; }
.check-badge {
  width: 66px; height: 66px; margin: 0 auto 4px; border-radius: 999px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(111, 207, 151, 0.14); color: var(--ok);
  animation: ph-pop 0.45s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}
.check-badge svg path { stroke-dasharray: 40; stroke-dashoffset: 40; animation: ph-draw 0.5s 0.18s ease forwards; }

@media (prefers-reduced-motion: reduce) {
  .access-sent, .check-badge { animation: none; }
  .check-badge svg path { stroke-dashoffset: 0; animation: none; }
}

/* Owner-verification gate: a single shared overlay (#owner-gate-overlay,
   index.html — a sibling of every .view, see the comment there for why it
   can't live inside one) sits fixed over the whole content viewport, offset
   by --sidebar-w (same trick #toast-host already uses). The real screen
   behind it stays rendered normally — for invoices/debts that's a generic
   skeleton, never real numbers (see showOwnerGate() callers in app.js); for
   voting/payments it's the actual demo content, harmless to show since
   nothing there is secret — and backdrop-filter blurs it live, so the card
   sits dead-center over a screen that visibly still has something on it. */
.owner-gate-overlay {
  position: fixed; inset: 0; left: var(--sidebar-w); z-index: 10;
  display: flex; align-items: center; justify-content: center; padding: 24px;
  background: var(--panel); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
}
.owner-gate-overlay.hidden { display: none; }

/* The lock plays once on arrival — the shackle draws itself in
   (stroke-dashoffset, path length ~19.6 for the two 3.5-unit legs + the
   semicircular arc, hence dasharray 22 for headroom) and "clicks" shut, then
   settles into a quiet ping loop. */
.owner-gate-lock { position: relative; display: inline-flex; color: var(--muted); }
.owner-gate-lock .shackle { stroke-dasharray: 22; stroke-dashoffset: 22; animation: ownerGateClose 0.55s 0.15s ease-out forwards; }
.owner-gate-lock .lock-body { animation: ownerGateThunk 0.32s ease-out 0.68s both; }
.owner-gate-lock .ring {
  position: absolute; inset: 0; border-radius: 999px; border: 1.5px solid var(--accent);
  opacity: 0; animation: ownerGateRing 2.2s cubic-bezier(0.2, 0.6, 0.4, 1) 0.75s infinite;
}
.owner-gate-lock .ring.d2 { animation-delay: 1.5s; }
@keyframes ownerGateClose { to { stroke-dashoffset: 0; } }
@keyframes ownerGateThunk { 0%, 100% { transform: scale(1); } 45% { transform: scale(1.05); } }
@keyframes ownerGateRing { 0% { transform: scale(0.7); opacity: 0.65; } 100% { transform: scale(1.9); opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .owner-gate-lock .shackle { animation: none; stroke-dashoffset: 0; }
  .owner-gate-lock .lock-body, .owner-gate-lock .ring { animation: none; }
}

/* Dev-only quick-login buttons on the sign-in card */
.dev-accounts { margin-top: 18px; padding-top: 14px; border-top: 1px dashed var(--line); display: flex; flex-direction: column; gap: 8px; }
.dev-label { font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.dev-accounts-row { display: flex; flex-wrap: nowrap; gap: 8px; }
.dev-accounts-row button { flex: 1; min-width: 0; padding-left: 8px; padding-right: 8px; }

/* ====================================================================
   "Kamienica" — new components (design overhaul). See docs/DESIGN-SYSTEM.md.
   ==================================================================== */

/* ---- View transitions + list stagger ---- */
@keyframes viewIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.content .view:not(.hidden) { animation: viewIn 0.34s cubic-bezier(0.2, 0.7, 0.2, 1) both; }
.card-list > *, .lf-list > *, .notif-list > *, .stat-grid > *, .post-list > *,
.offer-list > *, .vote-list > *, .doc-list > *, .topic-list > * {
  animation: rise 0.45s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}
.card-list > *:nth-child(2), .lf-list > *:nth-child(2), .notif-list > *:nth-child(2),
.post-list > *:nth-child(2), .offer-list > *:nth-child(2), .vote-list > *:nth-child(2),
.doc-list > *:nth-child(2), .topic-list > *:nth-child(2), .stat-grid > *:nth-child(2) { animation-delay: 0.05s; }
.card-list > *:nth-child(3), .lf-list > *:nth-child(3), .notif-list > *:nth-child(3),
.post-list > *:nth-child(3), .offer-list > *:nth-child(3), .vote-list > *:nth-child(3),
.doc-list > *:nth-child(3), .topic-list > *:nth-child(3), .stat-grid > *:nth-child(3) { animation-delay: 0.1s; }
.card-list > *:nth-child(4), .lf-list > *:nth-child(4), .notif-list > *:nth-child(4),
.post-list > *:nth-child(4), .offer-list > *:nth-child(4), .vote-list > *:nth-child(4),
.doc-list > *:nth-child(4), .topic-list > *:nth-child(4), .stat-grid > *:nth-child(4) { animation-delay: 0.15s; }
.card-list > *:nth-child(n+5), .lf-list > *:nth-child(n+5), .notif-list > *:nth-child(n+5),
.post-list > *:nth-child(n+5), .offer-list > *:nth-child(n+5), .vote-list > *:nth-child(n+5),
.doc-list > *:nth-child(n+5), .topic-list > *:nth-child(n+5) { animation-delay: 0.2s; }

/* ---- Page header (eyebrow + title + action) ---- */
.page-head { margin-bottom: 22px; }
.page-eyebrow {
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--accent); display: block; margin-bottom: 8px;
}
.mod-head h2 { font-size: 1.7rem; letter-spacing: -0.01em; }
.mod-sub { color: var(--muted); margin: -10px 0 20px; font-size: 0.95rem; max-width: 80ch; }

/* ---- Brand glyph (small building mark next to the wordmark) ---- */
.side-brand { display: flex; align-items: center; gap: 9px; }
.side-brand .brand-glyph { width: 20px; height: 20px; color: var(--accent); flex-shrink: 0; }
.side-brand .brand-glyph .gw { fill: var(--brass); }

/* ---- The building elevation: the signature mark ---- */
.building { display: block; width: 100%; height: auto; color: var(--muted); }
.building .b-stroke { fill: none; stroke: currentColor; stroke-width: 1.3; stroke-linecap: round; stroke-linejoin: round; }
.building .b-ground { stroke: var(--brass); opacity: 0.5; }
.building .b-window { fill: var(--accent); opacity: 0; }
.building .b-door { fill: var(--accent); opacity: 0; }

/* Login: draw the outline, then light the windows one by one. */
@keyframes bdraw { to { stroke-dashoffset: 0; } }
@keyframes blight { from { opacity: 0; } to { opacity: 0.92; } }
.building.draw .b-stroke { stroke-dasharray: 1; stroke-dashoffset: 1; animation: bdraw 1.7s ease 0.15s forwards; }
.building.draw .b-window { animation: blight 0.5s ease forwards; }
.building.draw .b-door { animation: blight 0.6s ease 1.4s forwards; }
.building.draw .b-window:nth-of-type(1) { animation-delay: 1.0s; }
.building.draw .b-window:nth-of-type(2) { animation-delay: 1.15s; }
.building.draw .b-window:nth-of-type(3) { animation-delay: 1.3s; }
.building.draw .b-window:nth-of-type(4) { animation-delay: 1.45s; }
.building.draw .b-window:nth-of-type(5) { animation-delay: 1.6s; }
.building.draw .b-window:nth-of-type(6) { animation-delay: 1.75s; }
.building.draw .b-window:nth-of-type(7) { animation-delay: 1.9s; }
.building.draw .b-window:nth-of-type(8) { animation-delay: 2.05s; }
.building.draw .b-window:nth-of-type(9) { animation-delay: 2.2s; }

/* Login hero: place the elevation as ambient art behind the manifesto. */
.hero-building {
  position: absolute; right: -2vw; bottom: 0; width: 42%; max-width: 440px;
  opacity: 0.5; pointer-events: none; color: rgba(233, 237, 233, 0.5);
}

/* ---- Empty states: the building, breathing, inviting action ---- */
.empty-state {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  padding: 6vh 24px; max-width: 420px; margin: 4vh auto 0;
}
.empty-art { width: 150px; margin-bottom: 26px; color: var(--muted); opacity: 0.85; }
@keyframes breathe { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-6px); } }
@keyframes twinkle { 0%, 100% { opacity: 0.25; } 50% { opacity: 0.85; } }
.empty-art .building { animation: breathe 6s ease-in-out infinite; }
.empty-art .b-window { opacity: 0.4; animation: twinkle 4s ease-in-out infinite; }
.empty-art .b-window:nth-of-type(2n) { animation-delay: 1.3s; }
.empty-art .b-window:nth-of-type(3n) { animation-delay: 2.1s; }
.empty-art .b-door { opacity: 0.7; }
.empty-state h3 { font-family: var(--display); font-weight: 600; font-size: 1.4rem; margin: 0 0 8px; color: var(--text); }
.empty-state p { color: var(--muted); margin: 0 0 20px; line-height: 1.55; }
.empty-state .es-action { display: inline-flex; gap: 10px; }

/* iOS install walkthrough. Deliberately larger than the surrounding empty
   state: this is the one instruction our least technical residents must follow
   unaided, so the numbers, icons and text all stay comfortably legible. */
.ios-steps {
  list-style: none;
  margin: 4px 0 0;
  padding: 0;
  text-align: left;
  display: grid;
  gap: 14px;
  max-width: 420px;
}
.ios-steps li {
  display: grid;
  grid-template-columns: 34px 1fr;
  align-items: center;
  gap: 12px;
  font-size: 1.02rem;
  line-height: 1.5;
  color: var(--text);
}
.ios-step-icon {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 9px;
  border: 1px solid var(--line);
  background: var(--solid);
  color: var(--accent);
}
.ios-step-icon svg { width: 21px; height: 21px; }
/* The app icon is the artwork itself — no frame around it, and it keeps the
   same rounding iOS applies on the Home Screen. */
.ios-step-icon.is-app { border: 0; background: none; }
.ios-step-icon.is-app img { width: 100%; height: 100%; border-radius: 8px; display: block; }

/* Generic icon-based empty state (when the building isn't the right symbol) */
.empty-icon {
  width: 76px; height: 76px; margin-bottom: 20px; border-radius: 20px;
  display: flex; align-items: center; justify-content: center;
  background: var(--accent-soft); color: var(--accent);
}
.empty-icon svg { width: 34px; height: 34px; }

/* Neighbour-chat empty state: the bubbles land one by one, then drift. The
   incoming bubbles are glass, the reply carries the accent, and the third one
   keeps typing — the room reads as waiting for you. Geometry: chat.js. */
.chat-bubbles { display: block; width: 100%; height: auto; }
.chat-bubbles .cb-body { fill: var(--panel-2); stroke: var(--line); stroke-width: 1.2; }
.chat-bubbles .cb-out { fill: var(--accent-soft); stroke: var(--accent-line); }
.chat-bubbles .cb-line { fill: var(--muted); opacity: 0.5; }
.chat-bubbles .cb-line-out { fill: var(--accent); opacity: 0.55; }
.chat-bubbles .cb-dot { fill: var(--accent); opacity: 0.35; }

/* fill-box keeps each bubble scaling around its own centre, not the viewBox. */
.chat-bubbles .cb, .chat-bubbles .cb-dot { transform-box: fill-box; transform-origin: center; }
/* No new keyframes: ph-pop is the app's entrance, breathe is the same drift the
   empty-state building already uses, typingBounce the same dots as .typing.
   Land once (delay #1), then hand the transform over to the endless drift
   (delay #2) — different periods so the bubbles fall out of step. */
.chat-bubbles .cb { animation: ph-pop 0.5s cubic-bezier(0.2, 0.7, 0.2, 1) both, breathe 5s ease-in-out infinite; }
.chat-bubbles .cb-1 { animation-delay: 0.05s, 0.8s; }
.chat-bubbles .cb-2 { animation-delay: 0.22s, 1.1s; animation-duration: 0.5s, 5.6s; }
.chat-bubbles .cb-3 { animation-delay: 0.42s, 1.4s; animation-duration: 0.5s, 6.2s; }
.chat-bubbles .cb-dot { animation: typingBounce 1.2s ease-in-out 1.6s infinite; }
.chat-bubbles .cb-dot:nth-of-type(2) { animation-delay: 1.75s; }
.chat-bubbles .cb-dot:nth-of-type(3) { animation-delay: 1.9s; }

/* ---- Skeleton loaders ---- */
@keyframes shimmer { to { background-position: 200% 0; } }
.skel {
  border-radius: var(--radius-sm);
  background: linear-gradient(90deg, var(--panel-2) 25%, var(--line) 37%, var(--panel-2) 63%);
  background-size: 200% 100%; animation: shimmer 1.3s ease-in-out infinite;
}
.skel-card { display: flex; gap: 14px; background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px; margin-bottom: 10px; }
.skel-thumb { width: 84px; height: 84px; border-radius: 9px; flex-shrink: 0; }
.skel-lines { flex: 1; display: flex; flex-direction: column; gap: 9px; padding-top: 4px; }
.skel-line { height: 12px; }
.skel-line.w-60 { width: 60%; } .skel-line.w-40 { width: 40%; } .skel-line.w-80 { width: 80%; }

/* ---- Toasts (redesigned: icon + accent bar + slide-in) ---- */
#toast-host { bottom: 28px; gap: 10px; }
@keyframes toastIn { from { opacity: 0; transform: translateY(16px) scale(0.97); } to { opacity: 1; transform: none; } }
.toast {
  pointer-events: auto; display: flex; align-items: center; gap: 11px;
  background: var(--panel); color: var(--text); font-weight: 500;
  padding: 13px 18px 13px 15px; border-radius: 12px; border: 1px solid var(--line);
  box-shadow: var(--shadow-lift); max-width: min(90vw, 420px);
  border-left: 3px solid var(--ok);
  animation: toastIn 0.32s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}
.toast.leaving { animation: toastIn 0.3s ease reverse forwards; }
.toast .toast-ico { flex-shrink: 0; display: flex; color: var(--ok); }
.toast .toast-ico svg { width: 20px; height: 20px; }
.toast-error { border-left-color: var(--danger); }
.toast-error .toast-ico { color: var(--danger); }
.toast-info { border-left-color: var(--accent); }
.toast-info .toast-ico { color: var(--accent); }

/* ---- Stat cards (payments / dashboards) ---- */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; margin-bottom: 20px; }
.stat-card { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 18px; box-shadow: var(--shadow); }
.stat-card .stat-label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; }
.stat-card .stat-value { font-family: var(--mono); font-size: 1.9rem; font-weight: 700; margin: 8px 0 2px; letter-spacing: -0.02em; }
.stat-card .stat-foot { font-size: 12px; color: var(--muted); }
.stat-card .stat-foot.up { color: var(--ok); } .stat-card .stat-foot.down { color: var(--danger); }

/* progress bar (fund status, vote tallies) */
.bar-track { height: 9px; background: var(--panel-2); border-radius: 999px; overflow: hidden; }
.bar-fill { height: 100%; border-radius: 999px; background: var(--accent); transition: width 0.6s cubic-bezier(0.2, 0.7, 0.2, 1); }
.bar-fill.brass { background: var(--brass); }

/* ---- "Mock / wkrótce" banner for demo modules ---- */
.demo-banner {
  display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--brass);
  background: var(--brass-soft); border: 1px solid var(--brass-line);
  border-radius: 10px; padding: 10px 14px; margin-bottom: 20px;
}
.demo-banner svg { width: 16px; height: 16px; flex-shrink: 0; }
/* In Documents the banner follows the knowledge-base card — give it space above. */
#documents-view .demo-banner { margin-top: 20px; }

/* ---- Announcements / notice board ---- */
.post-list { display: flex; flex-direction: column; gap: 12px; }
.post {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 18px 20px; box-shadow: var(--shadow);
}
.post.pinned { border-color: var(--brass-line); }
.post-top { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.post-top h3 { margin: 0; font-family: var(--display); font-weight: 600; font-size: 1.15rem; }
.post .pin-tag { font-family: var(--mono); font-size: 11px; color: var(--brass); border: 1px solid var(--brass-line); border-radius: 6px; padding: 1px 7px; }
.post-meta { font-size: 12px; color: var(--muted); margin-left: auto; }
.post p { margin: 0; color: var(--text); line-height: 1.55; }
.post-foot { display: flex; gap: 16px; margin-top: 14px; font-size: 13px; color: var(--muted); }
/* :not(.ann-react-add) — the reaction button keeps its .ghost outline here,
   the same control it is inside the announcement. Everything else in the foot
   is a bare text action. */
.post-foot button:not(.ann-react-add) { background: transparent; border: 0; color: var(--muted); padding: 0; font-weight: 600; display: inline-flex; align-items: center; gap: 6px; }
.post-foot button:not(.ann-react-add):hover { color: var(--accent); }
.post-foot svg { width: 15px; height: 15px; }

/* Announcement body preview: exactly three lines at any width, with the
   ellipsis added by the browser. A JS truncator could only count characters
   and would cut after two lines on a narrow phone, four on a wide desktop. */
.ann-clamp {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.post-foot .board-activity { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; }
/* On a card the reaction tile sits inline, not as the absolutely-positioned
   overlay a chat bubble uses. Both classes are on the same element, so this
   needs BOTH in the selector: .chat-react-tile is declared later in this file
   and at equal specificity would win, leaving the pill absolutely positioned
   and escaping to the bottom of the view. */
.chat-react-tile.board-react-tile { position: static; }

/* The announcement itself, sitting at the very top of its discussion. Reads as
   a quieter cousin of .post: same surface, no hover affordance, and a brass
   left edge so it never reads as somebody's message. */
.ann-tile {
  border: 1px solid var(--line);
  border-left: 3px solid var(--brass-line);
  border-radius: 14px;
  padding: 14px 16px;
  background: var(--panel);
  margin-bottom: 6px;
}
/* Inside the announcement the body is shown whole and is not a control — the
   three-line clamp and its pointer belong to the list card, where the text is
   a preview of somewhere to go. */
.ann-body-full { margin: 0; line-height: 1.55; white-space: pre-wrap; }
.ann-react-bar { display: flex; align-items: center; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
/* One button that opens the picker, rather than six emoji laid out in advance:
   the tile stays about the announcement, and choosing gets its own overlay. */
.ann-react-add { padding: 5px 9px; line-height: 0; }
.ann-react-add svg { width: 19px; height: 19px; }
.ann-react-add.mine { border-color: var(--accent-line); color: var(--accent); }
.ann-no-discussion { margin-top: 18px; }

/* ---- Parking offers ---- */
.offer-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px; }
.offer {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 18px; box-shadow: var(--shadow); display: flex; flex-direction: column; gap: 4px;
}
/* Same tinted-square-icon-tile language as .issue-thumb.ph / .doc-ico — a
   quiet accent-coloured anchor since every spot here is the same offer
   (free), not a category that needs its own colour code. */
.offer-icon {
  align-self: flex-start; width: 40px; height: 40px; margin-bottom: 6px; border-radius: 10px;
  display: flex; align-items: center; justify-content: center;
  background: var(--accent-soft); color: var(--accent);
}
.offer-icon svg { width: 20px; height: 20px; }
.offer .offer-spot { font-family: var(--mono); font-size: 12.5px; color: var(--muted); }
.offer h3 { margin: 2px 0 2px; font-family: var(--display); font-weight: 600; }
.offer .offer-when { font-size: 13px; color: var(--muted); }
.offer button { margin-top: 14px; }
.offer.taken { opacity: 0.6; }
.offer .free-tag { align-self: flex-start; font-size: 11px; font-weight: 700; color: var(--ok); background: var(--ok-soft); border-radius: 6px; padding: 2px 8px; margin-top: 8px; }

/* Share form: an absence is one span, so the two ends sit side by side joined
   by a rule, rather than reading as four unrelated fields in a column. */
/* Fixed middle column, not `auto`: with `auto` the connector sized itself to
   its caption, so a long word ("opcjonalnie") squeezed its own row's tiles
   narrower than the row above. Fixing it keeps all four tiles identical. */
.park-span { display: grid; grid-template-columns: 1fr 104px 1fr; align-items: center; gap: 10px; margin: 14px 0 4px; }
/* min-width:0 lets the 1fr columns actually share the space — without it the
   date input's intrinsic width wins and the columns drift apart again. */
.park-leg { min-width: 0; display: flex; flex-direction: column; gap: 6px; padding: 12px; border-radius: var(--radius-sm); background: var(--panel-2); border: 1px solid var(--line); }
.park-leg-tag { font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.park-leg input, .park-leg select { margin: 0; background: transparent; border: 0; padding: 2px 0; }
.park-leg input:focus, .park-leg select:focus { outline: none; box-shadow: none; }
/* Inline range picker. The native <input type="date"> dropdown is browser
   chrome and cannot be styled at all, so the dates are chosen on this grid —
   the same .park-grid the availability calendar uses, one size down. */
.park-pick { margin: 12px 0 4px; padding: 12px; border-radius: var(--radius-sm); border: 1px solid var(--line); }
.park-pick-nav { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.park-pick-nav h4 { margin: 0; font-family: var(--display); font-size: 15px; font-weight: 600; text-transform: capitalize; }
.park-pick-nav button { padding: 2px 10px; background: transparent; color: var(--text); font-size: 18px; line-height: 1; }
.park-pick-nav button:hover { filter: none; color: var(--accent); }
.park-pick-nav button:disabled { opacity: 0.3; cursor: default; }
.park-grid-compact { gap: 3px; }
.park-grid-compact .park-cell { min-height: 34px; padding: 0; }
.park-pick-cell { cursor: pointer; }
.park-pick-cell:hover { border-color: var(--accent-line); }
/* Endpoints filled, the days between them tinted: the span is the thing being
   chosen, so it has to be legible as one shape rather than two clicks — and
   as a *hover preview* before it's committed, per the same two classes.
   Compounded with .park-pick-cell (two classes, not one) so this reliably
   beats the plain .park-cell rule below regardless of source order — two
   single-class rules of equal specificity would otherwise let whichever is
   declared later in the file win, which is what made selected tiles render
   with the unselected (white/muted) look instead of the accent fill. */
.park-pick-cell.park-pick-in { background: var(--accent-soft); border-color: transparent; }
.park-pick-cell.park-pick-on { background: var(--accent); border-color: var(--accent); color: #fff; }
.park-pick-cell.park-pick-on .park-day-num { color: #fff; }
.park-pick-dur { margin: 10px 0 0; text-align: center; font-size: 12px; font-weight: 600; color: var(--accent); opacity: 0; transition: opacity 0.15s ease; }
.park-pick-dur.on { opacity: 1; }
.park-leg:focus-within { border-color: var(--accent-line); }
/* The connector carries the span's length — the one number the sharer is
   actually deciding on, so they don't have to count days in their head. */
/* Caption above the rule, not on it — centred on the line it read as struck
   through. The rule is ::after so it always sits under whatever caption the
   row carries, and the column keeps its height when the caption is hidden,
   so both rows stay aligned. */
.park-span-link { display: flex; flex-direction: column; align-items: center; gap: 5px; }
.park-span-link::after { content: ""; align-self: stretch; height: 1px; background: var(--line); }
.park-dur { padding: 2px 8px; border-radius: 999px; font-size: 12px; font-weight: 600; color: var(--accent); background: var(--accent-soft); opacity: 0; transition: opacity 0.15s ease; }
.park-dur.on { opacity: 1; }
/* The hours row reuses the connector for its "optional" note, so it must read
   as a caption rather than compete with the accent-coloured duration above. */
.park-dur-quiet { color: var(--muted); background: transparent; font-weight: 500; }
/* Confirm sits right and apart: the span above is input, this is the commit. */
.park-share-foot { display: flex; justify-content: flex-end; margin-top: 26px; }
@media (max-width: 420px) {
  /* Stacked: the rule turns from a bridge into a divider, chip still on it. */
  .park-span { grid-template-columns: 1fr; gap: 6px; }
  .park-span-link { min-height: 26px; }
}

.park-calendar-nav { display: flex; align-items: center; justify-content: space-between; margin: 18px 0 10px; }
.park-calendar-nav h3 { margin: 0; font-family: var(--display); font-weight: 600; text-transform: capitalize; }
.park-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; min-width: 0; }
.park-weekday { text-align: center; font-size: 12px; color: var(--muted); padding-bottom: 4px; min-width: 0; }
.park-cell {
  position: relative; aspect-ratio: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; border-radius: var(--radius-sm); background: var(--panel); border: 1px solid var(--line);
  font-family: inherit; color: var(--text); min-width: 0;
}
.park-cell-blank { background: transparent; border-color: transparent; }
.park-cell-past { opacity: 0.35; }
.park-day-num { font-size: 13px; }
.park-cell-avail { cursor: pointer; border-color: var(--ok-line); background: var(--ok-soft); }
.park-cell-avail:hover { border-color: var(--ok); background: var(--ok-soft); }
.park-badge { font-size: 11px; font-weight: 700; color: var(--ok); }
/* Corner mark for "you're the one who shared this day" — same glyph and
   colours as .park-icon-p on the tiles above, shrunk to fit a cell corner, so
   the two read as one visual language rather than two separate icons. */
.park-cell-mine {
  position: absolute; top: 6px; right: 6px; width: 14px; height: 14px; border-radius: 4px;
  background: var(--accent); color: #fff; font-family: var(--display); font-weight: 800;
  font-size: 9px; line-height: 14px; text-align: center;
}
.park-no-avail { margin: 10px 2px 0; font-size: 13px; color: var(--muted); text-align: center; }
@media (max-width: 640px) { .park-day-num { font-size: 12px; } .park-badge { font-size: 10px; } }

/* "My reservations" — horizontal, scrollable, so it stays compact next to
   the calendar instead of stacking vertically like a full list. */
.offer-carousel { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 4px; scroll-snap-type: x mandatory; scrollbar-width: none; -ms-overflow-style: none; }
.offer-carousel::-webkit-scrollbar { display: none; }
.offer-carousel > .offer, .offer-carousel > .park-mine-tile { flex: 0 0 100%; scroll-snap-align: start; }
@media (min-width: 640px) {
  /* Capped so the ticket stub doesn't stretch edge-to-edge on wide layouts;
     wireTileCarousel already tracks the nearest tile by scroll position, so
     several tiles being visible at once doesn't break the active dot. */
  .offer-carousel > .offer { flex: 0 0 220px; }
  .offer-carousel > .park-mine-tile { flex: 0 0 340px; }
}

.carousel-dots { display: flex; justify-content: center; gap: 7px; margin-top: 10px; }
.carousel-dot { width: 6px; height: 6px; padding: 0; border: 0; border-radius: 50%; background: var(--line); cursor: pointer; transition: background 0.15s, transform 0.15s; }
.carousel-dot:hover { background: var(--muted); }
.carousel-dot.active { background: var(--accent); transform: scale(1.4); }

/* "Moje rezerwacje" / "Moje udostępnienia" tile — a ticket stub. The whole
   "od–do" span lives in the left segment (a real parking ticket never prints
   only half its validity window), with a dashed divider standing in for a
   perforation; the right segment carries only the spot's identity. Replaced
   an earlier one-row layout where the date sat in a plain caption next to
   the spot name — validated against the ticket metaphor and picked over it. */
.park-mine-tile { position: relative; display: flex; border-radius: var(--radius-sm); background: var(--panel); border: 1px solid var(--line); overflow: hidden; }
.park-mine-stub { flex: 0 0 84px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px; padding: 14px 8px; background: var(--accent-soft); border-right: 1px dashed var(--line); }
.park-mine-icon { width: 26px; height: 26px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 12px; flex-shrink: 0; }
/* A same-month span reads as one line, "13 → 16", under a shared month label. */
.park-mine-span { display: flex; align-items: baseline; gap: 3px; }
.park-mine-span .d { font-size: 16px; font-weight: 800; line-height: 1; color: var(--text); }
.park-mine-span .arrow { font-size: 10px; color: var(--muted); }
.park-mine-day { font-size: 18px; font-weight: 800; line-height: 1; color: var(--text); }
.park-mine-mon { font-size: 9px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); }
/* A span crossing a month boundary (e.g. 30 sie – 2 wrz) can't share one
   month label, so it falls back to two compact day+month lines instead of
   the enlarged single-line arrow form. */
.park-mine-cross { font-size: 10px; font-weight: 700; color: var(--text); line-height: 1.4; text-align: center; }
.park-mine-main { flex: 1; padding: 14px 40px 14px 14px; display: flex; flex-direction: column; justify-content: center; gap: 3px; min-width: 0; }
.park-mine-spot { font-family: var(--display); font-weight: 600; font-size: 15px; color: var(--text); letter-spacing: -0.005em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.park-mine-zone { font-size: 12px; color: var(--muted); }
/* Optional, offer-only addendum below spot + zone — a share with no hours
   set shows neither this line nor an empty gap, since it just isn't rendered. */
.park-mine-hours { display: block; margin-top: 4px; font-size: 11px; color: var(--muted); }
.park-mine-cancel { position: absolute; top: 10px; right: 10px; width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; background: transparent; border: 0; color: var(--muted); font-size: 13px; cursor: pointer; border-radius: 50%; }
.park-mine-cancel:hover { color: var(--danger); background: var(--danger-soft); filter: none; }
/* Who's reserved this offer — informational only; the whole tile (below)
   is what's clickable. Compound selectors (".avatar.park-mine-avatar…") on
   purpose — .avatar's own rule sits later in this file (~line 1562) and
   would otherwise win on source order alone, same trap as
   .park-pick-cell.park-pick-on above. */
.park-mine-resv { display: flex; align-items: center; gap: 6px; margin-top: 4px; }
.avatar.park-mine-avatar { width: 20px; height: 20px; font-size: 9px; }
.park-mine-resv-name { font-size: 11.5px; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.park-mine-avatar-stack { display: inline-flex; }
.avatar.park-mine-avatar-stacked { margin-left: -7px; border: 2px solid var(--panel); }
.park-mine-avatar-stack .park-mine-avatar-stacked:first-child { margin-left: 0; }
/* A reserved offer's tile itself opens the details dialog — real .park-mine
   div, not a <button> (it already nests the ✕ cancel <button>, and buttons
   can't nest), so focus/hover states are added explicitly here. */
.park-mine-tile-clickable { cursor: pointer; }
.park-mine-tile-clickable:hover { border-color: var(--accent-line); }
.park-mine-tile-clickable:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* "Who booked this" dialog — one row per reserved day. */
.park-resv-list { display: flex; flex-direction: column; gap: 12px; }
.park-resv-row { display: flex; align-items: center; gap: 10px; }
.park-resv-name { flex: 1; min-width: 0; font-size: 13.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.park-resv-date { font-size: 12.5px; color: var(--muted); }

/* Appear/disappear (max-height/opacity/margin) is driven entirely from JS in
   renderMyReservations, same max-height-collapse technique as rmFinishDelete
   in community.js — a CSS keyframe on transform can't do this because it
   doesn't affect layout, so the calendar below would jump instead of sliding
   up in sync. */

/* Post-reserve reveal: who shared the spot, and the spot itself in large
   text — this is the only place the assigned spot is shown to the reserver. */
.park-reveal { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 4px 8px 8px; text-align: center; }
.park-reveal-avatar { width: 72px; height: 72px; border-radius: 50%; object-fit: cover; border: 3px solid var(--ok-soft); box-shadow: 0 0 0 2px var(--ok); }
.park-reveal-avatar-ph { display: flex; align-items: center; justify-content: center; background: var(--ok-soft); color: var(--ok); font-weight: 700; font-size: 26px; }
.park-reveal-owner { margin: 0; font-size: 13.5px; color: var(--muted); }
.park-reveal-owner strong { color: var(--text); }
/* Spot + zone as one string used to wrap onto two lines at 30px ("Parking
   zewnętrzny" alone on its own line), reading as one oversized run-on rather
   than two purposeful tiers. Split into a tight two-line block instead — the
   spot number keeps its size, the zone drops to a caption underneath it. */
.park-reveal-identity { display: flex; flex-direction: column; gap: 2px; }
.park-reveal-spot { margin: 0; font-family: var(--display); font-weight: 800; font-size: 30px; letter-spacing: -0.02em; }
.park-reveal-zone { margin: 0; font-size: 15px; font-weight: 500; color: var(--muted); }
.park-reveal button { margin-top: 6px; min-width: 150px; }

/* ---- Voting / resolutions ---- */
.vote-list { display: flex; flex-direction: column; gap: 14px; }
.proposal { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 20px; box-shadow: var(--shadow); }
.proposal-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.proposal h3 { margin: 0 0 4px; font-family: var(--display); font-weight: 600; font-size: 1.2rem; }
.proposal .prop-num { font-family: var(--mono); font-size: 12px; color: var(--brass); }
.proposal p { color: var(--muted); margin: 8px 0 16px; line-height: 1.5; }
.tally { display: flex; flex-direction: column; gap: 9px; margin: 14px 0; }
.tally-row { display: grid; grid-template-columns: 90px 1fr 44px; align-items: center; gap: 10px; font-size: 13px; }
.tally-row .tally-val { font-family: var(--mono); text-align: right; color: var(--muted); }
.vote-actions { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 16px; }
.vote-actions button { background: var(--panel-2); border: 1px solid var(--line); color: var(--text); }
.vote-actions button.for:hover { border-color: var(--ok); color: var(--ok); }
.vote-actions button.against:hover { border-color: var(--danger); color: var(--danger); }
.vote-actions button.abstain:hover { border-color: var(--brass); color: var(--brass); }
.vote-actions button.chosen { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }

/* ---- Documents ---- */
.doc-list { display: flex; flex-direction: column; gap: 8px; }
.doc-row {
  display: flex; align-items: center; gap: 14px; background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 13px 16px;
  cursor: pointer; transition: border-color 0.14s ease, transform 0.08s ease;
  color: var(--text); text-decoration: none; /* rows can be <a>; don't look like raw links */
}
.doc-row:hover { border-color: var(--accent-line); background: var(--panel-2); }
.doc-ico { width: 38px; height: 38px; border-radius: 9px; background: var(--accent-soft); color: var(--accent); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.doc-ico svg { width: 19px; height: 19px; }
.doc-row .doc-name { font-weight: 600; flex: 1; min-width: 0; display: flex; align-items: center; flex-wrap: wrap; gap: 4px 0; }
.doc-row .doc-meta { font-family: var(--mono); font-size: 12px; color: var(--muted); margin-left: 14px; text-align: right; max-width: 44%; flex-shrink: 0; }
.doc-list + .doc-list { margin-top: 8px; }
.doc-empty { color: var(--muted); margin: 14px 0 0; font-size: 0.95rem; }
/* Reference rows (akty prawne / dokumenty lokalne): title on top, description
   beneath it in the normal UI font (not the mono meta used by file rows). */
.doc-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.doc-body .doc-name { flex: none; }
.doc-desc { color: var(--muted); font-size: 0.9rem; line-height: 1.45; }

/* Link preview tiles inside chat bubbles (always at the bottom of the bubble). */
.link-tiles { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.link-tile {
  display: flex; align-items: stretch; gap: 0; overflow: hidden; text-decoration: none;
  color: var(--text); background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius-sm); transition: border-color 0.14s ease;
}
.link-tile:hover { border-color: var(--accent-line); }
.link-tile-img { flex-shrink: 0; width: 84px; align-self: stretch; object-fit: cover; background: var(--accent-soft); display: block; }
.link-tile-body { display: flex; flex-direction: column; gap: 3px; padding: 10px 12px; min-width: 0; }
.link-tile-title {
  font-weight: 600; font-size: 0.92rem; line-height: 1.3; word-break: break-word;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.link-tile-desc {
  color: var(--muted); font-size: 0.85rem; line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.link-tile-host { color: var(--accent); font-size: 0.78rem; font-family: var(--mono); margin-top: 2px; }
/* Knowledge-base row: a real, clickable doc that stands a little apart. */
.doc-row.kb { width: 100%; text-align: left; color: var(--text); font: inherit; filter: none; border-color: var(--accent-line); }
.doc-row.kb:hover { background: var(--panel-2); filter: none; }
.doc-tag { margin-left: 9px; font-size: 11px; font-weight: 600; color: var(--accent); background: var(--accent-fill); border-radius: 999px; padding: 2px 9px; }
/* Rendered markdown view */
.doc-view { max-width: none; line-height: 1.6; margin-top: 32px; }
.doc-view h3 { font-family: var(--display); font-weight: 600; margin: 0 0 14px; }
.doc-view h4 { font-family: var(--display); font-weight: 600; font-size: 1.05rem; margin: 22px 0 8px; }
.doc-view h5 { font-size: 0.95rem; font-weight: 600; margin: 16px 0 6px; }
.doc-view p { margin: 0 0 10px; }
.doc-view ul { margin: 0 0 14px; padding-left: 20px; }
.doc-view li { margin: 5px 0; }
/* Blocks the legal documents use that the knowledge base never did. */
.doc-view ol { margin: 0 0 14px; padding-left: 22px; }
.doc-view a { text-decoration: underline; }
.doc-view code { font-family: var(--mono); font-size: 0.88em; background: var(--line); padding: 1px 5px; border-radius: 4px; }
.doc-view hr { border: 0; border-top: 1px solid var(--line); margin: 26px 0; }
.doc-view blockquote { margin: 14px 0; padding: 10px 16px; border-left: 3px solid var(--line); }
.doc-view blockquote p:last-child { margin-bottom: 0; }
.doc-view .table-wrap { margin: 0 0 18px; }
.doc-view table { font-size: 14px; }
.doc-view th { text-align: left; font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); font-weight: 500; padding: 7px 10px 7px 0; border-bottom: 1px solid var(--line); vertical-align: bottom; }
.doc-view td { padding: 7px 10px 7px 0; border-bottom: 1px solid var(--line); vertical-align: top; }
.doc-view tr:last-child td { border-bottom: 0; }
/* The document opens in the shared modal, which already has its own top
   padding — the 32px from .doc-view would double it up. */
.debt-modal-body .doc-view { margin-top: 0; }
.legal-lang-note { margin: 0 0 4px; font-style: italic; }

/* ---- Legal document links (login footer, settings, registration form) ---- */
.legal-links { display: flex; flex-wrap: wrap; gap: 4px 14px; }
.legal-links a { color: var(--muted); text-decoration: none; font-size: 12px; }
.legal-links a:hover { text-decoration: underline; }
.login-legal { justify-content: center; margin-top: 18px; }
.set-legal { margin-top: 22px; padding-top: 14px; border-top: 1px solid var(--line); }
/* Consent checkbox: the box keeps its natural size next to wrapping text
   instead of stretching to the line height. */
.check-line { display: flex; align-items: flex-start; gap: 10px; margin: 4px 0 2px; font-size: 13.5px; line-height: 1.5; color: var(--text); cursor: pointer; }
/* The agreed-to half is set apart by weight alone — the whole sentence stays at
   full text colour, because it is a consent statement, not a hint. */
.check-line b { font-weight: 600; }
/* Custom box: the browser default is a hard square next to inputs with a 10px
   radius and a glass fill (see the `input` rule above), which is what made the
   original block look bolted on. */
.check-line input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  /* padding:0 is load-bearing. The global `input` rule above sets
     `padding: 10px 12px`, and appearance:none does not shed it — this is still
     an <input>. With box-sizing:border-box the 24px of horizontal padding
     exceeds the 18px width, so the box cannot honour it and stretches to fit:
     26x22 instead of 18x18. A rectangle, not a checkbox. */
  padding: 0;
  /* 1px nudge so the box sits on the cap-height of the first line, not above it. */
  flex: 0 0 auto; width: 18px; height: 18px; margin: 1px 0 0;
  border: 1px solid var(--line); border-radius: 6px; background: var(--panel-2);
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.check-line input[type="checkbox"]:hover { border-color: var(--accent-line); }
/* The tick is a real stroked path, not the usual rotated-border trick: that one
   is two straight edges meeting at a hard corner with butt ends, which at 18px
   reads as a blunt wedge. This has round caps and a round join, and is centred
   by the background positioner rather than by hand-tuned offsets. */
.check-line input[type="checkbox"]:checked {
  border-color: var(--accent);
  background:
    var(--accent)
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.6 8.5 6.7 11.6 12.4 4.9' fill='none' stroke='white' stroke-width='2.1' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")
    center / 11px 11px no-repeat;
}
.check-line input[type="checkbox"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* No `color` was ever set here, so these links fell back to the browser's
   default blue — a colour from outside the palette, on the one line that
   should read as a sentence rather than a list of links. */
.check-line a { color: var(--text); text-decoration: underline; text-decoration-color: var(--accent-line); text-decoration-thickness: 1px; text-underline-offset: 3px; }
.check-line a:hover { text-decoration-color: var(--accent); }

/* ---- Administracja: Mieszkańcy (residents drill-down) ---- */
.res-building { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); margin-bottom: 12px; overflow: hidden; box-shadow: var(--shadow); }
.res-building:last-child { margin-bottom: 0; }
.res-building-head {
  width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 14px 16px; background: transparent; border: 0; color: var(--text); text-align: left; cursor: pointer;
}
.res-building-head:hover { background: var(--panel-2); }
.res-building-head .act-chev { width: 18px; height: 18px; flex-shrink: 0; color: var(--muted); transition: transform 0.15s ease; }
.res-building-head.open .act-chev { transform: rotate(90deg); }
.res-building-name { font-weight: 600; }
.res-building-count { margin-left: auto; margin-right: 8px; color: var(--muted); font-size: 12.5px; }
.res-apts { border-top: 1px solid var(--line); padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; }
.res-apt { background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 12px 14px; }
.res-apt-label { font-weight: 600; font-size: 13px; margin-bottom: 8px; }
.res-apt-people { display: flex; flex-direction: column; gap: 10px; }
.res-empty { margin: 0; color: var(--muted); font-size: 13px; }
.res-person { display: flex; align-items: center; gap: 10px; }
.res-person-main { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }

/* ---- Administracja: Usługodawcy (list of rows; "+" in the header opens the
   same dialog as clicking a row — see .debt-modal for the dialog itself) ---- */
.prov-add-btn {
  width: 36px; height: 36px; flex-shrink: 0; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
}
.prov-add-btn svg { width: 18px; height: 18px; }
.prov-list { display: flex; flex-direction: column; gap: 10px; }
.prov-row {
  display: flex; align-items: center; gap: 14px; width: 100%; text-align: left; color: var(--text);
  padding: 14px 16px; background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow); cursor: pointer;
  transition: border-color 0.14s ease, transform 0.14s ease;
}
.prov-row:hover { border-color: var(--accent-line); transform: translateY(-1px); filter: none; }
.prov-row > .avatar { flex-shrink: 0; }
.prov-row-main { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
/* Avatar-change block inside the provider dialog. */
.prov-avatar-edit { display: flex; align-items: center; gap: 14px; margin: 0 0 16px; }
.prov-avatar-edit .avatar { flex-shrink: 0; }
.prov-avatar-edit .hint { font-size: 12px; color: var(--muted); margin: 6px 0 0; }
.prov-row-name { font-weight: 600; }
.prov-row-contact { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.prov-row-cat { font-size: 12.5px; color: var(--muted); }
.prov-row .act-chev { width: 18px; height: 18px; flex-shrink: 0; color: var(--muted); }
/* Provider login-account lifecycle pill (none / pending / active). */
.prov-acct-badge {
  display: inline-block; margin-left: 6px; padding: 1px 8px; border-radius: 999px;
  font-size: 11px; font-weight: 600; vertical-align: middle; line-height: 1.6;
  border: 1px solid transparent;
}
.prov-acct-badge.acct-none { background: color-mix(in srgb, var(--muted) 16%, transparent); color: var(--muted); }
.prov-acct-badge.acct-pending { background: color-mix(in srgb, #d9962a 18%, transparent); color: #b57d1c; border-color: color-mix(in srgb, #d9962a 30%, transparent); }
.prov-acct-badge.acct-active { background: color-mix(in srgb, #16a34a 16%, transparent); color: #15803d; border-color: color-mix(in srgb, #16a34a 30%, transparent); }
.prov-acct-line { font-size: 13px; color: var(--muted); margin: 4px 0 0; }
/* Providers see only their portal nav — the rest of the sidebar is hidden. */
body.is-provider .side-nav > :not(#provider-group) { display: none; }
/* Delete sits apart from Save inside the shared .debt-modal-foot (flex-end). */
.debt-modal-foot .prov-dialog-del { margin-right: auto; }

/* ---- Roadmap (platform-wide, admin-only): chat box + ranked list ---- */
.rm-chat {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); overflow: hidden; margin-bottom: 20px;
}
.rm-log { max-height: 280px; overflow-y: auto; display: flex; flex-direction: column; gap: 12px; padding: 14px 14px 4px; }
.rm-log.hidden { display: none; }
.rm-composer { border-top: none; padding: 12px 14px; }
.rm-composer textarea { flex: 1; resize: none; min-height: 38px; max-height: 96px; line-height: 1.4; }
.rm-list { display: flex; flex-direction: column; gap: 10px; }
.rm-card {
  display: flex; align-items: flex-start; gap: 14px; background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow); padding: 14px 16px;
}
.rm-rank { font-family: var(--mono); font-weight: 700; font-size: 1.05rem; color: var(--muted); width: 22px; flex-shrink: 0; text-align: center; }
.rm-body { flex: 1; min-width: 0; }
.rm-title-row { display: flex; align-items: flex-start; gap: 8px; }
.rm-title { flex: 1; font-weight: 600; }
.rm-desc { margin: 4px 0 0; color: var(--muted); font-size: 0.88rem; line-height: 1.45; }
.rm-del { flex-shrink: 0; background: transparent; border: 0; color: var(--muted); font-size: 18px; line-height: 1; cursor: pointer; padding: 2px 6px; }
.rm-del:hover { color: var(--danger); filter: none; }
/* Grace-period delete: the card's content is swapped for a countdown row. */
.rm-card.rm-deleting { align-items: center; }
.rm-del-sec { font-family: var(--mono); font-weight: 700; font-variant-numeric: tabular-nums; color: var(--danger); width: 22px; flex-shrink: 0; text-align: center; }
.rm-del-label { color: var(--muted); font-size: 0.88rem; }
.rm-del-cancel { margin-left: auto; background: transparent; border: 0; color: var(--accent); font-size: 0.85rem; font-weight: 600; cursor: pointer; }
.rm-del-cancel:hover { text-decoration: underline; filter: none; }

/* ---- Neighbour chat (topics + thread) ---- */
/* A conversation card, not a data row: the avatar stack leads (who's in
   it — the one thing worth recognizing at a glance), title + preview stack
   beside it, time sits quietly in its own corner. Unread state is a single
   signal (bold title + accent ring on the lead avatar + count pill) instead
   of several competing badges. */
.topic-list { display: flex; flex-direction: column; gap: 10px; }
.topic {
  display: flex; align-items: flex-start; gap: 14px; width: 100%; text-align: left; color: var(--text);
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 14px 16px; cursor: pointer; transition: border-color 0.14s ease, background-color 0.14s ease;
}
.topic:hover { border-color: var(--accent-line); background: var(--panel-2); filter: none; }
.topic-avatars { flex-shrink: 0; padding-top: 1px; }
.topic-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.topic-top-line { display: flex; align-items: baseline; gap: 10px; }
.topic-top-line strong {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--display); font-size: 15.5px; font-weight: 600; letter-spacing: -0.01em;
}
.topic.unread .topic-top-line strong { font-weight: 700; }
.topic-when { flex-shrink: 0; font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.topic-last-line { display: flex; align-items: center; gap: 8px; }
.topic-last { flex: 1; min-width: 0; color: var(--muted); font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.topic.unread .topic-last { color: var(--text); opacity: 0.82; }
.topic-unread {
  flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center;
  min-width: 19px; height: 19px; padding: 0 6px; background: var(--accent); color: #fff;
  border-radius: 999px; font-size: 11px; font-weight: 700;
}

/* Chat-style views (assistant + neighbourhood thread): the view fills the
   viewport as a flex column so the header/subtitle stay pinned and ONLY the
   thread scrolls — no whole-page over-scroll when the chat opens. */
#feedback-view.chat-mode, #assistant-info-view.chat-mode, .thread-mode { display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
#feedback-view.chat-mode > .mod-head, #feedback-view.chat-mode > .mod-sub,
#assistant-info-view.chat-mode > .mod-head, #assistant-info-view.chat-mode > .mod-sub,
.thread-mode > .back-topics, .thread-mode > .mod-head, .thread-mode > .thread-people { flex-shrink: 0; }
/* Same pinned-header/scrolling-thread shape as above, but sized to fill the
   dialog panel (see .assistant-dialog-panel) instead of the full viewport —
   flex: 1 (not height: 100%) since .assistant-brand is now a sibling above
   it taking its own space. .mod-head/.mod-sub/.thread-scroll/.composer have
   no horizontal padding of their own — they relied on the shared full-page
   view padding this element no longer gets now that it lives in a dialog. */
#assistant-view { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; padding: 18px 20px 16px; }
/* The assistant's other home: the same chat rendered full-page (community.js
   showAssistantInfo). Same flex-column job as #assistant-view above, minus the
   padding — the page view already brings its own. */
#assist-page { display: flex; flex-direction: column; flex: 1; min-height: 0; }
#assistant-view > .mod-head, #assistant-view > .mod-sub { flex-shrink: 0; }
.thread { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.thread-scroll { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 12px; padding: 8px 2px 18px; }
/* An intro (chatView cfg.empty) is a fixed stand-in, not a growing thread: it
   centres in the space it gets instead of hanging off the composer. It is sized
   to fit, so the inherited overflow-y: auto never fires — and `safe` keeps that
   honest on a window too short to hold it, yielding to flex-start so the top
   scrolls into reach instead of becoming unreachable above the centre. */
.thread-scroll.thread-intro { justify-content: safe center; }
.msg { max-width: 76%; }
.msg .msg-who { font-size: 12px; color: var(--muted); margin: 0 0 4px 12px; }
.msg .msg-bubble { background: var(--panel); border: 1px solid var(--line); border-radius: 4px 16px 16px 16px; padding: 11px 15px; line-height: 1.45; }
.msg .msg-time { font-family: var(--mono); font-size: 11px; color: var(--muted); margin: 4px 0 0 12px; }
.msg.me { align-self: flex-end; }
.msg.me .msg-who { text-align: right; margin: 0 12px 4px 0; }
.msg.me .msg-bubble { background: var(--accent-soft); border-color: var(--accent-line); border-radius: 16px 4px 16px 16px; }
.msg.me .msg-time { text-align: right; margin: 4px 12px 0 0; }
/* Neighbourhood chat: avatar beside each incoming message (scoped to
   #chat-view so the assistant chat in #assistant-view stays unchanged). */
#chat-view .msg:not(.me) { display: flex; gap: 8px; align-items: flex-start; }
#chat-view .msg:not(.me) .msg-main { min-width: 0; }
#chat-view .msg:not(.me) .msg-who { margin-left: 2px; }
#chat-view .msg:not(.me) .msg-time { margin-left: 2px; }
.avatar-chat { width: 30px; height: 30px; font-size: 12px; }
/* Participant avatar stack (topic rows + thread header) — reuses .avatar. The
   lead avatar is the row's focal point: sized up, and — when the thread is
   unread — wrapped in a thin accent ring, a single quiet "something's new"
   signal instead of competing badges. */
.avatar-stack { display: inline-flex; align-items: center; }
/* Solid fill, not the translucent --accent-soft the base .avatar uses — a
   stack of pale, see-through circles read as unfinished at this size.
   ".avatar.avatar-stacked" (two classes) — .avatar itself is declared later
   in this file and would otherwise win the background on source order alone. */
/* --solid (opaque), not --panel (deliberately translucent glass) — the
   overlap border must fully mask the avatar behind it, not let it bleed
   through. */
.avatar-stacked { width: 34px; height: 34px; font-size: 12.5px; margin-left: -13px; }
/* ".avatar.avatar-stacked" (two classes): .avatar is declared later in this
   file and sets the same "border" shorthand + "background" — same
   specificity would let it silently win on source order alone. */
.avatar.avatar-stacked { background: var(--accent-fill); border: 2px solid var(--solid); }
.avatar-stack .avatar-stacked:first-child { margin-left: 0; }
.avatar.stack-more { background: var(--accent-fill); color: var(--accent); }
.avatar-ring { box-shadow: 0 0 0 2px var(--accent); }
.thread-people { display: flex; align-items: center; gap: 8px; margin: -2px 0 12px; }
.thread-people-n { font-size: 12.5px; color: var(--muted); }
.composer { display: flex; gap: 10px; padding-top: 12px; border-top: 1px solid var(--line); }
.composer input { margin: 0; }
.composer button { flex-shrink: 0; }
.day-sep { text-align: center; font-family: var(--mono); font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; margin: 6px 0; }

.thread-mode { --view-max: 760px; }

@media (max-width: 760px) {
  .offer-list { grid-template-columns: 1fr; }
  .tally-row { grid-template-columns: 76px 1fr 40px; }
  .msg { max-width: 86%; }
}

/* ---- Reduced motion: quiet everything ---- */
@media (prefers-reduced-motion: reduce) {
  .content .view:not(.hidden), .card-list > *, .lf-list > *, .notif-list > *,
  .stat-grid > *, .post-list > *, .offer-list > *, .vote-list > *, .doc-list > *,
  .topic-list > *, .toast { animation: none !important; }
  .building.draw .b-stroke { stroke-dashoffset: 0; animation: none; }
  .building.draw .b-window, .building.draw .b-door { opacity: 0.9; animation: none; }
  .empty-art .building, .empty-art .b-window { animation: none; }
  .chat-bubbles .cb, .chat-bubbles .cb-dot { animation: none; }
  .skel { animation: none; }
  .sidebar, .sidebar-backdrop { transition: none; }
}

/* ---- Wizard: AI "needs more detail" note ---- */
.ai-note {
  background: var(--brass-soft); border: 1px solid var(--brass-line); color: var(--text);
  border-radius: 10px; padding: 11px 14px; margin: 14px 0; font-size: 14px;
}


/* Inline button spinner (login loading state) */
.btn-spinner {
  display: inline-block; width: 15px; height: 15px; vertical-align: -2px; margin-right: 8px;
  border: 2px solid rgba(255, 255, 255, 0.4); border-top-color: #fff; border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
@media (prefers-reduced-motion: reduce) { .btn-spinner { animation-duration: 1.6s; } }

/* Email verification widget (inside the login card) — reuses .spinner;
   .verify-check is its success swap-in (same footprint, centered checkmark)
   once verification lands. */
#verify-email-widget .spinner { margin: 8px auto 20px; }
.verify-check {
  width: 30px; height: 30px; margin: 8px auto 20px; color: var(--accent);
  display: flex; align-items: center; justify-content: center;
}

/* ---- Zgłoszenia filter chips ---- */
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
.chip { background: var(--panel-2); border: 1px solid var(--line); color: var(--muted); border-radius: 999px; padding: 7px 14px; font-size: 13px; font-weight: 600; }
.chip:hover { color: var(--text); filter: none; }
.chip.sel { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent); }

/* ---- Chat intake ---- */
.spinner.sm { width: 18px; height: 18px; border-width: 2px; }
/* The composer's icon buttons, identical wherever a composer appears — the
   chat thread's and the assistant's are the same control. Sized by the box
   (44x44, the minimum thumb target) rather than by padding around a glyph, so
   they are square and match each other. */
.intake-clip, .intake-mic {
  flex-shrink: 0; min-width: 44px; min-height: 44px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 16px; line-height: 1;
}
.intake-actions { display: flex; flex-direction: column; gap: 8px; padding-top: 12px; border-top: 1px solid var(--line); }

/* ---- Chat intake: typing indicator + photo tray ---- */
.typing { display: inline-flex; align-items: center; gap: 5px; padding: 10px 4px; }
.typing span { width: 7px; height: 7px; border-radius: 50%; background: var(--muted); opacity: 0.45; animation: typingBounce 1.2s infinite ease-in-out; }
.typing span:nth-child(2) { animation-delay: 0.15s; }
.typing span:nth-child(3) { animation-delay: 0.3s; }
@keyframes typingBounce { 0%, 60%, 100% { transform: translateY(0); opacity: 0.35; } 30% { transform: translateY(-5px); opacity: 0.9; } }
@media (prefers-reduced-motion: reduce) { .typing span { animation: none; opacity: 0.5; } }
.intake-clip svg, .intake-mic svg { width: 20px; height: 20px; display: block; }
/* Voice dictation mic — turns red + pulses while recording */
.intake-mic.recording { color: #fff; background: var(--danger, #e5484d); border-color: var(--danger, #e5484d); animation: micPulse 1.4s infinite ease-in-out; }
@keyframes micPulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(229, 72, 77, 0.5); } 50% { box-shadow: 0 0 0 6px rgba(229, 72, 77, 0); } }
@media (prefers-reduced-motion: reduce) { .intake-mic.recording { animation: none; } }
/* Photos sent in a chat bubble */
.msg-photos { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.msg-photos img { width: 110px; max-width: 100%; height: auto; border-radius: 9px; display: block; }
/* Pending attachments above the composer (not sent until text + Wyślij) */
.intake-photos { display: flex; flex-wrap: wrap; gap: 8px; padding: 10px 2px 4px; }
.intake-thumb { position: relative; }
.intake-thumb img { width: 56px; height: 56px; object-fit: cover; border-radius: 9px; border: 1px solid var(--line); display: block; }
/* Remove-photo button: a plain circle at rest, growing only under the pointer.
   No permanent halo — at this size a resting effect reads as a smudge rather
   than an affordance. */
.intake-thumb .thumb-x {
  position: absolute; top: -6px; right: -6px; width: 20px; height: 20px; padding: 0;
  border-radius: 999px; background: var(--danger); color: #fff; font-size: 13px; line-height: 1;
  display: flex; align-items: center; justify-content: center; border: 2px solid var(--bg);
  transition: transform 0.14s ease;
}
.intake-thumb .thumb-x:hover { transform: scale(1.18); }
@media (prefers-reduced-motion: reduce) { .intake-thumb .thumb-x { transition: none; } }

/* ---- Sidebar account button (settings) + foot actions ---- */
.account {
  background: transparent; border: 0; width: 100%; text-align: left; cursor: pointer;
  display: flex; align-items: center; gap: 10px; padding: 6px 8px; border-radius: 9px; color: var(--text);
}
.account:hover { background: var(--panel-2); filter: none; }
.account-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.account .who { color: var(--text); font-size: 12.5px; font-weight: 600; padding: 0; margin: 0; word-break: break-all; }
.account .home { color: var(--muted); font-size: 11.5px; }
.account-badge { margin-top: 2px; }
.foot-actions { display: flex; align-items: center; gap: 8px; }
.foot-actions .ghost:not(.theme-toggle) { flex: 1; }
/* Language switcher collapses to the same square icon-button size as the
   theme toggle beside it — see dd-flag-only in dropdown.js for the label. */
.dd-trigger.dd-flag-only {
  flex: 0 0 38px; width: 38px; height: 38px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center; font-size: 17px; line-height: 1;
}
.dd-trigger.dd-flag-only .dd-chev { display: none; }

/* Avatar (profile picture or initials) + verification corner badge ------- */
.avatar {
  position: relative; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: 50%;
  background: var(--accent-soft); color: var(--accent); font-weight: 700;
  border: 1px solid var(--line); line-height: 1;
}
.avatar img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }
.avatar-sm { width: 38px; height: 38px; font-size: 13px; }
.avatar-lg { width: 76px; height: 76px; font-size: 26px; }
.avatar-corner {
  position: absolute; right: -2px; bottom: -2px; width: 17px; height: 17px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  border: 2px solid var(--panel); color: #fff;
}
.avatar-corner svg { width: 11px; height: 11px; }
.avatar-corner.verified { background: var(--ok); }
.avatar-corner.pending { background: var(--muted); }
/* Join-request tile only — an applicant's DECLARED role, not a verification
   state (they're unverified by definition), so it borrows the same colors
   .resident-pill.owner/.tenant use rather than the verified/pending pair. */
.avatar-corner.owner { background: var(--ok); }
.avatar-corner.tenant { background: var(--accent); }
.avatar-lg .avatar-corner { width: 24px; height: 24px; right: 0; bottom: 0; }
.avatar-lg .avatar-corner svg { width: 15px; height: 15px; }

/* Resident-type pill: Właściciel / Najemca · verified|pending ------------ */
.resident-pill {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11px; font-weight: 600; line-height: 1; padding: 4px 8px; border-radius: 999px;
  border: 1px solid var(--line); color: var(--muted); background: var(--panel-2);
}
.resident-pill svg { width: 12px; height: 12px; }
.resident-pill.verified { color: var(--ok); border-color: rgba(70, 211, 154, 0.4); background: var(--ok-soft); }
.resident-pill.none { font-weight: 500; }
/* Plain role tags (no icon) on invite-list rows — owner/tenant color coding. */
.resident-pill.owner { color: var(--ok); border-color: var(--ok-line); background: var(--ok-soft); }
.resident-pill.tenant { color: var(--accent); border-color: var(--accent-line); background: var(--accent-soft); }
.resident-pill.role-admin { color: var(--brass); border-color: var(--brass-line); background: var(--brass-soft); }
/* Managing agent — an outside company, so it borrows the accent used for the
   other non-resident role rather than the owner/tenant status colours. */
.resident-pill.role-agent { color: var(--accent); border-color: var(--accent-line); background: var(--accent-soft); }

/* Settings profile header + admin verify cell --------------------------- */
/* Account settings — "Moje miejsca parkingowe". The "why bother" explanation
   lives behind the ? button rather than as a paragraph, so the section stays
   scannable for people who already know what it does. */
.set-head-row { display: flex; align-items: center; gap: 6px; }
.set-head-row .prov-add-btn { margin-left: auto; }  /* "+" to the far right */
/* Community settings — one card per section, because the sections are
   independent settings that save separately, and more of them are coming.
   (Account settings differs deliberately: a single 520px card with h3
   sub-headings, one profile saved in one go.) A bare .card is 520px and
   centred, which would read as a stray box in the 880px view column. */
.settings-section { max-width: none; }
.settings-section > h3 { margin: 0 0 6px; font-family: var(--display); font-weight: 600; }
.settings-section > .hint { margin: 0; }
/* The action belongs at the end of the form, on the side the eye finishes on.
   Shared with the announcement dialog (board.js), whose .ann-form-actions was
   this rule byte for byte. NOTE: .chat-new-actions, .app-modal-actions,
   .debt-modal-foot and .park-share-foot are the same pair plus a gap or
   padding — foldable into this class too, but that is a wider change. */
.form-actions { display: flex; justify-content: flex-end; }
/* Managing company: what the admin writes on one side, the NIP and what the
   register answered on the other. The registry column comes FIRST in the DOM
   so a narrow screen reads NIP → answer → name → e-mails without a second set
   of markup; past 48rem the two swap. Spacing is one gap per column, never
   margins between siblings — a margin chain breaks the moment the registry
   block is inserted between two fields, which is exactly what happens here. */
.set-split { display: flex; gap: 22px; align-items: flex-start; flex-wrap: wrap; margin-top: 20px; }
/* The columns zero their labels' margins, so the action row has nothing left
   to push it away — it gets the section's own 20px rhythm. Scoped, because
   .form-actions is shared with the announcement dialog, which sits in a
   flex-gap form and needs no margin of its own. */
.settings-section .form-actions { margin-top: 20px; }
.set-split > .set-col { flex: 1 1 19rem; min-width: 0; display: flex; flex-direction: column; gap: 20px; }
.set-split > .set-col label { margin: 0; }   /* the gap above owns the rhythm */
@media (min-width: 48rem) {
  .set-split > .set-col-own { order: 1; }
  .set-split > .set-col-registry { order: 2; }
}
/* Read-only: these are the register's facts, not a form. Green because it is
   the app's own "confirmed" colour (same token as a resolved report). */
.mgmt-registry { border: 1px solid var(--ok-line); background: var(--ok-soft); border-radius: 10px; padding: 14px 16px; }
.mgmt-legal { margin: 0; font-weight: 600; font-size: 13.5px; display: flex; align-items: flex-start; gap: 6px; }
.mgmt-tick { color: var(--ok); font-weight: 700; flex: none; }
.mgmt-facts { margin: 10px 0 0; display: grid; grid-template-columns: max-content 1fr; gap: 6px 18px; font-size: 12.5px; }
.mgmt-facts dt { color: var(--muted); }
.mgmt-facts dd { margin: 0; font-variant-numeric: tabular-nums; }
.mgmt-missing { margin: 0; }
.help-btn { background: none; border: 0; padding: 0; color: var(--muted); cursor: pointer; display: inline-flex; }
.help-btn svg { width: 16px; height: 16px; }
.help-btn:hover { color: var(--text); filter: none; }
.set-spot-list { list-style: none; margin: 10px 0 14px; padding: 0; }
.set-spot-list li { display: flex; align-items: center; gap: 8px; padding: 7px 0; border-bottom: 1px solid var(--line); }
.set-spot-list li span { flex: 1; }          /* pushes the delete button to the far right */
.set-spot-list .rm-del svg { width: 16px; height: 16px; display: block; }

.profile-head { display: flex; align-items: center; gap: 16px; margin: 6px 0 18px; }
.profile-head-meta { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.profile-head-name { font-size: 17px; font-weight: 700; color: var(--text); }
.profile-avatar-wrap { position: relative; display: inline-flex; flex-shrink: 0; }
.avatar-edit-btn {
  position: absolute; top: -2px; right: -2px; width: 26px; height: 26px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center; border-radius: 50%;
  border: 2px solid var(--panel); background: var(--panel-2); color: var(--muted); cursor: pointer;
}
.avatar-edit-btn:hover { color: var(--text); filter: none; }
.avatar-edit-btn svg { width: 13px; height: 13px; }

/* Compact variant of the shared avatar picker (see avatarPicker() in app.js)
   when opened as a standalone "change photo" dialog instead of an onboarding
   wizard step — the wizard's title/description are redundant here, and its
   full-width button looks disjointed in a fixed-size dialog. */
.avatar-dialog-body h2, .avatar-dialog-body .hint { display: none; }
.avatar-dialog-body .ob-avatar-stack { margin-top: 4px; }
.avatar-dialog-body .ob-actions { max-width: 220px; margin-left: auto; margin-right: auto; }
.avatar-dialog-body .ob-actions + .ob-skip { display: block; width: fit-content; margin: 22px auto 0; text-align: center; }
button.small, .ghost.small { font-size: 12.5px; padding: 6px 12px; }
.verify-cell { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }

/* Language switcher in the sidebar foot */
.lang-switch { margin: 0; padding: 7px 10px; font-size: 12.5px; font-weight: 600; }

/* Zaległości / arrears — a tile per block; clicking a tile opens the flat list */
.debt-updated-line { font-size: 13px; color: var(--muted); margin: 0 0 18px; }
.debt-tiles { display: flex; flex-direction: column; gap: 12px; }
.debt-tile { display: flex; align-items: center; gap: 18px; width: 100%; text-align: left; padding: 18px 20px; background: var(--panel); border: 1px solid var(--glass-border); border-radius: var(--radius); backdrop-filter: var(--blur); box-shadow: var(--shadow); cursor: pointer; transition: border-color 0.14s ease, transform 0.14s ease, box-shadow 0.14s ease; }
.debt-tile:hover { border-color: var(--accent-line); transform: translateY(-1px); box-shadow: var(--shadow-lift); filter: none; }
.debt-tile-main { display: flex; flex-direction: column; gap: 4px; }
.debt-tile-eyebrow { font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); }
.debt-tile-name { font-family: var(--display); font-weight: 800; font-size: 22px; line-height: 1; letter-spacing: -0.02em; color: var(--text); }
.debt-tile-meta { margin-left: auto; display: flex; flex-direction: column; align-items: flex-end; gap: 3px; }
.debt-tile-total { font-family: var(--display); font-weight: 700; font-size: 20px; letter-spacing: -0.02em; color: var(--muted); font-variant-numeric: tabular-nums; }
.debt-tile-total.owed { color: var(--danger); }
.debt-tile-total.credit { color: var(--ok); }
.debt-tile-sub { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.debt-tile .act-chev { width: 18px; height: 18px; flex-shrink: 0; color: var(--muted); opacity: 0.7; transition: transform 0.14s ease, color 0.14s ease; }
.debt-tile:hover .act-chev { color: var(--accent); transform: translateX(2px); }

/* Arrears summary on the home feed — a standing alert above the activity list */
.news-arrears-card { display: flex; align-items: center; gap: 14px; width: 100%; text-align: left; padding: 15px 18px; margin-bottom: 22px; background: var(--panel); border: 1px solid var(--glass-border); border-left: 3px solid var(--danger); border-radius: var(--radius); backdrop-filter: var(--blur); box-shadow: var(--shadow); cursor: pointer; transition: transform 0.14s ease, box-shadow 0.14s ease; }
.news-arrears-card:hover { transform: translateY(-1px); box-shadow: var(--shadow-lift); filter: none; }
.na-icon { display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; flex-shrink: 0; border-radius: 11px; background: var(--danger-soft); color: var(--danger); }
.na-icon svg { width: 20px; height: 20px; }
.na-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.na-eyebrow { font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); }
.na-total { font-family: var(--display); font-weight: 800; font-size: 20px; letter-spacing: -0.02em; color: var(--danger); font-variant-numeric: tabular-nums; }
.na-sub { font-size: 12.5px; color: var(--muted); }
.na-cta { margin-left: auto; display: inline-flex; align-items: center; gap: 4px; flex-shrink: 0; font-size: 13.5px; font-weight: 600; color: var(--accent); }
.na-cta .act-chev { width: 16px; height: 16px; transition: transform 0.14s ease; }
.news-arrears-card:hover .na-cta .act-chev { transform: translateX(2px); }

/* Parking reservation card — not an error/warning, so green instead of the
   arrears card's --danger red; icon reads as a real parking sign (blue
   square, white "P") rather than the outlined line-icon used elsewhere. */
.dash-parking-card { border-left-color: var(--ok); }
.dash-parking-card .na-total { color: var(--ok); }
/* Shared "parking sign" icon treatment — blue square, white P — used
   anywhere a parking icon appears (dashboard card, reservation tiles),
   instead of the generic outlined line-icon used elsewhere in the app. */
.park-icon-p { background: var(--accent); color: #fff; font-family: var(--display); font-weight: 800; font-size: 18px; line-height: 1; }

/* Custom dropdowns — the native <select> is hidden; this is the app-styled skin.
   .dd-trigger inherits the base select look (rule above) + the select's own
   instance classes; the panel is a white popup over a full-screen blur. */
.dd-native { display: none !important; }
.dd-trigger { display: inline-flex; align-items: center; justify-content: space-between; gap: 8px; cursor: pointer; text-align: left; }
.dd-trigger:hover { border-color: var(--accent-line); filter: none; }
.dd-trigger:disabled { opacity: 0.5; cursor: not-allowed; }
.dd-trigger .dd-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dd-trigger .dd-chev { width: 16px; height: 16px; flex-shrink: 0; opacity: 0.6; transition: transform 0.16s ease; }
.dd-trigger.dd-open .dd-chev { transform: rotate(180deg); }

.dd-backdrop { position: fixed; inset: 0; z-index: 80; background: transparent; backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); animation: ddFade 0.14s ease; }
@keyframes ddFade { from { opacity: 0; } to { opacity: 1; } }
.dd-panel { position: fixed; z-index: 81; min-width: 160px; max-width: min(92vw, 380px); overflow-y: auto; padding: 6px; background: var(--solid); border: 1px solid var(--glass-border); border-radius: 12px; box-shadow: var(--shadow-lift); animation: ddDown 0.16s cubic-bezier(0.16, 1, 0.3, 1); }
.dd-panel.dd-up { animation-name: ddUp; }
@keyframes ddDown { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
@keyframes ddUp { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.dd-option { display: flex; align-items: center; gap: 8px; width: 100%; margin: 0; text-align: left; padding: 9px 12px; border: 0; border-radius: 8px; background: transparent; color: var(--text); font: inherit; font-weight: 500; cursor: pointer; }
.dd-option:hover { background: var(--panel-2); filter: none; }
.dd-option.selected { background: var(--accent-soft); color: var(--accent-strong); font-weight: 600; }
.dd-option:disabled { opacity: 0.5; cursor: default; }
@media (prefers-reduced-motion: reduce) { .dd-backdrop, .dd-panel, .dd-trigger .dd-chev { animation: none; transition: none; } }

/* Per-flat list, shared by the dialog. */
.debt-table { width: 100%; border-collapse: collapse; }
.debt-table th { text-align: left; font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); font-weight: 500; padding: 6px 4px; border-bottom: 1px solid var(--line); }
.debt-table th:last-child, .debt-table td:last-child { text-align: right; }
.debt-table td { padding: 6px 4px; border-bottom: 1px solid var(--line); font-size: 14px; }
.debt-table tr:last-child td { border-bottom: 0; }
.debt-unit { color: var(--muted); font-variant-numeric: tabular-nums; }
.debt-amt { color: var(--muted); font-variant-numeric: tabular-nums; }
.debt-amt.owed { color: var(--danger); font-weight: 600; }
.debt-amt.credit { color: var(--ok); font-weight: 600; }
.debt-input { width: 104px; padding: 5px 8px; text-align: right; font-size: 13.5px; font-variant-numeric: tabular-nums; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-2); color: var(--text); }
.debt-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; border-color: transparent; }
.debt-floor-label { margin: 16px 0 4px; font-family: var(--mono); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.debt-floor-label:first-child { margin-top: 0; }
/* Admin-only "add an arrears entry" form — the only way to set an amount now
   (no more inline per-cell editing of the read-only list below it). */
.debt-add-form { display: flex; gap: 8px; margin-bottom: 16px; padding-bottom: 16px; border-bottom: 1px solid var(--line); }
.debt-add-form select { flex: 1; min-width: 0; padding: 5px 8px; font-size: 13.5px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-2); color: var(--text); }
.debt-add-form .debt-input { flex-shrink: 0; }
.debt-add-form button { flex-shrink: 0; padding: 5px 14px; font-size: 13.5px; }

/* Dialog: per-block flat list (✕ / backdrop / Escape to close) */
body.modal-open { overflow: hidden; }
.debt-modal-overlay { position: fixed; inset: 0; z-index: 60; display: flex; align-items: center; justify-content: center; padding: 24px; background: transparent; backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); animation: debtFade 0.16s ease; }
@keyframes debtFade { from { opacity: 0; } to { opacity: 1; } }
.debt-modal { width: 100%; max-width: 440px; max-height: 85vh; display: flex; flex-direction: column; background: var(--solid); border: 1px solid var(--glass-border); border-radius: var(--radius); box-shadow: var(--shadow-lift); overflow: hidden; animation: debtRise 0.18s cubic-bezier(0.16, 1, 0.3, 1); }
.debt-modal-wide { max-width: 620px; }
@keyframes debtRise { from { opacity: 0; transform: translateY(10px) scale(0.99); } to { opacity: 1; transform: none; } }
.debt-modal-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 16px 22px; border-bottom: 1px solid var(--line); }
.debt-modal-head h3 { margin: 0; font-family: var(--display); font-size: 18px; font-weight: 700; }
/* No title (e.g. the parking reveal dialog) — drop the divider and let the
   close button float on its own, instead of an empty bar with a dangling X. */
.debt-modal-head-bare { border-bottom: none; padding: 12px 12px 0; justify-content: flex-end; }
.debt-modal-x { background: transparent; border: 0; color: var(--muted); font-size: 16px; line-height: 1; padding: 4px 6px; cursor: pointer; }
.debt-modal-x:hover { color: var(--text); filter: none; }
/* One horizontal rhythm (22px) across head, body and foot. The body's old
   4px bottom left content sitting on the card edge in dialogs without a
   footer bar — every dialog in the app gets the roomier spacing. */
.debt-modal-body { overflow-y: auto; padding: 16px 22px 20px; }
.debt-modal-foot { display: flex; justify-content: flex-end; padding: 16px 22px; border-top: 1px solid var(--line); }
@media (prefers-reduced-motion: reduce) { .debt-modal-overlay, .debt-modal { animation: none; } }

/* Page-intro dialog body (page-intro.js) — built on .debt-modal, just its own
   centered icon/text/button layout instead of a form. */
.page-intro-body { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 14px; padding: 8px 4px 20px; }
.page-intro-icon { width: 48px; height: 48px; color: var(--accent); }
.page-intro-icon svg { width: 100%; height: 100%; }
.page-intro-body p.page-intro-lead { margin: 0; line-height: 1.5; }
/* Benefit bullets — left-aligned inside the otherwise-centered dialog, since a
   centered list of full sentences is harder to scan than a left-aligned one. */
.page-intro-points { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; text-align: left; width: 100%; }
.page-intro-points li { display: flex; gap: 10px; align-items: flex-start; line-height: 1.45; }
.page-intro-points .page-intro-check { flex-shrink: 0; width: 18px; height: 18px; margin-top: 2px; color: var(--accent); }
.page-intro-points .page-intro-check svg { width: 100%; height: 100%; }

/* Contact block (app.js contactBlockHtml) — shared by the "Pomoc" sidebar
   modal and the page-intro popup above, so styled to sit in either. */
.contact-lead { margin: 0 0 12px; color: var(--text); font-size: 13px; line-height: 1.55; }
.contact-card { width: 100%; display: flex; align-items: center; gap: 13px; text-align: left; background: var(--accent-soft); border-radius: 13px; padding: 12px 14px; }
.avatar.contact-avatar { width: 42px; height: 42px; }
.contact-info { min-width: 0; }
.contact-name { margin: 0 0 3px; font-weight: 700; font-size: 14px; }
.contact-links { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; font-size: 13px; }
.contact-links a { color: var(--accent); text-decoration: none; font-weight: 600; }
.contact-links a:hover { text-decoration: underline; }
.contact-dot { color: var(--muted); }

/* Asystent AI (community.js assistantIntroHtml) — the intro standing in for an
   empty chat thread. It has to fit the thread WITHOUT scrolling, so the six
   features are icon+title chips and their body copy moves into the dialog
   openAssistantFeatures() builds. The chips sit on the shared .stat-card
   surface (panel + var(--shadow)) so they read as cards like everything else;
   only layout, the tighter padding and the pressable affordance are added. */
.ai-feature-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; width: 100%; max-width: 680px; margin: 0 auto; }
@media (max-width: 900px) { .ai-feature-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .ai-feature-grid { grid-template-columns: 1fr; } }
.ai-feature-card {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 11px 13px;
  color: var(--text); font-size: 13px; font-weight: 500; line-height: 1.3; text-align: left; cursor: pointer;
  transition: box-shadow 0.16s ease, transform 0.16s ease, border-color 0.16s ease;
}
/* filter:none cancels the global button:hover brightness, which only muddies a
   translucent panel instead of lifting it. */
.ai-feature-card:hover { border-color: var(--accent-line); box-shadow: var(--shadow-lift); transform: translateY(-1px); filter: none; }
/* The chips' long form inside openModal(): icon on the soft accent tile the
   empty state uses, bold title, body copy beside it. */
.ai-feature-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 20px; }
.ai-feature-list li { display: flex; gap: 14px; align-items: flex-start; }
.ai-feature-list h4 { margin: 0 0 5px; font-family: var(--display); font-size: 15px; font-weight: 600; }
.ai-feature-list p { margin: 0; color: var(--muted); font-size: 14px; line-height: 1.55; }
.ai-feature-mark { flex-shrink: 0; display: grid; place-items: center; width: 36px; height: 36px; border-radius: 11px; background: var(--accent-soft); color: var(--accent); }
.ai-feature-mark svg { width: 19px; height: 19px; display: block; }
.ai-feature-card .ai-feature-icon { flex-shrink: 0; width: 18px; height: 18px; color: var(--accent); }
.ai-feature-card .ai-feature-icon svg { width: 100%; height: 100%; display: block; }
/* Phone (single column): full width against 42px of height reads as a bar, not
   a card. More vertical room and a slightly larger label restore the
   proportion. Must sit AFTER the base rules — a media query adds no
   specificity, so an earlier block would simply lose. */
@media (max-width: 560px) {
  .ai-feature-card { padding: 17px 14px; gap: 12px; font-size: 14px; }
  .ai-feature-card .ai-feature-icon { width: 20px; height: 20px; }
}
/* The CTA leads the intro instead of closing a page, and every pixel it gives
   back is a pixel the chips below need to clear the fold. */
.ai-cta { margin: 0 auto; padding: 0 24px 18px; }
.ai-cta .empty-icon { margin-bottom: 14px; }
.ai-cta p { margin-bottom: 0; }

/* Emergency call-out inside openModal() — a life/health/property emergency
   the assistant caught in chat (ai.ts urgency="emergency"). One job: get the
   resident to call the right service, impossible to miss or misread. */
.emergency-modal { display: flex; flex-direction: column; gap: 14px; padding: 4px 0 16px; text-align: center; }
.emergency-modal-warning { margin: 0; color: var(--danger); font-weight: 600; font-size: 15px; line-height: 1.5; }
.emergency-modal-service { margin: 0; font-family: var(--display); font-size: 22px; font-weight: 700; }
.emergency-modal-call { display: block; background: var(--danger); color: #fff; font-family: var(--display); font-size: 20px; font-weight: 700; text-decoration: none; padding: 16px; border-radius: var(--radius); }
.emergency-modal-call:hover { filter: brightness(1.08); }

/* Report owner: "you reported" badge, edit/delete actions, danger buttons. */
.status-badge.s-owner { background: var(--accent-soft); color: var(--accent-strong); }
.owner-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
button.danger { color: var(--danger); }
button.ghost.danger { border-color: var(--danger-soft); }
button.ghost.danger:hover { border-color: var(--danger); background: var(--danger-soft); color: var(--danger); filter: none; }
button.am-confirm.danger { background: var(--danger); color: #fff; }

/* Generic app modal — confirm dialogs over a full-screen blur. */
.app-modal-overlay { position: fixed; inset: 0; z-index: 90; display: flex; align-items: center; justify-content: center; padding: 24px; background: transparent; backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); animation: ddFade 0.14s ease; }
.app-modal { width: 100%; max-width: 380px; background: var(--solid); border: 1px solid var(--glass-border); border-radius: var(--radius); box-shadow: var(--shadow-lift); padding: 22px; animation: debtRise 0.18s cubic-bezier(0.16, 1, 0.3, 1); }
.app-modal-title { margin: 0 0 8px; font-family: var(--display); font-size: 18px; font-weight: 700; }
.app-modal-body { margin: 0 0 20px; color: var(--muted); font-size: 14px; line-height: 1.5; }
.app-modal-actions { display: flex; justify-content: flex-end; gap: 10px; }
@media (prefers-reduced-motion: reduce) { .app-modal-overlay, .app-modal { animation: none; } }

/* Assistant — floating launcher + chat-widget dialog, anchored bottom-right
   instead of centered like the generic .app-modal above. Same blur-backdrop
   and panel tokens (var(--solid)/--glass-border/--shadow-lift), just a
   different shape: tall and narrow, docked near the button that opened it. */
.assistant-fab {
  position: fixed; right: 24px; bottom: 24px; z-index: 70;
  width: 56px; height: 56px; padding: 0; border: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent); color: #fff; box-shadow: var(--shadow-lift);
  transition: transform 0.15s ease;
}
.assistant-fab:hover { transform: scale(1.06); filter: none; }
/* Rotate + pulse together without two animations fighting over the same
   element's "transform" — the wrap slowly spins (reusing @keyframes spin,
   already used by .spinner, just much slower), the svg inside it pulses
   independently (reusing @keyframes dot-pulse from the status timeline).
   Shared by the FAB icon and the dialog's own brand-bar icon below. */
.assistant-icon-spin { display: inline-flex; animation: spin 9s linear infinite; }
.assistant-icon-spin svg { width: 24px; height: 24px; animation: dot-pulse 2.2s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .assistant-icon-spin, .assistant-icon-spin svg { animation: none; } }
.assistant-dialog { display: none; }
.assistant-dialog.open { display: block; }
.assistant-dialog-backdrop {
  position: fixed; inset: 0; z-index: 71; background: transparent;
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); animation: ddFade 0.14s ease;
}
.assistant-dialog-panel {
  position: fixed; right: 24px; bottom: 24px; z-index: 72;
  width: min(400px, calc(100vw - 32px)); height: min(640px, calc(100vh - 110px));
  display: flex; flex-direction: column;
  background: var(--solid); border: 1px solid var(--glass-border); border-radius: var(--radius);
  box-shadow: var(--shadow-lift); overflow: hidden;
  animation: debtRise 0.18s cubic-bezier(0.16, 1, 0.3, 1);
}
/* Same icon+title flex-row idea as .side-brand (the app's own wordmark), but
   with its own back button leading — that's brand-specific, not something
   .side-brand's structure accounts for. */
.assistant-brand { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-bottom: 1px solid var(--line); flex-shrink: 0; }
.assistant-brand-icon { width: 26px; height: 26px; color: var(--accent); flex-shrink: 0; }
.assistant-brand-icon svg { width: 26px; height: 26px; }
.assistant-brand-title { margin: 0; font-family: var(--display); font-size: 1.25rem; font-weight: 700; letter-spacing: -0.01em; }
.assistant-dialog-back {
  background: transparent; border: 0; color: var(--muted); padding: 6px; border-radius: 8px; cursor: pointer; flex-shrink: 0;
}
.assistant-dialog-back:hover { color: var(--text); background: var(--panel-2); filter: none; }
.assistant-dialog-back svg { width: 18px; height: 18px; display: block; }
@media (prefers-reduced-motion: reduce) { .assistant-dialog-backdrop, .assistant-dialog-panel { animation: none; } }
/* Full-screen on narrow viewports — a docked 400px panel wouldn't fit. */
@media (max-width: 640px) {
  .assistant-dialog-panel { right: 0; bottom: 0; left: 0; top: 0; width: 100%; height: 100%; border-radius: 0; }
  .assistant-fab { right: 16px; bottom: 16px; }
}

/* Page-intro reopen button (page-intro.js, demo account only) — quiet
   outline style (not accent-filled like .assistant-fab) so the two floating
   buttons don't visually compete: this one top-right, the assistant
   bottom-right. */
.page-intro-fab {
  position: fixed; top: 16px; right: 16px; z-index: 65;
  width: 40px; height: 40px; padding: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--solid); border: 1px solid var(--glass-border); color: var(--muted);
  box-shadow: var(--shadow-lift); transition: color 0.15s ease;
  animation: pageIntroGlow 2.6s ease-in-out infinite;
}
.page-intro-fab:hover { color: var(--text); filter: none; }
.page-intro-fab svg { width: 20px; height: 20px; display: block; }
/* Below .mobile-topbar (sticky, ~860px breakpoint — see line 776) so it
   doesn't overlap the hamburger. */
@media (max-width: 860px) { .page-intro-fab { top: 64px; right: 12px; } }

/* Sonar ping — a big ring BEHIND the button (lower z-index than
   .page-intro-fab's 65, and painted from a separate sibling element rather
   than a pseudo-element inside the button), so the button's own face never
   visually changes color no matter how large or opaque the ring gets.
   Shown only while the button itself is visible — a sibling selector on the
   button's .hidden class, no JS wiring needed. */
.page-intro-fab-sonar {
  display: none; position: fixed; top: 16px; right: 16px; width: 40px; height: 40px;
  z-index: 64; pointer-events: none;
}
#page-intro-fab:not(.hidden) ~ .page-intro-fab-sonar { display: block; }
.page-intro-fab-sonar span {
  position: absolute; top: 50%; left: 50%; width: 280px; height: 280px; margin: -140px 0 0 -140px;
  border-radius: 50%; background: var(--accent);
  animation: pageIntroSonar 2.6s ease-out infinite;
}
@media (max-width: 860px) { .page-intro-fab-sonar { top: 64px; right: 12px; } }

@keyframes pageIntroSonar {
  0% { transform: scale(0.2); opacity: 0.35; }
  100% { transform: scale(1); opacity: 0; }
}
@keyframes pageIntroGlow {
  0%, 100% { box-shadow: var(--shadow-lift), 0 0 0 0 var(--accent-line); }
  50% { box-shadow: var(--shadow-lift), 0 0 10px 4px var(--accent-line); }
}
@media (prefers-reduced-motion: reduce) {
  .page-intro-fab { animation: none; }
  .page-intro-fab-sonar span { animation: none; }
}

/* ===== Neighborhood chat (chat.js) — real Stream-backed views ===== */
.chat-loading { padding: 40px; text-align: center; color: var(--muted); }
.chat-new-form { display: flex; flex-direction: column; gap: 8px; margin: 0 0 16px; padding: 14px; background: var(--panel); border: 1px solid var(--line); border-radius: 14px; backdrop-filter: var(--blur); }
.chat-new-actions { display: flex; justify-content: flex-end; gap: 8px; }

/* The publish/edit dialog's body. Deliberately NOT .chat-new-form: that one
   carries its own panel, border and padding because it sits on a bare page in
   the chat — inside .debt-modal it would draw a second frame around the first.
   Here the dialog IS the frame, so this is just the fields, at the app's
   normal type sizes. The only thing scaled up is the ROOM: announcements from
   the administration run long, so the dialog opens wide (see wide:true in
   board.js) with a body field already tall enough to write into. */
.ann-form { display: flex; flex-direction: column; gap: 12px; padding: 6px 0 4px; }
.ann-form input, .ann-form textarea { margin-top: 0; } /* gap handles the spacing */
.ann-form textarea { min-height: 240px; resize: vertical; }

/* Announcement header: the way back sits beside the title, sharing its row,
   instead of claiming a full-width bar of its own above it. min-width:0 lets
   a long title shrink rather than shove the admin menu off the row. */
/* Author line of the announcement: a face beside the name, the same avatar a
   chat message carries. .post-meta pushes itself right by default — here it
   sits next to the avatar it belongs to. */
.ann-author { display: flex; align-items: center; gap: 8px; }
.ann-author .post-meta { margin-left: 0; }
.ann-author-who { display: flex; flex-direction: column; gap: 1px; }
/* Quiet text, not a .resident-pill: a byline states who wrote this, while the
   pill on the residents screen doubles as a control you can click. */
.ann-author-role { font-size: 11px; color: var(--muted); letter-spacing: 0.02em; }

.ann-title-row { display: flex; align-items: center; gap: 10px; min-width: 0; }
/* Both header actions — the back arrow and the admin menu — are icon-only
   buttons flanking the title, so they share one size rather than each
   inheriting a different default. */
.ann-head-btn { flex-shrink: 0; padding: 4px 10px; font-size: 18px; line-height: 1; }
.avatar-xs { width: 22px; height: 22px; font-size: 10px; }
.avatar-more { background: var(--line); color: var(--muted); }
.chat-quote { font-size: 12px; color: var(--muted); border-left: 2px solid var(--accent); padding: 2px 8px; margin: 0 0 4px; }
.chat-reply-bar { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); padding: 6px 10px; border-top: 1px solid var(--line); }
.chat-reply-bar button { margin-left: auto; background: none; border: none; cursor: pointer; color: var(--muted); }
/* The chat's tray sits directly above the composer, so it carries the divider
   the assistant's does not need. Everything else comes from .intake-photos —
   one tray, one set of thumbnails. */
#chat-view .intake-photos, #board-view .intake-photos { border-top: 1px solid var(--line); }
/* The chat's photo tray uses .intake-thumb/.thumb-x — the same markup as the
   assistant's, so the remove button is the same control in both. Its own
   version lacked padding:0, which let the default button padding inflate the
   18px circle into an oval. */
/* Photo grid inside the bubble: up to 3 square thumbs, last one may carry
   a "+N" overlay; a single photo renders larger. */
.chat-gallery { display: grid; grid-template-columns: repeat(3, minmax(64px, 88px)); gap: 4px; margin-top: 4px; }
/* Text bubble with photos: keep the image close to the text and near the
   bubble edge (tighter bottom/side padding). */
.msg-bubble:has(.chat-gallery) { padding-bottom: 7px; }
.chat-gallery.single { grid-template-columns: minmax(120px, 220px); }
.chat-bubble-media .chat-gallery { margin-top: 0; }
.chat-bubble-media { padding: 5px; }
.chat-thumb { position: relative; padding: 0; border: none; background: none; cursor: pointer; border-radius: 9px; overflow: hidden; aspect-ratio: 1; touch-action: manipulation; }
.chat-gallery.single .chat-thumb { aspect-ratio: auto; }
.chat-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.chat-thumb-more { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; background: rgba(10, 12, 18, 0.55); color: #fff; font-size: 17px; font-weight: 650; }
/* Lightbox on the shared blurred scrim. */
.chat-lightbox { z-index: 95; display: flex; align-items: center; justify-content: center; }
.chat-lightbox img { max-width: 92vw; max-height: 82vh; border-radius: 12px; box-shadow: 0 18px 48px rgba(0, 0, 0, .35); }
.lb-close, .lb-prev, .lb-next { position: fixed; z-index: 96; width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--line); background: var(--solid); color: inherit; font-size: 20px; cursor: pointer; display: flex; align-items: center; justify-content: center; touch-action: manipulation; }
.lb-close { top: max(14px, env(safe-area-inset-top)); right: 14px; }
.lb-prev { left: 10px; top: 50%; transform: translateY(-50%); }
.lb-next { right: 10px; top: 50%; transform: translateY(-50%); }
.lb-count { position: fixed; bottom: max(16px, env(safe-area-inset-bottom)); left: 50%; transform: translateX(-50%); font-size: 13px; color: var(--muted); background: var(--solid); border: 1px solid var(--line); border-radius: 999px; padding: 2px 12px; font-variant-numeric: tabular-nums; }
/* The reaction chip overlaps the bubble's bottom corner (Messenger-style):
   bottom-right on neighbours' messages, bottom-left on your own. */
.msg-bubble-wrap { position: relative; display: inline-block; max-width: 100%; }
.chat-react-tile { position: absolute; bottom: -12px; z-index: 2; display: inline-flex; align-items: center; gap: 6px; font-size: 13px; padding: 2px 10px; border-radius: 999px; border: 1px solid var(--line); background: var(--solid); cursor: pointer; white-space: nowrap; }
.msg .chat-react-tile { right: -6px; }
.msg.me .chat-react-tile { right: auto; left: -6px; }
.chat-react-total { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.react-who { display: flex; align-items: center; gap: 10px; padding: 6px 10px; font-size: 14px; }
/* Long-press action menu (touch + mouse hold + right-click). Fixed to the
   viewport at the press point; clamped by JS so it never overflows.
   Sits inside the shared .dd-backdrop blurred scrim (no own backdrop rule). */
/* Opaque, not glass: --solid rather than the translucent --panel, and no
   backdrop-filter (pointless once nothing shows through, and it costs a
   compositing layer). The scrim behind still blurs the page — the menu itself
   reads as a solid sheet on top of it. Same token the dialogs use, so it
   follows the light/dark theme. */
.msg-menu { position: fixed; z-index: 40; display: flex; flex-direction: column; min-width: 180px; background: var(--solid); border: 1px solid var(--line); border-radius: 14px; padding: 8px; box-shadow: 0 12px 32px rgba(0, 0, 0, .22); }
.msg-menu-emoji { display: flex; gap: 2px; justify-content: space-between; padding: 2px 2px 8px; border-bottom: 1px solid var(--line); margin-bottom: 6px; }
.msg-menu-emoji button { background: none; border: none; font-size: 19px; cursor: pointer; padding: 4px; border-radius: 8px; }
.msg-menu-emoji button:hover { background: var(--line); }
/* The standalone reaction picker: the emoji are the whole content here, not a
   strip above a list of actions, so they get room and no divider under them. */
.msg-menu-picker { min-width: 0; }
.msg-menu-picker .msg-menu-emoji { border-bottom: 0; margin-bottom: 0; padding: 2px; gap: 4px; }
/* Square hit areas: sized by the box, not by however wide the glyph happens to
   be, so the hover highlight is a square rather than a lopsided rectangle. */
.msg-menu-picker .msg-menu-emoji button { font-size: 28px; padding: 0; width: 46px; height: 46px; display: grid; place-items: center; }
.msg-menu-act { background: none; border: none; text-align: left; font-size: 14px; color: inherit; cursor: pointer; padding: 8px 10px; border-radius: 8px; }
.msg-menu-act:hover { background: var(--line); }
.msg-menu-act.danger { color: #d33; }
.msg .msg-bubble { -webkit-touch-callout: none; }
.chat-edited { font-size: 10px; color: var(--muted); }
.chat-deleted { color: var(--muted); font-size: 12px; padding: 4px 0; }

/* ===== Mobile ergonomics (web interface guidelines audit, 2026-07-05) ===== */
/* Instant, zoom-free taps + no gray flash; feedback comes from :active below. */
button, a, .topic, .side-link, .chat-react-tile { touch-action: manipulation; -webkit-tap-highlight-color: transparent; }
.topic:active, .msg-menu-act:active, .msg-menu-emoji button:active, .chat-react-tile:active, .intake-clip:active, .intake-mic:active { background: var(--line); }
/* iOS zooms into inputs under 16px — keep composer inputs at 16px. */
#chat-view .composer input, .chat-new-form input, .chat-new-form textarea { font-size: 16px; }
/* Composer icon buttons carry their own 44x44 target — see .intake-clip. */
.chat-react-tile { min-height: 28px; }
/* Thread scroll stays inside its pane; momentum scrolling on iOS. */
.thread-scroll { overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
.msg-menu { overscroll-behavior: contain; }
@media (max-width: 760px) {
  /* dvh tracks the real visible height (iOS URL bar); vh clipped the composer. */
  .thread { height: calc(100dvh - 190px); }
  /* Keep the composer clear of the home indicator / notch inset. Every chat
     needs this, not just the neighbour one: the assistant renders the same
     .composer full-page, where it otherwise sits flush against the edge. */
  .composer { padding-bottom: max(16px, env(safe-area-inset-bottom)); }
  #chat-back { min-height: 44px; }
}

/* ===== Dashboard (home view) — sections wrap the SAME tiles their own
   modules use (.topic, .issue-card); only the section head is dashboard-only. */
#dashboard-view > #dash-chats, #dashboard-view > #dash-events { margin-top: 28px; }
.dash-section-head { margin-bottom: 12px; }
.dash-section-head h3 { font-family: var(--display); font-weight: 600; font-size: 1.05rem; margin: 0; }
.dash-more { font-size: 12.5px; padding: 4px 8px; }

/* --- admin setup checklist (dashboard) ----------------------------------- */
.checklist-card { padding: 20px 22px 8px; margin: 0 0 16px; max-width: none; box-shadow: var(--shadow); }
.checklist-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.checklist-title { font-family: var(--display); font-weight: 700; font-size: 1.02rem; margin: 0; }
.checklist-frac {
  font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: 12px; font-weight: 700;
  background: var(--accent-soft); color: var(--accent); border-radius: 999px; padding: 3px 10px;
}
.checklist-track { height: 4px; border-radius: 999px; background: var(--line); overflow: hidden; margin-bottom: 8px; }
.checklist-fill { height: 100%; background: var(--accent); border-radius: 999px; transition: width 0.2s ease; }

.checklist-row { display: flex; align-items: flex-start; gap: 12px; padding: 14px 0; border-top: 1px solid var(--line); }
.checklist-row:first-of-type { border-top: none; }
.checklist-row.clickable { cursor: pointer; }
.checklist-row.clickable:hover .checklist-row-head span { color: var(--accent); }

/* Custom checkbox for the two manual items — deliberately NOT a bare
   <input>, so it can never inherit the global input{width:100%} form-field
   rule (line ~355) that stretched a raw checkbox into a full-width bar. */
.checklist-check {
  appearance: none; -webkit-appearance: none;
  width: 20px; height: 20px; margin: 3px 0 0; padding: 0; flex-shrink: 0;
  border: 1.5px solid var(--line); border-radius: 6px; background: var(--panel-2);
  display: grid; place-content: center; cursor: pointer;
}
.checklist-check::after {
  content: ""; width: 10px; height: 10px;
  clip-path: polygon(9% 43%, 0 55%, 36% 90%, 100% 20%, 88% 8%, 35% 65%);
  background: var(--solid); opacity: 0; transform: scale(0.7);
}
.checklist-check:checked { background: var(--accent); border-color: var(--accent); }
.checklist-check:checked::after { opacity: 1; transform: scale(1); }
.checklist-check:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Read-only status dot for the two auto-detected items — a disabled
   checkbox would suggest the admin could tick it themselves. */
.checklist-dot { width: 20px; height: 20px; border-radius: 50%; flex-shrink: 0; margin-top: 1px; display: grid; place-content: center; }
.checklist-dot.done { background: var(--ok-soft); color: var(--ok); }
.checklist-dot.todo { background: var(--panel-2); border: 1.5px solid var(--line); }
.checklist-dot svg { width: 11px; height: 11px; }

.checklist-body { min-width: 0; flex: 1; }
.checklist-row-head { display: flex; align-items: center; gap: 8px; }
.checklist-row-head span { font-size: 14px; font-weight: 600; }
.checklist-chev { margin-left: auto; color: var(--muted); flex-shrink: 0; }
.checklist-note, .checklist-warn { margin: 3px 0 0; font-size: 12.5px; color: var(--muted); line-height: 1.45; }
.checklist-warn { color: var(--danger); }
.checklist-row .contact-card { margin-top: 8px; }

/* --- admin usage chart (dashboard) --------------------------------------- */
.usage-card { padding: 18px 18px 14px; margin: 0 0 16px; max-width: none; box-shadow: var(--shadow); }
/* Compact inline stat row in the card header — the number's font colour is the
   legend (total = body text, mouse = accent, keyboard = ok), no dots. */
.usage-head { display: flex; align-items: baseline; gap: 22px; margin-bottom: 12px; flex-wrap: wrap; }
.usage-stat { display: inline-flex; align-items: baseline; gap: 6px; }
.usage-stat-label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; }
.usage-stat-num { font-family: var(--mono); font-size: 1.25rem; font-weight: 700; letter-spacing: -0.02em; }
.usage-stat.total .usage-stat-num { color: var(--text); }
.usage-stat.mouse .usage-stat-num { color: var(--accent); }
.usage-stat.keys .usage-stat-num { color: var(--ok); }
.usage-chart { display: block; width: 100%; height: 100px; overflow: visible; }
.usage-axis { stroke: var(--line); stroke-width: 1; }
.usage-line { fill: none; stroke-width: 2; vector-effect: non-scaling-stroke; stroke-linejoin: round; stroke-linecap: round; }
.usage-line.mouse { stroke: var(--accent); }
.usage-line.keys { stroke: var(--ok); }
.usage-xaxis { display: flex; justify-content: space-between; margin-top: 4px; font-family: var(--mono); font-size: 11px; color: var(--muted); }

/* --- onboarding wizard --------------------------------------------------- */
/* .method-back sits flush at top:0/left:0 on the login screen — inset it
   16/16 here instead (scoped to .ob-card so the login screen is untouched). */
.ob-card .method-back { top:16px; left:16px; }
/* Every mockup for this wizard used a large, dark, bold step title — but only
   .login-card h2 (small/muted, shared with the plain login screen) ever
   shipped, so each step quietly stayed tiny until someone compared it against
   its own design. Fix it once, for the whole wizard, instead of catching it
   screen by screen: .ob-card h2 ties .login-card h2's specificity and wins on
   source order (defined later), without touching the actual login screen.
   h2.ob-admin-title (below) is more specific still, so the founder-admin
   screen's even bigger 22px/800 override stays intact on top of this. */
.ob-card h2 { font-size: 19px; font-weight: 700; color: var(--text); }
.ob-card .ob-progress { display:flex; gap:6px; justify-content:center; margin:4px 0 18px; }
.ob-dot { width:22px; height:4px; border-radius:2px; background:var(--line, #333); transition:background .2s; }
.ob-dot.on { background:var(--accent, #6aa9ff); }
/* Fields in this wizard sit inside a <label>, which supplies the 14px rhythm.
   This one has no visible label (the heading is the question), so it carries
   the spacing itself. Scoped to the id, not `.ob-card textarea`, so a future
   label-wrapped textarea doesn't end up double-spaced. */
#ob-mismatch-msg { margin-bottom:14px; }
.ob-actions { display:flex; gap:10px; margin-top:8px; }
.ob-actions button { flex:1; }
.ob-addr-field { position:relative; }
.ob-suggest { position:absolute; top:calc(100% + 4px); left:0; right:0; z-index:20; display:flex; flex-direction:column; border:1px solid var(--line,#333); border-radius:10px; overflow:hidden; background:var(--solid); box-shadow:0 10px 28px rgba(0,0,0,.28); }
.ob-hit { text-align:left; padding:10px 12px; background:transparent; border:0; border-bottom:1px solid var(--line,#2a2a2a); color:var(--text); cursor:pointer; }
/* Non-interactive dropdown row: "searching…" / "nothing found" states. */
.ob-note { text-align:left; padding:10px 12px; color:var(--muted,#98a2b3); border-bottom:1px solid var(--line,#2a2a2a); }
.ob-note:last-child { border-bottom:0; }
/* The dropdown's closing "create new community" action — visually an action,
   not another search hit. */
.ob-hit-create { color:var(--accent); font-weight:600; }
/* Founder "you're the admin" screen (user-approved variant A + L4): the
   shield disc "heartbeats" while radar rings ripple outward — a continuous
   cue, unlike the one-shot ph-pop entrances; the community name is woven
   accent-colored into the headline itself. */
.ob-admin-stage { position: relative; width: 96px; height: 96px; margin: 2px auto 6px; }
.ob-admin-stage .disc { position: absolute; inset: 12px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: var(--accent-soft); color: var(--accent); animation: obHeartbeat 2.6s ease-in-out infinite; }
.ob-admin-stage .disc svg { width: 34px; height: 34px; }
.ob-admin-stage .ring { position: absolute; inset: 12px; border-radius: 50%; border: 2px solid var(--accent); opacity: 0; animation: obRadar 2.6s ease-out infinite; }
.ob-admin-stage .ring:nth-child(2) { animation-delay: 1.3s; }
@keyframes obRadar { 0% { transform: scale(.86); opacity: .55; } 80% { transform: scale(1.45); opacity: 0; } 100% { opacity: 0; } }
@keyframes obHeartbeat { 0%, 86%, 100% { transform: scale(1); } 90% { transform: scale(1.07); } 94% { transform: scale(.99); } }
/* Overrides the tiny muted .login-card h2 (used by every other step title,
   e.g. "Twój adres") — this headline IS the message, so it gets the large,
   bold, high-contrast treatment the mockup specified. Selector is
   `h2.ob-admin-title`, NOT just `.ob-admin-title`: `.login-card h2` (a class
   + an element) outweighs a lone class, so a lone class here would silently
   lose font-size/weight/color to the tiny muted default despite coming later
   in the file — matching element+class ties the specificity and lets source
   order (this rule is defined after) decide. */
h2.ob-admin-title { margin: 0 0 10px; font-size: 22px; font-weight: 800; color: var(--text); line-height: 1.3; }
.ob-admin-name { color: var(--accent); }
.ob-admin-foot { font-size: .85rem; margin-top: 14px; }
@media (prefers-reduced-motion: reduce) {
  .ob-admin-stage .disc { animation: none; }
  .ob-admin-stage .ring { display: none; }
}
/* Closing full-width CTA: all copy sits above it, the button seals the card. */
.ob-cta-wide { width: 100%; margin-top: 16px; padding: 14px 0; font-size: 16px; }
/* Secondary action under the closing CTA — same width, ghost (see button.ghost),
   visibly less prominent than "Dalej". */
.ob-cta-rename { margin-top: 10px; display: flex; align-items: center; justify-content: center; gap: 8px; }
.ob-cta-rename svg { width: 16px; height: 16px; flex: none; }
.ob-hit:last-child { border-bottom:0; }
.ob-hit:hover { background:var(--panel-2, rgba(255,255,255,.05)); }
.ob-picked { font-weight:600; color:var(--text); margin:2px 0 10px; }
.ob-org-name { font-size:1.25rem; font-weight:700; margin:6px 0 16px; }
/* Owner-verification pending screen (showPending, app.js) — same
   .ob-org-name role (a matched community's name), but sized as the card's
   actual headline: bigger than .brand, since "which community" is the one
   fact this screen most needs to get across at a glance. A modifier on
   .ob-org-name rather than a standalone class, so it still reads as the
   same kind of element as the wizard's other matched-org displays. */
.ob-org-name.pending-community { font-size:30px; font-weight:800; line-height:1.15; letter-spacing:-0.01em; }
/* Status tiles: stacked, tap to choose (no "Dalej"). Owner = green, tenant = sky. */
.ob-roles { display:flex; flex-direction:column; gap:12px; margin-bottom:8px; }
.ob-role { position:relative; overflow:hidden; display:flex; align-items:center; gap:14px; text-align:left; padding:15px 16px; border-radius:14px; border:1.5px solid var(--line); background:var(--panel-2); color:var(--text); cursor:pointer; transition:transform .2s, border-color .2s, background .2s, box-shadow .2s, opacity .2s; }
.ob-role:hover { box-shadow:var(--shadow-lift); transform:translateY(-2px); }
.ob-role-icon { display:flex; align-items:center; justify-content:center; width:46px; height:46px; border-radius:12px; flex:none; transition:transform .2s, background .2s, color .2s; }
.ob-role-icon svg { width:24px; height:24px; }
.ob-role-text { display:flex; flex-direction:column; gap:2px; }
.ob-role-text strong { font-size:1.02rem; font-weight:650; }
.ob-role-text span { font-size:.82rem; opacity:.62; }
.ob-role.owner .ob-role-icon { background:var(--ok-soft); color:var(--ok); }
/* Tenant uses the app's own accent (not a standalone sky-blue) — the tile
   was the only place that color existed, so this is a pure alignment. */
.ob-role.tenant .ob-role-icon { background:var(--accent-soft); color:var(--accent); }
/* Hover (user-approved "T4"): the outline icon fills solid with the role's
   own color instead of the badge tint, plus a small directional tilt — owner
   tips left (a house settling into place), tenant tips right. */
.ob-role.owner:hover .ob-role-icon { background:var(--ok); color:#fff; transform:scale(1.08) rotate(-4deg); }
.ob-role.tenant:hover .ob-role-icon { background:var(--accent); color:#fff; transform:scale(1.08) rotate(4deg); }
.ob-role.selected { transform:scale(1.03); box-shadow:0 8px 24px rgba(0,0,0,.2); }
/* Once selected, hovering it again must NOT re-trigger the plain :hover
   lift/shadow (line above) — a chosen tile should already read as settled,
   not still angling to react to the cursor. Re-asserts .selected's own
   values at higher specificity instead of adding to them. */
.ob-role.selected:hover { box-shadow:0 8px 24px rgba(0,0,0,.2); transform:scale(1.03); }
.ob-role.owner.selected { border-color:var(--ok); background:var(--ok-soft); }
.ob-role.tenant.selected { border-color:var(--accent); background:var(--accent-soft); }
.ob-role.selected .ob-role-icon { transform:scale(1.12); }
/* Selected = permanently in the "filled" look :hover gives the icon, not
   just the outline tint — it shouldn't take a mouseover to see that this
   one is chosen. */
.ob-role.owner.selected .ob-role-icon { background:var(--ok); color:#fff; }
.ob-role.tenant.selected .ob-role-icon { background:var(--accent); color:#fff; }
.ob-role.dimmed { opacity:.35; transform:scale(.97); }
/* Same .ob-role tile, reused on the owner-verification pending screen to
   choose a contact method — mail borrows tenant's accent, phone borrows
   owner's green, since both pairs are just "first option / second option"
   here, not tied to those tiles' original owner/tenant meaning. */
.ob-role.mail .ob-role-icon { background:var(--accent-soft); color:var(--accent); }
.ob-role.phone .ob-role-icon { background:var(--ok-soft); color:var(--ok); }
.ob-role.mail:hover .ob-role-icon { background:var(--accent); color:#fff; transform:scale(1.08) rotate(-4deg); }
.ob-role.phone:hover .ob-role-icon { background:var(--ok); color:#fff; transform:scale(1.08) rotate(4deg); }
.ob-role.mail.selected { border-color:var(--accent); background:var(--accent-soft); }
.ob-role.phone.selected { border-color:var(--ok); background:var(--ok-soft); }
.ob-role.mail.selected .ob-role-icon { background:var(--accent); color:#fff; }
.ob-role.phone.selected .ob-role-icon { background:var(--ok); color:#fff; }
/* Onboarding avatar step: big clickable avatar, add-photo button, subtle skip */
.ob-avatar-stack { display:flex; flex-direction:column; align-items:center; gap:14px; margin:6px 0 2px; }
.ob-avatar-btn { border:0; background:transparent; padding:0; line-height:0; cursor:pointer; border-radius:50%; }
.ob-avatar-btn .avatar-lg { width:177px; height:177px; font-size:60px; }
.ob-avatar-btn .avatar-lg .avatar-corner { width:44px; height:44px; }
.ob-avatar-stack > button:not(.ob-avatar-btn):not(.ob-skip):not(.ghost) { width:100%; }
/* Desktop-only drag-and-drop target around the avatar circle (see isMobile
   in avatarPicker) — a visible ring while a file is dragged over it. */
.ob-avatar-dragover { box-shadow: 0 0 0 4px var(--accent-soft), 0 0 0 1.5px var(--accent) inset; }
/* Matches the design-admin.html mockup exactly: +10px below the avatar
   (was -8px — a NEGATIVE margin that pulled this text up under the circle
   instead of leaving a gap). */
.ob-drop-hint { margin:10px 0 0; text-align:center; }
/* Own top margin, not a sibling-combinator dependent on `.ob-actions`
   preceding it — most screens dropped that wrapper this session when their
   "Wstecz" button moved to the wizard's corner arrow, which silently zeroed
   this spacing wherever it happened (e.g. "Pomiń" sitting flush under the
   avatar's CTA). 14px matches the mockup's skiplink spacing. */
.ob-skip { background:none; border:0; color:var(--text); opacity:.55; text-decoration:underline; cursor:pointer; font-size:.85rem; padding:2px 8px; margin-top:14px; }
.ob-skip:hover { opacity:.85; }
/* Inline avatar cropper (drag to move, slider/wheel to zoom, circular crop) */
.cropper-stage { position:relative; max-width:100%; touch-action:none; cursor:grab; border-radius:12px; overflow:hidden; background:#000; }
.cropper-stage:active { cursor:grabbing; }
.cropper-canvas { display:block; }
.cropper-ring { position:absolute; inset:0; pointer-events:none; border-radius:50%; box-shadow:0 0 0 9999px rgba(0,0,0,.45); }
.cropper-zoom { -webkit-appearance:none; appearance:none; width:100%; height:6px; margin:10px 0; padding:0; border:0; border-radius:999px; background:var(--line); cursor:pointer; }
.cropper-zoom:focus, .cropper-zoom:focus-visible { outline:none; box-shadow:none; }
.cropper-zoom::-webkit-slider-thumb { -webkit-appearance:none; appearance:none; width:20px; height:20px; border-radius:50%; background:var(--accent); border:2px solid var(--panel); box-shadow:0 1px 3px rgba(0,0,0,.35); cursor:grab; }
.cropper-zoom::-moz-range-thumb { width:20px; height:20px; border:2px solid var(--panel); border-radius:50%; background:var(--accent); box-shadow:0 1px 3px rgba(0,0,0,.35); cursor:grab; }
.cropper-zoom::-moz-range-track { height:6px; border-radius:999px; background:var(--line); }

/* --- Mieszkańcy: apartment tile (full resident cards) → edit dialog ---- */
.res-apt-row { display:block; width:100%; text-align:left; cursor:pointer; font:inherit; color:inherit; }
.res-apt-row:hover { border-color:var(--accent,#6aa9ff); }
/* Join-request tile — approve/reject sit on the tile itself, so it isn't a
   button-wrapped .res-apt-row like the apartment tiles above; it's a plain
   row (spacing comes from .res-apts' own `gap`) with its own controls. */
.jr-tile { display:flex; align-items:flex-start; gap:14px; position:relative; overflow:hidden;
  transition:max-height .42s cubic-bezier(.4,0,.2,1), opacity .32s ease, transform .32s ease; }
.jr-tile.jr-collapsing { pointer-events:none; transform:scale(.98); }
.jr-wash { position:absolute; inset:-4px; opacity:0; pointer-events:none; transition:opacity .3s ease; border-radius:10px; }
.jr-tile.jr-wash-approve .jr-wash { opacity:1; background:var(--ok-soft); }
.jr-tile.jr-wash-reject .jr-wash { opacity:1; background:var(--danger-soft); }
.jr-tile.jr-highlight { animation:jr-pulse 1.6s ease 2; border-radius:10px; }
@keyframes jr-pulse { 0%, 100% { background:transparent; } 50% { background:var(--accent-soft); } }

.jr-main { flex:1; min-width:0; display:flex; flex-direction:column; gap:4px; justify-content:center; z-index:1; }
.jr-name { font-weight:650; }
.jr-line-row { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }

.jr-actions { display:flex; gap:8px; flex:none; align-self:center; z-index:1; transition:opacity .22s ease, transform .22s ease; }
.jr-tile.jr-deciding .jr-actions { opacity:0; transform:translateY(-4px) scale(.94); pointer-events:none; }
.jr-btn { display:inline-flex; align-items:center; justify-content:center; gap:6px;
  height:32px; padding:0 12px; border-radius:9px; border:1px solid transparent;
  font:inherit; font-size:12.5px; font-weight:650; cursor:pointer; white-space:nowrap;
  transition:transform .12s ease, filter .15s ease, box-shadow .15s ease; }
.jr-btn:active { transform:translateY(1px); }
.jr-btn svg { width:14px; height:14px; }
/* Both gradient stops individually clear WCAG AA (4.5:1) against white text —
   #12813f ≈5.3:1, #0a5c28 ≈8.1:1 — same literal pair in both themes since
   they're checked against white text, not a themed background. */
.jr-btn-approve { background:linear-gradient(135deg, #12813f, #0a5c28); color:#fff; box-shadow:0 6px 16px -7px rgba(10,92,40,.65); }
.jr-btn-approve:hover { filter:brightness(1.07); box-shadow:0 8px 20px -7px rgba(10,92,40,.75); }
.jr-btn-reject { background:var(--panel-2); border-color:var(--line); color:var(--danger); }
.jr-btn-reject:hover { background:var(--danger-soft); border-color:var(--danger); }

.jr-stamp { position:absolute; right:2px; top:50%; margin-top:-16px;
  width:32px; height:32px; border-radius:999px; display:grid; place-items:center;
  opacity:0; transform:scale(.4); z-index:1;
  transition:opacity .26s ease .12s, transform .32s cubic-bezier(.34,1.56,.64,1) .12s; }
.jr-tile.jr-deciding .jr-stamp { opacity:1; transform:scale(1); }
.jr-stamp.approve { background:linear-gradient(135deg, #12813f, #0a5c28); color:#fff; }
.jr-stamp.reject { background:var(--danger); color:#fff; }
.jr-stamp svg { width:17px; height:17px; }

@media (prefers-reduced-motion: reduce) {
  .jr-tile, .jr-wash, .jr-actions, .jr-btn, .jr-stamp { transition:none !important; }
  .jr-tile.jr-highlight { animation:none !important; }
}
.resident-pill.clickable { cursor:pointer; }
.resident-pill.clickable:hover { filter:brightness(1.1); }
.apt-invite-row-controls { display:flex; align-items:stretch; gap:8px; margin-top:18px; }
.apt-invite-email { flex:1; min-width:0; margin-top:0; padding:8px 10px; }
/* .apt-invite-role also lands on the visible custom-dropdown trigger button,
   not just the hidden native <select> — dropdown.js copies the select's own
   className onto it (see enhance() in dropdown.js), so this rule reaches both. */
.apt-invite-role { flex:0 0 120px; margin-top:0; width:auto; padding:8px 10px; }
.apt-invite-send { flex:0 0 auto; width:42px; height:42px; padding:0; display:flex; align-items:center; justify-content:center; }
.apt-invite-send svg { width:16px; height:16px; }
.apt-invites-list { margin-top:18px; }
.apt-invites-list h4 { margin:0 0 10px; font-size:12px; font-weight:700; text-transform:uppercase; letter-spacing:.03em; color:var(--muted); }
.apt-invite-row { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:10px 12px; margin-bottom:8px; background:var(--panel-2); border:1px solid var(--line); border-radius:var(--radius-sm); }
.apt-invite-row:last-child { margin-bottom:0; }
.apt-invite-row-main { display:flex; flex-direction:column; gap:4px; min-width:0; }
.apt-invite-row-main strong { font-size:14px; overflow-wrap:anywhere; }
.apt-invite-meta { display:flex; align-items:center; gap:8px; font-size:12px; }
.apt-invite-revoke { flex-shrink:0; background:none; border:none; color:var(--muted); cursor:pointer; font-size:16px; line-height:1; padding:6px; border-radius:6px; }
.apt-invite-revoke:hover { color:var(--danger); background:var(--danger-soft); }
