/* ============================================
   Mosaica — Shared Design System
   ============================================ */

/* Fonts (Montserrat / Fraunces / JetBrains Mono) load from local vendored
   woff2 files via /vendor/fonts/mosaica-fonts.css in each HTML file's <head>.
   Migrated off Google Fonts CDN by PR #751 (2026-08-14); index.html's last
   CDN <link> holdover was retired by the "Unify interactive-element styles"
   task's hygiene cleanup. Local files load in parallel with shared.css. */

:root {
  --bg: #fff;
  --surface: #fff;
  --ink: #1a1a1a;
  --ink-2: #4a4a4a;
  --ink-3: #8a8683;
  --line: #eceae5;
  --line-2: #e2dfd8;
  --chip: #f3f1ec;
  --field: #f2f0ed;
  --accent: #c4553a; /* Terracotta. Restored 2026-09-07 on the founder's
     instruction ("change the green color and all its shades back to
     terracotta like it was originally"), reverting the C5 (2026-08-07)
     swap to the DR1 dark-green ramp. This is the exact pre-DR1 value from
     `git show 15a5679^:shared.css`, not a re-picked approximation. */
  --ok: #2f7d4f;
  --ok-bg: #e6f4ec;
  --warn: #c47a2a;
  --warn-bg: #fef3e2;
  --shadow-sm: 0 1px 2px rgba(20,18,15,.04), 0 2px 6px rgba(20,18,15,.04);
  --shadow-md: 0 4px 12px rgba(20,18,15,.06), 0 12px 32px rgba(20,18,15,.08);
  --shadow-lg: 0 18px 48px rgba(20,18,15,.14);
  --r-sm: 8px; --r: 14px; --r-lg: 20px; --r-xl: 28px;
  /* FOUNDER BATCH V3 (2026-07-28, founder-found): wide-screen content cap.
     Applied to nav/filter-bar/hero/main-content INNER wrappers so their
     background-bearing OUTER elements stay full-bleed while readable
     content centers with symmetric margins beyond this width. */
  --max-content-width: 1400px;
  --serif: "Fraunces", Georgia, serif;
  --sans: "Montserrat", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, Menlo, monospace;

  /* DR1 (2026-08-03, FOUNDER BATCH V3, APPROVED — see TASKS.md DR1) —
     font-family, resolved: this PR originally proposed "Playfair Display"
     per DR1's own literal text, while leaving --serif itself untouched
     (still Fraunces) since flipping it would be an instant project-wide
     re-render. Founder verdict on PR #640: "use Fraunces everywhere ...
     it is what actually renders today; make config match reality." No
     font swap happens anywhere — --serif stays Fraunces, and the type
     ramp below uses it as-is. DR2 applies these sizes/weights project-wide
     with the existing --serif token, no Google Fonts <link> changes needed.

     TYPE RAMP proper. Exactly 6 size/weight combinations, chosen
     by consolidating the app's own existing (inconsistent) serif-heading sizes
     — grep across home.html/shared.css/index.html today finds --serif headings
     at 20/22/32/36/38/48/58px with no shared discipline. This ramp is the
     deliberate 6-value replacement DR2 will apply project-wide; values are not
     estimated, each is picked from (or immediately adjacent to) an already-
     rendered size in the app today:
     - --type-display: 48px/500 — hero/marketing headlines (home.html's own
       .hero-title uses --serif at this weight, just inconsistently at 58px
       desktop/40px mobile; 48px is the disciplined middle DR2 will apply).
     - --type-h1: 32px/500 — page/section headings, listing titles, prices
       (matches shared.css's existing .stat-num verbatim: font-size 32px,
       font-weight 500).
     - --type-h2: 20px/600 — card/subsection headings, modal titles (matches
       shared.css's existing modal-heading precedent verbatim: 20px/600).
     - --type-body: 15px/400 — body copy (matches shared.css body{} verbatim:
       font-size 15px, default weight 400).
     - --type-label: 13px/500 — UI labels, buttons, nav, pills (13px/500 is
       the single most common sans UI size across shared.css: .pill, footer
       links, nav chips all already render at 13px/500).
     - --type-data: 12px/500 — mono data labels (12px is the most frequent
       existing var(--mono) usage — index.html .card-price .psf, home.html
       .dc-count, buyer-dashboard.html .wl-count/.addon-row-meta family all
       render at 12px; 500 weight matches the one place a mono weight is
       already set explicitly, 404.html's .nf-code).
     Each token is a single shorthand-free pair (-size/-weight) so DR2 can
     apply `font: var(--type-h1-weight) var(--type-h1-size)/1.2 var(--serif)`
     style declarations without inventing new numbers.

     DR2 (2026-08-03) applied these tokens to every shared.css rule whose
     literal size/weight pair exactly matched a ramp entry (.section-title →
     --type-h1, .nav-links a/.chip/.stat-label/.pill → --type-label,
     .footer-logo → --type-h2) — zero rendering change, same computed
     pixels. Near-matches that would require an actual value change
     (.logo 22px/600, .stat-val 36px/500, .section-subtitle 16px, etc.) were
     left as literals rather than forced onto the ramp, since changing them
     IS a real visual change needing founder/visual-checker sign-off — filed
     as DR2-followup, along with the much larger index.html token
     application (index.html has its own separate inline token system per
     CLAUDE.md's "two systems side by side" note) and the two named visual-
     redesign sub-items (filters modal H1, wizard header H4). */
  --type-display-size: 48px; --type-display-weight: 500; /* Fraunces */
  --type-h1-size: 32px;      --type-h1-weight: 500;      /* Fraunces */
  --type-h2-size: 20px;      --type-h2-weight: 600;      /* Fraunces */
  --type-body-size: 15px;    --type-body-weight: 400;    /* Montserrat */
  --type-label-size: 13px;   --type-label-weight: 500;   /* Montserrat */
  --type-data-size: 12px;    --type-data-weight: 500;    /* JetBrains Mono */

  /* DR1 — SPACING SCALE. An 8 step scale; every value except --space-8
     already appeared verbatim as a padding/margin/gap literal in shared.css
     (4px .stat gap, 8px many gaps, 12px .row2/.row3 gaps rounded from 14px,
     16px .field gap, 24px .site-nav-inner/.footer-bottom gaps, 32px
     .footer-grid gap/.divider margin, 48px .site-footer padding) —
     --space-8 (64px) is the clean 2x step toward the one outlier value in
     the file (.site-footer's 80px margin-top, a deliberate one-off DR2 left
     unchanged rather than force into the scale). DR2 (2026-08-03) applied
     these tokens to every shared.css rule whose literal value exactly
     matched a step — zero rendering change, same computed pixels — see the
     rules themselves for the `var(--space-N)` references. Values with no
     exact match (11px/14px/28px/30px/40px/80px etc.) were left as literals
     rather than forced to the nearest step, which would be a real visual
     change requiring founder/visual-checker sign-off — filed as DR2-followup. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;

  /* TERRACOTTA ACCENT RAMP — restored 2026-09-07 (founder instruction).
     Was the DR1 dark-green ramp; the three tokens keep their ROLES and
     revert to the terracotta values this project shipped before DR1:
       --accent-2      base   #c4553a  (pre-DR1 --accent, shared.css)
       --accent-2-ink  dark   #8a3422  (pre-DR1 --accent-ink, index.html)
       --accent-2-bg   tint   #fde8e2  (pre-DR1 .pill-accent background)
     Every value is taken from the pre-DR1 tree, not invented to fit.

     Contrast, measured with the WCAG relative-luminance formula (not
     estimated), so the trade-off is on the record rather than discovered
     later:
       --accent-2     (#c4553a) vs white text:            4.46:1
       --accent-2-ink (#8a3422) vs white text:            8.09:1
       --accent-2 as TEXT on --accent-2-bg (#fde8e2):     3.78:1
       --accent-2-ink as TEXT on --accent-2-bg:           6.87:1
     The green ramp measured 11.08 / 14.75 / 9.49 on those first three, so
     this is a real reduction: 4.46:1 sits just under the 4.5:1 AA
     threshold for normal text, and accent-as-text-on-tint at 3.78:1 is
     well under it. Both were equally true of the pre-DR1 design this
     restores — DR1 did not invent a problem, it fixed one — and the
     founder asked for the original look back knowing the numbers.
     If the contrast matters more than the exact original hue later, the
     one-token change is to use --accent-2-ink for text-on-tint (6.87:1)
     while fills keep the brand --accent-2. */
  --accent-2: #c4553a;
  --accent-2-ink: #8a3422;
  --accent-2-bg: #fde8e2;
}

/* DR1 — preview-mode banner note, RESOLVED (founder verdict on PR #640:
   "Preview banner stays amber"). The founder-preview banner across
   dashboard.html/buyer-dashboard.html/timeline.html/negotiation.html/
   recap.html already conditions its own background on `previewActive ?
   'var(--warn-bg)' : 'var(--chip)'` (amber var(--warn) family) — it never
   used var(--accent) in the first place, so this was already the live
   behavior at zero cost. Stays visually distinct from the new green CTA
   color once DR2 lands, preserving the banner's own warning signal. No
   code change needed here. */

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: var(--sans); background: var(--bg); color: var(--ink);
  font-size: var(--type-body-size); line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
button { font: inherit; color: inherit; cursor: pointer; background: none; border: 0; padding: 0; }
a { color: inherit; text-decoration: none; }
input, select, textarea { font: inherit; color: inherit; }

/* ---- NAV ---- */
.site-nav {
  position: sticky; top: 0; z-index: 60;
  background: rgba(255,255,255,.88);
  backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid var(--line);
}
.site-nav-inner {
  padding: 14px clamp(24px, 3vw, 48px);
  display: flex; align-items: center; gap: var(--space-5);
  max-width: var(--max-content-width); margin-left: auto; margin-right: auto;
}
.logo {
  font-family: var(--serif); font-weight: 600; font-size: 22px;
  letter-spacing: -0.02em; display: flex; align-items: center; gap: var(--space-2);
}
.logo-mark { width: 22px; height: 22px; display: grid; grid-template-columns: 1fr 1fr; gap: 2px; }
.logo-mark i { background: var(--ink); border-radius: 2px; }
.logo-mark i:nth-child(1), .logo-mark i:nth-child(4) { background: var(--accent); }
.logo-mark i:nth-child(4) { opacity: .5; }
.nav-links { display: flex; gap: var(--space-1); margin-left: var(--space-4); }
.nav-links a {
  padding: var(--space-2) 14px; border-radius: 999px;
  font-size: var(--type-label-size); font-weight: var(--type-label-weight); color: var(--ink-2);
  transition: background .15s;
}
.nav-links a:hover { background: var(--chip); color: var(--ink); }
.nav-links a.active { background: var(--ink); color: #fff; }
.nav-right { margin-left: auto; display: flex; gap: var(--space-2); align-items: center; }
.avatar {
  width: 36px; height: 36px; border-radius: 999px;
  background: linear-gradient(135deg, #d8b894, #a87850);
  color: #fff; font-weight: 600; font-size: 13px;
  display: grid; place-items: center; border: 1px solid var(--line-2);
}

/* ---- BUTTONS ---- */
.btn {
  padding: 11px 20px; border-radius: 999px;
  font-size: 13px; font-weight: 600; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  transition: all .15s ease; border: 1px solid transparent;
  white-space: nowrap;
}
.btn-primary { background: var(--ink); color: #fff; }
.btn-primary:hover { background: #000; transform: translateY(-1px); box-shadow: var(--shadow-sm); }
.btn-primary:disabled { opacity: .4; transform: none; cursor: not-allowed; }
.btn-ghost { background: #fff; color: var(--ink); border-color: var(--line-2); }
.btn-ghost:hover { border-color: var(--ink); }
.btn-accent { background: var(--accent); color: #fff; }
.btn-accent:hover { background: #a8422a; }
.btn-sm { padding: var(--space-2) 14px; font-size: 12px; }
/* SWEEP #24 — a de-emphasised action (rejecting an offer) that must still read
   as actionable: muted ink, no frame, underlined. Both dashboards had this as
   the same four-property inline style on their "Odrzuć" button; a class so the
   stacked-layout rule below can reframe it, which an inline style would win
   against. */
.btn-quiet { background: transparent; color: var(--ink-3); text-decoration: underline; text-underline-offset: 3px; }
.btn-quiet:hover { color: var(--ink); }
.btn-lg { padding: 14px 28px; font-size: 14px; }

/* ---- FORM ---- */
.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: var(--space-4); }
.field label { font-size: 11px; font-weight: 700; color: var(--ink-3); letter-spacing: .04em; text-transform: uppercase; }
.field input, .field select, .field textarea {
  padding: 13px var(--space-4); border-radius: 12px;
  border: 1.5px solid var(--line-2); background: #fff;
  font-size: 16px; transition: border-color .15s, box-shadow .15s;
}
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-color: var(--ink);
  box-shadow: 0 0 0 3px rgba(26,26,26,.07);
}
.field textarea { resize: vertical; min-height: 100px; line-height: 1.6; }
.field select { appearance: none; cursor: pointer;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%234a4a4a' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
  background-repeat: no-repeat; background-position: right 14px center;
  padding-right: 40px;
}
.field-prefix { position: relative; }
.field-prefix .pfx {
  position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
  font-size: 14px; color: var(--ink-3); pointer-events: none;
}
.field-prefix input { padding-left: 30px; }

/* ---- BADGE ---- */
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: var(--space-1) 10px; border-radius: 999px;
  font-size: 11px; font-weight: 600;
  background: #fff; box-shadow: var(--shadow-sm);
}
.badge-ok { background: var(--ok-bg); color: var(--ok); }
.badge-warn { background: var(--warn-bg); color: var(--warn); }
.badge-ink { background: var(--ink); color: #fff; }
.badge-chip { background: var(--chip); color: var(--ink-2); }

/* ---- CARD ---- */
.list-card {
  background: #fff; border-radius: var(--r-lg);
  border: 1px solid var(--line); overflow: hidden;
  transition: box-shadow .2s, transform .2s;
}
.list-card:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); }

/* ---- PROGRESS BAR ---- */
.progress-bar { height: 3px; background: var(--line); border-radius: 999px; overflow: hidden; }
.progress-bar-fill { height: 100%; background: var(--ink); border-radius: 999px; transition: width .4s cubic-bezier(.22,.61,.36,1); }

/* ---- SECTION HEADINGS ---- */
.section-title {
  font-family: var(--serif); font-size: var(--type-h1-size); font-weight: var(--type-h1-weight);
  letter-spacing: -0.02em; line-height: 1.15;
}
.section-subtitle { font-size: 16px; color: var(--ink-2); margin-top: var(--space-2); }

/* ---- CHIP / TAG ---- */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: var(--space-2) var(--space-4); border-radius: 999px;
  background: var(--chip); font-size: var(--type-label-size); font-weight: var(--type-label-weight);
  border: 1px solid transparent; transition: all .15s;
}
.chip:hover { border-color: var(--ink); }
.chip.selected { background: var(--ink); color: #fff; }

/* ---- DIVIDER ---- */
.divider { height: 1px; background: var(--line); margin: var(--space-6) 0; }

/* ---- MODAL ---- */
.modal-overlay {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(10,10,10,.45);
  display: grid; place-items: center; padding: 20px;
}
.modal {
  background: var(--bg); border-radius: 22px;
  box-shadow: var(--shadow-lg); overflow: hidden;
  max-height: 90vh; overflow-y: auto;
}

/* ---- STAT ---- */
.stat { display: flex; flex-direction: column; gap: var(--space-1); }
.stat-val {
  font-family: var(--serif); font-size: 36px; font-weight: 500;
  letter-spacing: -0.025em; line-height: 1;
}
.stat-label { font-size: var(--type-label-size); color: var(--ink-3); font-weight: var(--type-label-weight); }
.stat-delta { font-size: 12px; font-weight: 600; }
.stat-delta.up { color: var(--ok); }
.stat-delta.down { color: var(--accent); }

/* ---- MISC ---- */
.pill { padding: 6px 14px; border-radius: 999px; font-size: var(--type-label-size); font-weight: var(--type-label-weight); }
.pill-ok { background: var(--ok-bg); color: var(--ok); }
.pill-warn { background: var(--warn-bg); color: var(--warn); }
.pill-accent { background: var(--accent-2-bg); color: var(--accent); }
.tag-row { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.row3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 14px; }

/* ---- FOOTER ---- */
.site-footer {
  background: var(--ink); color: rgba(255,255,255,.7);
  padding: var(--space-7) 0; margin-top: 80px;
}
.site-footer-inner {
  /* FOUNDER BATCH V3 (2026-07-28): .site-footer keeps its full-bleed --ink
     background; this inner wrapper centers content within it. Same shared
     component as .site-nav/.site-nav-inner (already capped above) — fixing
     both keeps every page using this shared footer/nav pair consistent,
     not just the 5 pages V3's acceptance text names explicitly. Round-2
     visual-checker found padding must live on this SAME element (not the
     outer .site-footer) to match .section's composition — see home.html's
     .hero-inner comment for the full reasoning. */
  padding: 0 clamp(24px, 3vw, 48px);
  max-width: var(--max-content-width); margin-left: auto; margin-right: auto;
}
.footer-logo { font-family: var(--serif); color: #fff; font-size: var(--type-h2-size); font-weight: var(--type-h2-weight); margin-bottom: var(--space-5); }
.footer-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-6); }
.footer-col h4 { color: #fff; font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; margin-bottom: var(--space-3); }
.footer-col a { display: block; font-size: 13px; margin-bottom: var(--space-2); color: rgba(255,255,255,.6); transition: color .15s; }
.footer-col a:hover { color: #fff; }
/* FOUNDER BATCH LAUNCH HARDENING v2, F1 — "Zgłoś uwagę" is a <button>, not an
   <a href="#">, matching every other data-open-feedback trigger in this same
   feature (nav menu, sidebars). A real href="#" anchor made the automated
   dead-end-audit.js classifier (which only recognizes inline onclick=, not a
   document-level delegated data-* listener) flag it as a false dead-end.
   Resets the UA button chrome to render identically to its sibling <a> links. */
.footer-col button.footer-feedback-btn {
  display: block; font-size: 13px; margin-bottom: var(--space-2); color: rgba(255,255,255,.6); transition: color .15s;
  background: none; border: 0; padding: 0; width: 100%; text-align: left; cursor: pointer; font-family: inherit;
}
.footer-col button.footer-feedback-btn:hover { color: #fff; }
.footer-bottom { margin-top: 40px; padding-top: var(--space-5); border-top: 1px solid rgba(255,255,255,.1); font-size: 12px; color: rgba(255,255,255,.4); }

/* ---- MOBILE NAV FIT (Task 27 follow-up applied to shared.css site-nav) ----
   The full desktop nav (logo + .nav-links + nav-right buttons) overflows the
   375/414px viewports. home.html + neighbourhood.html ship the same rule
   inline; this shared.css copy covers every other page that uses the
   shared site-nav. The audit-origin `header-missing-login-affordance-
   marketing-pages` fix (2026-07-20) added a `Zaloguj się` ghost button
   alongside the existing `Wystaw za darmo` primary — two nav-right
   buttons together overflow 375/414px, so mobile hides the primary and
   keeps the more-contextual ghost affordance. The hidden `Wystaw za
   darmo` stays reachable via the footer's "Dla sprzedających" column
   (`Wystaw nieruchomość` link), matching home.html's own Task 27
   follow-up rationale (its hero also duplicates the CTA). */
@media (max-width: 768px) {
  .site-nav .nav-links { display: none; }
  .site-nav .nav-right .btn-primary { display: none; }
}
@media (max-width: 480px) {
  .site-footer { padding: 36px 0; }
  .site-footer-inner { padding: 0 18px; }
  .footer-grid { grid-template-columns: 1fr 1fr; gap: var(--space-5); }
}

/* ---- TARGET PICKER (src/target-picker.js) ----
   SWEEP #7 follow-up (2026-08-15). Replaces the native <select> in the
   add-on service cards on buyer-dashboard.html and dashboard.html.

   Why a replacement rather than a restyle: `appearance: none` reaches the
   CLOSED control only — the open dropdown is painted by the operating
   system, so its rows, corners and highlight colour are unreachable from
   CSS. The founder screenshotted exactly that (Arial rows, square corners,
   blue highlight) and asked for the product's own design language.

   The closed trigger deliberately mirrors `.field select` above — same
   1.5px --line-2 border, same 12px radius, same focus ring — so it reads as
   the same species of control as every other picker in the product. Only
   the type scale differs: these sit inside service cards, which run on
   --type-label-size rather than the form's 16px. */
.mtp { position: relative; }
.mtp-trigger {
  width: 100%; display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border: 1.5px solid var(--line-2); border-radius: 12px;
  background: var(--surface); color: var(--ink);
  font-family: var(--sans); font-size: var(--type-label-size);
  text-align: left; cursor: pointer;
  transition: border-color .15s, box-shadow .15s;
}
.mtp-trigger:hover { border-color: var(--ink-3); }
.mtp-trigger:focus-visible {
  outline: none; border-color: var(--ink);
  box-shadow: 0 0 0 3px rgba(26,26,26,.07);
}
/* Nothing chosen yet reads as placeholder, not as a value. */
.mtp-trigger.is-placeholder { color: var(--ink-3); }
/* BACKLOG.md "Pre-existing .mtp-trigger-text ellipsis" (filed 2026-09-05) —
   CLAUDE.md text-layout rule 1 bans white-space:nowrap on non-money text;
   measured live at 375px a real listing title+address needs 400px against
   a 301px trigger. Two-line clamp (the option BACKLOG's own note named),
   matching the identical pattern already established for card titles
   (index.html:1100 / buyer-dashboard.html:211 — same
   -webkit-line-clamp:2/-webkit-box-orient:vertical/overflow:hidden shape). */
.mtp-trigger-text { flex: 1; min-width: 0; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; white-space: normal; line-height: 1.3; }
.mtp-chevron { flex-shrink: 0; color: var(--ink-2); transition: transform .15s; }
.mtp.is-open .mtp-chevron { transform: rotate(180deg); }

.mtp-list {
  position: absolute; z-index: 40; top: calc(100% + var(--space-1)); left: 0; right: 0;
  margin: 0; padding: var(--space-1); list-style: none;
  max-height: 260px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 12px; box-shadow: var(--shadow-md);
}
.mtp-list:focus { outline: none; }
.mtp-option {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--r-sm); cursor: pointer;
  font-size: var(--type-label-size); color: var(--ink);
}
/* Hover and keyboard share one highlight — the row under the pointer and
   the row under the arrow keys must never disagree. */
.mtp-option.is-active { background: var(--chip); }
.mtp-option.is-selected { font-weight: var(--type-label-weight); }
.mtp-option-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.mtp-option-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mtp-option-sub {
  font-size: var(--type-data-size); color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mtp-check { flex-shrink: 0; color: var(--accent); }

/* ---------------------------------------------------------------------------
   OFFER CARD — one negotiation, on either dashboard.
   SWEEP #17 part 2 (2026-08-23). Lifted verbatim from dashboard.html so the
   buyer panel renders the same card the founder preferred, rather than a
   second, differently-shaped list beside it. Buyer-only and seller-only
   elements live on modifier classes below; the box, the amount, the message
   and the action bar are shared.
   --------------------------------------------------------------------------- */
/* Offer card (screen 2) */
.offer-card {
  background: #fff; border: 2px solid #f0c880; border-radius: var(--r-lg);
  padding: 22px; margin-bottom: 14px; position: relative;
}
.offer-card-new-chip { display: inline-flex; align-items: center; gap: 5px; background: #fef0d0; color: #8a5a10; border-radius: 999px; padding: var(--space-1) 11px; font-size: 10px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; margin-bottom: 14px; }
/* SWEEP #3: a counter the seller sent is their own move waiting on the buyer,
   not a new incoming offer — same chip shape, neutral palette (the existing
   .chip / --ink-2 pair) so it never reads as "something needs your action". */
.offer-card-own-chip { background: var(--chip); color: var(--ink-2); }
/* Same card, calmer frame: the amber 2px border is the "a buyer is waiting on
   you" signal. Your own counter is waiting on THEM, so it takes the neutral
   line every other resting card on this panel uses. */
.offer-card.offer-card-own { border-color: var(--line); }
.offer-card-awaiting { font-size: var(--type-label-size); color: var(--ink-3); }
.offer-card-buyer { display: flex; align-items: center; gap: var(--space-3); margin-bottom: 14px; }
.offer-card-buyer-avatar { width: 44px; height: 44px; border-radius: 999px; background: linear-gradient(135deg, #b8d0e0, #6090b0); color: #fff; font-weight: 700; font-size: 16px; display: grid; place-items: center; flex-shrink: 0; }
.offer-card-buyer-name { font-size: 15px; font-weight: 700; }
.offer-card-badges { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 4px; }
/* SWEEP #17: context lines on a one-card-per-negotiation offer card. */
.offer-card-prev { font-size: var(--type-data-size); color: var(--ink-3); margin-top: var(--space-1); }
.offer-card-count { font-size: var(--type-data-size); color: var(--ink-3); align-self: center; }
.offer-card-badge { display: inline-flex; align-items: center; gap: var(--space-1); padding: 3px var(--space-2); border-radius: 999px; font-size: 10px; font-weight: 700; background: var(--ok-bg); color: var(--ok); }
.offer-card-amount { font-family: var(--serif); font-size: 36px; font-weight: 500; letter-spacing: -0.025em; color: var(--ink); margin-bottom: 4px; white-space: nowrap; }
.offer-card-diff { font-size: 14px; font-weight: 600; margin-bottom: 12px; }
.offer-card-meta { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--ink-2); margin-bottom: 12px; }
.offer-card-message { border-left: 3px solid var(--line-2); padding: 10px 14px; margin-bottom: 14px; font-size: 14px; font-style: italic; color: var(--ink-2); background: var(--chip); border-radius: 0 8px 8px 0; }
.offer-card-actions { display: flex; align-items: center; gap: 10px; }
/* Pushes a trailing "Szczegóły →" to the far end of the action row. A class,
   not an inline style={{marginLeft:'auto'}}, so the narrow-width rule below
   can actually override it — an inline style wins over any stylesheet rule
   short of !important, which is how the auto margin survived wrapping and
   left a lone half-width button floated right on its own line. */
.offer-card-actions-end { margin-left: auto; }

/* SWEEP #24 — "stacking button in the containers should always be aligned and
   follow elementary principles of design not have assymetries".
   dashboard.html already had this rule locally; buyer-dashboard.html never
   got it, and after SWEEP #17 both pages render the same .offer-card. So it
   lives here now and the local copy is gone — the same one-consumer-only
   drift SWEEP #17's picker rows had.

   Measured on buyer-dashboard.html before this rule: the four-button row was
   462px of content in a 280px card at 390px wide (210px at 320px wide), so
   "Odrzuć" and "Szczegóły →" ran past the card and were clipped by
   .section-card's overflow: hidden — invisible to the horizontal-scroll gate
   because clipping is not overflow. And "Czeka na odpowiedź sprzedającego",
   being a flex sibling, wrapped to two lines and stretched every button in
   its row from 37px to 59px tall. Equal widths, equal heights, nothing
   clipped. */
@media (max-width: 768px) {
  .offer-card-actions { flex-wrap: wrap; gap: var(--space-2); }
  /* No grow. With flex-grow a button left alone on the last line stretches to
     full width, so a three-button row rendered as two half-width buttons and
     one double-width one — measured 157/157 then 322 on the seller card. A
     fixed half-width basis makes every line the same grid.
     `min-width: fit-content`, not 0 (2026-08-25 viewport matrix): the old
     comment claimed a long label "narrows instead of overflowing", but a
     nowrap label cannot narrow — it clips. Measured on the seller visit card
     at 360-414px: "Zaproponuj inny termin" needed 153px in a 143px button.
     With fit-content the button never shrinks below its own label; one long
     button takes more than half and its neighbour wraps, short labels keep
     the half-width grid exactly as before. */
  .offer-card-actions .btn { flex: 0 1 calc(50% - var(--space-1)); min-width: fit-content; min-height: 44px; }
  /* Anything that is not a control — the awaiting sentence, the "3 oferty"
     count — takes its own full-width row. As flex siblings they sat inside the
     button grid, stretching the button next to them (Odrzuć measured 268px
     against its 157px neighbours) and pushing the next one onto a line of its
     own. They are metadata, not buttons; they do not belong in the grid.
     order: -1 lifts them ABOVE the buttons rather than leaving them mid-stack,
     where they split the grid into two half-empty rows. Reading order is right
     either way: "Czeka na odpowiedź sprzedającego" explains why there is only
     one button under it, and "3 oferty" is context for the offer above. */
  .offer-card-actions > :not(.btn) { flex: 1 1 100%; order: -1; }
  /* No auto margins once the row wraps — an item pushed to the far end of a
     wrapped line is the asymmetry this rule exists to remove. */
  .offer-card-actions-end { margin-left: 0; }
  /* Stacked, every control occupies an identical cell (measured: 136x44 at
     390px). An unframed one leaves a visible hole in that grid — the founder's
     "assymetries" — so it takes the same line its neighbours have. The muted
     ink is what keeps the hierarchy; the frame is just the cell. */
  .offer-card-actions .btn-quiet { border-color: var(--line); background: var(--surface); text-decoration: none; }
}

/* Buyer-side additions. The buyer's card leads with the PROPERTY (they know
   who the seller is; they are choosing between flats), where the seller's
   leads with the buyer. Everything else — the box, the amount, the diff, the
   message, the action bar — is the shared treatment above. */
/* FOUNDER DESIGN RULES (2026-08-25, PR #885 review) — the property-ident
   block. The founder: "We should always go for the same card design so we
   should have a certain rules about the position of core elements". The rules,
   verbatim from that review:
   1. Always a photo — one small one; its goal is recognition, not showcase.
      Same PROPORTIONS as listing photos (index.html's .media is 4:3), so the
      thumbnail reads as a miniature of the listing, not a different crop.
   2. Next to the photo, aligned to the TOP: the property name; below it, one
      address line — exactly the buyer offer tab's shape.
   3. The same block opens offers, negocjacje and harmonogram cards for both
      buyer and seller.
   This block is that rule. 64×48 is the 4:3 box closest to the previous
   sizes (56×44 here, 72×56 on rows); 56×42 on narrow screens. */
.offer-card-listing { display: flex; align-items: flex-start; gap: var(--space-3); margin-bottom: 14px; }
/* FOUNDER DESIGN RULES v3: the seller card's head row — ident left, buyer in
   the right top corner with the financing + move-in chips stacked under the
   name, right-aligned. Below 560px the corner drops under the ident and
   left-aligns so nothing overlaps. */
.offer-card-head-row { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-3); }
.offer-card-head-row .offer-card-listing { flex: 1 1 0; min-width: 0; }
.offer-card-buyer--corner { display: flex; align-items: flex-start; gap: var(--space-2); flex-shrink: 0; }
.offer-card-buyer--corner .offer-card-buyer-col { display: flex; flex-direction: column; align-items: flex-end; gap: var(--space-1); }
.offer-card-buyer--corner .offer-card-badges { flex-direction: column; align-items: flex-end; gap: var(--space-1); }
@media (max-width: 560px) {
  .offer-card-head-row { flex-wrap: wrap; }
  .offer-card-buyer--corner { flex: 1 1 100%; }
  .offer-card-buyer--corner .offer-card-buyer-col,
  .offer-card-buyer--corner .offer-card-badges { align-items: flex-start; }
}
.offer-card-photo { width: 64px; height: 48px; border-radius: var(--r-sm); background-size: cover; background-position: center; flex-shrink: 0; }
.offer-card-listing-title { font-size: 15px; font-weight: 700; }
.offer-card-listing-sub { font-size: var(--type-data-size); color: var(--ink-3); margin-top: 2px; }
/* v5 (2026-08-27): the status pill moved to the card's TOP-LEFT (the old
   banner position), so this row is the card's first element and its margin is
   ordinary rhythm again. */
.offer-card-status-row { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; margin-bottom: 14px; }

/* SWEEP #59 — the transaction is visible from the offer stage. The founder:
   "the transaction should be visible also in the offer stage for both buyer
   and seller, just mark the stage that the offer is in but have it visible
   also in the offers panel". A slim six-dot pipeline on the offer card, one
   dot per harmonogram stage, wearing the SAME state colors timeline.html's
   stage numbers wear (done = filled ink, active = amber ring #f0c070,
   upcoming = --line-2 outline) so the strip reads as a miniature of the
   harmonogram it points at, not a new vocabulary. Data comes from
   MosaicaDeals.buildOfferStageStrip — the same resolveStageStatuses the
   harmonogram uses. */
/* FOUNDER DESIGN RULES v4 (2026-08-27): "add more spacing and the same
   amount of spacing consistently for both buy side and sell side between etap
   counter and zaakceptuj buttons". With the status pill at the top (v5), the
   strip borders the buttons directly on both cards, so ITS bottom margin is
   that shared gap — one value, 20px, from this one rule. */
.offer-stage-strip { display: flex; align-items: center; gap: 5px; margin-bottom: 20px; min-width: 0; }
.oss-dot { width: 9px; height: 9px; border-radius: 999px; flex-shrink: 0; border: 1.5px solid var(--line-2); background: #fff; }
.oss-dot.done { background: var(--ink); border-color: var(--ink); }
.oss-dot.active { border-color: #f0c070; background: #fffdf8; box-shadow: 0 0 0 3px rgba(240, 192, 112, .25); }
.oss-label { font-size: var(--type-label-size); color: var(--ink-3); margin-left: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
/* The label's two forms. Ellipsizing "Etap 1 z 6 · Oferta uzgodnio…" cut the
   stage NAME — the one word the strip exists to say (founder screenshot,
   2026-08-25). Narrow screens drop the long prefix, keep the name whole. */
.oss-label .oss-step-short { display: none; }
@media (max-width: 560px) {
  .oss-label .oss-step-long { display: none; }
  .oss-label .oss-step-short { display: inline; }
}

/* ---------------------------------------------------------------------------
   PICKER ROW — "which negotiation / which transaction is this about?"
   SWEEP #17 part 2 (2026-08-23). The founder: "for consistency you should
   adopt this style on the other sections when the offers are to be chosen -
   for instance the negotiations section (not inside but on the picker), the
   visits section and the harmonogram section (not inside but on the picker)".

   There were FOUR copies of this row — .panel-pick-* on both dashboards
   (byte-identical), .neg-pick-* on negotiation.html and .tl-pick-* on
   timeline.html — and they had already drifted: three different border
   treatments, three different hover colours, three different title sizes, two
   different amount sizes. One definition now, keyed off every existing class
   name so no markup had to move.

   Aligned to the offer card it sits beside: same serif amount that never
   breaks, same chip family, same ink ramp.
   --------------------------------------------------------------------------- */
/* SWEEP #62/#63 (2026-08-24) — one list idiom, the viewing list's. The
   founder, on Negocjacje/Harmonogram/Lista oczekujących: "can you make it
   similar style as viewing cards with a little photo on the left?" and, on the
   mix of styles across sections: "some sections are one style and some are the
   other... I think it's clearer on the wide background and would require less
   work but again, prioritize as per my design philosophy".

   The decision, stated once so every future section lands on the right side
   of it:
   - ROWS for choosing — a list whose items each lead somewhere (viewings,
     negotiation picker, harmonogram picker, waitlist) renders as FLAT
     full-width rows on the section card: photo thumbnail left, hairline
     divider between rows, no per-row border box. This is `.visit-upcoming`'s
     idiom, now shared.
   - CARDS for acting — a surface carrying decisions and actions (the offer
     card with its amount, Zaakceptuj/Kontroferta, message) keeps its bordered
     card, because a bordered box is what groups an action set with the thing
     it acts on.
   The picker rows below therefore drop their bordered-box treatment (border,
   radius, 18px inset, 10px stride) for the viewing row's flat shape, and gain
   a `.pick-photo` thumbnail slot. */
/* FOUNDER DESIGN RULES v3 (2026-08-25) — ONE card style, bordered. The
   founder, seeing divider rows and bordered cards side by side: "we now have
   2 styles of cards... all the sections that are still in the separator line
   style change to the border line style visible on the other sections — we
   need only one style (please keep all the same specs of the border)". The
   canonical specs are .visit-card's, copied verbatim: #fff, 1.5px var(--line-2),
   var(--r-lg), padding 20px, 12px stride. This also retires the full-bleed
   beige hover ("the awkward highlight") — a bordered card signals hover the
   way the old pickers always did, by darkening its border. */
.panel-pick-row, .neg-pick-row, .tl-pick-row {
  /* flex-start — founder rule 2: the property name aligns to the TOP of the
     photo, the address line under it. */
  display: flex; align-items: flex-start; gap: var(--space-4); width: 100%;
  text-align: left; text-decoration: none; color: inherit; cursor: pointer;
  font-family: var(--sans);
  background: #fff; border: 1.5px solid var(--line-2); border-radius: var(--r-lg);
  padding: 20px; margin-bottom: 12px;
  transition: border-color .15s;
}
.panel-pick-row:hover, .neg-pick-row:hover, .tl-pick-row:hover { border-color: var(--ink); }
/* The thumbnail — same box as the viewing list's .osc-photo (72×56, r10),
   named here because that class lives in buyer-dashboard.html's own styles.
   The icon variant is for rows whose subject has no photograph (a waitlist
   entry is a district, not a listing) — an icon tile keeps the rhythm without
   fabricating imagery (HONESTY RULES). */
/* v4 (founder, 2026-08-27): "the photo size is always the height that the
   booking date (sob 29) has on the visits tab — so photo dimensions same on
   negotiations, harmonogram and visits (for both buyer and seller)". The date
   box measures 60×80 at every width, so the row thumbnails are 80px tall —
   aspect-ratio keeps the 4:3 rule exact instead of a hand-rounded width. The
   offer card's smaller 64×48 ident photo is untouched: the founder froze that
   card's design ("buyers offer design — perfect let's leave it intact"). */
.pick-photo { height: 80px; aspect-ratio: 4 / 3; border-radius: 10px; background-color: var(--chip); background-size: cover; background-position: center; flex-shrink: 0; }
.pick-photo--icon { display: grid; place-items: center; color: var(--ink-3); }

/* The viewing row, shared by both panels (FOUNDER DESIGN RULES v3: buyer and
   seller panels "in design should be consistent with one another"). Same
   bordered-card shell as every other card; date box + 4:3 photo + property
   info left, the status/actions column on the right, vertically centred —
   the founder: "Both the visit status on the buyers side and the confirm,
   propose other date buttons can be centered to the horizontal axis of the
   card." Values lifted from buyer-dashboard.html's own family, which this
   definition replaces. */
.visit-upcoming {
  display: flex; align-items: flex-start; gap: var(--space-4);
  background: #fff; border: 1.5px solid var(--line-2); border-radius: var(--r-lg);
  padding: 20px; margin-bottom: 12px;
}
.visit-upcoming:last-child { margin-bottom: 0; }
.osc-photo { height: 80px; aspect-ratio: 4 / 3; border-radius: 10px; background-color: var(--chip); background-size: cover; background-position: center; flex-shrink: 0; }
.vu-date { text-align: center; background: var(--chip); border-radius: 10px; padding: 10px 14px; flex-shrink: 0; min-width: 60px; }
.vu-day { font-size: 10px; font-weight: 700; color: var(--ink-3); text-transform: uppercase; }
.vu-num { font-family: var(--serif); font-size: 26px; font-weight: 500; line-height: 1; color: var(--ink); }
.vu-time { font-size: 11px; color: var(--ink-3); font-weight: 600; margin-top: 2px; }
.vu-info { flex: 1; min-width: 0; }
.vu-title { font-size: 14px; font-weight: 700; }
.vu-sub { font-size: 12px; color: var(--ink-3); margin-top: 2px; }
.vu-type { display: flex; align-items: center; gap: var(--space-1); font-size: 11px; font-weight: 600; margin-top: 4px; color: var(--ink-2); }
.vu-actions { display: flex; flex-direction: column; gap: 6px; align-items: flex-end; align-self: center; }
/* The seller card's right column: the visitor in the top corner (v3: "the
   visitor info can go to the right top corner"), the Potwierdź / Zaproponuj
   buttons centred in the space beneath — the flex-1 wrapper is what centres
   them on the card's axis without ever overlapping the corner. */
.vu-side { margin-left: auto; display: flex; flex-direction: column; align-items: flex-end; align-self: stretch; gap: var(--space-2); flex-shrink: 0; }
.vu-side-buyer { display: flex; align-items: center; gap: var(--space-2); font-size: 13px; font-weight: 700; white-space: nowrap; }
.vu-side-avatar { width: 28px; height: 28px; border-radius: 999px; background: linear-gradient(135deg, #c0d8e8, #708090); color: #fff; font-weight: 700; font-size: 12px; display: grid; place-items: center; flex-shrink: 0; }
/* v4: "these should be in one horizontal row" — Potwierdź and Zaproponuj
   inny termin sit side by side; align-items centres the row on the card's
   axis inside the flex-1 slot. */
.vu-side-actions { flex: 1; display: flex; flex-direction: row; flex-wrap: wrap; justify-content: flex-end; align-items: center; align-content: center; gap: var(--space-2); }
@media (max-width: 768px) {
  /* The info column stays beside the photo (text-layout rule 3); only the
     right column drops below, full width, left-aligned. */
  .visit-upcoming { flex-wrap: wrap; }
  .vu-info { flex: 1 1 0; min-width: 0; }
  .vu-actions { flex-direction: row; align-items: center; gap: 10px; flex: 1 1 100%; flex-wrap: wrap; align-self: auto; }
  .vu-side { flex: 1 1 100%; align-items: flex-start; align-self: auto; }
  .vu-side-actions { flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: flex-start; }
}
@media (max-width: 430px) {
  /* v4: with the 80px-tall photo beside the 60px date box, a ≤430px viewport
     leaves the text column too narrow for its own longest words (measured at
     360px: "Malczewskiego" needs 91px in 73). No room to the right means
     text-layout rule 3 does not apply — the info line takes its own
     full-width row under the date + photo pair. */
  .vu-info { flex: 1 1 100%; min-width: 0; }
}

/* FOUNDER DESIGN RULES (2026-08-25) — "all the tabs (negocjacje, harmonogram,
   etc) should have a white background like the offers tab or services tab
   have". The white rectangular container every section sits in. Values copied
   from buyer-dashboard.html's own .section-card family (which stays in place
   and, being page-local and identical, keeps winning there) so the seller's
   sections can wear the same container from shared.css. */
.section-card { background: #fff; border: 1px solid var(--line); border-radius: var(--r-lg); margin-bottom: 24px; overflow: hidden; }
.section-card-head { padding: 18px 22px; border-bottom: 1px solid var(--line); display: flex; align-items: center; gap: 10px; }
.section-card-title { font-family: var(--serif); font-size: 18px; font-weight: 500; letter-spacing: -0.015em; flex: 1; }
.section-card-body { padding: 18px 22px; }
.panel-pick-main, .neg-pick-main, .tl-pick-main { flex: 1; min-width: 0; }
.panel-pick-title, .neg-pick-title, .tl-pick-title { font-size: var(--type-body-size); font-weight: 700; }
.panel-pick-sub, .neg-pick-sub, .tl-pick-sub { font-size: var(--type-label-size); color: var(--ink-3); margin-top: 2px; }
.panel-pick-side, .neg-pick-side, .tl-pick-side {
  margin-left: auto; display: flex; flex-direction: column; align-items: flex-end;
  gap: 6px; flex-shrink: 0;
}
/* Money never breaks — CLAUDE.md "Text layout rules" 4. */
.panel-pick-amount, .neg-pick-amount, .tl-pick-amount {
  font-family: var(--serif); font-size: 18px; font-weight: 500;
  letter-spacing: -0.015em; white-space: nowrap;
}
.panel-pick-chip, .neg-pick-chip, .tl-pick-chip {
  display: inline-flex; align-items: center; padding: 3px 10px; border-radius: 999px;
  font-size: 11px; font-weight: 700; background: var(--chip); color: var(--ink-3);
  flex-shrink: 0; white-space: nowrap;
}
.panel-pick-chip.turn, .neg-pick-chip.turn, .tl-pick-chip.turn { color: var(--warn); background: var(--warn-bg); }
/* Founder, 2026-09-04: "if negocjacje are closed AND the offer is in
   harmonogram the negocjacje property box should be somehow greyed out
   because otherwise the buyer and seller will keep cliking on it first, and
   the harmonogram section should be emphasized somehow for instance with the
   otwarta chip being visibly green instead of gray."
   Reuses the existing --ok pair (the same green the rest of the product uses
   for a good state); no new colour enters the system. */
.panel-pick-chip.open, .neg-pick-chip.open, .tl-pick-chip.open { color: var(--ok); background: var(--ok-bg); }
/* The superseded negotiation row: still readable, still clickable (the
   history is genuinely there and people do go back to it), just visibly no
   longer where the action is. Not `display:none` — hiding a negotiation the
   user remembers having would read as data loss. */
.panel-pick-row.is-superseded { opacity: .55; }
.panel-pick-row.is-superseded:hover { opacity: .8; }
.panel-pick-note { font-size: var(--type-data-size); color: var(--ink-3); margin-top: 2px; }
/* SWEEP #28 — the stride belongs BETWEEN cards, not after the last one. */
.panel-pick-row:last-child, .neg-pick-row:last-child, .tl-pick-row:last-child { margin-bottom: 0; }
@media (max-width: 560px) {
  /* SWEEP #62 mobile repair (founder screenshot, 2026-08-25): the old rule
     kept side and main as flex siblings and only changed their alignment —
     with the photo added, the title column starved to one word per line and
     the serif amount printed OVER the title. Measured on the founder's own
     phone screenshot. The row wraps instead: photo + text stay side by side
     (text to the RIGHT of the photo — text-layout rule 3), and the
     price + status drop to their own full-width line underneath. */
  .panel-pick-row, .neg-pick-row, .tl-pick-row { flex-wrap: wrap; }
  .panel-pick-main, .neg-pick-main, .tl-pick-main { flex: 1 1 0; min-width: 0; }
  .panel-pick-side, .neg-pick-side, .tl-pick-side {
    flex: 1 1 100%; margin-left: 0;
    flex-direction: row; align-items: center; justify-content: space-between;
  }
  /* v4: no mobile shrink — the photo matches the date box's 80px height at
     every width, per the founder's rule above. */
  .visit-upcoming { padding: var(--space-4); gap: var(--space-3); }
  .panel-pick-row, .neg-pick-row, .tl-pick-row { padding: var(--space-4); }
}


/* --- Service catalogue cards -------------------------------------------
   Moved out of buyer-dashboard.html (founder batch 2026-09-04): the services
   panel is now generated from src/service-catalog.js and rendered on BOTH
   dashboards, so its styles cannot live inside one page. Unchanged from the
   originals apart from the three additions at the end. */
.addon-cat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: var(--space-4); }
.addon-cat-card {
  border: 1px solid var(--line); border-radius: var(--r); padding: var(--space-5);
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2);
}
.addon-cat-icon { color: var(--ink-3); }
.addon-cat-name { font-family: var(--serif); font-size: var(--type-h2-size); font-weight: var(--type-h2-weight); color: var(--ink); }
.addon-cat-desc { font-size: var(--type-label-size); color: var(--ink-2); line-height: 1.5; }
.addon-cat-price { font-size: var(--type-label-size); font-weight: var(--type-h2-weight); color: var(--ink); }
.addon-cat-card .btn { margin-top: var(--space-1); }
/* The button leads to where the service's target gets chosen, so the card
   states that plainly rather than letting the button imply a one-click order. */
.addon-cat-note { font-size: var(--type-data-size); color: var(--ink-3); line-height: 1.45; }
/* SWEEP #7 follow-up: the offer picker inside a service card.
   This comment used to claim it borrowed `.wl-district-select`'s shape "so
   the two selects read the same". That rule does not exist on main — it is
   created by SWEEP #8 (PR #763), still unmerged — so the control rendered
   completely unstyled, which is what the founder screenshotted. The picker is
   now the design-system listbox in src/target-picker.js, styled in
   shared.css. */
.addon-cat-pick-label { font-size: var(--type-data-size); color: var(--ink-3); }
.addon-cat-pick { width: 100%; margin-bottom: var(--space-1); }
/* A CTA with no offer chosen yet: visibly inert rather than removed, so the
   card's height does not jump the moment a selection is made. */
.addon-cat-card .btn.is-disabled { opacity: .45; cursor: not-allowed; }
/* SWEEP #27 (2026-08-23): "when there is no transactions, grey out the
   services that can't be bought". The legal-check card already dimmed its CTA
   until an offer was picked; Notariusz sat beside it with a full-strength
   black button that led to a transaction the user did not have. Same state,
   two different signals.
   Greyed via the muted ink token rather than a blanket opacity: dimming the
   card wholesale would drop the description and price below readable
   contrast, and an unbuyable service still has to be legible — the point is
   that you can read what it is and see that it is not yet yours to order. */
.addon-cat-card--soon { border-style: dashed; }
.addon-cat-card--soon .addon-cat-name,
.addon-cat-card--soon .addon-cat-price { color: var(--ink-3); }
.addon-cat-foot { margin-top: var(--space-4); font-size: var(--type-data-size); color: var(--ink-3); }
/* SWEEP #7 follow-up — multi-offer ordering. The total sits directly above
   the button so the number you read and the number you commit to are one
   glance apart. */
.addon-cat-total { font-size: var(--type-label-size); color: var(--ink-2); margin-bottom: var(--space-1); }
.addon-cat-total strong { color: var(--ink); font-weight: var(--type-h2-weight); }
.addon-cat-form {
  /* .addon-cat-card is align-items:flex-start, so a child with no width
     shrinks to its content and any width:100% inside it resolves against
     that shrunk box -- which left the variant and date inputs visibly
     narrower than the picker directly above them. */
  align-self: stretch;
  display: flex; flex-direction: column; gap: var(--space-1); margin-top: var(--space-1);
}
.addon-cat-input {
  width: 100%; padding: var(--space-3) var(--space-4);
  border: 1.5px solid var(--line-2); border-radius: 12px;
  background: var(--surface); color: var(--ink);
  font-family: var(--sans); font-size: var(--type-label-size);
  margin-bottom: var(--space-2);
}
.addon-cat-input:focus { outline: none; border-color: var(--ink); box-shadow: 0 0 0 3px rgba(26,26,26,.07); }
.addon-cat-actions { display: flex; gap: var(--space-2); align-items: center; }
.addon-cat-error { font-size: var(--type-data-size); color: var(--warn); margin-bottom: var(--space-2); }
.addon-cat-done { font-size: var(--type-label-size); color: var(--ok); margin-top: var(--space-2); }
/* Additions for the generated cards. */
.addon-cat-status { font-size: var(--type-data-size); color: var(--ink-3); }
.addon-cat-empty { font-size: var(--type-data-size); color: var(--ink-3); line-height: 1.45; }
.addon-cat-consent { display: flex; align-items: flex-start; gap: var(--space-2); font-size: var(--type-data-size); color: var(--ink-2); line-height: 1.45; }
.addon-cat-consent input { width: auto; flex-shrink: 0; margin-top: 3px; }

/* Sub-heading inside a merged panel. The founder batch 2026-09-04 merged
   "Twoje zamowienia" and "Uslugi ktore mozesz zamowic" into one card, which
   left two groups needing labels one level below .section-card-title. Reuses
   the label type token rather than introducing a third heading size — one
   type hierarchy per block (CLAUDE.md text-layout rule 4). */
.section-sub-head {
  font-size: var(--type-label-size);
  font-weight: var(--type-h2-weight);
  color: var(--ink-2);
  margin-bottom: var(--space-2);
}
.addon-cat-block { margin-top: var(--space-4); border-top: 1px solid var(--line); padding-top: var(--space-4); }

/* --- Compact services panel (founder, 2026-09-05) ---------------------------
   Replaces the card grid the founder called "way too overwhelming": one
   property picker per panel, availability chips set once, one line per
   service. Tokens only. */
.svc-panel { display: flex; flex-direction: column; gap: var(--space-3); }
.svc-panel-picker { max-width: 420px; }
.svc-availability { display: flex; flex-direction: column; gap: var(--space-1); }
.svc-chips { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.svc-chip {
  font-family: var(--sans);
  font-size: var(--type-data-size);
  color: var(--ink-2);
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  padding: var(--space-1) var(--space-2);
  cursor: pointer;
}
.svc-chip.on { background: var(--ink); border-color: var(--ink); color: var(--bg); }
/* The active exclusive chip is disabled (nothing for a click to do) but must
   not LOOK broken — it keeps the selected treatment, only the cursor calms. */
.svc-chip:disabled { cursor: default; }
.svc-rows { display: flex; flex-direction: column; }
.svc-group-head { margin-bottom: var(--space-1); }
.svc-row-wrap { border-bottom: 1px solid var(--line); }
.svc-row-wrap:last-child { border-bottom: none; }
.svc-row { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-2) 0; }
.svc-row-main { flex: 1; min-width: 0; }
.svc-row-name { display: block; font-size: var(--type-label-size); font-weight: var(--type-h2-weight); color: var(--ink); }
.svc-row-desc { display: block; font-size: var(--type-data-size); color: var(--ink-3); line-height: 1.45; }
/* Money never breaks (CLAUDE.md text-layout rule 4). */
.svc-row-price { white-space: nowrap; }
.svc-row .btn { flex-shrink: 0; }
.svc-row-ordered { flex-shrink: 0; font-size: var(--type-data-size); color: var(--ink-3); }
.svc-row-confirm, .svc-row-status { display: flex; flex-direction: column; gap: var(--space-2); background: var(--chip); border-radius: var(--r-sm); padding: var(--space-2) var(--space-3); margin-bottom: var(--space-2); }
.svc-row-note { font-size: var(--type-data-size); color: var(--ink-2); line-height: 1.45; }
.svc-row-actions { display: flex; gap: var(--space-2); }
.svc-more-toggle {
  align-self: flex-start;
  font-family: var(--sans);
  font-size: var(--type-data-size);
  color: var(--ink-3);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
