/* =============================================================================
   Superservice Design System
   Canonical reference: docs/design/design-system.md
   Direction: minimal, polished, spacious, fast. Apple-like clarity,
   Telegram-like simplicity. Light-first with an automatic dark theme.
   Edit tokens here; do not hardcode colors/spacing in templates.
   ========================================================================== */

/* ---- Tokens ---------------------------------------------------------------*/
:root {
  color-scheme: light;

  /* Brand.
     These are the *fallback* only. The real accent is set per market and per domain
     in admin, and `apps/core/theming.py` derives hover, pressed, tint, on-accent and
     the whole dark ramp from that one value into a <style> block in <head>. Editing
     the values here changes what an unconfigured market looks like and nothing else.

     Blue, after a detour. The turquoise tried on 2026-08-04 is now a preset rather
     than the default: *"currently I don't like this green-ish primary color, the
     buttons and some icons are looking old, I liked the blue more"*. */
  --accent: #0071e3;
  --accent-hover: #0077ed;
  --accent-press: #0064c8;
  --accent-soft: rgba(0, 113, 227, 0.10);
  --on-accent: #ffffff;

  /* Saffron, the warm counterpoint. Reserved for money and for the one number on
     a screen that should be read before the others. Used anywhere else it stops
     meaning anything. */
  --gold: #a76a12;
  --gold-soft: rgba(167, 106, 18, 0.12);

  /* Neutral surfaces */
  --bg: #fbfbfd;
  --surface: #ffffff;
  --surface-2: #f5f5f7;
  --border: rgba(0, 0, 0, 0.08);
  --border-strong: rgba(0, 0, 0, 0.16);
  --overlay: rgba(0, 0, 0, 0.45);

  /* Text */
  --text: #1d1d1f;
  --text-muted: #6e6e73;
  --text-subtle: #86868b;

  /* Feedback */
  --success: #1d8a4e;
  --success-soft: rgba(29, 138, 78, 0.12);
  --danger: #d70015;
  --danger-soft: rgba(215, 0, 21, 0.10);

  /* Typography */
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
    Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
  --step--1: clamp(0.82rem, 0.79rem + 0.15vw, 0.9rem);
  --step-0: clamp(0.95rem, 0.92rem + 0.18vw, 1.05rem);
  --step-1: clamp(1.12rem, 1.04rem + 0.4vw, 1.35rem);
  --step-2: clamp(1.4rem, 1.25rem + 0.75vw, 1.85rem);
  --step-3: clamp(1.8rem, 1.5rem + 1.5vw, 2.6rem);
  --step-4: clamp(2.4rem, 1.9rem + 2.6vw, 3.75rem);
  --step-5: clamp(3rem, 2.2rem + 4vw, 5rem);

  /* Spacing scale (4px base) */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-7: 2.75rem;
  --space-8: 3.5rem;
  --space-9: 5rem;
  --space-10: 7rem;

  /* Radius */
  --radius-sm: 10px;
  --radius: 16px;
  --radius-lg: 24px;
  --radius-xl: 32px;
  --radius-pill: 999px;

  /* Shadows (soft, low-opacity) */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 3px rgba(0, 0, 0, 0.06);
  --shadow: 0 10px 30px rgba(0, 0, 0, 0.06), 0 2px 8px rgba(0, 0, 0, 0.04);
  --shadow-lg: 0 24px 60px rgba(0, 0, 0, 0.10), 0 6px 18px rgba(0, 0, 0, 0.06);

  /* Layout + motion */
  --container: 1120px;
  --gutter: clamp(1.25rem, 0.8rem + 2vw, 2rem);
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --dur: 200ms;
  --ring: 0 0 0 4px var(--accent-soft);
}

@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
    /* Lifted well past the light value: the same hex on black reads muddy, and a
       dark theme needs its accent to be the brightest thing on the screen. Derived
       per market by `theming.ramp()`; these are the unconfigured fallback. */
    --accent: #409fff;
    --accent-hover: #5faeff;
    --accent-press: #2190ff;
    --accent-soft: rgba(64, 159, 255, 0.16);
    --on-accent: #101418;

    --gold: #e9a94a;
    --gold-soft: rgba(233, 169, 74, 0.16);

    --bg: #000000;
    --surface: #111114;
    --surface-2: #1c1c1f;
    --border: rgba(255, 255, 255, 0.10);
    --border-strong: rgba(255, 255, 255, 0.20);

    --text: #f5f5f7;
    --text-muted: #a1a1a6;
    --text-subtle: #86868b;

    --success: #40c463;
    --success-soft: rgba(64, 196, 99, 0.16);
    --danger: #ff5a5f;
    --danger-soft: rgba(255, 90, 95, 0.14);

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow: 0 12px 34px rgba(0, 0, 0, 0.5);
    --shadow-lg: 0 28px 64px rgba(0, 0, 0, 0.6);
  }
}

/* ---- Base -----------------------------------------------------------------*/
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--step-0);
  line-height: 1.55;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4 { margin: 0; line-height: 1.08; letter-spacing: -0.02em; font-weight: 650; }
/* An unbroken word at this size overflows a phone — profile names and fallback email
   addresses can both lack a convenient break opportunity. */
h1 { font-size: var(--step-4); letter-spacing: -0.04em; overflow-wrap: anywhere; }
h2 { font-size: var(--step-2); }
h3 { font-size: var(--step-1); }
p { margin: 0; }

a { color: var(--accent); text-decoration: none; transition: color var(--dur) var(--ease); }
a:hover { color: var(--accent-hover); }

img { max-width: 100%; height: auto; display: block; }

:focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--radius-sm); }

.skip-link {
  /* Off-screen until focused. `inset-inline-start` (not `left`) so it lands on
     the correct side in RTL; no shadow while hidden or it bleeds into view. */
  position: absolute; inset-inline-start: var(--space-4); top: -3rem;
  background: var(--surface); color: var(--text); padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-sm); z-index: 100;
  transition: top var(--dur) var(--ease);
}
.brand__logo { display: block; height: 1.75rem; width: auto; max-width: 12rem; object-fit: contain; }
.skip-link:focus { top: var(--space-4); box-shadow: var(--shadow); }

/* ---- Layout ---------------------------------------------------------------*/
.wrap { width: 100%; max-width: var(--container); margin-inline: auto; padding-inline: var(--gutter); }
.section { padding-block: var(--space-8); }
.section--tight { padding-block: var(--space-6); }

.stack > * + * { margin-top: var(--space-4); }
.stack-lg > * + * { margin-top: var(--space-6); }

.cluster { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }
.cluster--center { justify-content: center; }

.grid { display: grid; gap: var(--space-5); grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr)); }

.eyebrow {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: var(--step--1); font-weight: 600; letter-spacing: 0.02em;
  color: var(--text-muted); text-transform: none;
}
.muted { color: var(--text-muted); }
.subtle { color: var(--text-subtle); }
.lead { font-size: var(--step-1); color: var(--text-muted); line-height: 1.5; }

/* ---- Navigation -----------------------------------------------------------*/
.nav {
  /* Above .filter-bar (z-index 30): the nav creates a stacking context, so the
     open-menu scrim inside it can only cover the page if the whole header does. */
  position: sticky; top: 0; z-index: 50;
  /* No backdrop-filter here: it makes the header a containing block for
     position:fixed descendants, which clipped the full-screen menu scrim to the
     header's own 56px. Fully opaque rather than the 94% it used to be — without a
     blur behind it, 6% transparency just showed page content sliding through the
     menu while scrolling. */
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}
.nav-inner { min-height: 56px; display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); }
.brand { font-weight: 700; letter-spacing: -0.03em; font-size: var(--step-1); color: var(--text); white-space: nowrap; }

/* Nav: state lives in a hidden checkbox (see base.html for why not <details>).
   Mobile — links collapse into a panel behind the burger. Desktop — burger hidden,
   links inline, subcategories on hover/focus. */
.nav-burger {
  display: inline-flex; align-items: center; justify-content: center;
  width: 3rem; height: 3rem; cursor: pointer;
  border-radius: var(--radius-sm); color: var(--text);
}
.nav-burger .icon { width: 1.6em; height: 1.6em; }
.nav-burger:hover { background: var(--surface-2); }
.nav-toggle:focus-visible + .nav-burger { outline: none; box-shadow: var(--ring); }

/* Mobile menu is a near-full-width sheet with generous rows. A cramped dropdown
   is hard for anyone with larger fingers, reduced vision or an older device —
   and this same markup ships inside the native app wrappers. */
.nav-links {
  position: absolute; z-index: 40;
  inset-inline: var(--space-4); top: calc(100% + var(--space-2));
  display: none; flex-direction: column; align-items: stretch; gap: var(--space-1);
  max-height: 75vh; overflow-y: auto; padding: var(--space-3);
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg); box-shadow: var(--shadow);
}
.nav-toggle:checked ~ .nav-links { display: flex; }

.nav-scrim { display: none; position: fixed; inset: 0; z-index: 35; cursor: default; }
.nav-toggle:checked ~ .nav-scrim { display: block; }

.nav-item { display: flex; align-items: center; }
.nav-links a {
  padding: var(--space-4); border-radius: var(--radius);
  color: var(--text); font-size: var(--step-1); font-weight: 600; flex: 1;
  min-height: 3rem; display: flex; align-items: center;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.nav-links a:hover { background: var(--surface-2); color: var(--text); }
.nav-links a > .icon { flex: none; margin-inline-end: var(--space-2); }

/* A parent category shows its own link plus an arrow that reveals its children,
   so a visitor can reach a subcategory without loading the category page first. */
.nav-item--parent { display: flex; flex-wrap: wrap; align-items: center; }
.nav-sub-arrow {
  display: inline-flex; align-items: center; justify-content: center;
  width: 3rem; height: 3rem; cursor: pointer; border-radius: var(--radius);
  color: var(--text-muted); flex: none;
}
.nav-sub-arrow:hover { background: var(--surface-2); color: var(--text); }
.nav-sub-toggle:focus-visible + .nav-sub-arrow { outline: none; box-shadow: var(--ring); }
.caret {
  width: 0.45em; height: 0.45em;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg); margin-top: -0.2em;
  transition: transform var(--dur) var(--ease);
}
.nav-sub-toggle:checked + .nav-sub-arrow .caret { transform: rotate(-135deg); margin-top: 0.15em; }
.nav-sub {
  display: none; flex-direction: column; width: 100%; gap: var(--space-1);
  padding-inline-start: var(--space-4); padding-block-end: var(--space-2);
}
.nav-sub-toggle:checked ~ .nav-sub { display: flex; }
.nav-sub a { font-size: var(--step-0); color: var(--text-muted); font-weight: 500; min-height: 2.75rem; }

/* Profile Details keeps optional social channels available without letting four
   similar inputs dominate the form. Native details preserves the no-JS path and
   opens automatically when validation has something inside to explain. */
.form-disclosure {
  grid-column: 1 / -1;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface-2); padding: 0 var(--space-4);
}
.form-disclosure > summary {
  min-height: 3rem; display: flex; align-items: center;
  cursor: pointer; font-weight: 650; color: var(--text);
}
.form-disclosure__fields { padding-block: 0 var(--space-4); }

.lang-switch { display: inline-flex; gap: var(--space-1); align-items: center; }
.lang-switch form { display: inline; margin: 0; }
.lang-option { font: inherit; font-size: var(--step--1); font-weight: 600; cursor: pointer; padding: var(--space-1) var(--space-2); border-radius: var(--radius-pill); border: 1px solid var(--border); background: var(--surface); color: var(--text-muted); }
.lang-option:hover { background: var(--surface-2); color: var(--text); }
.lang-option--active { background: var(--accent-soft); color: var(--accent); border-color: transparent; }

@media (min-width: 720px) {
  .nav-burger, .nav-scrim { display: none; }
  .nav-toggle:checked ~ .nav-scrim { display: none; }
  .nav-links {
    position: static; display: flex; flex-direction: row; flex-wrap: wrap;
    align-items: center; justify-content: flex-end; gap: var(--space-1);
    min-width: 0; max-height: none; overflow: visible; padding: 0;
    background: none; border: 0; box-shadow: none;
  }
  .nav-links > a, .nav-item--parent > a {
    padding: var(--space-2) var(--space-3); border-radius: var(--radius-pill);
    color: var(--text-muted); font-size: var(--step--1); font-weight: 550; flex: initial;
  }
  .nav-links a:hover { color: var(--text); }
  .nav-item--cta { color: var(--accent) !important; font-weight: 650 !important; }

  /* Subcategories open on hover or keyboard focus. On desktop the arrow is part of
     the same hover target as the label — a separate control next to it contradicts
     the mental model, and clicking it fought the hover state (the caret flipped
     while the panel stayed open). Here it is a pure affordance: pointer-events are
     off so a click falls through to the category link. */
  .nav-item--parent { position: relative; flex-wrap: nowrap; align-items: center; }
  .nav-item--parent > a { display: inline-flex; align-items: center; gap: var(--space-2); }
  .nav-sub-arrow {
    position: absolute; inset-inline-end: var(--space-2); top: 50%;
    transform: translateY(-50%);
    width: auto; height: auto; pointer-events: none; background: none;
  }
  .nav-item--parent > a { padding-inline-end: var(--space-6); }
  /* Hover/focus drives the caret, not the checkbox, so the two never disagree. */
  .nav-sub-toggle:checked + .nav-sub-arrow .caret { transform: rotate(45deg); margin-top: -0.2em; }
  .nav-item--parent:hover .nav-sub-arrow .caret,
  .nav-item--parent:focus-within .nav-sub-arrow .caret { transform: rotate(-135deg); margin-top: 0.15em; }
  /* The panel floats below the bar. Without this it inherits the mobile rule
     (static, full width) and renders inside the menu bar itself. */
  .nav-sub {
    position: absolute; inset-inline-start: 0; top: calc(100% + var(--space-2));
    min-width: 13rem; width: max-content; max-width: 22rem;
    padding: var(--space-2); gap: 2px;
    background: var(--surface); border: 1px solid var(--border-strong);
    border-radius: var(--radius); box-shadow: var(--shadow);
  }
  /* Bridge the visual gap between the item and its panel. Without it the mouse
     crosses a strip that belongs to neither element, :hover is lost and the panel
     closes mid-travel. The bridge is inside .nav-sub, so hovering it keeps the
     ancestor .nav-item--parent hovered. Invisible, and never on mobile where the
     panel is in flow. */
  .nav-sub::before {
    content: ""; position: absolute; inset-inline: 0;
    bottom: 100%; height: var(--space-3);
  }
  .nav-item--parent:hover .nav-sub,
  .nav-item--parent:focus-within .nav-sub { display: flex; }
  .nav-sub a {
    padding: var(--space-2) var(--space-3); font-size: var(--step--1);
    color: var(--text); border-radius: var(--radius-sm);
  }
  .nav-sub a:hover { background: var(--surface-2); }
}

/* ---- Icons ----------------------------------------------------------------*/
.icon {
  width: 1.25em; height: 1.25em; flex: none;
  vertical-align: -0.2em; /* optical alignment with adjacent text */
}
.icon--sm { width: 1em; height: 1em; }

/* ---- Hero -----------------------------------------------------------------*/
.hero { text-align: center; padding-block: var(--space-7) var(--space-5); }
/* Directory and profile pages lead with content, not with a landing-page hero:
   the filter bar and results should be near the fold. A full hero belongs on the
   home page, where setting the scene is the job. */
.hero--compact { padding-block: var(--space-5) var(--space-4); }
.hero--compact h1 { font-size: var(--step-3); }
.hero--compact .lead { margin-top: var(--space-3); font-size: var(--step-0); }
.hero h1 { max-width: 16ch; margin-inline: auto; }
.hero .lead { max-width: 62ch; margin: var(--space-5) auto 0; }
.hero .cluster { margin-top: var(--space-6); }

/* ---- Buttons --------------------------------------------------------------*/
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  padding: 0.68em 1.25em; border-radius: var(--radius-pill);
  font: inherit; font-weight: 600; font-size: var(--step-0);
  border: 1px solid transparent; cursor: pointer; white-space: nowrap;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
    border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.btn:active { transform: translateY(1px); }
.btn--primary { background: var(--accent); color: var(--on-accent); }
.btn--primary:hover { background: var(--accent-hover); color: var(--on-accent); }
.btn--primary:active { background: var(--accent-press); }
.btn--secondary { background: var(--surface); color: var(--text); border-color: var(--border-strong); }
.btn--secondary:hover { background: var(--surface-2); color: var(--text); }
/* The chip whose panel is open. Used by the option-detail buttons, where the pressed
   one has to stay visibly pressed after the page reloads — the panel is a URL, so
   there is no client state to show it. */
.btn--active { background: var(--surface-2); border-color: var(--accent); color: var(--accent); }
.btn--ghost { background: transparent; color: var(--accent); }
.btn--ghost:hover { background: var(--accent-soft); }
.btn--sm { padding: 0.5em 0.95em; font-size: var(--step--1); }
.btn--block { width: 100%; }

/* ---- Cards ----------------------------------------------------------------*/
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: var(--space-5);
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.card--interactive:hover { transform: translateY(-3px); box-shadow: var(--shadow); border-color: var(--border-strong); }
.card > * + * { margin-top: var(--space-3); }
.card h3 { letter-spacing: -0.02em; }
.card__foot { margin-top: var(--space-4); }

/* ---- Chips, pills, badges -------------------------------------------------*/
.pill, .chip, .badge {
  display: inline-flex; align-items: center; gap: var(--space-2);
  border-radius: var(--radius-pill); font-size: var(--step--1); font-weight: 550;
  padding: 0.4em 0.85em; border: 1px solid var(--border);
  background: var(--surface); color: var(--text-muted);
}
.chip { transition: background var(--dur) var(--ease), color var(--dur) var(--ease), border-color var(--dur) var(--ease); }
a.chip:hover { background: var(--surface-2); color: var(--text); border-color: var(--border-strong); }
.badge--accent { background: var(--accent-soft); color: var(--accent); border-color: transparent; }
.badge--success { background: var(--success-soft); color: var(--success); border-color: transparent; }
.badge--danger { background: var(--danger-soft); color: var(--danger); border-color: transparent; }

/* ---- Tabs -----------------------------------------------------------------*/
/* Profile section tabs. All sections are rendered; only the selected one shows.
   No JavaScript and no extra URLs — a hidden radio drives it, and because each
   radio sits immediately before its own label and panel, the rules below need no
   per-profile ids. `order` lifts every label into a row above the panels. */
/* ---- Profile section tabs ---------------------------------------------------
   Redesigned 2026-08-04. These were small pill chips, which understated the whole
   point of the product: one profile covering several niches is the differentiator,
   and the control that moves between them should look like primary navigation
   rather than a row of filter tags.

   Still CSS-only, and the structure is load-bearing: each radio sits immediately
   before its label and panel, so `input:checked + label + panel` does the work with
   no per-profile ids and no script. That adjacency is why there is no wrapper
   element around the labels — a wrapper would break the sibling chain. The rule
   under the strip is drawn by the panel's top border instead. */
.tabs { display: flex; flex-wrap: wrap; gap: var(--space-1); align-items: flex-start; }
.tabs__radio {
  /* Hidden but focusable: clip rather than display:none, or the tabs stop working
     from the keyboard and a screen reader loses the control entirely. */
  position: absolute; width: 1px; height: 1px; margin: -1px;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}
.tabs__label {
  order: 1; cursor: pointer; scroll-snap-align: start; white-space: nowrap;
  display: inline-flex; align-items: center; gap: var(--space-2);
  min-height: 3rem; padding: 0 var(--space-4);
  border: none; border-block-end: 2px solid transparent; background: none;
  font-size: var(--step-0); font-weight: 600; color: var(--text-muted);
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.tabs__label:hover { color: var(--text); }
.tabs__radio:checked + .tabs__label {
  color: var(--text); border-block-end-color: var(--accent);
}
.tabs__radio:focus-visible + .tabs__label { outline: none; box-shadow: var(--ring); }
.tabs__panel {
  order: 2; flex: 1 1 100%; display: none;
  border-block-start: 1px solid var(--border);
  margin-block-start: -1px; /* meets the active tab's own underline */
  padding-block-start: var(--space-5);
}
.tabs__radio:checked + .tabs__label + .tabs__panel { display: block; }
.tab {
  border: 1px solid var(--border-strong); border-radius: var(--radius-pill);
  padding: var(--space-2) var(--space-4); background: var(--surface);
  color: var(--text); font-weight: 600; font-size: var(--step--1);
}
.tab:hover { background: var(--surface-2); color: var(--text); }

/* ---- Forms ----------------------------------------------------------------*/
.field { display: grid; gap: var(--space-2); text-align: start; }
.label { font-size: var(--step--1); font-weight: 650; color: var(--text); }
.label .req { color: var(--danger); }
.help { font-size: var(--step--1); color: var(--text-subtle); }
.input, .textarea, .select {
  width: 100%; font: inherit; color: var(--text);
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); padding: 0.7em 0.85em;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.input:focus, .textarea:focus, .select:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
.textarea { min-height: 7.5rem; resize: vertical; }
.select[multiple] { min-height: 8rem; padding: var(--space-2); }
.field--error .input, .field--error .textarea, .field--error .select { border-color: var(--danger); }
.field__error { font-size: var(--step--1); color: var(--danger); font-weight: 550; }
.form-grid { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr)); align-items: start; }
.form-grid > * { min-inline-size: 0; }
.field--wide { grid-column: 1 / -1; }
/* Checkbox beside its wording, not stacked under it: a bare checkbox stretched
   into a grid cell rendered adrift from the label it belongs to. `order` does the
   swap so the markup stays shared with every other field type. */
.field--check { grid-template-columns: auto minmax(0, 1fr); align-items: center; }
.field--check input[type="checkbox"] { order: 1; width: 1.1rem; height: 1.1rem; margin: 0; accent-color: var(--accent); }
.field--check .label { order: 2; font-weight: 550; }
.field--check .help, .field--check .field__error { order: 3; grid-column: 1 / -1; }

/* ---- Filter bar (dropdown facets) -----------------------------------------*/
.filter-bar {
  /* Sticks to the top so the filters stay reachable while scrolling results,
     without a modal or an interstitial step. `top: -1px` + a slightly stronger
     shadow once stuck keeps it from looking like a detached floating bar. */
  position: sticky; top: -1px; z-index: 30;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); padding: var(--space-4);
  transition: box-shadow var(--dur) var(--ease);
}
@supports (animation-timeline: view()) {
  .filter-bar { box-shadow: var(--shadow-sm); }
}
.filter-bar__fields { display: grid; gap: var(--space-3); grid-template-columns: 1fr; }
.filter-bar__actions { display: flex; gap: var(--space-2); align-items: center; }
.filter-bar__selected {
  display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center;
  margin-top: var(--space-4); padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}
.filter-note { margin-top: var(--space-3); }

/* Dropdown field: <details> styled as a form control, panel overlays content. */
.dropdown { position: relative; }
.dropdown__control {
  display: flex; align-items: baseline; gap: var(--space-2);
  width: 100%; cursor: pointer; list-style: none;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  padding: 0.7em 0.85em; font-size: var(--step-0);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.dropdown__control::-webkit-details-marker { display: none; }
.dropdown__control:hover { border-color: var(--accent); }
.dropdown[open] > .dropdown__control { border-color: var(--accent); box-shadow: var(--ring); }
.dropdown__control:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
.dropdown__label { font-size: var(--step--1); font-weight: 650; color: var(--text-muted); }
.dropdown__value { font-weight: 600; flex: 1; }
.dropdown__caret {
  /* Physical borders are deliberate here: right+bottom rotated 45deg is a
     symmetric down-chevron, so it reads the same in RTL. Logical properties
     would flip it to point the wrong way. */
  width: 0.5em; height: 0.5em; align-self: center; flex: none;
  border-right: 2px solid var(--text-subtle); border-bottom: 2px solid var(--text-subtle);
  transform: rotate(45deg); margin-top: -0.25em;
}
.dropdown[open] .dropdown__caret { transform: rotate(-135deg); margin-top: 0.15em; }
/* Mobile: fields are stacked, so the panel stays in flow — an overlay would
   cover the next field and the Apply button. Desktop switches to an overlay
   below so the results grid is never pushed down. */
/* The panel itself no longer scrolls — the tree inside it does. When the panel was
   the scroll container, the "Selected" strip and the Apply button scrolled with it:
   sticky kept them on screen, but the rows slid *underneath* and the last option was
   permanently half-hidden behind the button. Three rows in a column, with only the
   middle one scrolling, is the shape that has no overlap to fix. */
.dropdown__panel {
  margin-block-start: var(--space-2);
  min-width: 100%; max-height: min(65vh, 30rem);
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: var(--space-2); display: flex; flex-direction: column; gap: 2px;
}
.picker__tree { overflow-y: auto; min-height: 0; flex: 1; }
.option {
  display: flex; align-items: center; gap: var(--space-3);
  padding: 0.5em 0.6em; border-radius: var(--radius-sm); cursor: pointer;
  font-size: var(--step--1); color: var(--text); white-space: nowrap;
}
.option input { width: 1.15em; height: 1.15em; }
.option:hover { background: var(--surface-2); }
.option input { accent-color: var(--accent); flex: none; margin: 0; }
.option__name { flex: 1; }
.option__count { color: var(--text-subtle); font-variant-numeric: tabular-nums; }
/* An ancestor of the current selection. Not checked — checking Alborz as well as
   Karaj would mean the whole province — but the branch you are inside has to look
   different from the thirty provinces you are not. */
.option--on-path .option__name { color: var(--text); font-weight: 650; }
.option--on-path { background: var(--surface-2); }

/* ---- Tree picker (expandable multi-select over a hierarchy) -----------------
   Replaced a flat indented list, where depth was one class per level and so had a
   hard ceiling — locations have five levels and the list could style three. Nesting
   the markup instead means indentation is structural: a child is inside its parent,
   at any depth, and a branch can collapse. Everything below mirrors in RTL because
   the indent is `padding-inline-start` on the nested list, not a per-level margin. */
.picker__list { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; }
.picker__children > .picker__list {
  padding-inline-start: var(--space-4);
  border-inline-start: 2px solid var(--border);
  margin-inline-start: var(--space-3);
}
.picker__summary {
  display: flex; align-items: center; gap: var(--space-1);
  list-style: none; cursor: pointer; border-radius: var(--radius-sm);
}
.picker__summary::-webkit-details-marker { display: none; }
.picker__summary:hover { background: var(--surface-2); }
.picker__summary .option { flex: 1; min-width: 0; }
.picker__summary .option:hover { background: transparent; }
.picker__summary .option__name { overflow: hidden; text-overflow: ellipsis; }

/* The twisty belongs to the disclosure, the control to the choice. Keeping them
   visibly separate is the whole reason a branch can be opened without being picked —
   but they were separate and *tiny*, so opening a province meant hitting a 7px
   chevron. The arrow still looks small; its hit area is 2.75rem square, which is the
   size a thumb actually needs. The box is the target, the chevron is just the paint. */
.picker__twisty {
  position: relative; flex: none;
  width: 2.75rem; height: 2.75rem;
  border-radius: var(--radius-sm);
}
.picker__twisty::after {
  content: ""; position: absolute; inset-block-start: 50%; inset-inline-start: 50%;
  width: 0.5em; height: 0.5em;
  margin-block-start: -0.35em; margin-inline-start: -0.25em;
  border-right: 2px solid var(--text-muted); border-bottom: 2px solid var(--text-muted);
  transform: rotate(-45deg); transform-origin: center;
  transition: transform var(--dur) var(--ease);
}
.picker__summary:hover .picker__twisty { background: var(--surface-3, var(--surface-2)); }
.picker__branch[open] > .picker__summary > .picker__twisty::after { transform: rotate(45deg); }
.picker__summary:focus-visible { outline: none; box-shadow: var(--ring); }
/* Leaves line up with their siblings' labels, not with the twisty they do not have. */
.option--leaf { margin-inline-start: 2.75rem; }
.picker__children .option--leaf { margin-inline-start: 2.75rem; }

/* A branch that contains the choice. Its own control stays unchecked — picking Karaj
   is not picking Alborz — so the mark says "in here" rather than "this". The script
   also sets `indeterminate`, which renders as a dash in the box itself. */
.option--ancestor { background: var(--accent-soft); }
.option--ancestor .option__name { color: var(--text); font-weight: 650; }

/* A parent that cannot be chosen at all: sections are leaf-only, so it is a heading
   with a disclosure, and it must not look like a control that failed to work. */
.option--container { cursor: pointer; }
.option--container .option__name {
  font-weight: 650; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.03em; font-size: var(--step--2);
}

/* Rows are touch-sized. A picker is used with a thumb, in a scrolling panel, where a
   mis-tap either chooses the wrong place or collapses the branch you just opened. */
.picker__list .option { min-height: 2.75rem; }
.option__name { overflow-wrap: anywhere; white-space: normal; }

/* Chosen options, above the tree and outside its scroll area — the answer to "what
   have I picked?" must not scroll away with the thing you are picking from. */
.picker__chosen {
  flex: none;
  display: grid; gap: var(--space-2);
  padding: var(--space-2); margin-block-end: var(--space-2);
  background: var(--surface-2); border-radius: var(--radius-sm);
}
.picker__chosen[hidden] { display: none; }
.picker__chosen-label {
  font-size: var(--step--2); font-weight: 650; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.04em;
}
.picker__chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.chip {
  display: inline-flex; align-items: center; gap: var(--space-1);
  padding: 0.25em 0.6em; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--border-strong);
  font-size: var(--step--2); color: var(--text); line-height: 1.6;
}
.chip--removable { cursor: pointer; font: inherit; font-size: var(--step--2); }
.chip--removable:hover { border-color: var(--accent); }
.chip__x { color: var(--text-subtle); font-size: 1.15em; line-height: 1; }
.picker__actions {
  flex: none;
  padding-block-start: var(--space-2); margin-block-start: var(--space-2);
  border-top: 1px solid var(--border); background: var(--surface);
}

/* Selection tree — an already-chosen hierarchy shown in place (owner panel).
   Rows, not cards: a card per node split one nested place into several tiles that
   read as unrelated areas. Depth comes from a `--depth` custom property set on the
   row, so it scales to all five location levels without a class per level, and the
   spine plus indent use logical properties to mirror in RTL. */
.tree { list-style: none; margin: 0; padding: 0; }
.tree__row {
  display: flex; align-items: center; gap: var(--space-3);
  /* Deep rows carry a long name plus type, flag and Remove. Without wrapping,
     a long name (Persian district names run long) overlapped the type label at
     390px — the row must reflow instead. */
  flex-wrap: wrap;
  padding-block: var(--space-2);
  /* Mobile-first: a neighborhood sits four levels deep, and the wider desktop
     step would spend a quarter of a 390px screen on indent alone. */
  padding-inline-start: calc(var(--depth, 0) * var(--space-3));
  font-size: var(--step--1);
}
.tree__row + .tree__row { border-top: 1px solid var(--border); }
.tree__name { font-weight: 600; color: var(--text); min-width: 0; overflow-wrap: anywhere; }
.tree__type { color: var(--text-subtle); white-space: nowrap; }
/* Ancestors nobody picked: present for orientation, so they recede and offer no
   controls. Without them a bare "Karaj" never says which province it is in. */
.tree__row--context .tree__name { font-weight: 500; color: var(--text-muted); }
.tree__flag {
  font-size: var(--step--1); font-weight: 700; letter-spacing: 0.02em;
  text-transform: uppercase; color: var(--accent); white-space: nowrap;
}
/* Not-yet-live states are information, not achievements — they should not read
   as loud as the accent colour reserved for "this is working". */
.tree__flag--muted { color: var(--text-subtle); }
.dashboard-handle {
  margin: var(--space-2) 0 0;
  color: var(--text-muted);
  font-size: var(--step-1);
  direction: ltr;
  unicode-bidi: isolate;
}
.dashboard-tools { align-items: stretch; }
.dashboard-tools .btn { position: relative; }

/* The balance is the only number an owner needs before deciding what to do. It is
   set like an account-book total, while the surrounding controls keep the existing
   product rhythm instead of turning payments into a separate visual brand. */
.balance-hero { border-bottom: 1px solid var(--border); }
.balance-amount {
  margin: var(--space-3) 0 0; color: var(--text); font-size: var(--step-4);
  font-weight: 750; letter-spacing: -0.035em;
}
.balance-amount small { color: var(--text-muted); font-size: var(--step-0); letter-spacing: 0; }
.payment-layout { display: grid; gap: var(--space-5); align-items: start; }
.payment-instructions {
  padding: var(--space-4); border-inline-start: 3px solid var(--accent);
  background: var(--surface-2); border-radius: var(--radius-sm);
}
.payment-instructions p { margin: var(--space-2) 0 0; }
.amount-presets { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.amount-chip {
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--text);
  cursor: pointer;
  font: inherit;
  font-size: var(--step--1);
  padding: var(--space-2) var(--space-3);
}
.amount-chip:hover,
.amount-chip[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }
.amount-chip:focus-visible { outline: none; box-shadow: var(--ring); }
.payment-method-options { display: grid; gap: var(--space-2); }
.payment-method-options label {
  display: flex; align-items: center; gap: var(--space-2);
  min-block-size: 2.75rem; padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface);
}
.payment-method-options label:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
.payment-list { list-style: none; margin: 0; padding: 0; }
.payment-list li {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
  padding-block: var(--space-3); border-top: 1px solid var(--border);
}
.payment-list li:first-child { border-top: 0; }
.payment-list li > div { display: grid; gap: var(--space-1); min-width: 0; }
.payment-numbers { text-align: end; flex: none; }
.payment-claim-state { align-items: end; justify-items: end; flex: none; }
.money-positive { color: var(--success); }
@media (min-width: 56rem) {
  .payment-layout { grid-template-columns: minmax(18rem, 0.8fr) minmax(25rem, 1.2fr); }
  .payment-statement { position: sticky; top: var(--space-5); }
}
@media (max-width: 32rem) {
  .payment-list--statement li { align-items: start; }
  .payment-list--statement .payment-numbers { max-width: 45%; }
}
.setup-dot {
  inline-size: 0.45rem;
  block-size: 0.45rem;
  border-radius: var(--radius-pill);
  background: var(--danger);
  flex: none;
}
/* Supporting text (a section's bio excerpt). Truncates rather than pushing the
   controls off the row; on a narrow screen it wraps to its own line instead. */
/* Supporting text (a section's bio excerpt). Truncates rather than pushing the
   controls off the row. On mobile it always takes its own line — sharing the row
   left it inline on short names and wrapped on long ones, which read as broken. */
.tree__note {
  color: var(--text-muted); flex: 1 1 100%; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tree__actions { margin-inline-start: auto; flex: none; }
/* Separate root hierarchies from each other more strongly than sibling rows. */
.tree + .tree { margin-top: var(--space-4); padding-top: var(--space-4); border-top: 2px solid var(--border-strong); }

/* Profile setup checklist (owner dashboard). Shares the tree's row rhythm but is
   flat, so it gets its own names — `.tree__*` means hierarchy. Done steps recede
   to one quiet line; what is left to do is what the block is about. */
.checklist { list-style: none; margin: 0; padding: 0; }
.checklist__item {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  padding-block: var(--space-3); font-size: var(--step--1);
}
.checklist__item + .checklist__item { border-top: 1px solid var(--border); }
.checklist__mark { flex: none; font-weight: 700; color: var(--text-subtle); }
.checklist__item--done { padding-block: var(--space-2); }
.checklist__item--done .checklist__mark { color: var(--success); }
.checklist__item--done .checklist__label { color: var(--text-muted); }
.checklist__label { font-weight: 600; color: var(--text); }
/* Required is a property of the step, not a thing the button does — the button
   always says what it does. */
.checklist__required {
  flex: none; font-size: var(--step--1); font-weight: 700; letter-spacing: 0.02em;
  text-transform: uppercase; color: var(--danger);
}
/* The reason, not the instruction. Its own line on mobile, inline when there is
   room — on a wide screen three stacked lines per step read as disconnected. */
.checklist__why { color: var(--text-muted); flex: 1 1 100%; min-width: 0; }
.checklist__action { margin-inline-start: auto; flex: none; }

/* Media grids. Fixed aspect tiles so a wall of mixed portrait and landscape photos
   still reads as a grid rather than a ragged column.

   The tinted background is a deliberate placeholder: with width/height on every img
   the browser reserves the box before the bytes arrive, so a slow connection sees a
   calm grey block that becomes a photo — not a page that jumps. That is cheaper and
   steadier than a skeleton animation, which mostly advertises that something is
   slow. */
.media-grid { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fill, minmax(min(100%, 180px), 1fr)); }
.media-tile { margin: 0; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; background: var(--surface); }
.media-tile img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; background: var(--surface-2); }
.media-tile figcaption { padding: var(--space-3); font-size: var(--step--1); }
.media-tile--logo { border-color: var(--accent); }

/* Profile logo on the public page and on result cards. */
/* `margin-inline: auto` because the global reset makes images block-level, so the
   hero's text-align does not centre them. */
/* Avatars. The slot is the same size whether or not there is a photo, so a listing
   has one layout rather than two, and a profile without a picture reads as "not yet"
   instead of "broken". Sizes are set here rather than by the image, so swapping a
   preset never changes the page. */
/* Reorderable rows. The grip only appears once the script has made them draggable,
   so it never advertises an affordance that does not work. */
.tree__grip { display: none; color: var(--text-subtle); cursor: grab; letter-spacing: -2px; }
.is-reorderable .tree__grip { display: inline; }
.is-reorderable .tree__row { background: var(--surface); }
.tree__row.is-dragging { opacity: 0.5; }
.block-image { width: 100%; border-radius: var(--radius-sm); object-fit: cover; background: var(--surface-2); }
.category-hero { width: 100%; max-height: 22rem; object-fit: cover; border-radius: var(--radius); background: var(--surface-2); }
.avatar {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm); object-fit: cover; background: var(--surface-2);
  overflow: hidden;
}
.avatar--sm { width: 2.5rem; height: 2.5rem; }
/* No auto margin any more: the identity block is a grid and the avatar has its
   own column, so centring it here fought the layout. Larger, too — it is the
   only image in the header now that the block is not centred. */
.avatar--lg { width: 5rem; height: 5rem; border-radius: var(--radius); }
@media (min-width: 40rem) { .avatar--lg { width: 6.5rem; height: 6.5rem; } }
/* The initial, when there is no photo. Two tints so a page of them is not a grid of
   identical grey squares; both hold contrast against their text in either theme. */
.avatar--empty { font-weight: 700; color: var(--text-muted); letter-spacing: -0.02em; }
.avatar--empty[data-tint="a"] { background: var(--surface-2); }
.avatar--empty[data-tint="b"] { background: var(--accent-soft, var(--surface-2)); }
.avatar--sm.avatar--empty { font-size: var(--step-0); }
.avatar--lg.avatar--empty { font-size: var(--step-2); display: flex; }

/* Selected-filter chips (removable) */
.facet-group__label { font-size: var(--step--1); font-weight: 700; color: var(--text-muted); }
.facet {
  display: inline-flex; align-items: center; gap: var(--space-2);
  border: 1px solid var(--border-strong); border-radius: var(--radius-pill);
  padding: 0.4em 0.85em; font-size: var(--step--1); font-weight: 600;
  color: var(--text); background: var(--surface);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.facet:hover { background: var(--surface-2); color: var(--text); border-color: var(--border-strong); }
.facet--active { background: var(--accent); color: var(--on-accent); border-color: transparent; }
.facet--active:hover { background: var(--accent-hover); color: var(--on-accent); }
.facet__x { font-weight: 700; margin-inline-start: var(--space-1); }

@media (min-width: 720px) {
  .filter-bar__fields {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
    gap: var(--space-4); align-items: center;
  }
  .dropdown__panel {
    position: absolute; z-index: 20;
    inset-inline-start: 0; inset-block-start: calc(100% + var(--space-2));
    margin-block-start: 0;
  }
  /* Room to spare here, so give the hierarchy a clearer step than on mobile. */
  .tree__row { padding-inline-start: calc(var(--depth, 0) * var(--space-5)); }
  /* Wide enough for the note to share the row with the name and controls. */
  .tree__note { flex: 1 1 12rem; }
  .checklist__why { flex: 1 1 14rem; }
}

/* ---- Category tiles (home) ------------------------------------------------*/
.tiles { display: grid; gap: var(--space-3); grid-template-columns: repeat(auto-fill, minmax(min(100%, 200px), 1fr)); }
.tile {
  display: flex; flex-direction: column; gap: var(--space-1);
  min-height: 4.5rem; justify-content: center;
  padding: var(--space-4) var(--space-5);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); color: var(--text);
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.tile:hover { border-color: var(--accent); transform: translateY(-2px); color: var(--text); }
.tile__name { font-weight: 650; font-size: var(--step-0); }
.tile__meta { font-size: var(--step--1); color: var(--text-subtle); }

/* ---- Breadcrumb -----------------------------------------------------------*/
.breadcrumb { padding-block-start: var(--space-5); font-size: var(--step--1); }
.breadcrumb ol { list-style: none; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin: 0; padding: 0; }
.breadcrumb li { display: inline-flex; align-items: center; gap: var(--space-2); color: var(--text-subtle); }
/* Separator is a slash rather than an arrow: direction-neutral, so it needs no RTL
   variant. Generated content is decorative and hidden from assistive tech. */
.breadcrumb li + li::before { content: "/"; color: var(--border-strong); }
.breadcrumb a { color: var(--text-muted); }
.breadcrumb a:hover { color: var(--text); text-decoration: underline; }
.breadcrumb [aria-current="page"] { color: var(--text); font-weight: 550; }

/* ---- Directory results -----------------------------------------------------*/
.results-bar {
  display: flex; flex-wrap: wrap; gap: var(--space-3);
  align-items: baseline; justify-content: space-between;
}
.results-count { color: var(--text-muted); font-size: var(--step--1); margin: 0; }
.sort { display: flex; flex-wrap: wrap; gap: var(--space-1); align-items: center; }
.sort__label { font-size: var(--step--1); color: var(--text-subtle); margin-inline-end: var(--space-1); }
.sort__option {
  font-size: var(--step--1); font-weight: 550; color: var(--text-muted);
  padding: var(--space-1) var(--space-3); border-radius: var(--radius-pill);
}
.sort__option:hover { background: var(--surface-2); color: var(--text); }
.sort__option--active { background: var(--accent-soft); color: var(--accent); font-weight: 650; }

.result-meta { display: flex; flex-wrap: wrap; gap: var(--space-4); align-items: center; color: var(--text-subtle); font-size: var(--step--1); }
.meta-item { display: inline-flex; align-items: center; gap: var(--space-2); }
.result-meta .dot { width: 3px; height: 3px; border-radius: 50%; background: currentColor; opacity: 0.6; }

/* ---- Alerts / notices -----------------------------------------------------*/
.alert {
  display: flex; gap: var(--space-3); align-items: flex-start;
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: var(--space-4); background: var(--surface); color: var(--text-muted);
}
.alert--success { background: var(--success-soft); border-color: transparent; color: var(--success); }
.alert--info { background: var(--accent-soft); border-color: transparent; color: var(--accent); }
.alert--danger, .alert--error { background: var(--danger-soft); border-color: transparent; color: var(--danger); }
.notice { border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-4); background: var(--surface-2); color: var(--text-muted); }
.notice__body { display: grid; gap: var(--space-1); flex: 1 1 auto; }
.notice__body .help { margin: 0; }

/* ---- Footer ---------------------------------------------------------------*/
.footer { border-top: 1px solid var(--border); margin-top: var(--space-9); padding-block: var(--space-6); color: var(--text-subtle); font-size: var(--step--1); }
.footer-inner { display: flex; flex-wrap: wrap; gap: var(--space-4); justify-content: space-between; align-items: center; }

/* ---- Utilities ------------------------------------------------------------*/
.text-center { text-align: center; }
.divider { height: 1px; background: var(--border); border: 0; margin-block: var(--space-6); }

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

/* Shared confirmation dialog. Small, centred, and phrased by the product rather than
   by the browser — window.confirm cannot be styled and, on some mobile browsers,
   offers to suppress every later dialog. */
.confirm {
  border: none; border-radius: var(--radius); padding: 0;
  max-width: min(92vw, 26rem); background: var(--surface); color: var(--text);
  box-shadow: var(--shadow);
}
.confirm::backdrop { background: rgba(0, 0, 0, 0.5); }
.confirm__body { display: grid; gap: var(--space-4); padding: var(--space-5); }
.confirm__message { margin: 0; font-weight: 550; }
.btn--danger { background: var(--danger); color: #fff; border-color: transparent; }
.btn--danger:hover { filter: brightness(0.94); }

/* Reviews. Stars are text, not images: they scale with the type, need no request,
   and read correctly to a screen reader through the label beside them. */
.rating { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--step--1); }
.rating__stars { color: var(--accent); letter-spacing: 1px; }
.reviews { display: grid; gap: var(--space-3); margin-top: var(--space-5); padding-top: var(--space-5); border-top: 1px solid var(--border); }
.review { padding-block: var(--space-3); border-top: 1px solid var(--border); }
/* The reviewer's own review, shown back to them rather than as a filled form. */
.review--mine { background: var(--surface-2); border-radius: var(--radius); padding: var(--space-4); border-top: none; }
.review__edit summary { cursor: pointer; }
.review + .review { margin-top: 0; }
.review__reply {
  margin-top: var(--space-3); padding: var(--space-3);
  border-inline-start: 3px solid var(--border-strong); background: var(--surface-2);
  border-radius: var(--radius-sm); font-size: var(--step--1);
}
.rating__choice { display: inline-flex; align-items: center; gap: var(--space-1); font-size: var(--step--1); cursor: pointer; }
.rating__choice input { accent-color: var(--accent); }

/* ---- Authentication ---------------------------------------------------------
   Signing in is the one screen where someone types something half-remembered, on a
   phone, often in a hurry, and where failing means not becoming a user at all. It
   gets a narrower column, larger controls and more space than a settings form.

   The alternative route sits *outside* the card, as a full-width line rather than a
   footnote under the button. It used to be small ghost text below the form, which is
   how someone who registered by phone ended up staring at a password field. */
.auth { max-width: 26rem; margin-inline: auto; }
.auth__head { text-align: center; margin-block-end: var(--space-5); }
.auth__head h1 { margin-block: var(--space-2) 0; }
.auth__head .lead { margin-block-start: var(--space-3); font-size: var(--step--1); }
.auth__card { gap: var(--space-5); }
.auth__card .input {
  /* 16px minimum: anything smaller makes iOS Safari zoom the page on focus, which
     scrolls the field out of view at the exact moment of typing into it. */
  font-size: max(1rem, var(--step-0));
  padding-block: 0.85em;
}
.auth__alt {
  margin-block-start: var(--space-5);
  text-align: center;
  font-size: var(--step--1);
  color: var(--text-muted);
}
.auth__alt a { font-weight: 600; }

/* ---- Switch -----------------------------------------------------------------
   `is_active` was a bare checkbox labelled "Is active", which reads as a form field
   to fill in rather than a thing that is currently on. A switch says which state you
   are in *now* and that flipping it takes effect — the same information the checkbox
   technically carried and nobody read.

   Still a real `<input type="checkbox">` underneath: it submits, it focuses, screen
   readers announce it, and with CSS off it degrades to a checkbox rather than
   vanishing. Only the paint changes. */
.switch {
  display: flex; align-items: center; gap: var(--space-3);
  cursor: pointer; min-height: 2.75rem;
}
.switch input {
  appearance: none; -webkit-appearance: none;
  flex: none; margin: 0; cursor: pointer;
  width: 2.75rem; height: 1.6rem; border-radius: 999px;
  background: var(--border-strong); position: relative;
  transition: background var(--dur) var(--ease);
}
.switch input::after {
  content: ""; position: absolute; inset-block-start: 0.2rem; inset-inline-start: 0.2rem;
  width: 1.2rem; height: 1.2rem; border-radius: 50%;
  background: var(--surface); box-shadow: var(--shadow-sm);
  transition: transform var(--dur) var(--ease);
}
.switch input:checked { background: var(--accent); }
/* Logical translation, so the knob travels the correct way in RTL. */
.switch input:checked::after { transform: translateX(1.15rem); }
[dir="rtl"] .switch input:checked::after { transform: translateX(-1.15rem); }
.switch input:focus-visible { outline: none; box-shadow: var(--ring); }
.switch__text { font-weight: 550; }
.switch__state { color: var(--text-subtle); font-size: var(--step--1); }

/* A row whose thing is switched off. Legible, clearly secondary, never invisible —
   an owner has to be able to find what they turned off in order to turn it back on. */
.is-off { opacity: 0.55; }
.is-off .tree__name { text-decoration: line-through; text-decoration-thickness: 1px; }

/* ---- Owner section rows -----------------------------------------------------
   Name, status, path and bio were one line, so the most important thing on the
   screen — which section is this — had to be picked out of a sentence. Two lines:
   what it is, then everything about it. */
.tree__row--rich {
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-areas: "grip name actions" ". meta actions";
  gap: var(--space-2) var(--space-3);
  align-items: center;
  padding-block: var(--space-4);
}
.tree__row--rich .tree__grip { grid-area: grip; font-size: var(--step-1); }
.tree__row--rich .tree__head { grid-area: name; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.tree__row--rich .tree__meta { grid-area: meta; display: grid; gap: var(--space-1); min-width: 0; }
.tree__row--rich .tree__actions { grid-area: actions; }
.tree__row--rich .tree__name { font-size: var(--step-0); font-weight: 650; }
.tree__row--rich .tree__path { font-size: var(--step--2); color: var(--text-subtle); }
.tree__row--rich .tree__note { font-size: var(--step--1); color: var(--text-muted); white-space: normal; }
@media (max-width: 559px) {
  /* On a phone the controls get their own row rather than squeezing the name. */
  .tree__row--rich {
    grid-template-columns: auto 1fr;
    grid-template-areas: "grip name" ". meta" ". actions";
  }
}
/* The grip only appears once the script has made rows draggable, so it never
   advertises an affordance that does not work. Sized for a thumb. */
.is-reorderable .tree__row--rich .tree__grip {
  display: flex; align-items: center; justify-content: center;
  width: 2.5rem; height: 2.5rem; border-radius: var(--radius-sm);
}
.is-reorderable .tree__row--rich .tree__grip:hover { background: var(--surface-2); }
/* A switch spans the form grid: it is a statement about the whole thing being
   edited, not one cell of a two-column layout. */
.field--switch { grid-column: 1 / -1; }

/* The same switch as a submit button, for toggling a row in place. `role="switch"`
   with `aria-checked` is the accessible equivalent of the checkbox version, and it
   needs no JavaScript: pressing it posts and the row comes back in its new state. */
.switch__button {
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; background: none; padding: var(--space-2); cursor: pointer;
  min-width: 2.75rem; min-height: 2.75rem; border-radius: var(--radius-sm);
}
.switch__button:hover { background: var(--surface-2); }
.switch__button:focus-visible { outline: none; box-shadow: var(--ring); }
.switch__track {
  width: 2.25rem; height: 1.3rem; border-radius: 999px;
  background: var(--border-strong); position: relative;
  transition: background var(--dur) var(--ease);
}
.switch__track::after {
  content: ""; position: absolute; inset-block-start: 0.15rem; inset-inline-start: 0.15rem;
  width: 1rem; height: 1rem; border-radius: 50%;
  background: var(--surface); box-shadow: var(--shadow-sm);
  transition: transform var(--dur) var(--ease);
}
[role="switch"][aria-checked="true"] .switch__track { background: var(--accent); }
[role="switch"][aria-checked="true"] .switch__track::after { transform: translateX(0.95rem); }
[dir="rtl"] [role="switch"][aria-checked="true"] .switch__track::after { transform: translateX(-0.95rem); }

/* Text for screen readers only. The row toggle's meaning is carried by position and
   colour for sighted users; someone using a screen reader needs the words. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
/* The switch's words stack: what it is, then what turning it off does. Inline they
   ran into one sentence — "Show in the directory Turn off to hide it without
   deleting anything" — which reads as a single confused instruction. */
.switch__body { display: grid; gap: var(--space-1); min-width: 0; }
.switch__body .help { margin: 0; }

/* ---- Link cards -------------------------------------------------------------
   The parts of one section, reachable from the section. Content and services are
   per subcategory but lived only under their own dashboard entries, so editing a
   section meant going back out to a list and finding the same subcategory again. */
.linkcards { display: grid; gap: var(--space-3); grid-template-columns: 1fr; }
@media (min-width: 560px) { .linkcards { grid-template-columns: repeat(3, 1fr); } }
.linkcard {
  display: grid; gap: var(--space-1); align-content: start;
  padding: var(--space-4); min-height: 4.5rem;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); color: inherit; text-decoration: none;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.linkcard:hover { border-color: var(--accent); box-shadow: var(--shadow-sm); }
.linkcard__title { font-weight: 650; color: var(--accent); }
.linkcard__note { font-size: var(--step--1); color: var(--text-subtle); }

/* ---- Test-mode code ---------------------------------------------------------
   Deliberately loud and deliberately ugly. The danger of a mode that shows one-time
   codes on screen is not that it exists — it is that somebody forgets it is on. A
   page that announces its own insecurity gets noticed; a quiet admin setting does
   not. This is meant to be the first thing you want to remove. */
.test-code {
  border: 2px dashed var(--danger);
  border-radius: var(--radius);
  padding: var(--space-4);
  background: var(--danger-soft);
  display: grid; gap: var(--space-2);
}
.test-code__badge {
  justify-self: start;
  background: var(--danger); color: #fff;
  font-size: var(--step--2); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em;
  padding: 0.15em 0.6em; border-radius: 999px;
}
.test-code__body { margin: 0; }
.test-code__value {
  font-size: var(--step-2); font-variant-numeric: tabular-nums;
  letter-spacing: 0.15em; margin-inline-start: 0.35em;
  user-select: all;  /* one tap selects the whole code */
}
.test-code__warning { margin: 0; font-size: var(--step--1); color: var(--danger); }
/* A heading inside a form, separating the section's own fields from the ones its
   subcategory declares. Spans the grid so it reads as a divider, not a label. */
.form-section-title {
  grid-column: 1 / -1;
  margin-block: var(--space-4) 0;
  font-size: var(--step-0);
  padding-block-start: var(--space-4);
  border-top: 1px solid var(--border);
}

/* ---- Specs — a subcategory's declared fields, answered -----------------------
   These replaced feature chips. A chip could only say a field was present; these
   carry the answer, which is what a visitor actually compares profiles on. */
.spec {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-3); padding-block: var(--space-2);
  border-bottom: 1px solid var(--border);
}
.spec:last-child { border-bottom: 0; }
.spec__name { color: var(--text-muted); font-size: var(--step--1); }
.spec__value { font-weight: 600; text-align: end; }
/* Specs stack. They briefly inherited the chip row's wrapping flex and rendered
   side by side, so a name and its answer could end up on different lines from each
   other while two different specs sat together. */
.specs { list-style: none; margin: 0; padding: 0; display: grid; }
/* Inside an offering card the same rows are a menu line rather than a comparison
   table — several of them stack in one section, so they are quieter and tighter and
   separated from the facts above instead of being boxed like the section's own table. */
.specs--compact {
  margin-block-start: var(--space-2);
  padding-block-start: var(--space-2);
  border-top: 1px solid var(--border);
}
.specs--compact .spec {
  padding-block: var(--space-1);
  border-bottom: 0;
  gap: var(--space-2);
}
.specs--compact .spec__name { font-size: var(--step--2); }
.specs--compact .spec__value { font-size: var(--step--1); font-weight: 650; }

/* ---- Feature filters --------------------------------------------------------
   A subcategory's own fields as filters. Plain controls, not the tree picker: a
   price is a range and a level is a short list, and the picker's whole job is depth
   that these do not have. */
.ffilter { display: grid; gap: var(--space-2); padding: var(--space-3) var(--space-2); }
.ffilter + .ffilter { border-top: 1px solid var(--border); }
.ffilter__label { font-size: var(--step--1); font-weight: 650; color: var(--text-muted); }
.ffilter__range { display: flex; align-items: center; gap: var(--space-2); }
.ffilter__range .input { flex: 1; min-width: 0; }
.ffilter__dash { color: var(--text-subtle); flex: none; }
.input--sm, .select--sm { padding-block: 0.5em; font-size: var(--step--1); }

/* ---- Profile card -----------------------------------------------------------
   What someone chooses between two listings on: who, where, whether it can be
   booked, what it scored, and the one or two numbers its subcategory says matter.
   The rating and the specs were already in the database and shown nowhere. */
.pcard { display: grid; gap: var(--space-3); align-content: start; }
.pcard__head { display: grid; grid-template-columns: auto 1fr auto; gap: var(--space-3); align-items: center; }
.pcard__title { min-width: 0; }
.pcard__title h3 { margin: 0; font-size: var(--step-0); overflow-wrap: anywhere; }
.pcard__niche { font-size: var(--step--2); color: var(--text-subtle); }
.pcard__snippet { margin: 0; font-size: var(--step--1); }

/* A score without its count is unreadable — 5.0 from one review is not 4.6 from
   ninety — so the two never appear apart. */
.rating-badge {
  display: inline-flex; align-items: center; gap: 0.25em; flex: none;
  font-size: var(--step--1); font-weight: 650;
}
.rating-badge .icon { color: var(--warning, #e8a33d); }
.rating-badge__count { color: var(--text-subtle); font-weight: 500; font-size: var(--step--2); }

/* The offering that satisfied the filters, named above its own specs. Accent-tinted
   because it is an answer to what the visitor asked, not another line of description —
   and the specs under it describe *it*, not the whole niche. */
.pcard__match {
  margin: 0; font-size: var(--step--1); font-weight: 650;
  color: var(--accent); overflow-wrap: anywhere;
}

/* Two specs, laid out as label-then-value pairs so a price reads as a price. */
.pcard__specs { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); }
.pcard__spec { font-size: var(--step--1); font-weight: 600; }
.pcard__spec-name { color: var(--text-subtle); font-weight: 500; }

/* ---- Contact box ------------------------------------------------------------
   The three things a visitor came to do, under the name, as equal targets. They
   used to be scattered: the phone at the foot of the page, the locations in a card
   below that, the booking form inside a tab. */
.contact-box {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: var(--space-2); margin-block-end: var(--space-4);
}
.contact-box__action {
  display: grid; justify-items: center; gap: var(--space-1);
  padding: var(--space-3) var(--space-2); min-height: 3.5rem;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); color: var(--accent);
  font-size: var(--step--2); font-weight: 600; text-decoration: none;
  cursor: pointer; font-family: inherit;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.contact-box__action:hover { border-color: var(--accent); background: var(--accent-soft); }
.contact-box__action:focus-visible { outline: none; box-shadow: var(--ring); }
.contact-box__action .icon { width: 1.35em; height: 1.35em; }

/* A dialog used as a phone-style sheet: full width at the bottom on small screens,
   a centred card on larger ones. */
.sheet::backdrop { background: rgba(0, 0, 0, 0.45); }
.sheet__body { display: grid; gap: var(--space-3); padding: var(--space-5); }
@media (min-width: 560px) {
  .sheet { margin-block: auto; border-radius: var(--radius); }
}

/* ---- Offerings with places --------------------------------------------------
   A class that is full still shows: a card that vanishes when it fills reads as a
   page that lost something, and someone who came back for it needs to be told. */
.offering__state { margin: 0; font-size: var(--step--1); font-weight: 650; }
.offering__state--open { color: var(--success, #1c6b3c); }
.offering__state--full { color: var(--text-subtle); }
.offering--closed { opacity: 0.72; }
/* A sheet holding a long form scrolls inside itself rather than growing past the
   viewport, so the submit button is always reachable. */
/* The amount to pay, on the request page. Big, because it is the one number the
   person is there to read, and tabular so digits line up in either script. */
.pay-amount {
  font-size: var(--step-3); font-weight: 700; margin: 0;
  font-variant-numeric: tabular-nums;
}

/* ---- Services: the accordion and its menu -----------------------------------
   Closed, a service is its name, its cheapest price and a button. Open, it is the
   menu — the options the owner arranged, each with its own price and details.

   The grid is what puts the button on the same line as the name while keeping it a
   sibling of the <details> rather than a child of the <summary>: a button inside a
   summary is a nested interactive control, announced wrong and toggling the panel
   when clicked. */
.offerings { display: grid; gap: var(--space-3); }
.offering {
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface);
}
.offering--closed { opacity: 0.7; }
.offering__row {
  display: grid; grid-template-columns: 1fr auto; align-items: start;
  gap: var(--space-3); padding: var(--space-4);
}
.offering__disclosure, .offering__summary--static { min-width: 0; }
.offering__summary {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--space-2) var(--space-3);
  cursor: pointer;
  /* The whole row is the target, not just the words — this is a phone-first
     control and a 14px text hit area is the classic mobile miss. */
  padding-block: var(--space-1);
  font-size: var(--step-0); font-weight: 650;
}
.offering__summary--static { cursor: auto; }
.offering__summary::-webkit-details-marker { display: none; }
.offering__summary::marker { content: ""; }
.offering__summary::after {
  /* Logical, so it flips with the document in Persian. Rotates when open. */
  content: "";
  inline-size: 0.5em; block-size: 0.5em; margin-inline-start: auto;
  border-inline-end: 2px solid var(--text-subtle); border-block-end: 2px solid var(--text-subtle);
  transform: rotate(45deg); transition: transform var(--transition, 150ms) ease;
}
.offering__summary--static::after { content: none; }
.offering__disclosure[open] > .offering__summary::after { transform: rotate(-135deg); }
.offering__name { min-width: 0; overflow-wrap: anywhere; }
.offering__facts { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-3); align-items: baseline; }
.offering__price { font-weight: 700; font-variant-numeric: tabular-nums; }
.offering__fact { font-size: var(--step--2); color: var(--text-subtle); font-weight: 400; }
.offering__fact--open { color: var(--success); font-weight: 650; }
.offering__fact--off { color: var(--text-subtle); font-weight: 650; }
.offering__note {
  font-size: var(--step--1); color: var(--text-muted);
  margin-block: var(--space-2) 0; font-weight: 400;
}
.offering__action { flex: none; }

.menu { list-style: none; margin-block: var(--space-3) 0; padding: 0; display: grid; gap: var(--space-3); }
.menu__row {
  display: grid; gap: var(--space-2);
  padding-block-start: var(--space-3);
  border-block-start: 1px solid var(--border-subtle, var(--border));
}
.menu__head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2) var(--space-3);
}
.menu__name { font-weight: 650; font-size: var(--step--1); min-width: 0; overflow-wrap: anywhere; }
.menu__fact { font-size: var(--step--2); color: var(--text-subtle); }
/* Pushed to the far edge so a column of prices lines up down the menu, which is how
   a printed menu is read. Tabular figures keep the digits in step. */
.menu__price { margin-inline-start: auto; font-weight: 700; font-variant-numeric: tabular-nums; }
.spec__image { max-inline-size: 6rem; border-radius: var(--radius-sm, 4px); block-size: auto; }

@media (max-width: 34rem) {
  /* The button goes full width under the name rather than squeezing the title into
     a few characters. */
  .offering__row { grid-template-columns: 1fr; }
  .offering__action { inline-size: 100%; }
}

/* The sheet rises from the bottom on a phone, the way an app's does — it was
   appearing centred, which reads as a desktop dialog dropped onto a phone. */
.sheet {
  position: fixed; inset: auto 0 0 0;
  width: 100%; max-width: 34rem; margin-inline: auto;
  border: 0; padding: 0;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  background: var(--surface); color: var(--text);
  box-shadow: var(--shadow-lg);
  animation: sheet-rise 240ms var(--ease);
}
@keyframes sheet-rise { from { transform: translateY(100%); } to { transform: translateY(0); } }
@media (prefers-reduced-motion: reduce) { .sheet { animation: none; } }
/* `display` ONLY on an open dialog.
   A closed <dialog> is hidden by the browser's own `display: none`. Any rule that
   sets `display` on `.sheet` unconditionally beats it, and the sheet then sits on
   the page as an interstitial from the moment it loads — which is exactly what
   happened, and is the same mistake as the `[hidden]` + `display: flex` one in the
   image widget. `tests/test_css_contracts.py` now refuses both. */
.sheet[open] { display: block; }
.sheet--tall[open] { display: flex; flex-direction: column; max-height: 88vh; }
.sheet--tall .sheet__body { overflow-y: auto; }
@media (min-width: 560px) {
  /* Desktop has room for a centred card, and a strip along the bottom of a wide
     screen reads as a cookie banner. */
  .sheet { inset: 0; margin: auto; border-radius: var(--radius); animation: none; }
}

/* ---- The service builder ----------------------------------------------------
   Three containers on one page, replacing a fifteen-field form and three screens
   an owner moved between to set one service up. Each box is a step with a number,
   a switch and a list; the "+ Add" at the foot is the only way in, so there is one
   obvious next action per container rather than a grid of fields.
   See docs/product/service-builder.md. */
.builder { display: grid; gap: var(--space-5); max-inline-size: 52rem; margin-inline: auto; }
.builder__box {
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); padding: var(--space-5);
  display: grid; gap: var(--space-3);
}
/* A container switched off stays visible but recedes: hiding it would leave the
   owner wondering where the menu went, and the switch is what brings it back. */
.builder__box--off { opacity: 0.55; }
.builder__head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
.builder__title { display: flex; align-items: center; gap: var(--space-3); font-size: var(--step-1); margin: 0; }
.builder__step {
  display: grid; place-items: center;
  inline-size: 1.75rem; block-size: 1.75rem; border-radius: 50%;
  background: var(--accent); color: #fff;
  font-size: var(--step--1); font-weight: 700; font-variant-numeric: tabular-nums;
}
.builder__add { justify-self: start; }

/* ---- Panels: a modal that is a URL ------------------------------------------
   `open` is set in the markup so the panel still renders with JavaScript off — as
   a block at the foot of the page rather than an overlay. These rules only apply
   the overlay treatment when the browser actually put it in the top layer. */
.panel {
  border: 1px solid var(--border); border-radius: var(--radius-lg, 16px);
  background: var(--surface); color: var(--text);
  padding: 0; max-inline-size: 40rem; inline-size: 100%;
  margin-block: var(--space-5); margin-inline: auto;
}
.panel--narrow { max-inline-size: 30rem; }
.panel--calendar { max-inline-size: min(72rem, calc(100vw - 2 * var(--gutter))); }
.panel--calendar:modal { max-block-size: 94vh; }
.panel--calendar .panel__head { position: static; }
.panel:modal { max-block-size: 88vh; overflow: auto; }
.panel::backdrop { background: rgb(0 0 0 / 0.45); }
.panel__form { display: contents; }
.panel__head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-block-end: 1px solid var(--border);
  /* Sticky so the title and the close stay reachable in a long panel — the option
     panel grows with every declared field its subcategory has. */
  position: sticky; inset-block-start: 0; background: var(--surface); z-index: 1;
}
.panel__title { margin: 0; font-size: var(--step-0); }
.panel__body { padding: var(--space-5); }
.panel__foot {
  padding: var(--space-4) var(--space-5);
  border-block-start: 1px solid var(--border);
  position: sticky; inset-block-end: 0; background: var(--surface);
}
.panel__legend {
  font-size: var(--step--2); font-weight: 650; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--text-subtle); margin: 0;
}
/* What was just added, at the top of the panel it was added from — the owner
   entering four variations watches the list grow without closing anything. */
.panel__added { list-style: none; margin: 0; padding: var(--space-3) var(--space-5) 0; display: grid; gap: var(--space-2); }
.panel__added li { display: flex; align-items: baseline; gap: var(--space-3); font-size: var(--step--1); }
.panel__added-name { font-weight: 650; }
.panel__added-price { margin-inline-start: auto; font-variant-numeric: tabular-nums; color: var(--text-subtle); }

.catalogue { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.catalogue__item {
  display: grid; gap: 2px; padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border); border-radius: var(--radius);
  color: var(--text); text-decoration: none;
}
.catalogue__item:hover { background: var(--surface-2); border-color: var(--accent); }
.catalogue__label { font-weight: 650; }
.catalogue__hint { font-size: var(--step--2); color: var(--text-subtle); }

/* Shown after submission, not in the form — so it sits apart from the rows that
   can be dragged, and the separation is the explanation. */
.tree__row--after { border-block-start: 2px dashed var(--border); }

/* ---- Editing the profile from the profile page -------------------------------
   The page renders exactly as a visitor sees it, plus handles on what exists and
   dashed boxes where something could. Owner-only, edit mode only — a visitor never
   receives any of this. See docs/product/editing-in-place.md. */
/* ---- Editing controls ------------------------------------------------------
   Rebuilt 2026-08-04. Three complaints from the owner, each a specific fix:

   1. The controls were small. 1.9rem is below every touch-target guideline; they
      are now 2.5rem, which is the smallest thing a thumb hits reliably.
   2. The icons were text characters (✎ ↑ ↓ ◉ ◎ ✕), which render at different
      weights on every platform — and the show/hide pair was genuinely unreadable.
      They are now sprite icons; see templates/_icons.html.
   3. Every bar looked identical, so it was never obvious which one acted on what.
      A bar is now a labelled header physically attached to the top of its subject:
      shared border radius, no gap, and the subject carries a matching outline. */
.edit-tools {
  display: flex; align-items: center; gap: var(--space-1);
  flex-wrap: wrap;
}
.edit-tools__grip { cursor: grab; color: var(--text-subtle); display: grid; place-items: center; }
.edit-tools__btn {
  display: grid; place-items: center;
  inline-size: 2.5rem; block-size: 2.5rem;
  border: 1px solid var(--border); border-radius: 50%;
  background: var(--surface); color: var(--accent);
  font-size: var(--step--1); line-height: 1; cursor: pointer; text-decoration: none;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.edit-tools__btn:hover { background: var(--surface-2); border-color: var(--accent); }
.edit-tools__btn:focus-visible { outline: none; box-shadow: var(--ring); }
.edit-tools__btn--danger { color: var(--danger, #c0392b); }

/* The bar sits inside the card it edits — containment does the work that a pair
   of dashed outlines was doing badly. See "Editing, without extra boxes" below. */
.edit-row { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.edit-row__label {
  font-size: var(--step--2); color: var(--text-subtle);
  text-transform: uppercase; letter-spacing: 0.05em; font-weight: 600;
  margin-inline-start: auto;
}
.is-off { opacity: 0.45; }

.placeholder {
  display: grid; gap: 2px; justify-items: center; text-align: center;
  padding: var(--space-5); border: 2px dashed var(--accent); border-radius: var(--radius);
  color: var(--accent); text-decoration: none; background: transparent;
}
.placeholder:hover { background: var(--surface-2); }
.placeholder__plus { font-size: var(--step-2); line-height: 1; font-weight: 300; }
.placeholder__label { font-weight: 650; }
.placeholder__hint { font-size: var(--step--2); color: var(--text-subtle); }

/* The `+` sits in the tab strip as a member of it — its position is what says what
   it adds. Not a radio, so it needs the label's look without the tab's state. */
.tabs__label--add { color: var(--accent); cursor: pointer; }
.tabs__off { font-size: var(--step--2); color: var(--text-subtle); font-weight: 400; }
.offering-edit { display: grid; gap: 0; }

/* ---- Calendar operations ---------------------------------------------------
   A quiet settings stack above one dense time surface. The time ruler is the one
   distinctive element because time, not decoration, is what the owner manipulates. */
.schedule-manager { max-inline-size: 70rem; margin-inline: auto; }
.schedule-settings {
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); overflow: clip;
}
.schedule-settings > summary {
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-3);
  padding: var(--space-4); cursor: pointer; font-weight: 650;
}
.schedule-settings > summary::marker { color: var(--accent); }
.schedule-settings__body { padding: 0 var(--space-4) var(--space-4); }
.schedule-settings-form { display: grid; gap: var(--space-4); }
.schedule-settings-form__fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.schedule-settings-form__actions { grid-column: 1 / -1; }
.schedule-timezone { border-block-start: 1px solid var(--border); padding-block-start: var(--space-3); }
.schedule-timezone > summary {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3);
  cursor: pointer; font-size: var(--step--1); font-weight: 650;
}
.schedule-timezone__body { padding-block-start: var(--space-3); }
.schedule-block-form .field:has(.calendar-parts),
.schedule-block-form > .cluster { grid-column: 1 / -1; }
.schedule-inline-form {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3); align-items: end;
}
.schedule-inline-form .field { margin: 0; }
.schedule-inline-form .field:has(input[name="weekdays"]) { grid-column: 1 / -1; }
.schedule-inline-form #id_weekdays {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: var(--space-1);
  margin: 0; padding: 0;
}
.schedule-inline-form #id_weekdays label {
  display: grid; grid-template-columns: auto minmax(0, 1fr); place-items: center;
  min-block-size: 2.75rem; padding: var(--space-1); gap: var(--space-1);
  border: 1px solid var(--border); border-radius: var(--radius-sm); cursor: pointer;
  font-size: var(--step--1); text-align: center;
}
.schedule-inline-form #id_weekdays label:has(input:checked) {
  border-color: var(--accent); background: var(--accent-soft); color: var(--accent);
}
.schedule-inline-form #id_weekdays input { inline-size: 1rem; block-size: 1rem; accent-color: var(--accent); }
.hour-minute { display: flex; align-items: center; gap: var(--space-2); max-inline-size: 20rem; }
.hour-minute__part { display: grid; gap: var(--space-1); flex: 1; min-inline-size: 0; }
.hour-minute__part small { color: var(--text-subtle); font-size: var(--step--2); }
.hour-minute__separator { margin-block-end: var(--space-5); color: var(--text-muted); font-weight: 750; }

.calendar-board {
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--surface); padding: var(--space-4); overflow: visible;
}
.calendar-board__toolbar {
  position: sticky; z-index: 4; inset-block-start: 0;
  display: grid; gap: var(--space-3); margin: calc(-1 * var(--space-4));
  margin-block-end: 0; padding: var(--space-3) var(--space-4);
  border-block-end: 1px solid var(--border); background: var(--surface);
}
.calendar-board__head {
  display: flex; flex-wrap: wrap; align-items: end; justify-content: space-between;
  gap: var(--space-3);
}
.calendar-board__head h2 { margin: 0; }
.calendar-board__nav { display: grid; grid-template-columns: 3rem minmax(5rem, auto) 3rem; justify-content: center; gap: var(--space-2); }
.calendar-board__step { min-inline-size: 3rem; min-block-size: 3rem; padding: 0; font-size: var(--step-2); }
.calendar-detail {
  inline-size: min(34rem, calc(100% - 2 * var(--space-4))); max-block-size: calc(100dvh - 2 * var(--space-4));
  padding: 0; border: 0; border-radius: var(--radius-lg); background: transparent;
  box-shadow: var(--shadow-lg); overflow: auto;
}
.calendar-detail::backdrop { background: var(--overlay); }
.calendar-inspector {
  padding: var(--space-5); border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--surface);
}
.calendar-inspector__head { display: flex; justify-content: space-between; gap: var(--space-3); }
.calendar-inspector h3 { margin: 0; }
.calendar-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-2); margin: 0; }
.calendar-facts div { padding: var(--space-2); border-radius: var(--radius-sm); background: var(--surface); }
.calendar-facts dt { color: var(--text-muted); font-size: var(--step--1); }
.calendar-facts dd { margin: 0; font-weight: 650; }
.segmented {
  display: inline-grid; grid-auto-flow: column; padding: var(--space-1);
  border-radius: var(--radius-pill); background: var(--surface-2);
}
.segmented a {
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-pill);
  color: var(--text-muted); font-size: var(--step--1); font-weight: 650;
}
.segmented a[aria-current="page"] { background: var(--surface); color: var(--accent); box-shadow: var(--shadow-sm); }

.time-board { display: grid; gap: var(--space-2); overflow-x: auto; padding-block-end: var(--space-2); }
.time-board--day { grid-template-columns: minmax(18rem, 1fr); }
.time-board--week { grid-template-columns: repeat(7, minmax(9rem, 1fr)); }
.time-day { min-inline-size: 0; }
.time-day > h3 {
  display: flex; justify-content: space-between; gap: var(--space-2);
  padding: var(--space-2); font-size: var(--step--1); color: var(--text-muted);
}
.time-day__track {
  position: relative; block-size: 36rem; overflow: hidden;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.time-hour {
  display: block; block-size: 1.5rem; padding-inline: var(--space-1);
  border-block-start: 1px solid var(--border); color: var(--text-subtle);
  font-size: var(--step--1); font-variant-numeric: tabular-nums;
}
.calendar-slot, .calendar-event {
  position: absolute; z-index: 1; inset-inline: 2.5rem var(--space-1);
  inset-block-start: var(--calendar-start); block-size: max(var(--calendar-span), 1.6rem);
  padding: var(--space-1) var(--space-2); overflow: hidden;
  border: 1px solid var(--accent); border-radius: var(--radius-sm);
  font: inherit; font-size: var(--step--2); text-align: start;
  text-decoration: none;
}
.calendar-slot {
  background: var(--accent-soft); color: var(--accent); cursor: pointer;
}
.calendar-slot:hover { background: var(--accent); color: var(--on-accent); }
.calendar-slot small, .calendar-event span { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.calendar-event { z-index: 2; background: var(--surface); color: var(--text); border-color: var(--border-strong); }
.calendar-event--booking { background: var(--success-soft); border-color: var(--success); }
.calendar-event--maintenance { background: var(--danger-soft); border-color: var(--danger); }
.calendar-event--default-unavailable,
.calendar-event--default-busy,
.calendar-event--default-maintenance { z-index: 0; border-style: dashed; color: var(--text-muted); }
.calendar-event--default-maintenance { background: var(--danger-soft); border-color: var(--danger); }

.month-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 1px; background: var(--border); }
.month-weekday {
  padding-block: var(--space-2); background: var(--surface-2); color: var(--text-muted);
  font-size: var(--step--1); font-weight: 700; text-align: center;
}
.month-weekday__short { display: none; }
.month-day {
  min-block-size: 5rem; padding: var(--space-2); background: var(--surface);
  color: var(--text); display: flex; justify-content: space-between; align-items: start;
}
.month-day:hover { background: var(--accent-soft); }
.month-day.is-outside { color: var(--text-subtle); background: var(--surface-2); }
.month-day__count {
  display: grid; place-items: center; min-inline-size: 1.5rem; block-size: 1.5rem;
  padding-inline: var(--space-1); border-radius: var(--radius-pill);
  background: var(--accent); color: var(--on-accent); font-size: var(--step--2);
}

/* The public picker is a readable set of native disclosures without JavaScript.
   Enhancement turns the same radios into a compact date rail and time-chip grid. */
.appointment-picker { display: grid; gap: var(--space-4); min-inline-size: 0; margin: 0; padding: 0; border: 0; }
.appointment-dates { display: none; gap: var(--space-2); overflow-x: auto; padding-block-end: var(--space-1); scrollbar-width: thin; }
.appointment-picker.is-enhanced .appointment-dates { display: flex; }
.appointment-date {
  flex: 0 0 auto; display: grid; gap: var(--space-1); min-inline-size: 5.5rem; min-block-size: 3.5rem;
  place-content: center; padding: var(--space-2) var(--space-3); border: 1px solid var(--border);
  border-radius: var(--radius); background: var(--surface); color: var(--text); cursor: pointer;
}
.appointment-date span { color: var(--text-muted); font-size: var(--step--2); }
.appointment-date.is-active { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.appointment-day { border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
.appointment-day > summary { padding: var(--space-3); cursor: pointer; font-weight: 650; }
.appointment-picker.is-enhanced .appointment-day { border: 0; }
.appointment-picker.is-enhanced .appointment-day > summary { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.appointment-times { display: grid; grid-template-columns: repeat(auto-fill, minmax(5.25rem, 1fr)); gap: var(--space-2); padding: var(--space-3); }
.appointment-time { position: relative; cursor: pointer; }
.appointment-time input { position: absolute; opacity: 0; pointer-events: none; }
.appointment-time span {
  display: grid; place-items: center; min-block-size: 2.75rem; padding: var(--space-2);
  border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--text);
  font-variant-numeric: tabular-nums;
}
.appointment-time input:checked + span { border-color: var(--accent); background: var(--accent); color: var(--on-accent); }
.appointment-time input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }
.appointment-time:hover span { border-color: var(--accent); }
.appointment-length { max-inline-size: 24rem; }
.appointment-summary { margin: 0; padding: var(--space-3); border-radius: var(--radius-sm); background: var(--surface-2); white-space: pre-line; }

@media (max-width: 40rem) {
  .panel--calendar { max-inline-size: 100%; margin-inline: 0; border-radius: var(--radius-lg) var(--radius-lg) 0 0; }
  .schedule-inline-form { grid-template-columns: 1fr; }
  .schedule-settings-form__fields { grid-template-columns: 1fr; }
  .schedule-inline-form #id_weekdays { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .calendar-facts { grid-template-columns: 1fr; }
  .calendar-board { margin-inline: calc(-1 * var(--space-4)); border-inline: 0; border-radius: 0; }
  .calendar-board__toolbar { inset-block-start: 0; }
  .calendar-board__head { align-items: stretch; }
  .calendar-board__head .segmented { inline-size: 100%; }
  .calendar-board__head .segmented a { text-align: center; }
  .calendar-detail { inline-size: calc(100% - 2 * var(--space-3)); }
  .time-board--week { grid-template-columns: repeat(7, minmax(12rem, 1fr)); }
  .month-day { min-block-size: 3.5rem; padding: var(--space-1); }
  .month-weekday { padding-inline: 0; }
  .month-weekday__long { display: none; }
  .month-weekday__short { display: inline; }
}
/* Calendar date parts remain explicit because native date inputs submit Gregorian
   values even when their browser chrome appears localized. */
.calendar-parts {
  display: grid;
  grid-template-columns: minmax(6rem, 1.15fr) minmax(8rem, 1.5fr) minmax(5rem, .8fr);
  gap: var(--space-2);
}
.calendar-parts:has(> :nth-child(5)) {
  grid-template-columns: minmax(6rem, 1.15fr) minmax(8rem, 1.5fr) repeat(3, minmax(4.5rem, .75fr));
}
@media (max-width: 40rem) {
  .calendar-parts,
  .calendar-parts:has(> :nth-child(5)) { grid-template-columns: 1fr 1fr; }
  .calendar-parts > :first-child { grid-column: 1 / -1; }
}

/* Pagination: centred, roomy enough to tap, and logical-property safe for RTL. */
.pagination {
  justify-content: center;
  align-items: center;
  gap: var(--space-3);
  margin-block-start: var(--space-6);
  flex-wrap: wrap;
}

/* The owner's calendar shortcut, sitting before a service's name on its accordion.
   Frequent enough that hunting for it in a handle strip was the wrong trade. */
.offering__calendar {
  display: inline-grid; place-items: center;
  inline-size: 2.25rem; block-size: 2.25rem;
  margin-inline-end: var(--space-2);
  border-radius: 50%; border: 1px solid var(--border);
  background: var(--surface); color: var(--accent); text-decoration: none;
  flex: none;
}
.offering__calendar:hover { background: var(--surface-2); border-color: var(--accent); }

/* Per-domain footer: what this site is, then the policies. Never the menu again. */
.footer-about { max-inline-size: 42ch; }
.footer-name { font-weight: 600; color: var(--text); }
.footer-text {
  margin-block-start: var(--space-2); margin-block-end: 0;
  font-size: var(--step--1); color: var(--text-muted); line-height: 1.6;
}
.footer-links { gap: var(--space-4); flex-wrap: wrap; }

/* The notification bell. A count, not a dot: "you have three things" is actionable,
   "you have something" only prompts a click to find out. */
.nav-bell { position: relative; display: inline-grid; place-items: center; }
.nav-bell__dot {
  position: absolute; inset-block-start: -0.15rem; inset-inline-end: -0.35rem;
  min-inline-size: 1.15rem; padding: 0 0.25rem;
  border-radius: var(--radius-pill);
  background: var(--accent); color: var(--on-accent);
  font-size: var(--step--2); font-weight: 700; line-height: 1.15rem; text-align: center;
}

/* =============================================================================
   App layer — redesign 2026-08-04
   -----------------------------------------------------------------------------
   The direction is the owner's: Apple/iOS and recent Telegram. That is not a
   palette, it is a set of *idioms*, and the page was missing all of them:

   - **Grouped inset lists.** iOS Settings is the reference — content lives in
     rounded cards floating on a tinted page, with hairline separators inset from
     the leading edge. It reads as "sections of one thing" rather than a stack of
     unrelated boxes.
   - **A bottom tab bar on mobile.** Most visitors are on a phone; a hamburger at
     the top of a long page puts navigation where the thumb is not.
   - **Folder-style section tabs.** The signature. Telegram's chat folders are
     exactly the metaphor this product needs: one profile that is several
     businesses, switched between without leaving.

   Tokens are unchanged on purpose. Repainting the palette would touch forty
   templates to solve a problem that is compositional, not chromatic.
   ========================================================================== */

:root {
  /* A tinted page so white cards read as raised. The single most characteristic
     thing about the iOS grouped look, and it costs one value. */
  --page-tint: #f2f2f7;
  --hairline: rgba(60, 60, 67, 0.16);
  --tabbar-height: 3.75rem;
  --safe-bottom: env(safe-area-inset-bottom, 0px);
}

@media (prefers-color-scheme: dark) {
  :root { --page-tint: #000000; --hairline: rgba(84, 84, 88, 0.5); }
}
:root[data-theme="light"] { --page-tint: #f2f2f7; --hairline: rgba(60, 60, 67, 0.16); }
:root[data-theme="dark"] { --page-tint: #000000; --hairline: rgba(84, 84, 88, 0.5); }

/* ---- Grouped inset list --------------------------------------------------- */
.group {
  background: var(--surface);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}
.group + .group { margin-block-start: var(--space-5); }
/* The heading sits *outside* the card, quiet and uppercase — iOS puts section
   titles above the group, not inside it, which is what makes the group read as a
   single object rather than a titled box. */
.group-title {
  display: block;
  font-size: var(--step--1); font-weight: 600;
  color: var(--text-subtle);
  text-transform: uppercase; letter-spacing: 0.06em;
  padding-inline: var(--space-4);
  margin-block: var(--space-6) var(--space-2);
}
.group__item {
  display: flex; align-items: center; gap: var(--space-3);
  min-block-size: 3rem; padding: var(--space-3) var(--space-4);
}
/* Separators inset from the leading edge only — the detail that makes a list feel
   native rather than like a table. */
.group__item + .group__item { box-shadow: inset 0 1px 0 var(--hairline); }
.group__item + .group__item { margin-inline-start: 0; }

/* ---- Profile identity -----------------------------------------------------
   Avatar beside the name rather than above it, and not centred. Centring cost a
   whole phone screen of height before anything the visitor came for, and it made
   the profile look like a person's social page rather than a business's front
   door. Side by side is what Instagram and LinkedIn both settled on, for the same
   reason: the identity is a header, not a hero image. */
.identity {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas: "avatar who" "meta meta" "metrics metrics" "actions actions";
  align-items: center;
  gap: var(--space-3) var(--space-4);
  padding-block: var(--space-5) var(--space-4);
}
.identity > .avatar { grid-area: avatar; }
.identity__who { grid-area: who; min-inline-size: 0; }
.identity__name {
  font-size: var(--step-2); font-weight: 700; letter-spacing: -0.02em;
  line-height: 1.15; margin: 0;
  overflow-wrap: anywhere;
}
.identity__handle {
  display: block;
  font-size: var(--step--1); color: var(--text-subtle);
  direction: ltr; unicode-bidi: isolate;
}
.identity__meta {
  grid-area: meta;
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-1) var(--space-3);
  font-size: var(--step--1); color: var(--text-muted);
  margin: 0;
}
.identity__meta > * { display: inline-flex; align-items: center; gap: 0.35em; }
.identity__meta .icon { inline-size: 1em; block-size: 1em; flex: none; }
.identity__meta a { color: inherit; text-decoration: none; }
.identity__meta a:hover { color: var(--accent); text-decoration: underline; }
/* Dot separators drawn by CSS rather than typed into the template, so a hidden
   rating or a missing location never leaves a dangling bullet. */
.identity__meta > * + *::before {
  content: "·"; color: var(--text-subtle);
  margin-inline-end: 0.35em;
}
.identity__actions {
  grid-area: actions;
  display: flex; gap: var(--space-2); flex-wrap: wrap;
}
.identity__actions .btn { flex: 1 1 9rem; }

/* The owner's two controls, pinned to the top corner of the header where iOS puts
   navigation-bar buttons. They are chrome for this page, not content on it, so
   they must not push the name down or sit in the visitor's reading order. */
.identity__tools {
  grid-area: who; justify-self: end; align-self: start;
  display: flex; gap: var(--space-1);
}
.icon-btn {
  display: inline-grid; place-items: center; position: relative;
  inline-size: 2.25rem; block-size: 2.25rem;
  border-radius: 50%; border: 0;
  background: var(--surface-2); color: var(--text-muted);
  text-decoration: none; cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.icon-btn:hover { background: var(--accent-soft); color: var(--accent); }
.icon-btn:focus-visible { outline: none; box-shadow: var(--ring); }
.icon-btn__badge {
  position: absolute; inset-block-start: -0.15rem; inset-inline-end: -0.15rem;
  min-inline-size: 1.1rem; padding-inline: 0.2rem;
  border-radius: var(--radius-pill);
  background: var(--danger); color: #fff;
  font-size: 0.6rem; font-weight: 700; line-height: 1.1rem; text-align: center;
}

@media (min-width: 40rem) {
  .identity {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "avatar who tools" "avatar meta tools" "metrics metrics metrics" "actions actions actions";
    align-items: start;
  }
  .identity > .avatar { grid-row: span 2; align-self: center; }
  .identity__tools { grid-area: tools; justify-self: end; }
  .identity__actions { justify-content: flex-start; }
  .identity__actions .btn { flex: 0 0 auto; }
}

/* ---- The insight summary --------------------------------------------------
   Three numbers under the name, tappable as one control that opens the full
   analytics. The owner asked for a summary "near user's name and profile photo"
   that opens a modal — so this is deliberately a *button*, not three links: the
   summary's job is to be read at a glance and to be one target for the thumb. */
.metrics {
  grid-area: metrics;
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  inline-size: 100%; gap: var(--space-1);
  padding: var(--space-2) var(--space-1);
  border: 0; border-radius: var(--radius);
  background: var(--surface-2); color: inherit;
  cursor: pointer; text-decoration: none;
  transition: background var(--dur) var(--ease);
}
.metrics:hover { background: var(--accent-soft); }
.metrics:focus-visible { outline: none; box-shadow: var(--ring); }
.metric { display: grid; gap: 1px; justify-items: center; }
.metric__value {
  font-size: var(--step-1); font-weight: 700; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums; line-height: 1.1;
}
.metric__label {
  font-size: var(--step--2); color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.05em;
}
.metric + .metric { box-shadow: inset 1px 0 0 var(--hairline); }
/* The separator is a leading border, so in RTL it must sit on the other side —
   `inset 1px` is a physical direction and there is no logical form for a shadow. */
[dir="rtl"] .metric + .metric { box-shadow: inset -1px 0 0 var(--hairline); }

/* ---- Folder tabs: the signature ------------------------------------------
   One profile is several businesses. These are not filters and they are not
   page navigation — switching one changes everything below it, which is why
   they are attached to the identity above rather than floating over content.

   The radio/label/panel adjacency is load-bearing: `input:checked + label +
   panel` does the work with no script and no per-profile ids, so no wrapper may
   be introduced around the labels. */
.tabs {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  align-items: flex-start;
}
.tabs__radio {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}
.tabs__label {
  order: 1; cursor: pointer; white-space: nowrap;
  display: inline-flex; align-items: center; gap: var(--space-2);
  min-block-size: 2.5rem; padding-inline: var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--surface-2); border: 1px solid transparent;
  font-size: var(--step-0); font-weight: 600; color: var(--text-muted);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
              transform var(--dur) var(--ease);
}
.tabs__label:hover { color: var(--text); background: var(--accent-soft); }
.tabs__label:active { transform: scale(0.97); }
.tabs__radio:checked + .tabs__label {
  background: var(--accent); color: var(--on-accent);
  box-shadow: 0 4px 12px var(--accent-soft);
}
.tabs__radio:focus-visible + .tabs__label { outline: none; box-shadow: var(--ring); }

/* The division the owner asked for.
   -----------------------------------------------------------------------------
   Highlighting the selected pill was not enough: *"it doesn't divide the page in
   my mental model"*. A tab that only recolours a word leaves the page below it
   looking like the same continuous page it was before, so nothing tells you that
   what you are reading belongs to the niche you picked.

   So the panel is now a physical container — it bleeds to the edge of the layout,
   sits on the tinted page colour with a hairline above it, and carries the
   section's own name as a caption. Everything inside it visibly *is* that
   section. The tint stops at the container's edge rather than the viewport's,
   which reads as a large card on a desktop and as a full-width sheet on a phone,
   and avoids the 100vw-versus-scrollbar overflow bug entirely.

   `input:checked + label + panel` still does the showing, so no wrapper may come
   between these three elements. */
.tabs__panel {
  order: 2; flex: 1 1 100%; display: none;
  margin-inline: calc(-1 * var(--gutter));
  margin-block-start: var(--space-4);
  padding: var(--space-5) var(--gutter) var(--space-6);
  background: var(--page-tint);
  box-shadow: inset 0 1px 0 var(--hairline);
}
.tabs__radio:checked + .tabs__label + .tabs__panel { display: block; }
@media (min-width: 48rem) {
  .tabs__panel {
    margin-inline: 0; padding-inline: var(--space-5);
    border-radius: var(--radius-lg);
    box-shadow: none;
  }
}
/* The caption repeats the selected tab inside the panel. Redundant on a wide
   screen and essential on a phone, where the rail scrolls out of view the moment
   you start reading. */
.tabs__caption {
  display: flex; align-items: baseline; gap: var(--space-2);
  margin-block-end: var(--space-4);
  font-size: var(--step--1); color: var(--text-subtle);
  text-transform: uppercase; letter-spacing: 0.06em; font-weight: 600;
}
.tabs__label--add {
  color: var(--accent); background: transparent;
  border-style: dashed; border-color: var(--border-strong);
}
.tabs__off {
  font-size: var(--step--2); font-weight: 500; opacity: 0.7;
}

/* ---- Pane heads -----------------------------------------------------------
   A section panel holds four different kinds of thing — services, content,
   pictures, comments — and they used to run together into one column with no
   seam. This is the iOS grouped-list header: a quiet uppercase title, a count,
   and the one action that belongs to that group, aligned to the trailing edge.

   The count is part of the title because "Services 3" answers a question the
   owner has before they have finished reading the word. */
.pane-head {
  display: flex; align-items: center; gap: var(--space-2);
  margin-block: var(--space-6) var(--space-3);
}
.pane-head:first-child { margin-block-start: 0; }
.pane-head__title {
  margin: 0; font-size: var(--step--1); font-weight: 600;
  color: var(--text-subtle);
  text-transform: uppercase; letter-spacing: 0.06em;
}
.pane-head__count {
  font-variant-numeric: tabular-nums;
  color: var(--text-subtle); font-weight: 500;
}
.pane-head__action { margin-inline-start: auto; }

/* ---- The per-section insight strip ----------------------------------------
   Owner-only, at the top of the section it describes. Placed above the bio
   rather than below it on purpose: an owner opening their own profile is
   checking how it is doing, and a visitor never sees this at all, so nothing is
   pushed down for the person who came to read.

   Content blocks get no equivalent — the owner's own call, and the right one:
   a view count on a paragraph is a number nobody can act on. */
.insight-strip {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-2);
  border-radius: var(--radius);
  background: var(--surface);
  margin-block-end: var(--space-4);
}
.insight-strip .metric__value { font-size: var(--step-0); }

/* ---- Editing, without extra boxes -----------------------------------------
   The first attempt wrapped every editable thing in a dashed strip plus a dashed
   subject. It made the page busier than the thing it was meant to clarify.

   Containment does the same job for free: the controls sit *inside* the card
   they act on, in its own header row. Nothing new is drawn, and there is no
   question which section a Remove belongs to because it is inside that section. */
.editable { position: relative; }
.editable__bar {
  display: flex; align-items: center; gap: var(--space-1);
  padding-block-end: var(--space-2);
  margin-block-end: var(--space-2);
  box-shadow: inset 0 -1px 0 var(--hairline);
}
.editable__what {
  font-size: var(--step--2); font-weight: 600; color: var(--text-subtle);
  text-transform: uppercase; letter-spacing: 0.05em;
  margin-inline-end: auto;
}
/* On a pointer device the controls stay quiet until wanted; on touch there is no
   hover, so they are always legible. */
@media (hover: hover) {
  .editable__bar .edit-tools { opacity: 0.55; transition: opacity var(--dur) var(--ease); }
  .editable:hover .editable__bar .edit-tools,
  .editable:focus-within .editable__bar .edit-tools { opacity: 1; }
}

/* ---- Mobile tab bar -------------------------------------------------------
   Fixed to the bottom, where a thumb is. Hidden on wide screens, where the
   header menu is already reachable. */
.tabbar {
  position: fixed; inset-inline: 0; inset-block-end: 0; z-index: 50;
  display: flex; align-items: stretch;
  block-size: calc(var(--tabbar-height) + var(--safe-bottom));
  padding-block-end: var(--safe-bottom);
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: saturate(180%) blur(20px);
  box-shadow: inset 0 1px 0 var(--hairline);
}
.tabbar__item {
  flex: 1; display: grid; place-items: center; gap: 2px;
  padding-block: var(--space-2);
  color: var(--text-subtle); text-decoration: none;
  font-size: 0.65rem; font-weight: 500;
}
.tabbar__item[aria-current="page"] { color: var(--accent); }
.tabbar__item .icon { inline-size: 1.5rem; block-size: 1.5rem; }
.tabbar__badge {
  position: absolute; margin-inline-start: 1.1rem; margin-block-start: -0.5rem;
  min-inline-size: 1rem; padding-inline: 0.2rem;
  border-radius: var(--radius-pill);
  background: var(--danger); color: #fff;
  font-size: 0.6rem; font-weight: 700; line-height: 1rem; text-align: center;
}
@media (min-width: 48rem) { .tabbar { display: none; } }
@media (max-width: 47.99rem) {
  body { padding-block-end: calc(var(--tabbar-height) + var(--safe-bottom)); }
}

@media (prefers-reduced-motion: reduce) {
  .tabs__label, .editable__bar .edit-tools { transition: none; }
  .tabs__label:active { transform: none; }
}

/* Search: one field, one button, results grouped by what they are. */
.search-form { display: flex; gap: var(--space-2); }
.search-form .input { flex: 1; }
.group__item[href] { color: inherit; text-decoration: none; }
.group__item[href]:hover { background: var(--surface-2); }

/* =============================================================================
   App layer II — navigation, the account sheet, search  (2026-08-04)
   -----------------------------------------------------------------------------
   The brief: *"treating profile as one place to do everything that user need to
   do with it's account and profile, like what people can do in instagram or
   linkedin"*, with settings that feel like iOS Settings, and a menu whose
   rightmost item is the person's own face.

   Two ideas do most of the work here.

   **Account things and category things are different things.** The old header
   mixed a market's categories with Dashboard, Requests, Balance and Sign out in
   one flat list, so the menu grew every time the product did. Now the start of
   the bar is navigation — where you can go on this site — and the end of it is
   you: search, notifications, and your avatar. Nothing else is ever added to the
   end cluster; new account destinations go inside the sheet.

   **The sheet is the settings app.** One `<dialog>` of grouped inset lists,
   opened by the gear, reachable from the profile and from the header. It is the
   only place Sign out appears, at the bottom, in red, exactly where iOS puts a
   destructive row — which is also how it stopped being a first click.
   ========================================================================== */

/* ---- Header end cluster --------------------------------------------------- */
.nav-you {
  display: flex; align-items: center; gap: var(--space-1);
  margin-inline-start: auto;
}
/* The burger keeps the categories; the account cluster is never inside it, so a
   phone still reaches notifications and the profile in one tap. */
.nav-you .icon-btn { background: transparent; }
.nav-you .icon-btn:hover { background: var(--accent-soft); }

/* The avatar is the button. A person recognises their own picture faster than
   they read the word "Account", which is the entire reason every social product
   converged on this. */
.usermenu { position: relative; }
.usermenu > summary {
  list-style: none; cursor: pointer;
  display: inline-grid; place-items: center;
  border-radius: 50%;
}
.usermenu > summary::-webkit-details-marker { display: none; }
.usermenu > summary:focus-visible { outline: none; box-shadow: var(--ring); }
.usermenu[open] > summary .avatar { box-shadow: 0 0 0 2px var(--accent); }
.usermenu__panel {
  position: absolute; inset-block-start: calc(100% + var(--space-2));
  inset-inline-end: 0; z-index: 60;
  inline-size: max-content; min-inline-size: 14rem; max-inline-size: 18rem;
  padding: var(--space-2);
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
}
.usermenu__panel a,
.usermenu__panel button {
  display: flex; align-items: center; gap: var(--space-3);
  inline-size: 100%; min-block-size: 2.5rem;
  padding: var(--space-2) var(--space-3);
  border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--text);
  font: inherit; text-align: start; text-decoration: none; cursor: pointer;
}
.usermenu__panel a:hover,
.usermenu__panel button:hover { background: var(--surface-2); }
.usermenu__panel .icon { color: var(--text-subtle); flex: none; }
.usermenu__head {
  padding: var(--space-2) var(--space-3) var(--space-3);
  border-block-end: 1px solid var(--hairline);
  margin-block-end: var(--space-2);
}
.usermenu__name { display: block; font-weight: 600; }
.usermenu__handle {
  display: block; font-size: var(--step--1); color: var(--text-subtle);
  direction: ltr; unicode-bidi: isolate;
}
@media (max-width: 47.99rem) {
  /* On a phone the tab bar already carries all of this, and a second copy in the
     header is one more thing between the visitor and the page. */
  .nav-you { display: none; }
}

/* ---- The account sheet: iOS Settings --------------------------------------
   Grouped inset lists on a tinted sheet, each row a label, an optional trailing
   value, and a chevron. The value on the right is what makes a settings list
   readable without opening anything — you learn your balance and how many
   requests are waiting from the list itself. */
.sheet--settings::backdrop { background: var(--overlay); }
.sheet--settings .sheet__body {
  background: var(--page-tint);
  padding: var(--space-5) var(--space-4) var(--space-6);
}
.sheet__title {
  display: flex; align-items: center; gap: var(--space-3);
  margin-block-end: var(--space-4);
}
.sheet__title h2 { margin: 0; font-size: var(--step-1); }
.sheet__title .icon-btn { margin-inline-start: auto; }

.settings-row {
  display: flex; align-items: center; gap: var(--space-3);
  min-block-size: 2.9rem; padding: var(--space-2) var(--space-4);
  color: var(--text); text-decoration: none;
  background: none; border: 0; inline-size: 100%; font: inherit;
  text-align: start; cursor: pointer;
}
.settings-row + .settings-row { box-shadow: inset 0 1px 0 var(--hairline); }
.settings-row:hover { background: var(--surface-2); }
.settings-row__icon {
  display: grid; place-items: center; flex: none;
  inline-size: 1.75rem; block-size: 1.75rem;
  border-radius: 7px;
  background: var(--accent); color: var(--on-accent);
}
.settings-row__icon .icon { inline-size: 1rem; block-size: 1rem; }
.settings-row__label { flex: 1; min-inline-size: 0; }
.settings-row__value {
  color: var(--text-subtle); font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* A chevron that points the way the language runs. `scale(-1,1)` rather than a
   second glyph, so one icon serves both directions. */
.settings-row__go {
  flex: none; color: var(--text-subtle); opacity: 0.6;
  inline-size: 1rem; block-size: 1rem;
}
[dir="rtl"] .settings-row__go { transform: scale(-1, 1); }
.settings-row--danger { color: var(--danger); }
.settings-row--danger .settings-row__icon { background: var(--danger); color: #fff; }
/* Tinted per group, so the list scans by colour before it is read — the one
   thing iOS Settings does that makes a long list navigable at a glance. */
.settings-row--money .settings-row__icon { background: var(--gold); color: #fff; }
.settings-row--alert .settings-row__icon { background: #cf6b1e; color: #fff; }

/* ---- Mobile tab bar, revised ----------------------------------------------
   Four destinations, the last one being the person. Home · Search · Alerts ·
   Profile, reading from the leading edge, so in an RTL market the profile lands
   under the right thumb exactly as the owner asked — logical properties give
   that for free and it flips correctly if a market ever runs LTR.

   Dashboard has left the bar. It was occupying a permanent slot to reach a page
   that is now a sheet inside the profile, which is where the owner wanted it. */
/* The tab bar is the one place in this stylesheet that is deliberately *physical*
   rather than logical.
   -----------------------------------------------------------------------------
   The owner's correction, 2026-08-04: *"I want the profile on the right for both
   rtl and ltr versions and then the current order which is profile, notifications,
   search, home, starting from right to left, both on rtl and ltr these can stay
   the same."*

   Logical properties gave the opposite — in RTL the leading edge is the right, so a
   DOM order of Home · Search · Alerts · Profile put the profile on the *left*.
   Forcing the bar to `direction: ltr` pins that DOM order to left-to-right
   regardless of the page, so Profile is the rightmost item in every language.

   This is right and it is not an inconsistency. Everywhere else the interface
   mirrors because it is *reading* — text, forms, lists all follow the language. A
   thumb does not. The bar is a physical control at the bottom of a physical device,
   and the person holding it has the same hand in Tehran and in Zurich.

   Only the bar's own axis is forced; each item's contents stay logical, so an icon
   and its label still order correctly for the language inside their cell. */
.tabbar { direction: ltr; }
.tabbar__item { position: relative; text-align: center; direction: inherit; }
[dir="rtl"] .tabbar__item { direction: rtl; }
.tabbar__item[aria-current="page"] { color: var(--accent); font-weight: 600; }
/* Apple indicates the active tab by colour and weight, not by an underline or a
   pill. The one addition is a small dot for the profile tab, because an avatar
   photo cannot be tinted the way an icon can. */
.tabbar__item--you[aria-current="page"]::after {
  content: ""; position: absolute; inset-block-end: 0.35rem;
  inline-size: 4px; block-size: 4px; border-radius: 50%;
  background: var(--accent);
}
.tabbar__item--you[aria-current="page"] .avatar { box-shadow: 0 0 0 2px var(--accent); }
.tabbar__item .avatar { inline-size: 1.6rem; block-size: 1.6rem; font-size: 0.7rem; }

/* ---- Search ---------------------------------------------------------------
   One field, mixed results, each row saying what kind of thing it is by its
   leading glyph: a pin is a place, a tag is a niche, a picture is a business.
   Instagram's search does exactly this and nobody needs it explained. */
.searchbar {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--surface-2);
}
.searchbar .icon { color: var(--text-subtle); flex: none; }
.searchbar input {
  flex: 1; min-inline-size: 0;
  border: 0; background: transparent; color: var(--text);
  font: inherit; font-size: var(--step-0);
}
.searchbar input:focus { outline: none; }
.searchbar:focus-within { box-shadow: var(--ring); }
/* The submit button exists for the keyboard and for no-JS, but it should not
   compete with the field on a phone. */
.searchbar button { flex: none; }

.result {
  display: flex; align-items: center; gap: var(--space-3);
  min-block-size: 3.5rem; padding: var(--space-2) var(--space-4);
  color: var(--text); text-decoration: none;
}
.result + .result { box-shadow: inset 0 1px 0 var(--hairline); }
.result:hover { background: var(--surface-2); }
.result__kind {
  display: grid; place-items: center; flex: none;
  inline-size: 2.5rem; block-size: 2.5rem;
  border-radius: 50%;
  background: var(--surface-2); color: var(--text-subtle);
}
.result__kind--place { background: var(--accent-soft); color: var(--accent); }
.result__kind--niche { background: var(--gold-soft); color: var(--gold); }
.result__body { min-inline-size: 0; flex: 1; }
.result__name {
  display: block; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The covered subcategories, exactly as the directory card head shows them —
   the same fact in the same words in both places, so a result is recognisable
   as the card it leads to. */
.result__meta {
  display: block; font-size: var(--step--1); color: var(--text-subtle);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Before anything is typed, and again when the field is cleared, the page shows
   what there is to browse rather than an empty box. An empty screen is an
   invitation to act, and a directory's answer to "what is here" is its niches. */
.catgrid {
  display: grid; gap: var(--space-3);
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
}
.cattile {
  display: grid; align-content: space-between; gap: var(--space-4);
  min-block-size: 6rem; padding: var(--space-4);
  border-radius: var(--radius);
  background: var(--surface); color: var(--text); text-decoration: none;
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.cattile:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
.cattile .icon { color: var(--accent); }
.cattile__name { font-weight: 600; line-height: 1.25; }
.cattile__count { font-size: var(--step--1); color: var(--text-subtle); }

/* ---- Money ----------------------------------------------------------------
   A price with no unit is a number, and a number on its own is unreadable in a
   market whose prices run to seven figures. The unit is set quieter and smaller
   than the amount so the amount is still what you see first. */
.price { display: inline-flex; align-items: baseline; gap: 0.3em; }
.price__amount { font-weight: 600; font-variant-numeric: tabular-nums; }
.price__unit { font-size: 0.8em; color: var(--text-muted); font-weight: 500; }
.price__from {
  font-size: 0.8em; color: var(--text-subtle);
  margin-inline-end: 0.2em;
}

/* ---- Relative time --------------------------------------------------------
   Rendered by the server as an absolute date and rewritten in the browser to
   "3 hours ago" in the visitor's own locale and zone. Server-side relative time
   would be wrong for anyone in another zone and would poison the page cache. */
time[data-relative] { white-space: nowrap; }

@media (prefers-reduced-motion: reduce) {
  .cattile, .metrics, .icon-btn { transition: none; }
  .cattile:hover { transform: none; }
}

/* =============================================================================
   App layer III — modals, sticky tabs, live search  (2026-08-04, second pass)
   -----------------------------------------------------------------------------
   The owner on the settings sheet: *"the best ui we have now, it's so fresh and
   clean and friendly, I wish all parts of our system would be elegant and minimal
   like this."* So the sheet's treatment becomes the general one, and the rough
   edges he named on it get fixed everywhere rather than in one place.
   ========================================================================== */

/* ---- The backdrop ---------------------------------------------------------
   A blur, not just a scrim. This is what makes an iOS or macOS sheet read as
   *in front of* the page rather than drawn on top of it — the page is still
   there, still recognisable, and unmistakably not the thing you are using.

   Kept subtle: 6px is enough to defocus text without the page appearing to melt,
   and the tint underneath still carries the contrast the blur alone cannot. */
dialog::backdrop {
  background: rgba(0, 0, 0, 0.32);
  backdrop-filter: blur(6px) saturate(120%);
  -webkit-backdrop-filter: blur(6px) saturate(120%);
  animation: backdrop-in 200ms var(--ease);
}
@keyframes backdrop-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { dialog::backdrop { animation: none; } }
/* A phone is doing this on a compositor with a fraction of the headroom, and a
   full-screen blur behind a scrolling sheet is where it shows. Desktop is where
   the owner asked for it — *"especially on desktop"* — so that is where it goes. */
@media (max-width: 47.99rem) {
  dialog::backdrop { backdrop-filter: none; -webkit-backdrop-filter: none; background: var(--overlay); }
}

/* ---- Every sheet scrolls, and its close button stays put -------------------
   The owner's report: *"when I click outside of a modal it should be closed or at
   least make the X button to close sticky if it's risky to lose the modal by
   clicking outside of it so user could use the close button without scrolling
   back up."*

   Both, in fact, because they answer different risks. Click-outside is handled in
   `dialog.js` and is refused for a sheet that would lose typed input. The sticky
   header is here, and applies to all of them — a long settings list should not
   require scrolling back to the top to leave. */
/* `display` on `[open]` only — the bare selector must never set it, or a closed
   dialog beats the browser's own `display: none` and sits on the page as an
   interstitial. `tests/test_css_contracts.py` enforces that; this is why the height
   cap and the flex column are split across two rules rather than one. */
.sheet { max-block-size: 88vh; }
.sheet[open] { display: flex; flex-direction: column; }
.sheet__body { overflow-y: auto; overscroll-behavior: contain; }
.sheet__title {
  position: sticky; inset-block-start: 0; z-index: 2;
  margin-block: calc(-1 * var(--space-5)) var(--space-4);
  padding-block: var(--space-4) var(--space-3);
  /* The sheet body can be tinted or plain, so the bar inherits rather than picking
     one — otherwise the settings sheet grows a white stripe across its top. */
  background: inherit;
  box-shadow: 0 1px 0 var(--hairline);
}
.sheet--settings .sheet__body { padding-block-start: var(--space-5); }

/* ---- Scrollbars, inside our surfaces only ---------------------------------
   The owner's line: *"the scroll bar default design on the browser should be kept
   but all modals scroll bars might need improvement cause that's a ui inside our
   website and app."* Exactly right, and the distinction is the whole rule — the
   page's scrollbar belongs to the browser and to the person's own settings; a
   scrollbar *inside* a sheet is a piece of our interface.

   Scoped to scrolling surfaces we drew. `html`/`body` are deliberately untouched. */
.sheet__body,
.usermenu__panel,
.panel__body {
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}
.sheet__body::-webkit-scrollbar,
.usermenu__panel::-webkit-scrollbar,
.panel__body::-webkit-scrollbar { inline-size: 10px; block-size: 10px; }
.sheet__body::-webkit-scrollbar-track,
.usermenu__panel::-webkit-scrollbar-track,
.panel__body::-webkit-scrollbar-track { background: transparent; }
.sheet__body::-webkit-scrollbar-thumb,
.usermenu__panel::-webkit-scrollbar-thumb,
.panel__body::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border-radius: var(--radius-pill);
  /* A transparent border with background-clip is how a thumb gets inset from its
     track without the track needing a colour of its own. */
  border: 3px solid transparent;
  background-clip: content-box;
}
.sheet__body::-webkit-scrollbar-thumb:hover,
.usermenu__panel::-webkit-scrollbar-thumb:hover,
.panel__body::-webkit-scrollbar-thumb:hover { background: var(--text-subtle); background-clip: content-box; }

/* ---- Sticky section tabs --------------------------------------------------
   *"still the tabs could be improved so let the user know what tab they're in. we
   can make the tabs sticky so user can switch between them"*.

   Only when there is more than one — a single tab that follows you down the page
   is a wasted bar. The `:has()` guard does that in CSS; where `:has()` is missing
   the rail simply does not stick, which is the old behaviour. */
@supports selector(:has(*)) {
  .tabs:has(.tabs__radio:nth-of-type(2)) .tabs__label {
    position: sticky;
    inset-block-start: calc(var(--nav-height, 56px) + var(--space-2));
    z-index: 20;
  }
}
/* A rail of pills sitting directly on scrolling content is unreadable, so it gets
   its own surface once it is stuck. Drawn on the labels' shared row rather than a
   wrapper, because a wrapper would break `input:checked + label + panel`. */
.tabs { position: relative; }
.tabs__label { backdrop-filter: saturate(180%) blur(12px); }

/* The current tab, said in words as well as colour. A filled pill tells you which
   one is active only if you can compare it with the others — this caption is on
   screen even when the rail has scrolled away, and it carries a checkmark so the
   relationship is stated rather than implied. */
.tabs__caption::before {
  content: "";
  inline-size: 0.45rem; block-size: 0.45rem;
  border-radius: 50%;
  background: var(--accent);
  align-self: center;
  flex: none;
}

/* ---- Search, live -------------------------------------------------------- */
/* The inner field had a focus ring of its own inside the bar's focus ring. Two
   rings for one control reads as a bug, and it is — the bar is the control. */
.searchbar input,
.searchbar input:focus,
.searchbar input:focus-visible { border: 0; outline: none; box-shadow: none; }
.searchbar button[disabled] { opacity: 0.4; cursor: not-allowed; }
/* Results swap under the field as you type. Only the opacity moves: shifting the
   list makes the page feel like it is fighting you while you are still typing. */
.search-results { transition: opacity 120ms var(--ease); }
.search-results[data-busy] { opacity: 0.5; }
@media (prefers-reduced-motion: reduce) { .search-results { transition: none; } }

/* ---- The account menu, made readable --------------------------------------
   *"the desktop sub menu items can be a bit larger and more readable"*. */
.usermenu__panel { min-inline-size: 16rem; padding: var(--space-2); }
.usermenu__panel a,
.usermenu__panel button {
  min-block-size: 2.9rem;
  padding: var(--space-3);
  font-size: var(--step-0);
  gap: var(--space-3);
}
.usermenu__panel .icon { inline-size: 1.15rem; block-size: 1.15rem; }
.usermenu__name { font-size: var(--step-0); }

/* ---- Identity, aligned ----------------------------------------------------
   The handle sat on its own line with the heading's line-height still applied to
   the block above it, so it read as detached from the name rather than under it.
   An explicit line-height and a hair of space is all it needed. */
.identity__who { display: grid; gap: 2px; align-content: center; }
.identity__name { line-height: 1.2; }
.identity__handle { line-height: 1.2; margin: 0; }
/* Same fix in the account menu, where the two lines had the same problem. */
.usermenu__head { display: grid; gap: 2px; }
.usermenu__name, .usermenu__handle { line-height: 1.25; }

/* ---- Page heads and list rows ---------------------------------------------
   Every owner-facing page had its own composition: a `.hero` here, a `.card`
   wrapping a `<ul class="payment-list">` there, a `.tree` somewhere else. They
   were built at different times and it showed.

   The owner's instruction after seeing the settings sheet: *"I wish all parts of
   our system would be elegant and minimal like this."* So the sheet's vocabulary —
   a quiet title, grouped inset lists, rows with a trailing value — becomes the
   vocabulary for every page, and these are the two pieces it was missing. */
.page-head {
  display: grid; gap: var(--space-1);
  padding-block: var(--space-6) var(--space-4);
}
.page-head__eyebrow {
  font-size: var(--step--1); font-weight: 600; color: var(--text-subtle);
  text-transform: uppercase; letter-spacing: 0.06em;
}
.page-head h1 { margin: 0; font-size: var(--step-2); letter-spacing: -0.02em; }
.page-head__lead { margin: 0; color: var(--text-muted); max-inline-size: 60ch; }
.page-head__actions { display: flex; gap: var(--space-2); flex-wrap: wrap; margin-block-start: var(--space-2); }

/* A list row that is a *destination*, not a table cell: a title, a line of
   context under it, and a trailing state. The whole row is the target, because a
   row where only the four-letter word "Open" is tappable is a row that is hard to
   tap. */
.list-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; gap: var(--space-1) var(--space-3);
  padding: var(--space-3) var(--space-4);
  color: var(--text); text-decoration: none;
}
.list-row + .list-row { box-shadow: inset 0 1px 0 var(--hairline); }
.list-row:hover { background: var(--surface-2); }
.list-row__mark {
  grid-row: span 2; align-self: center;
  inline-size: 0.5rem; block-size: 0.5rem; border-radius: 50%;
  background: transparent; flex: none;
}
/* Unread is a dot, not a badge that says "New". The dot is the convention every
   mail and message client already taught, it needs no translation, and it does not
   push the title along the row. */
.list-row--unseen .list-row__mark { background: var(--accent); }
.list-row--unseen .list-row__title { font-weight: 650; }
.list-row__title { min-inline-size: 0; overflow-wrap: anywhere; }
.list-row__meta {
  grid-column: 2; font-size: var(--step--1); color: var(--text-subtle);
  display: flex; flex-wrap: wrap; gap: 0 var(--space-2);
}
.list-row__meta > * + *::before { content: "·"; margin-inline-end: var(--space-2); }
.list-row__trail {
  grid-row: span 2; align-self: center;
  display: flex; align-items: center; gap: var(--space-2);
  font-size: var(--step--1); color: var(--text-subtle); white-space: nowrap;
}
.list-row__go { inline-size: 1rem; block-size: 1rem; opacity: 0.5; flex: none; }
[dir="rtl"] .list-row__go { transform: scale(-1, 1); }

/* An empty screen is an invitation to act, never a shrug. Every one of these
   states names the thing that is missing and gives the control that creates it. */
.empty {
  display: grid; justify-items: center; gap: var(--space-3);
  padding: var(--space-8) var(--space-5);
  text-align: center;
  background: var(--surface); border-radius: var(--radius);
}
.empty__icon {
  display: grid; place-items: center;
  inline-size: 3rem; block-size: 3rem; border-radius: 50%;
  background: var(--accent-soft); color: var(--accent);
}
.empty h2 { margin: 0; font-size: var(--step-1); }
.empty p { margin: 0; color: var(--text-muted); max-inline-size: 44ch; }

/* Filter chips, shared by the requests inbox and the reports range picker. */
.chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.chip {
  display: inline-flex; align-items: center; gap: var(--space-2);
  min-block-size: 2.25rem; padding-inline: var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--surface-2); color: var(--text-muted);
  font-size: var(--step--1); font-weight: 600; text-decoration: none;
}
.chip:hover { background: var(--accent-soft); color: var(--accent); }
.chip--active { background: var(--accent); color: var(--on-accent); }
.chip__count { font-variant-numeric: tabular-nums; opacity: 0.75; }

/* A stat tile — the reports page's totals, and the balance page's headline. */
.tiles {
  display: grid; gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
}
.tile {
  display: grid; gap: var(--space-1);
  padding: var(--space-4);
  border-radius: var(--radius); background: var(--surface);
}
.tile__value {
  font-size: var(--step-2); font-weight: 700; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums; line-height: 1.1;
}
.tile__label { font-size: var(--step--1); color: var(--text-subtle); }
.tile--money .tile__value { color: var(--gold); }

/* ---- The directory, brought up to the same finish --------------------------
   The result cards were the oldest surface in the product and read that way:
   heavier shadows, a tighter grid and a sort control built from text links, all
   from before the rest of the interface settled on inset groups and pill chips.

   The markup is deliberately untouched — those cards carry the SEO-relevant
   structure and several tests assert their classes. This is finish, not surgery. */
.pcard {
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  border: 1px solid transparent;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}
.pcard:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow);
  border-color: var(--border);
}
.pcard__head { align-items: center; gap: var(--space-3); }
.pcard__title h3 { margin: 0; font-size: var(--step-0); letter-spacing: -0.01em; line-height: 1.25; }
.pcard__niche { font-size: var(--step--1); color: var(--text-subtle); }
.pcard__snippet { font-size: var(--step--1); line-height: 1.55; }

/* The results bar: a count that reads as a sentence, and sort as chips — the same
   control the requests inbox and the reports range picker already use. Three
   different-looking ways to say "filter this list" was two too many. */
.results-bar {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: var(--space-3);
  margin-block-end: var(--space-4);
}
.results-count { margin: 0; font-size: var(--step--1); color: var(--text-muted); }
.sort { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.sort__label {
  font-size: var(--step--2); font-weight: 600; color: var(--text-subtle);
  text-transform: uppercase; letter-spacing: 0.06em;
}
.sort__option {
  display: inline-flex; align-items: center;
  min-block-size: 2rem; padding-inline: var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--surface-2); color: var(--text-muted);
  font-size: var(--step--1); font-weight: 600; text-decoration: none;
}
.sort__option:hover { background: var(--accent-soft); color: var(--accent); }
.sort__option--active { background: var(--accent); color: var(--on-accent); }

@media (prefers-reduced-motion: reduce) {
  .pcard { transition: none; }
  .pcard:hover { transform: none; }
}
