:root {
  /* Declovia's own palette — Pine, the default of four (see the
     [data-palette] blocks below for Indigo/Ember/Plum). Surfaces and the
     accent are drawn from the same green that already marked a healthy
     state or a resolved case, pushed one register deeper so it reads as
     brand ink rather than a status highlight; neutrals carry a slight
     warm-green bias rather than defaulting to plain grey (a considered
     neutral, not an inherited one). Replaces DDF's old borrowed-from-
     Aufmass token values — the NAMES are unchanged so nothing downstream
     had to be rewritten, only what each one resolves to. */
  --bg: #f6f7ef;
  --panel: #fdfdf9;
  --panel-2: #eef1e5;
  --border: #dde2d0;
  --text: #151d16;
  --muted: #5c6656;
  --accent: #1f5c3f;
  --accent-hover: #123625;
  --accent-soft: #e3f0e6;
  --row-hover: #e9f1e2;
  --danger: #a12626;
  --danger-hover: #d64545;
  --danger-bg: #fdecec;
  --onaccent: #f4faf3;
  /* --- chrome + brand ------------------------------------------------- */
  --head-ink: #2d3a2c;      /* table headers and eyebrow labels — a step down from --text without going as pale as --muted */
  --border-strong: #c3caae; /* the darker rule that separates a section, not a row */
  --zebra: #f9faf5;         /* alternating row tint; deliberately barely-there */
  --darkbar: #151d16;       /* fixed-dark chip surface: toasts, bulk-action bars. The SAME across every theme/palette on purpose, so it must never be built from --text, which flips */
  --ondark: #ffffff;        /* text on --darkbar */
  --brand-grey: #5c6656;    /* wordmark's quiet half */
  --fiber: #1f5c3f;         /* secondary emphasis accent — aliases --accent in every palette here; kept as its own token for call sites that reached for it before this rebrand */
  --fiber-soft: #e3f0e6;

  /* status: -ink (text/icon), -bg (tint), -line (border). Semantic, so it
     stays FIXED across every palette below — good/warn/danger/info never
     change meaning just because the brand accent does. --danger above is
     this family's "ink" and stays the one name for that colour. */
  --good-ink: #1f6b3b;    --good-bg: #e3f3e8;  --good-line: #2f9e5f;
  --warn-ink: #8a5a00;    --warn-bg: #fdf4e0;  --warn-line: #e0c088;
  --danger-line: #e0b4b4;
  --info-ink: #1f4f9c;    --info-bg: #e2ecfb;  --info-line: #5b8def;
  --neutral-ink: #5a6270; --neutral-bg: #f1f2f4; --neutral-line: #a8adb7;
  font-family: "Archivo", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

/* Theming follows the light/dark token pattern (see DEVELOPER_GUIDE.md):
   :root holds the light palette; the media query covers "system" preference
   (skipped when the user has explicitly chosen light); [data-theme="dark"]
   covers an explicit choice, so it wins over the OS setting either way.
   Palette (Pine/Indigo/Ember/Plum, via [data-palette]) is the orthogonal
   second axis — every palette below repeats this same light/media/explicit
   trio, so the two axes combine correctly in all four combinations. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0d130e;
    --panel: #141b12;
    --panel-2: #1c2418;
    --border: #2b3527;
    --text: #e9efe3;
    --muted: #a2ac96;
    --accent: #5fbf86;
    --accent-hover: #86d7a3;
    --accent-soft: #17301f;
    --row-hover: #1a2a1c;
    --danger: #ef8b8b;
    --danger-hover: #e8615f;
    --danger-bg: #3a1c1c60;
    --onaccent: #0b1a10;
    --head-ink: #c3d0bc;
    --border-strong: #3c4936;
    --zebra: #111710;
    --brand-grey: #a2ac96;
    --fiber: #5fbf86;
    --fiber-soft: #17301f;

    --good-ink: #6fd49a;    --good-bg: #163324;  --good-line: #2f9e5f;
    --warn-ink: #e0b34d;    --warn-bg: #3a2f12;  --warn-line: #6b5320;
    --danger-line: #6b3232;
    --info-ink: #86adf0;    --info-bg: #1c2c4a;  --info-line: #3f5f9e;
    --neutral-ink: #b7bcc4; --neutral-bg: #2a2d32; --neutral-line: #565b63;
  }
}
:root[data-theme="dark"] {
  --bg: #0d130e;
  --panel: #141b12;
  --panel-2: #1c2418;
  --border: #2b3527;
  --text: #e9efe3;
  --muted: #a2ac96;
  --accent: #5fbf86;
  --accent-hover: #86d7a3;
  --accent-soft: #17301f;
  --row-hover: #1a2a1c;
  --danger: #ef8b8b;
  --danger-hover: #e8615f;
  --danger-bg: #3a1c1c60;
  --onaccent: #0b1a10;
  --head-ink: #c3d0bc;
  --border-strong: #3c4936;
  --zebra: #111710;
  --brand-grey: #a2ac96;
  --fiber: #5fbf86;
  --fiber-soft: #17301f;

  --good-ink: #6fd49a;    --good-bg: #163324;  --good-line: #2f9e5f;
  --warn-ink: #e0b34d;    --warn-bg: #3a2f12;  --warn-line: #6b5320;
  --danger-line: #6b3232;
  --info-ink: #86adf0;    --info-bg: #1c2c4a;  --info-line: #3f5f9e;
  --neutral-ink: #b7bcc4; --neutral-bg: #2a2d32; --neutral-line: #565b63;
}

/* ==================================================================== */
/* Indigo — cooler, more technical/precise register.                    */
:root[data-palette="indigo"] {
  --bg: #f4f4fb; --panel: #fdfdff; --panel-2: #eaeaf5; --border: #d7d7e8; --border-strong: #b9b9d8;
  --text: #171426; --muted: #5b5875; --head-ink: #2a2748; --zebra: #f8f8fc;
  --accent: #3d3aa8; --accent-hover: #26216f; --accent-soft: #e6e5f7; --row-hover: #ececf8; --onaccent: #f8f8fc;
  --brand-grey: #5b5875; --fiber: #3d3aa8; --fiber-soft: #e6e5f7;
}
@media (prefers-color-scheme: dark) {
  :root[data-palette="indigo"]:not([data-theme="light"]) {
    --bg: #0d0c17; --panel: #15142a; --panel-2: #1e1d38; --border: #302e4d; --border-strong: #423f66;
    --text: #ecebfb; --muted: #a7a3c9; --head-ink: #cfcdec; --zebra: #110f22;
    --accent: #8b86f0; --accent-hover: #a9a5f5; --accent-soft: #201d3f; --row-hover: #221f3e; --onaccent: #0d0c17;
    --brand-grey: #a7a3c9; --fiber: #8b86f0; --fiber-soft: #201d3f;
  }
}
:root[data-palette="indigo"][data-theme="dark"] {
  --bg: #0d0c17; --panel: #15142a; --panel-2: #1e1d38; --border: #302e4d; --border-strong: #423f66;
  --text: #ecebfb; --muted: #a7a3c9; --head-ink: #cfcdec; --zebra: #110f22;
  --accent: #8b86f0; --accent-hover: #a9a5f5; --accent-soft: #201d3f; --row-hover: #221f3e; --onaccent: #0d0c17;
  --brand-grey: #a7a3c9; --fiber: #8b86f0; --fiber-soft: #201d3f;
}

/* Ember — warm rust on stone, deliberately not the cream+terracotta       */
/* combination that reads as a generic AI-design default.                 */
:root[data-palette="ember"] {
  --bg: #f9f6f1; --panel: #fefcf9; --panel-2: #f0e6d8; --border: #e2d2b8; --border-strong: #c9a97b;
  --text: #25190f; --muted: #7a5f45; --head-ink: #3c2a18; --zebra: #faf7f2;
  --accent: #b3551f; --accent-hover: #8a3f14; --accent-soft: #fae4d0; --row-hover: #f5ead9; --onaccent: #fbf7f3;
  --brand-grey: #7a5f45; --fiber: #b3551f; --fiber-soft: #fae4d0;
}
@media (prefers-color-scheme: dark) {
  :root[data-palette="ember"]:not([data-theme="light"]) {
    --bg: #15100a; --panel: #1e160e; --panel-2: #291f14; --border: #3d2d1c; --border-strong: #57402a;
    --text: #f3e6d8; --muted: #c2a688; --head-ink: #e3cdb5; --zebra: #120d08;
    --accent: #e2793a; --accent-hover: #f0954f; --accent-soft: #2e1c10; --row-hover: #291d13; --onaccent: #15100a;
    --brand-grey: #c2a688; --fiber: #e2793a; --fiber-soft: #2e1c10;
  }
}
:root[data-palette="ember"][data-theme="dark"] {
  --bg: #15100a; --panel: #1e160e; --panel-2: #291f14; --border: #3d2d1c; --border-strong: #57402a;
  --text: #f3e6d8; --muted: #c2a688; --head-ink: #e3cdb5; --zebra: #120d08;
  --accent: #e2793a; --accent-hover: #f0954f; --accent-soft: #2e1c10; --row-hover: #291d13; --onaccent: #15100a;
  --brand-grey: #c2a688; --fiber: #e2793a; --fiber-soft: #2e1c10;
}

/* Plum — a quieter kind of bold: mulberry over blush stone.               */
:root[data-palette="plum"] {
  --bg: #faf5f8; --panel: #fefbfd; --panel-2: #efe1e8; --border: #dfc6d3; --border-strong: #c39cb1;
  --text: #241019; --muted: #6e4a5b; --head-ink: #3a1d2c; --zebra: #faf6f9;
  --accent: #7a2456; --accent-hover: #591a3f; --accent-soft: #f3dfea; --row-hover: #f6ebf0; --onaccent: #fbf6f8;
  --brand-grey: #6e4a5b; --fiber: #7a2456; --fiber-soft: #f3dfea;
}
@media (prefers-color-scheme: dark) {
  :root[data-palette="plum"]:not([data-theme="light"]) {
    --bg: #140a10; --panel: #1e1119; --panel-2: #281721; --border: #402332; --border-strong: #593046;
    --text: #f2e2ea; --muted: #c093a8; --head-ink: #e3c8d6; --zebra: #110810;
    --accent: #d9689e; --accent-hover: #e88bb5; --accent-soft: #2e1520; --row-hover: #281620; --onaccent: #140a10;
    --brand-grey: #c093a8; --fiber: #d9689e; --fiber-soft: #2e1520;
  }
}
:root[data-palette="plum"][data-theme="dark"] {
  --bg: #140a10; --panel: #1e1119; --panel-2: #281721; --border: #402332; --border-strong: #593046;
  --text: #f2e2ea; --muted: #c093a8; --head-ink: #e3c8d6; --zebra: #110810;
  --accent: #d9689e; --accent-hover: #e88bb5; --accent-soft: #2e1520; --row-hover: #281620; --onaccent: #140a10;
  --brand-grey: #c093a8; --fiber: #d9689e; --fiber-soft: #2e1520;
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--text); font-size: 14px; }
/* Bricolage Grotesque carries headings and the wordmark — enough
   character to read as a brand, disciplined enough to sit over a dense
   data table two lines down. JetBrains Mono is reserved for things that
   are literally code or schema: column/field names, JSON. */
h1, h2, h3, .page-head h1, .brand, .login-logo { font-family: "Bricolage Grotesque", "Archivo", sans-serif; }
/* A real type scale — until now h1/h2/h3 fell back to the browser's own
   em-relative defaults, which put an ordinary <h2> card title (~21px)
   visibly ABOVE a page's own <h1> (.page-head h1 was pinned to 20px), so
   nothing on screen reliably signalled "this is the most important text
   here." Three fixed steps, each with its own weight, replace that. */
h1 { font-size: 26px; font-weight: 800; line-height: 1.25; margin: 0 0 4px; }
h2 { font-size: 18px; font-weight: 700; line-height: 1.3; margin: 0 0 4px; }
h3 { font-size: 15px; font-weight: 700; line-height: 1.35; margin: 0 0 4px; }
code, .mono { font-family: "JetBrains Mono", ui-monospace, monospace; }
/* Author rules with an explicit `display` (e.g. .login-wrap { display:flex })
   have the same specificity as the UA's built-in `[hidden] { display:none }`
   and, being author-origin, win the tie — so the hidden attribute alone
   does nothing on an element whose class also sets display. Force it. */
[hidden] { display: none !important; }
button { font: inherit; cursor: pointer; }
/* Form controls default to the theme's own surface. Without this they fall
   back to the browser's white, which is invisible-on-white in light mode's
   panels and a glaring white slab in dark mode — the page search box was
   exactly that. Checkboxes and radios are excluded: they render natively and
   a background on them breaks the tick. */
input, select, textarea { font: inherit; }
input:not([type=checkbox]):not([type=radio]), select, textarea {
  background: var(--panel); color: var(--text); border: 1px solid var(--border); border-radius: 6px;
}
input:not([type=checkbox]):not([type=radio]):focus, select:focus, textarea:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft);
}
::placeholder { color: var(--muted); opacity: 1; }
a { color: var(--accent); }

/* ---------------------------------------------------------------- login */
.login-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; }
.login-card { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 32px; width: 320px; display: flex; flex-direction: column; gap: 12px; box-shadow: 0 2px 10px rgba(0,0,0,.06); }
.login-logo { font-size: 24px; font-weight: 700; color: var(--text); display: inline-flex; align-items: center; gap: 10px; }
.login-sub { margin: 0 0 8px; color: var(--muted); }
.login-card label { display: flex; flex-direction: column; gap: 4px; font-weight: 600; font-size: 12px; color: var(--muted); }
.login-card input { padding: 8px 10px; border: 1px solid var(--border); border-radius: 6px; }
.login-card button { margin-top: 8px; background: var(--accent); color: var(--onaccent); border: none; padding: 10px; border-radius: 6px; font-weight: 600; }
.login-version { margin-top: 2px; text-align: center; font-size: 10px; color: var(--muted); opacity: .7; }
.login-card button:hover { background: var(--accent-hover); }

/* ---------------------------------------------------------------- shell */
.topbar {
  display: flex; align-items: center; gap: 16px; padding: 14px 20px;
  /* A white bar with a rule under it, not a saturated brand slab — the same
     choice Aufmass makes. In an application people stare at all day the
     chrome should recede and let the data carry the colour; a green band
     across the top also spends the brand accent in the one place it can
     never be used to mean anything. Everything below reads from tokens, so
     `background: var(--accent); color: var(--onaccent)` is the whole edit
     if a brand-coloured bar is ever wanted back. */
  background: var(--panel); color: var(--text);
  border-bottom: 1px solid var(--border);
  flex: 0 0 auto; z-index: 60;
}
/* Two-tone wordmark: the product name in the brand grey, "via" — the path
   from schema to app — emphasised in the accent. */
.topbar .brand { font-weight: 400; font-size: 16px; letter-spacing: -.01em; color: var(--brand-grey); display: inline-flex; align-items: center; gap: 8px; }
.topbar .brand svg { flex-shrink: 0; }
.topbar .brand b { font-weight: 700; color: var(--accent); }
.nav-filter { padding: 6px 10px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg); color: var(--text); width: 220px; }
.nav-filter:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.tz-select { padding: 5px 8px; border-radius: 6px; border: 1px solid var(--border); background: var(--panel); color: var(--text); font-size: 12px; max-width: 170px; }
.spacer { flex: 1; }
.link-btn { background: none; border: 1px solid var(--border); color: var(--text); padding: 6px 10px; border-radius: 6px; }
.link-btn:hover { background: var(--panel-2); border-color: var(--border-strong); }

/* ---------------------------------------------------------------- profile menu */
.profile-menu { position: relative; }
.profile-trigger { display: flex; align-items: center; gap: 8px; background: none; border: none; color: var(--text); padding: 4px 8px 4px 4px; border-radius: 20px; }
.profile-trigger:hover { background: var(--panel-2); }
.profile-avatar { width: 26px; height: 26px; border-radius: 50%; background: var(--accent-soft); color: var(--accent-hover); display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; overflow: hidden; flex-shrink: 0; }
.profile-avatar img { width: 100%; height: 100%; object-fit: cover; }
.profile-avatar-lg { width: 48px; height: 48px; font-size: 18px; }
.profile-panel { position: absolute; top: calc(100% + 8px); inset-inline-end: 0; width: 300px; z-index: 50; color: var(--text); box-shadow: 0 8px 24px rgba(0,0,0,.15);
  /* #appView clips overflow, and there is no document scroll to fall back
     on, so a tall panel must scroll itself or its bottom rows (Sign out)
     become unreachable on short windows. */
  max-height: calc(100dvh - 72px); overflow-y: auto; }
.profile-panel .field-row { margin-bottom: 12px; }
.profile-panel select, .profile-panel input { width: 100%; padding: 6px 8px; border: 1px solid var(--border); border-radius: 6px; }
.profile-panel hr { border: none; border-top: 1px solid var(--border); margin: 14px 0; }

/* Favorites/History topbar triggers: same .profile-trigger/.profile-panel
   overlay mechanics as the changeset/avatar menus, just icon+label instead
   of an avatar. */
.topbar-icon-trigger { font-size: 13px; }
.topbar-icon-trigger .topbar-icon { font-size: 14px; line-height: 1; }
.topbar-badge { background: var(--accent); color: var(--onaccent); font-size: 10.5px; font-weight: 700;
  min-width: 16px; height: 16px; padding: 0 4px; border-radius: 9px; display: inline-flex;
  align-items: center; justify-content: center; }
.topbar-list-panel { width: 300px; padding: 8px; }
.topbar-list { border: none; margin-bottom: 0; max-height: 340px; overflow-y: auto; }
.topbar-list .row { text-decoration: none; color: var(--text); cursor: pointer; }
.topbar-list .row:hover { background: var(--panel-2); }
.topbar-list .row .col-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
.topbar-list .row .col-type { font-size: 11px; }
.topbar-list .row .unstar { background: none; border: none; color: var(--muted); cursor: pointer; font-size: 13px; padding: 2px 4px; flex: 0 0 auto; }
.topbar-list .row .unstar:hover { color: var(--danger, #d33); }
.topbar-list-empty { padding: 18px 10px; text-align: center; color: var(--muted); font-size: 12.5px; }

#appView { display: flex; flex-direction: column; height: 100vh; height: 100dvh; overflow: hidden; }
.shell { display: flex; flex: 1 1 auto; min-height: 0; }
.sidebar { flex: 0 0 230px; width: 230px; background: var(--panel); border-inline-end: 1px solid var(--border); padding: 12px 0; overflow-y: auto; }

/* ---------------------------------------------------------------- nav tree
   3-level collapsible accordion (folder > folder|module > module|link),
   ported from Aufmass's admin.js nav-tree interaction: chevron rotates
   open, open/closed state persisted per-branch, matching search force-opens
   an ancestor branch. */
.nav-branch { display: flex; align-items: center; gap: 8px; width: calc(100% - 20px); margin: 1px 10px; padding: 10px 8px; background: none; border: none;
  border-radius: 8px; color: var(--text); font: inherit; font-weight: 600; text-align: start; cursor: pointer; }
.nav-branch:hover { background: var(--panel-2); }
.nav-branch .chev { display: inline-block; width: 10px; font-size: 10px; color: var(--muted); transition: transform .12s ease; flex: 0 0 auto; }
.nav-folder.open > .nav-branch .chev { transform: rotate(90deg); }
.nav-branch .nav-icon { flex: 0 0 auto; opacity: .85; }
.nav-branch .nav-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-children { display: none; padding-inline-start: 16px; }
.nav-folder.open > .nav-children { display: block; }
.sidebar a.nav-leaf { display: flex; align-items: center; gap: 8px; padding: 10px 8px; border-radius: 8px; color: var(--text); text-decoration: none; font-size: 13.5px; flex: 1; min-width: 0; }
.sidebar a.nav-leaf:hover, .sidebar a.nav-leaf.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.sidebar a.nav-leaf .nav-icon { opacity: .7; width: 14px; text-align: center; flex: 0 0 auto; }
.sidebar a.nav-leaf .nav-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Leaf rows are a flex sibling pair (anchor + star button), not a button
   nested inside the anchor — nesting interactive content inside an <a> is
   invalid HTML and silently breaks descendant rendering. */
.nav-leaf-row { display: flex; align-items: center; margin: 1px 10px; }
.nav-leaf-row .nav-star { opacity: 0; flex: 0 0 auto; background: none; border: none; cursor: pointer;
  color: var(--muted); font-size: 14px; padding: 6px 10px 6px 2px; line-height: 1; }
.nav-leaf-row:hover .nav-star, .nav-leaf-row .nav-star.active { opacity: 1; }
.nav-leaf-row .nav-star.active { color: #e0a800; }
.nav-leaf-row .nav-star:hover { color: #e0a800; }

.main { flex: 1 1 auto; min-width: 0; padding: 24px; overflow: auto; }
.ui-page-frame { width: 100%; height: 100%; border: none; border-radius: 8px; background: #fff; display: block; }

/* ---------------------------------------------------------------- widgets */
.page-head { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
.page-head h1 { margin: 0; }
.page-head .count { color: var(--muted); font-size: 13px; }
.btn { background: var(--accent); color: var(--onaccent); border: none; padding: 10px 18px; border-radius: 999px; font-weight: 600; }
.btn:hover { background: var(--accent-hover); }
.btn.secondary { background: var(--panel); color: var(--text); border: 1px solid var(--border); }
.btn.secondary:hover { background: var(--panel-2); }
.btn.sm { padding: 4px 8px; font-size: 12px; }
.btn.danger { background: var(--danger); }
.btn.danger:hover { background: var(--danger-hover); }
.search-box { padding: 8px 10px; border: 1px solid var(--border); border-radius: 6px; width: 280px; }

/* Borderless — a data grid reads as part of the page, not a boxed
   component floating on it. Only the header's own rule and each row's
   bottom divider carry structure; .card/dropdowns keep the elevation
   shadow (below), since those genuinely are separate surfaces. */
table.grid { width: 100%; border-collapse: collapse; background: transparent; }
table.grid th, table.grid td { text-align: start; padding: 14px 16px; border-bottom: 1px solid var(--border); font-size: 13px; }
/* Tabular figures so digits line up down a column — the one typographic
   setting that makes a data grid readable, and what Aufmass sets globally
   on its tables. */
table.grid td { font-variant-numeric: tabular-nums; }
table.grid th { background: transparent; color: var(--muted); font-weight: 700; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
  border-bottom: 2px solid var(--border-strong); }
/* Zebra before hover, so hover always wins the cascade. --zebra is a
   barely-there tint: enough to track a row across wide tables, not enough
   to read as a state. */
table.grid tbody tr:nth-child(even) { background: var(--zebra); }
table.grid tbody tr:hover { background: var(--row-hover); cursor: pointer; }
table.grid td.actions { white-space: nowrap; }
.badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 12px; background: var(--accent-soft); color: var(--accent); }
.badge.muted { background: var(--panel-2); color: var(--muted); }
.badge.danger { background: var(--danger-bg); color: var(--danger); }

/* Choice pills — a list view's status/priority/etc. column. A dot plus a
   soft fill plus a matching border, not just colored text, so a value
   reads at a glance even to someone scanning past rather than reading
   every cell. Color is picked deterministically from the value itself
   (pillColorClass() in app.js), not from what the value *means* — this
   platform has no per-choice color setting, and a value's spelling is the
   only thing every table has in common. Reuses the existing semantic
   (good/warn/danger/info/neutral) and flow-diagram tokens rather than
   inventing a parallel palette — eight buckets, all already theme-correct
   in light, dark, and every accent palette this app ships. */
.pill { display: inline-flex; align-items: center; gap: 6px; padding: 4px 11px; border-radius: 999px; font-size: 12px; font-weight: 700;
  border: 1px solid; white-space: nowrap; }
.pill::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: 0 0 auto; }
.pill-a { color: var(--good-ink); background: var(--good-bg); border-color: var(--good-line); }
.pill-b { color: var(--warn-ink); background: var(--warn-bg); border-color: var(--warn-line); }
.pill-c { color: var(--danger); background: var(--danger-bg); border-color: var(--danger-line); }
.pill-d { color: var(--info-ink); background: var(--info-bg); border-color: var(--info-line); }
.pill-e { color: var(--neutral-ink); background: var(--neutral-bg); border-color: var(--neutral-line); }
.pill-f { color: var(--flow-logic); background: var(--flow-logic-soft); border-color: var(--flow-logic-border); }
.pill-g { color: var(--flow-action); background: var(--flow-action-soft); border-color: var(--flow-action-border); }
.pill-h { color: var(--flow-custom); background: var(--flow-custom-soft); border-color: var(--flow-custom-border); }

/* A reference field pointing at a person (system_user/employee/app_user —
   see PERSON_REFERENCE_TABLES in app.js) gets a small initials avatar
   ahead of the display name in a list view, the same convention the
   topbar's own profile button already uses (.profile-avatar). */
.ref-avatar { display: inline-flex; align-items: center; gap: 8px; }
.ref-avatar .avatar-chip { width: 22px; height: 22px; border-radius: 50%; background: var(--accent); color: var(--onaccent);
  display: inline-flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700; flex: 0 0 auto; }
.pager { display: flex; gap: 8px; align-items: center; margin-top: 12px; color: var(--muted); font-size: 13px; }

.chip { display: inline-block; padding: 1px 8px; border-radius: 20px; font-size: 11px; border: 1px solid var(--border); color: var(--muted); background: var(--panel-2); }

/* ---------------------------------------------------------------- menu builder */
.mb-layout { display: flex; gap: 20px; align-items: flex-start; }
.mb-tree { flex: 1 1 480px; min-width: 320px; }
.mb-inspector { flex: 0 0 380px; position: sticky; top: 16px; }
.mb-inspector.card { max-width: none; }
.mb-node { }
.mb-row { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border: 1px solid transparent; border-radius: 6px; cursor: pointer; background: var(--panel); margin: 1px 0; }
.mb-row:hover { background: var(--panel-2); }
.mb-row.drop-target { border-color: var(--accent); background: var(--accent-soft); }
.mb-handle { cursor: grab; color: var(--muted); flex: 0 0 auto; }
.mb-label { font-weight: 500; }
.mb-children { margin-inline-start: 26px; }
.mb-slot { height: 6px; border-radius: 3px; margin: 1px 0; }
.mb-slot.hover { background: var(--accent); }

.card { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 26px; max-width: 640px;
  box-shadow: 0 1px 2px rgba(0,0,0,.04), 0 4px 12px -4px rgba(0,0,0,.06); }
.field-row { display: flex; flex-direction: column; gap: 6px; margin-bottom: 20px; }
.field-row label { font-weight: 600; font-size: 12px; color: var(--muted); }
.field-row .req { color: var(--danger); }
.field-row input[type=text], .field-row input[type=number], .field-row input[type=date],
.field-row input[type=datetime-local], .field-row select, .field-row textarea, .field-row input:not([type]) {
  padding: 10px 12px; border: 1px solid var(--border); border-radius: 6px; width: 100%; max-width: 420px;
}
.field-row textarea { min-height: 80px; }
.field-row.checkbox { flex-direction: row; align-items: center; gap: 8px; }

/* The standalone record-form page (.record-card) — not the side drawer,
   which stays single-column on purpose (see .record-drawer-body .record-form
   above). A plain .field-row's input caps at 420px so a full-width page
   never stretches a short field into an unreadably long line; inside a
   two-column grid that cap already roughly matches the column's own width,
   so it's dropped here rather than fighting the grid for space. Fields that
   want the full row — a long text area, a checkbox, a reference/list widget
   — span both columns instead of sharing one with whatever comes next. */
.record-card { max-width: 900px; }
.record-form { display: grid; grid-template-columns: 1fr 1fr; column-gap: 28px; row-gap: 20px; }
.record-form .field-row { margin-bottom: 0; }
.record-form .field-row input, .record-form .field-row select, .record-form .field-row textarea { max-width: none; }
.record-form .field-row.checkbox,
.record-form .field-row.ref-field,
.record-form .field-row:has(textarea),
.record-form .field-row:has(.attachment-widget) { grid-column: 1 / -1; }
.record-form .form-actions { grid-column: 1 / -1; }
.form-actions { display: flex; gap: 10px; margin-top: 18px; }
.error-banner { background: var(--danger-bg); color: var(--danger); padding: 10px 12px; border-radius: 6px; font-size: 13px; }
.meta-hint { color: var(--muted); font-size: 12px; margin-top: -6px; margin-bottom: 8px; }

.ref-widget { position: relative; max-width: 420px; }
.ref-widget input[type=text] { width: 100%; }
.ref-suggestions { position: absolute; top: 100%; inset-inline-start: 0; inset-inline-end: 0; z-index: 10; background: var(--panel); border: 1px solid var(--border); border-radius: 6px; margin-top: 2px; max-height: 220px; overflow-y: auto; box-shadow: 0 4px 12px rgba(0,0,0,.08); }
.ref-option { padding: 8px 10px; cursor: pointer; font-size: 13px; }
.ref-option:hover { background: var(--panel-2); }

/* ---------------------------------------------------------------- inline cell editing
   Double-click any editable list-view cell to edit it in place. */
table.grid td.editable-cell:hover { background: var(--row-hover); box-shadow: inset 0 0 0 1px var(--border); cursor: text; }
table.grid td.editable-cell.editing { padding: 5px 8px; background: var(--panel); box-shadow: inset 0 0 0 2px var(--accent); }
table.grid td.editable-cell.editing select,
table.grid td.editable-cell.editing input[type=text],
table.grid td.editable-cell.editing input[type=number],
table.grid td.editable-cell.editing input[type=date],
table.grid td.editable-cell.editing input[type=datetime-local] {
  width: 100%; border: none; background: none; padding: 0; font: inherit; color: var(--text);
}
table.grid td.editable-cell.editing .ref-widget { max-width: none; }
table.grid td.editable-cell.editing .field-row { margin: 0; }

/* First data column: a real link to the record's form view. Every other
   column no longer opens the record on a plain click at all. */
table.grid td .row-link { color: inherit; text-decoration: none; }
table.grid td .row-link:hover { text-decoration: underline; color: var(--accent); }

.field-list { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; margin-bottom: 20px; }
.field-list .row { display: flex; align-items: center; gap: 10px; padding: 11px 16px; border-bottom: 1px solid var(--border); font-size: 13px; }
.field-list .row:last-child { border-bottom: none; }
.field-list .col-name { font-weight: 600; }
.field-list .col-type { color: var(--muted); }
.field-list .row .spacer { flex: 1; }

.empty-state { color: var(--muted); padding: 30px; text-align: center; }

/* ---------------------------------------------------------------- script editor
   CodeMirror 5 "ddf" theme — reads its surface off our own tokens so it
   adapts to light/dark automatically instead of needing two stock CM
   themes; syntax colors are fixed hues chosen for contrast on both a
   light and dark --panel-2, same idea a lot of editor themes use. */
.CodeMirror.cm-s-ddf {
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
  border-radius: 8px; font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 13px;
}
.cm-s-ddf .CodeMirror-gutters { background: var(--panel-2); border-inline-end: 1px solid var(--border); }
.cm-s-ddf .CodeMirror-linenumber { color: var(--muted); }
/* Physical on purpose: CodeMirror lays out its own document left-to-right
   whatever the page direction is, so the caret border must not flip. */
.cm-s-ddf .CodeMirror-cursor { border-left: 1px solid var(--text); }
.cm-s-ddf .cm-keyword { color: #c678dd; }
.cm-s-ddf .cm-string, .cm-s-ddf .cm-string-2 { color: #98c379; }
.cm-s-ddf .cm-number { color: #d19a66; }
.cm-s-ddf .cm-comment { color: var(--muted); font-style: italic; }
.cm-s-ddf .cm-def, .cm-s-ddf .cm-variable-2 { color: #61afef; }
.cm-s-ddf .cm-property { color: #e06c75; }
.cm-s-ddf .cm-atom { color: #d19a66; }
.cm-s-ddf .cm-bracket { color: var(--muted); }
.cm-s-ddf .CodeMirror-matchingbracket { color: var(--accent) !important; font-weight: 700; }

.script-console { background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px; font-size: 13px; }
.script-console .log-line { font-family: ui-monospace, monospace; font-size: 12px; padding: 2px 0; color: var(--text); }
.script-console .log-hint { color: var(--muted); font-family: ui-monospace, monospace; font-size: 12px; }
.script-globals { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }
.script-globals code { background: var(--panel-2); border: 1px solid var(--border); border-radius: 4px; padding: 1px 6px; font-size: 11.5px; }

.rule-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin-bottom: 18px; flex-wrap: wrap; }
.rule-tabs button { appearance: none; background: none; border: none; border-bottom: 2px solid transparent; padding: 9px 4px; margin-inline-end: 18px;
  font: inherit; font-weight: 600; color: var(--muted); cursor: pointer; }
.rule-tabs button:hover { color: var(--text); }
.rule-tabs button.active { color: var(--accent); border-bottom-color: var(--accent); }

/* ---------------------------------------------------------------- script studio */
.studio-layout { display: flex; gap: 20px; align-items: flex-start; }
.studio-files { flex: 0 0 260px; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 10px; max-height: 78vh; overflow-y: auto; position: sticky; top: 16px; }
.studio-group { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); font-weight: 700; margin: 14px 6px 6px; }
.studio-group:first-child { margin-top: 4px; }
.studio-subgroup { font-size: 12px; font-weight: 600; color: var(--text); margin: 8px 6px 2px; }
.studio-empty { color: var(--muted); font-size: 12px; padding: 4px 6px 8px; }
.studio-file { display: block; width: 100%; text-align: start; appearance: none; background: none; border: none; border-radius: 6px;
  padding: 6px 8px; margin: 1px 0; font: inherit; font-size: 13px; color: var(--text); cursor: pointer; }
.studio-file:hover { background: var(--row-hover); }
.studio-file.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.studio-main { flex: 1 1 auto; min-width: 0; }
.studio-tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); margin-bottom: 14px; flex-wrap: wrap; }
.studio-tab { display: flex; align-items: center; gap: 8px; padding: 8px 6px 8px 12px; border-bottom: 2px solid transparent; cursor: pointer; font-size: 13px; color: var(--muted); }
.studio-tab:hover { color: var(--text); }
.studio-tab.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
.studio-tab .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); display: none; }
.studio-tab.dirty .dot { display: inline-block; }
.studio-tab .close { appearance: none; background: none; border: none; color: var(--muted); cursor: pointer; font-size: 15px; line-height: 1; padding: 0 4px; }
.studio-tab .close:hover { color: var(--danger); }
.studio-empty-editor { color: var(--muted); padding: 60px 20px; text-align: center; border: 1px dashed var(--border); border-radius: 8px; }

/* ---------------------------------------------------------------- business rule canvas */
.rule-track { margin-bottom: 24px; }
.rule-track-head { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); font-weight: 700; margin-bottom: 8px; }
.rule-node { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; margin-bottom: 8px; cursor: grab; }
.rule-node.dragging { opacity: .4; }
.rule-node-head { display: flex; align-items: center; gap: 8px; }
.rule-node-head .handle { color: var(--muted); flex: 0 0 auto; }
.rule-node-title { font-weight: 600; flex: 1; }
.rule-node-chips { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
.rule-track-drop { height: 8px; border-radius: 4px; }
.rule-track-drop.hover { background: var(--accent); }
.rule-popover { border-top: 1px solid var(--border); margin-top: 10px; padding-top: 10px; }

/* ---------------------------------------------------------------- toast + confirm dialog
   First overlay/notification primitives in the app — z-index sits above
   .profile-panel's 50 so a toast/dialog always renders on top of it. */
.toast-container { position: fixed; top: 16px; inset-inline-end: 16px; z-index: 200; display: flex; flex-direction: column; gap: 8px; max-width: 360px; }
.toast { background: var(--panel); color: var(--text); border: 1px solid var(--border); border-inline-start: 3px solid var(--accent);
  border-radius: 8px; padding: 10px 14px; font-size: 13.5px; box-shadow: 0 8px 24px rgba(0,0,0,.15); cursor: pointer; }
.toast-error { border-inline-start-color: var(--danger); }
.toast-success { border-inline-start-color: var(--accent); }

.confirm-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.35); z-index: 210; display: flex; align-items: center; justify-content: center; }
.confirm-dialog { max-width: 420px; width: calc(100% - 40px); box-shadow: 0 8px 24px rgba(0,0,0,.2); }
.confirm-message { margin: 0; font-size: 14.5px; color: var(--text); }

/* ---------------------------------------------------------------- accessibility
   Clean slate before this — no :focus/:focus-visible/outline rule existed
   anywhere in the file, so every interactive element relied on the
   browser's unstyled default. One global rule covers all of them; the two
   custom drag-and-drop rows get a slightly tighter offset since their own
   layout already has a border/padding the default outline-offset would sit
   awkwardly against. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.mb-row:focus-visible, .rule-node:focus-visible { outline-offset: -2px; }

/* ---------------------------------------------------------------- home dashboard */
.home-layout { display: flex; gap: 20px; align-items: flex-start; flex-wrap: wrap; }
.home-recents { flex: 1 1 320px; max-width: 480px; }
.home-recent-row { text-decoration: none; color: inherit; cursor: pointer; }
.home-shortcuts { flex: 1 1 260px; display: flex; flex-direction: column; gap: 12px; }
.home-shortcut { display: flex; flex-direction: column; gap: 2px; text-decoration: none; color: inherit; max-width: none; }
.home-shortcut:hover { border-color: var(--accent); }
.home-shortcut-icon { font-size: 22px; }
.home-shortcut-label { font-weight: 600; }

/* ---------------------------------------------------------------- GIS Studio
   List-drives-map split view — first resizable panel in the app. The
   resizer's own width is fixed; listPane's width is set inline by the drag
   handler in renderGisStudio, this file only styles it. */
/* Leaflet numbers its own panes 400-1000 (marker 600, popup 700, the
   control corners 1000). Those are meant to order the map's internals
   against each other, but .leaflet-container establishes no stacking
   context, so they compete with the whole app: a map anywhere on the page
   painted straight through .confirm-backdrop and .att-preview-backdrop,
   both of which sit at 210. Giving the container its own context keeps the
   internal ordering exactly as Leaflet intends and stops it leaking — the
   alternative, bidding the app's overlays above 1000, would have to be
   redone for every future overlay and every Leaflet plugin. */
.leaflet-container { position: relative; z-index: 0; isolation: isolate; }

.gis-layout { display: flex; align-items: stretch; height: 65vh; margin-top: 12px; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; background: var(--panel); }
.gis-list-pane { width: 320px; min-width: 220px; max-width: 70%; flex-shrink: 0; overflow-y: auto; }
.gis-resizer { width: 6px; flex-shrink: 0; cursor: col-resize; background: var(--panel-2); touch-action: none; }
.gis-resizer:hover, .gis-resizer.dragging { background: var(--accent); }
.gis-map-pane { flex: 1; min-width: 0; position: relative; }
#gisMap { position: absolute; inset: 0; }

.gis-record-row { display: flex; align-items: center; gap: 8px; padding: 7px 12px; border-bottom: 1px solid var(--border); font-size: 13px; cursor: pointer; }
.gis-record-row:hover { background: var(--row-hover); }
.gis-record-row input[type=checkbox] { flex-shrink: 0; width: auto; }
.gis-record-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gis-record-row.unplottable { color: var(--muted); cursor: default; }
.gis-record-row.unplottable:hover { background: none; }

/* ---------------------------------------------------------------- filter builder
   Composable AND/OR condition groups (first group/nesting pattern in the
   app) — recursive: .filter-group-children's left border/indent is what
   makes nesting depth visually legible, no per-depth class needed. */
.filter-group { border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; margin: 6px 0; background: var(--panel-2); }
.filter-group-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.filter-op-toggle { display: inline-flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
.fg-op-btn { appearance: none; border: none; background: var(--panel); color: var(--muted); font: inherit; font-weight: 700; font-size: 11px; letter-spacing: .03em; padding: 4px 12px; cursor: pointer; }
.fg-op-btn:hover { color: var(--text); }
.fg-op-btn.active { background: var(--accent); color: var(--onaccent); }
.filter-group-children { display: flex; flex-direction: column; gap: 6px; margin-inline-start: 4px; padding-inline-start: 12px; border-inline-start: 2px solid var(--border); }
.filter-group-actions { display: flex; gap: 8px; margin-top: 8px; }

/* ---------------------------------------------------------------- help drawer
   Global "Help & Documentation" panel (topbar icon, top right). Kept
   mounted at all times and toggled via a class, not [hidden] — the latter
   is `display:none !important` (see the accessibility note above), which
   can't transition. z-index sits between .profile-panel (50) and
   .toast-container (200) so toasts/confirm dialogs still surface above it. */
.help-trigger { background: none; border: 1px solid var(--border); color: var(--muted); width: 32px; height: 32px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; font-size: 15px; font-weight: 700; flex-shrink: 0; }
.help-trigger:hover { background: var(--panel-2); color: var(--text); border-color: var(--border-strong); }
.help-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.3); z-index: 180; opacity: 0; pointer-events: none; transition: opacity .18s ease; }
.help-backdrop.open { opacity: 1; pointer-events: auto; }
.help-drawer { position: fixed; top: 0; inset-inline-end: 0; bottom: 0; width: 420px; max-width: 92vw; background: var(--panel); color: var(--text);
  z-index: 190; box-shadow: -8px 0 24px rgba(0,0,0,.18); display: flex; flex-direction: column;
  transform: translateX(100%); transition: transform .22s ease; }
.help-drawer.open { transform: translateX(0); }
@media (prefers-reduced-motion: reduce) { .help-drawer, .help-backdrop { transition: none; } }
.help-head { display: flex; align-items: center; gap: 10px; padding: 16px 16px 12px; border-bottom: 1px solid var(--border); flex-shrink: 0; }
.help-head h2 { margin: 0; font-size: 16px; flex: 1; }
.help-close { background: none; border: none; color: var(--muted); font-size: 20px; line-height: 1; padding: 4px 6px; border-radius: 6px; }
.help-close:hover { background: var(--panel-2); color: var(--text); }
.help-search { padding: 12px 16px; border-bottom: 1px solid var(--border); flex-shrink: 0; }
.help-search input { width: 100%; padding: 8px 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--panel); color: var(--text); }
.help-body { flex: 1; overflow-y: auto; padding: 4px 16px 24px; }
.help-section-title { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); font-weight: 700; margin: 16px 0 8px; }
.help-example { display: block; width: 100%; text-align: start; background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px;
  padding: 10px 12px; margin-bottom: 8px; cursor: pointer; font: inherit; color: var(--text); }
.help-example:hover { border-color: var(--accent); }
.help-example .ex-title { font-weight: 600; font-size: 13px; margin-bottom: 3px; }
.help-example .ex-prompt { font-size: 12px; color: var(--muted); font-style: italic; }
.help-doc-row { display: block; width: 100%; text-align: start; background: none; border: none; border-bottom: 1px solid var(--border);
  padding: 10px 4px; cursor: pointer; font: inherit; color: var(--text); }
.help-doc-row:hover .help-doc-title { color: var(--accent); }
.help-doc-title { font-weight: 600; font-size: 13.5px; }
.help-doc-summary { font-size: 12px; color: var(--muted); margin-top: 2px; }
.help-doc-snippet mark, .help-doc-title mark { background: var(--accent-soft); color: var(--accent); border-radius: 2px; padding: 0 1px; }
.help-back { display: inline-flex; align-items: center; gap: 4px; background: none; border: none; color: var(--accent); font: inherit; font-weight: 600; font-size: 13px; padding: 4px 0 12px; cursor: pointer; }
.help-article h1, .help-article h2, .help-article h3 { line-height: 1.3; }
.help-article h1 { font-size: 18px; margin: 4px 0 10px; }
.help-article h2 { font-size: 15px; margin: 20px 0 8px; }
.help-article h3 { font-size: 13.5px; margin: 16px 0 6px; }
.help-article p { font-size: 13.5px; line-height: 1.55; margin: 0 0 10px; }
.help-article ul, .help-article ol { font-size: 13.5px; line-height: 1.55; margin: 0 0 10px; padding-inline-start: 20px; }
.help-article li { margin-bottom: 3px; }
.help-article code { background: var(--panel-2); border-radius: 4px; padding: 1px 5px; font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 12px; }
.help-article pre { background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; overflow-x: auto; margin: 0 0 12px; }
.help-article pre code { background: none; padding: 0; }
.help-article a { text-decoration: underline; }
.help-article table { border-collapse: collapse; font-size: 12.5px; margin: 0 0 14px; width: 100%; }
.help-article th, .help-article td { border: 1px solid var(--border); padding: 5px 8px; text-align: start; vertical-align: top; }
.help-article th { background: var(--panel-2); font-weight: 700; }
.help-article > table { display: block; overflow-x: auto; }
.help-empty { color: var(--muted); font-size: 13px; padding: 20px 4px; text-align: center; }

/* ---------------------------------------------------------------- group by
   Right-click (or the header's hover-revealed kebab) any column to fold the
   list into a tree of that column's values, optionally stacking a second
   level. .grp-* row indent reuses .filter-group-children's left-border
   language deliberately — one visual vocabulary for "this is nested". */
.btn.ghost { background: none; border: none; color: var(--accent); font-weight: 600; font-size: 12.5px; padding: 4px 6px; cursor: pointer; }
.btn.ghost:hover { text-decoration: underline; }

.group-strip { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; background: var(--accent-soft);
  border: 1px solid var(--border); border-radius: 8px; padding: 9px 12px; margin-bottom: 14px; font-size: 12.5px; }
.group-strip .gs-label { font-weight: 700; flex-shrink: 0; }
.chip-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.chip { display: flex; align-items: center; gap: 5px; background: var(--panel); border: 1px solid var(--border);
  border-radius: 20px; padding: 4px 6px 4px 10px; font-size: 12px; font-weight: 600; cursor: grab; -webkit-user-select: none; user-select: none; }
.chip.dragging { opacity: .4; }
.chip .lvl { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 10px; color: var(--muted); font-weight: 700; }
.chip .dir, .chip .rm { border: none; background: none; color: var(--muted); cursor: pointer; padding: 2px; font-size: 11px;
  line-height: 1; border-radius: 4px; }
.chip .dir:hover { background: var(--panel-2); color: var(--text); }
.chip .rm { width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; font-size: 12px; }
.chip .rm:hover { background: var(--danger); color: #fff; }

table.grid thead th .th-inner { display: flex; align-items: center; gap: 6px; }
table.grid thead th.grouped { color: var(--accent); }
table.grid thead th .sort-arrow { font-size: 10px; color: var(--accent); }
table.grid thead th .group-badge { font-size: 11px; color: var(--accent); }
table.grid thead th .kebab { opacity: 0; margin-inline-start: auto; flex-shrink: 0; width: 20px; height: 20px; border-radius: 5px;
  border: none; background: transparent; color: var(--muted); cursor: pointer; display: flex; align-items: center;
  justify-content: center; font-size: 13px; }
table.grid thead th:hover .kebab { opacity: 1; }
table.grid thead th .kebab:hover { background: var(--panel); color: var(--text); }

tr.group-row { cursor: pointer; }
tr.group-row td { padding: 8px 12px; border-bottom: 1px solid var(--border); background: var(--panel-2); }
tr.group-row:hover td { background: var(--panel-3, var(--border)); }
tr.group-row[data-depth="0"] td { background: var(--panel-2); font-weight: 600; }
.group-cell { display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: 13px; }
.chev { display: inline-flex; align-items: center; justify-content: center; width: 14px; flex-shrink: 0; color: var(--muted);
  transition: transform .12s ease; }
tr.group-row.collapsed .chev { transform: rotate(-90deg); }
.group-count { font-weight: 600; color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums;
  background: var(--panel); border-radius: 10px; padding: 1px 8px; }
.group-empty { color: var(--muted); font-style: italic; font-weight: 600; }
.depth-guide { display: inline-block; flex-shrink: 0; }

.ctx-menu { position: fixed; z-index: 400; min-width: 200px; background: var(--panel); border: 1px solid var(--border);
  border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.15); padding: 5px; font-size: 13px;
  max-height: 70vh; overflow-y: auto; }
.ctx-item { padding: 7px 9px; border-radius: 5px; cursor: pointer; color: var(--text); }
.ctx-item:hover { background: var(--accent-soft); color: var(--accent-hover); }
.ctx-sep { height: 1px; background: var(--border); margin: 5px 3px; }
.ctx-sep-label { font-size: 10.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--muted);
  padding: 6px 9px 3px; }

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

/* ---------------------------------------------------------------- flow designer
   A visual, drag-and-drop-flavored automation builder (lib/flows.mjs).
   Semantic node-type colors — deliberately the same amber/blue pair the
   group-by tree's State/Priority badges already established this
   session, plus one new accent (violet) reserved for "this step is
   user-extensible," so it reads as a distinct concept, not decoration. */
:root {
  --flow-logic: #93670a; --flow-logic-soft: #fdf1da; --flow-logic-border: #eed7a6;
  --flow-action: #33528f; --flow-action-soft: #e3ecff; --flow-action-border: #c3d3f5;
  --flow-custom: #6e4fb3; --flow-custom-soft: #efe9fb; --flow-custom-border: #d7c8f2;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --flow-logic: #e3b559; --flow-logic-soft: #3a2f10; --flow-logic-border: #5c4a1c;
    --flow-action: #9db6ea; --flow-action-soft: #1c2a42; --flow-action-border: #2f4470;
    --flow-custom: #c3aef0; --flow-custom-soft: #2a2140; --flow-custom-border: #453368;
  }
}
:root[data-theme="dark"] {
  --flow-logic: #e3b559; --flow-logic-soft: #3a2f10; --flow-logic-border: #5c4a1c;
  --flow-action: #9db6ea; --flow-action-soft: #1c2a42; --flow-action-border: #2f4470;
  --flow-custom: #c3aef0; --flow-custom-soft: #2a2140; --flow-custom-border: #453368;
}

.flow-palette { width: 240px; flex-shrink: 0; background: var(--panel); border: 1px solid var(--border); border-radius: 8px;
  padding: 8px; max-height: 78vh; overflow-y: auto; position: sticky; top: 16px; }
.pal-group-title { font-size: 10.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--muted);
  padding: 10px 6px 4px; }
.pal-group-title:first-child { padding-top: 4px; }
.pal-item { display: flex; align-items: center; gap: 9px; padding: 7px 6px; border-radius: 7px; cursor: default; }
.pal-item:hover { background: var(--panel-2); }
.pal-ic { width: 24px; height: 24px; border-radius: 6px; display: flex; align-items: center; justify-content: center;
  flex-shrink: 0; font-size: 12.5px; }
.pal-ic.logic { background: var(--flow-logic-soft); color: var(--flow-logic); }
.pal-ic.action { background: var(--flow-action-soft); color: var(--flow-action); }
.pal-ic.custom { background: var(--flow-custom-soft); color: var(--flow-custom); }
.pal-item .lbl { font-weight: 600; font-size: 12.5px; }
.pal-item .sub { font-size: 11px; color: var(--muted); }

.flow-canvas { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: center; padding: 4px 0 60px; }
.flow-connector { width: 2px; height: 22px; background: var(--border); display: flex; align-items: center; justify-content: center; position: relative; }
.flow-add-btn { width: 22px; height: 22px; border-radius: 50%; border: 1.5px solid var(--border); background: var(--panel);
  color: var(--muted); display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 14px;
  line-height: 1; flex-shrink: 0; }
.flow-add-btn:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

.flow-node { width: 100%; max-width: 480px; background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
  padding: 11px 13px; display: flex; align-items: flex-start; gap: 10px; cursor: pointer; position: relative; }
.flow-node:hover { border-color: var(--muted); }
.flow-node.selected { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.flow-node-ic { width: 28px; height: 28px; border-radius: 8px; display: flex; align-items: center; justify-content: center;
  flex-shrink: 0; font-size: 14px; }
.flow-node.trigger .flow-node-ic { background: var(--accent-soft); color: var(--accent-hover); }
.flow-node.logic .flow-node-ic { background: var(--flow-logic-soft); color: var(--flow-logic); }
.flow-node.action .flow-node-ic { background: var(--flow-action-soft); color: var(--flow-action); }
.flow-node.custom .flow-node-ic { background: var(--flow-custom-soft); color: var(--flow-custom); }
.flow-node-body { flex: 1; min-width: 0; }
.flow-node-kind { font-size: 10px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
.flow-node-title { font-weight: 700; font-size: 13px; margin-top: 1px; }
.flow-node-sub { font-size: 11.5px; color: var(--muted); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.flow-node-del { position: absolute; top: 6px; inset-inline-end: 6px; width: 20px; height: 20px; border-radius: 5px; border: none;
  background: none; color: var(--muted); cursor: pointer; opacity: 0; font-size: 12px; }
.flow-node:hover .flow-node-del { opacity: 1; }
.flow-node-del:hover { background: var(--danger-bg); color: var(--danger); }

.flow-branches { display: flex; gap: 24px; width: 100%; max-width: 480px; justify-content: center; }
.flow-branch { flex: 1; display: flex; flex-direction: column; align-items: center; }
.flow-branch-head { font-size: 10.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; padding: 2px 10px;
  border-radius: 20px; margin-bottom: 8px; }
.flow-branch-head.yes { color: var(--accent-hover); background: var(--accent-soft); }
.flow-branch-head.no { color: var(--muted); background: var(--panel-2); }
.flow-loop-body { width: 100%; max-width: 480px; border: 1.5px dashed var(--flow-logic-border); border-radius: 10px;
  padding: 12px 12px 2px; background: var(--flow-logic-soft); margin-top: 2px; display: flex; flex-direction: column; align-items: center; }
.flow-loop-label { font-size: 10.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--flow-logic);
  margin-bottom: 8px; align-self: flex-start; }

.flow-inspector { width: 320px; flex-shrink: 0; background: var(--panel); border: 1px solid var(--border); border-radius: 8px;
  padding: 14px; max-height: 78vh; overflow-y: auto; position: sticky; top: 16px; }
.flow-inspector .insp-kind { font-size: 10.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; margin-bottom: 4px; }
.flow-inspector .insp-title-input { font-weight: 700; font-size: 14.5px; border: 1px solid transparent; background: none;
  padding: 2px 4px; width: 100%; margin-bottom: 10px; border-radius: 5px; color: var(--text); }
.flow-inspector .insp-title-input:hover, .flow-inspector .insp-title-input:focus { border-color: var(--border); }
.flow-inspector .insp-empty { color: var(--muted); font-size: 12.5px; padding: 30px 8px; text-align: center; line-height: 1.6; }

/* "Build with AI" (flowOpenAiBuilder): the prompt box and the draft outline
   it returns. Logical properties throughout — the outline indents by nesting
   depth, and in Arabic that indent has to grow from the other side. */
.ai-flow-composer { display: flex; gap: 8px; align-items: flex-end; }
.ai-flow-prompt { flex: 1; min-width: 0; resize: vertical; font: inherit; padding: 8px 10px;
  border: 1px solid var(--border); border-radius: 8px; background: var(--panel-2); color: var(--text); }
.ai-flow-prompt:focus { outline: none; border-color: var(--accent); }
.ai-flow-outline { border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.ai-flow-step { padding: 7px 10px; font-size: 12.5px; border-bottom: 1px solid var(--border); }
.ai-flow-step:last-child { border-bottom: none; }
.ai-flow-step .lbl { font-weight: 600; }
.ai-flow-step .sub { color: var(--muted); }

/* The clarification block: what the model could not take from the
   description on its own. Styled as a question to answer, not as an error —
   nothing here is wrong yet, it is waiting on a decision only the author
   can make. */
.ai-flow-clarify { margin-top: 12px; border: 1px solid var(--border); border-inline-start: 3px solid var(--accent);
  border-radius: 8px; padding: 12px 14px; background: var(--panel-2); }
.ai-flow-clarify-head { font-weight: 600; font-size: 12.5px; margin-bottom: 10px; }
.ai-flow-clarify-item { margin-bottom: 12px; }
.ai-flow-clarify-item:last-of-type { margin-bottom: 0; }
.ai-flow-clarify-item .q { font-size: 13px; margin-bottom: 3px; }
.ai-flow-clarify-item .sub { font-size: 11.5px; color: var(--muted); margin-bottom: 6px; }
.ai-flow-clarify-item .tag { text-transform: uppercase; letter-spacing: .04em; font-size: 10px; font-weight: 700;
  border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px; margin-inline-end: 6px; }
.ai-flow-answer { width: 100%; font: inherit; font-size: 12.5px; padding: 6px 9px; border: 1px solid var(--border);
  border-radius: 6px; background: var(--panel); color: var(--text); }
.ai-flow-answer:focus { outline: none; border-color: var(--accent); }

/* ---------------------------------------------------- list row selection */
/* The leading checkbox column on every list view. Sized to its content so
   it can't take width from the data columns, and explicitly width:auto on
   the input because form inputs elsewhere are stretched to 100%. */
table.grid th.sel, table.grid td.sel { width: 1%; white-space: nowrap; padding-inline-end: 0; }
table.grid th.sel input[type=checkbox], table.grid td.sel input[type=checkbox] {
  width: auto; margin: 0; cursor: pointer; accent-color: var(--accent); vertical-align: middle;
}
table.grid tbody tr.row-selected { background: var(--accent-soft); }
table.grid tbody tr.row-selected:hover { background: var(--accent-soft); }
.ctx-item.disabled { color: var(--muted); cursor: default; }
.ctx-item.disabled:hover { background: none; color: var(--muted); }

/* ---------------------------------------------------------------- ask data
   The "Ask about Data" feature (lib/ai-ask.mjs): the model writes and runs
   its own read-only SQL against AI-scoped ddf_data.* tables to answer a
   question. Two surfaces — a floating bottom-right widget (compact by
   default, expandable to fill the screen; .help-head/.help-close still
   shared with the help drawer) and a full page at #/system/ask-data
   (.ask-data-page). */

/* The trigger: a round FAB, bottom right, independent of the topbar so
   it's reachable from any scroll position — the spot users already expect
   a chat-style assistant to live. z-index sits above ordinary page content
   but *below* .help-backdrop (180) — the help drawer is modal, and a FAB
   floating above its dim would be clickable through it. */
.ask-fab { position: fixed; inset-inline-end: 24px; bottom: 24px; z-index: 175; width: 52px; height: 52px; border-radius: 50%;
  background: var(--accent); color: var(--onaccent); border: none; box-shadow: 0 6px 20px rgba(0,0,0,.25);
  display: flex; align-items: center; justify-content: center; font-size: 22px; line-height: 1; cursor: pointer;
  transition: transform .15s ease, box-shadow .15s ease; }
.ask-fab:hover { transform: scale(1.06); box-shadow: 0 8px 26px rgba(0,0,0,.32); }
.ask-fab.active { transform: scale(.94); }
@media (max-width: 640px) { .ask-fab { inset-inline-end: 16px; bottom: 16px; } }

/* The panel: a compact card anchored just above the FAB by default,
   expandable in place to fill the viewport (.fullscreen). Both states are
   expressed as explicit inset/width/height — never "auto" — so the
   transition between them animates instead of snapping. Unlike the help
   drawer this one is non-modal, so it sits *below* .help-backdrop (180):
   opening the modal help drawer must dim and cover it, not sit under it. */
.ask-drawer { position: fixed; inset-inline-end: 24px; bottom: 88px; width: 380px; height: 560px;
  max-width: calc(100vw - 48px); max-height: calc(100vh - 112px);
  background: var(--panel); color: var(--text); z-index: 176; border-radius: 16px; overflow: hidden;
  box-shadow: 0 16px 48px rgba(0,0,0,.28); display: flex; flex-direction: column;
  opacity: 0; transform: translateY(16px) scale(.96); transform-origin: bottom right; pointer-events: none;
  transition: opacity .16s ease, transform .16s ease, inset-inline-end .22s ease, bottom .22s ease,
    width .22s ease, height .22s ease, border-radius .22s ease; }
/* transform-origin takes no logical keyword, so the grow-from-the-FAB
   animation needs an explicit mirror in RTL. */
[dir="rtl"] .ask-drawer { transform-origin: bottom left; }
.ask-drawer.open { opacity: 1; transform: none; pointer-events: auto; }
/* 100dvh, not 100vh: on mobile the latter is measured against the
   viewport with browser chrome retracted, so the composer sits under it. */
.ask-drawer.fullscreen { inset-inline-end: 0; bottom: 0; width: 100vw; height: 100dvh; max-width: none; max-height: none; border-radius: 0; }
/* Fullscreen is for more thread history, not wider lines — the thread keeps a
   readable measure and centres in the extra space. Only the thread: the sub
   and composer carry full-bleed divider borders, which a max-width would
   shrink into rules floating mid-screen. */
.ask-drawer.fullscreen .ask-data-thread { width: 100%; max-width: 780px; margin-inline: auto; }
.ask-drawer.fullscreen .ask-bubble { max-width: 78%; }
@media (max-width: 640px) { .ask-drawer { inset-inline-end: 12px; bottom: 84px; width: calc(100vw - 24px); } }
@media (prefers-reduced-motion: reduce) { .ask-drawer, .ask-fab { transition: none; } }
.ask-drawer .help-head h2 { font-size: 15px; }
.ask-data-sub { color: var(--muted); font-size: 12.5px; margin: 0; padding: 12px 16px; border-bottom: 1px solid var(--border); flex-shrink: 0; }
.ask-data-thread { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 12px; }
.ask-bubble { max-width: 92%; }
.ask-bubble.user { align-self: flex-end; }
.ask-bubble.assistant { align-self: flex-start; }
.ask-bubble .ask-bubble-inner { border-radius: 12px; padding: 10px 14px; font-size: 13.5px; line-height: 1.5; white-space: pre-wrap; }
.ask-bubble.user .ask-bubble-inner { background: var(--accent); color: var(--onaccent); }
.ask-bubble.assistant .ask-bubble-inner { background: var(--panel-2); color: var(--text); }
.ask-bubble.assistant .ask-bubble-inner code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 12px; }

.ask-viz { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
.ask-viz-kpi { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 8px 12px; }
.ask-viz-kpi .kpi-title { font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.ask-viz-kpi .kpi-value { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; margin-top: 2px; }
.ask-viz-bar { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; }
.ask-viz-bar .bar-title { font-size: 11px; font-weight: 700; color: var(--muted); margin-bottom: 8px; }
.ask-viz-bar .bar-row { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; font-size: 12px; }
.ask-viz-bar .bar-label { width: 100px; flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); }
.ask-viz-bar .bar-track { flex: 1; background: var(--panel-2); border-radius: 4px; height: 14px; overflow: hidden; display: block; }
.ask-viz-bar .bar-fill { background: var(--accent); height: 100%; border-radius: 4px; display: block; }
/* Physical on purpose: this is a numeric column, and digits stay
   right-aligned in an RTL layout too — direction is about text, not
   figures. */
.ask-viz-bar .bar-value { width: 44px; text-align: right; flex-shrink: 0; font-variant-numeric: tabular-nums; color: var(--text); font-weight: 600; }
.ask-viz-table { border: 1px solid var(--border); border-radius: 8px; overflow: hidden; font-size: 12px; }
.ask-viz-table table { width: 100%; border-collapse: collapse; }
.ask-viz-table th, .ask-viz-table td { padding: 5px 9px; border-bottom: 1px solid var(--border); text-align: start; }
.ask-viz-table th { background: var(--panel-2); color: var(--muted); font-weight: 700; }
.ask-viz-table tr:last-child td { border-bottom: none; }
/* .ask-viz is a flex column, so the map needs a fixed pixel height — a
   percentage collapses to zero and Leaflet renders nothing into it. */
.ask-viz-map { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; }
.ask-viz-map .map-title { font-size: 11px; font-weight: 700; color: var(--muted); margin-bottom: 8px; }
.ask-viz-map .map-canvas { height: 260px; border-radius: 6px; overflow: hidden; background: var(--panel-2); }
.ask-map-popup strong { display: block; margin-bottom: 4px; }
.ask-map-popup table { border-collapse: collapse; margin-bottom: 4px; }
.ask-map-popup th, .ask-map-popup td { padding: 1px 0; text-align: start; vertical-align: top; font-weight: 400; }
.ask-map-popup th { color: var(--muted); padding-inline-end: 10px; white-space: nowrap; }

.ask-queries { margin-top: 8px; }
.ask-queries summary { cursor: pointer; font-size: 11px; color: var(--muted); font-weight: 600; }
.ask-queries summary:hover { color: var(--text); }
.ask-query { margin-top: 6px; background: var(--panel); border: 1px solid var(--border); border-radius: 6px; padding: 7px 9px; }
.ask-query .aq-purpose { font-size: 11.5px; color: var(--muted); margin-bottom: 4px; }
.ask-query code { display: block; font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 11px; white-space: pre-wrap; word-break: break-word; color: var(--text); }
.ask-query .aq-meta { font-size: 10.5px; color: var(--muted); margin-top: 4px; }
.ask-query .aq-error { color: var(--danger); font-size: 11.5px; margin-top: 4px; }

.ask-data-composer { display: flex; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--border); flex-shrink: 0; }
.ask-data-composer textarea { flex: 1; resize: none; padding: 8px 10px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--panel); color: var(--text); font: inherit; font-size: 13px; }
.ask-data-composer .btn { align-self: flex-end; flex-shrink: 0; }

/* The full-page variant: same thread/composer, framed as a card that fills
   the main panel (the shell's .main scrolls; the thread scrolls inside). */
.ask-data-page { display: flex; flex-direction: column; height: 100%; min-height: 0; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.ask-data-page .ask-data-thread { background: var(--bg, transparent); }
.ask-data-page .ask-data-composer textarea { background: var(--panel-2); }

/* ------------------------------------------------------ attachments panel */
.attachments-panel { max-width: none; }
.attachment-list { display: flex; flex-direction: column; gap: 6px; }
.attachment-row { display: flex; align-items: center; gap: 10px; padding: 7px 9px; border: 1px solid var(--border);
  border-radius: 7px; background: var(--panel-2); }
.attachment-row .att-thumb { width: 34px; height: 34px; flex-shrink: 0; display: flex; align-items: center;
  justify-content: center; font-size: 18px; background: var(--panel); border-radius: 5px; overflow: hidden; }
.attachment-row .att-thumb img { width: 100%; height: 100%; object-fit: cover; }
.attachment-row .att-name { font-size: 13px; font-weight: 600; word-break: break-all; }
.attachment-row .att-sub { font-size: 11.5px; color: var(--muted); }
.attachment-row .att-rm:hover { color: var(--danger); text-decoration: none; }
.attachment-panel-empty { color: var(--muted); font-size: 12px; }

/* ------------------------------------------------- attachment preview
   Clicking a thumbnail opens the file itself rather than only proving it
   uploaded. Anything openable says so on hover; the modal sizes to the
   viewport and lets the media inside it scale down rather than overflow. */
.att-openable { cursor: zoom-in; }
.att-openable:hover { outline: 2px solid var(--accent); outline-offset: 1px; border-radius: 6px; }

.att-preview-backdrop { position: fixed; inset: 0; z-index: 210; background: rgba(0,0,0,.62);
  display: flex; align-items: center; justify-content: center; padding: 24px; }
.att-preview { background: var(--panel); color: var(--text); border-radius: 12px; overflow: hidden;
  display: flex; flex-direction: column; max-width: min(1100px, 100%); max-height: 100%;
  box-shadow: 0 24px 64px rgba(0,0,0,.4); }
.att-preview-head { display: flex; align-items: center; gap: 10px; padding: 12px 14px;
  border-bottom: 1px solid var(--border); flex-shrink: 0; }
.att-preview-name { font-weight: 600; font-size: 13.5px; word-break: break-all; }
.att-preview-sub { font-size: 11.5px; color: var(--muted); white-space: nowrap; }
.att-preview-body { padding: 14px; overflow: auto; display: flex; align-items: center; justify-content: center; min-height: 0; }
/* Scale to fit the dialog, never past the file's own size — a 64px icon
   blown up to fill 1100px is worse than the icon. */
.att-preview-media { max-width: 100%; max-height: calc(100vh - 160px); object-fit: contain; }
.att-preview-audio { width: min(420px, 100%); }
.att-preview-frame { width: min(900px, 88vw); height: calc(100vh - 180px); border: none; background: var(--panel-2); }
.att-preview-text { margin: 0; width: min(900px, 88vw); max-height: calc(100vh - 180px); overflow: auto;
  font-size: 12.5px; line-height: 1.55; white-space: pre-wrap; word-break: break-word;
  background: var(--panel-2); border-radius: 8px; padding: 12px; }
.att-preview-none { text-align: center; color: var(--muted); font-size: 13px; padding: 40px 24px; max-width: 420px; }
.att-preview-none .ic { font-size: 40px; margin-bottom: 10px; }
@media (max-width: 640px) { .att-preview-backdrop { padding: 10px; } }
.attachment-widget input[type=file] { width: auto; }
.attachments-panel input[type=file] { width: auto; }

/* --------------------------------------------------------------- ask voice
   The mic sits before the input and the speaker after it, so the composer
   reads left to right as "speak → type → send → hear". Recording state is a
   colour change plus a pulse: on a factory floor the screen is often at arm's
   length, and a static icon change is easy to miss. */
.ask-mic, .ask-speaker { flex-shrink: 0; align-self: flex-end; padding: 8px 10px; font-size: 15px; line-height: 1; }
.ask-mic.recording { background: var(--danger); border-color: var(--danger); color: #fff; animation: askPulse 1.4s ease-in-out infinite; }
.ask-speaker.on { background: var(--accent); border-color: var(--accent); color: var(--onaccent); }
@keyframes askPulse { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }
@media (prefers-reduced-motion: reduce) { .ask-mic.recording { animation: none; } }

/* ------------------------------------------------------- right-to-left
   Layout itself needs no rules here: the stylesheet uses logical
   properties throughout (margin-inline-start, inset-inline-end, text-align:
   start), so `dir="rtl"` on <html> flips it. What logical properties cannot
   reach is glyphs baked into the markup — the nav tree's ▶ disclosure
   marker and the ← / → in pagination and "open record" links are literal
   characters, so they point the wrong way in Arabic until mirrored. */
/* Only the *closed* caret is directional. `transform` is a single
   property, so a blanket mirror here would clobber the rotations that
   express open/collapsed state — hence one rule per state, and the open
   nav caret is left alone because a caret pointing down means the same
   thing in both directions. */
[dir="rtl"] .nav-branch .chev { transform: scaleX(-1); }
[dir="rtl"] tr.group-row.collapsed .chev { transform: rotate(90deg); }

/* The wordmark is a logo, not a sentence. "D<b>DF</b>" is two bidi runs, so
   in an RTL paragraph the browser reorders them and it renders "DFD" —
   correct per the bidi algorithm, wrong as a brand mark. */
[dir="rtl"] .topbar .brand,
[dir="rtl"] .login-logo { direction: ltr; }

/* Chrome that mixes Latin/digits with Arabic — the change-set name, the
   record count, the rows-per-page select — takes its direction from its own
   first strong character instead of inheriting the page's. Without this,
   "14 records" renders as "records 14" and a change set called
   "Change Set 2026-08-25 16:26" has its words transposed. */
[dir="rtl"] .page-head .count,
[dir="rtl"] #changesetName,
[dir="rtl"] #pagerLimit,
[dir="rtl"] #pagerLimit option { unicode-bidi: plaintext; }

/* Identifiers stay left-to-right inside Arabic text. Without this a cell
   reading "WO100003" next to Arabic reorders under the bidi algorithm and
   a reference like NG-S-102 can render with its parts transposed — wrong,
   and hard to spot unless you read Arabic. */
[dir="rtl"] .mono,
[dir="rtl"] code,
[dir="rtl"] table.grid td.ltr-cell { direction: ltr; unicode-bidi: embed; text-align: start; }

/* ----------------------------------------------------------- table merge
   The merge screen. Colour carries the same meaning as the report's own
   severities — a blocked row is a decision waiting, not an error — so the
   left border is the only signal that changes, and the layout stays still
   while a person works through the list. */
.mg-map { border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.mg-row { display: grid; grid-template-columns: 200px 24px 190px 1fr; gap: 10px; align-items: center;
  padding: 9px 12px; border-bottom: 1px solid var(--border); border-inline-start: 3px solid transparent; }
.mg-row:last-child { border-bottom: none; }
.mg-row.warn { border-inline-start-color: var(--accent); }
.mg-row.block { border-inline-start-color: var(--danger); }
.mg-row .mg-src strong { font-size: 13px; }
.mg-row .mg-src .sub, .mg-row .mg-note { font-size: 11.5px; color: var(--muted); }
.mg-row .mg-arrow { color: var(--muted); text-align: center; }
.mg-row select { width: 100%; font-size: 12.5px; padding: 5px 7px; }
.mg-row .verdict { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10.5px;
  border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px; margin-inline-end: 5px; }

.mg-blockers { border-inline-start: 3px solid var(--danger); }
.mg-blocker { padding: 10px 0; border-bottom: 1px solid var(--border); }
.mg-blocker:last-child { border-bottom: none; }
.mg-blocker-head { font-size: 13px; margin-bottom: 3px; }
.mg-blocker .sub { font-size: 12px; color: var(--muted); line-height: 1.5; }
.mg-blocker .verdict { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10.5px;
  border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px; }
.mg-action { margin-top: 8px; padding: 9px 11px; background: var(--panel-2); border-radius: 8px; }
.mg-route { display: flex; align-items: center; gap: 8px; margin-top: 6px; font-size: 12.5px; flex-wrap: wrap; }
.mg-route code { background: var(--panel); border: 1px solid var(--border); border-radius: 4px; padding: 1px 6px; }
.mg-route select, .mg-route input { font-size: 12.5px; padding: 5px 7px; min-width: 170px; }

.mg-conflicts { margin-top: 6px; }
.mg-conflict { display: flex; gap: 12px; align-items: center; font-size: 12px; padding: 4px 0;
  border-bottom: 1px solid var(--border); flex-wrap: wrap; }
.mg-conflict:last-child { border-bottom: none; }
.mg-conflict code { font-size: 11.5px; color: var(--muted); }

.mg-facts { display: flex; flex-direction: column; gap: 5px; font-size: 12.5px; }
@media (max-width: 900px) { .mg-row { grid-template-columns: 1fr; gap: 4px; } .mg-row .mg-arrow { display: none; } }
