/* =========================================================================
   BaladiApp — the Arabic design layer
   =========================================================================
   Loaded AFTER Metronic (styles.css) and after arabic-rtl.css, so it has the
   last word. It is built almost entirely on Metronic's own CSS custom
   properties, which means existing pages inherit it without being touched:
   Metronic's components read --primary, --background, --border and friends,
   so redefining those re-skins every .kt-* component at once.

   Metronic (Tailwind v4) declares its semantic tokens in `:root` and
   overrides them in `.dark`, which an inline script puts on <html>. This file
   follows the same shape, so light and dark are two token sets rather than
   two stylesheets.

   WHAT IS OVERRIDDEN, and where:
     Metronic tokens (:root and .dark below) ...... --background --foreground
       --card --card-foreground --popover --popover-foreground --primary
       --primary-foreground --secondary --secondary-foreground --muted
       --muted-foreground --accent --accent-foreground --destructive --border
       --input --ring --radius
     arabic-rtl.css's type scale .................. --text-2xs … --text-base
     Own namespace ................................ --baladi-*

   COLOUR, and one correction to the brief
     The brand accent is Flat UI "Belize Hole" #2980b9. Measured against
     white it is 4.30:1 — enough for WCAG 1.4.11 non-text contrast (3:1) but
     NOT for text (4.5:1). So the accent is split by use:
       --baladi-accent        #2980b9  focus ring, icons, active-nav bar, charts
       --baladi-accent-strong #21709f  anything with text on or in it:
                                       links, and solid primary buttons
                                       (white on #21709f = 5.41:1)
       --baladi-accent-light  #3498db  hover tints
       --baladi-link                   link text, redefined per mode because
                                       #21709f on the dark surfaces is 2.96:1
     Every pair in tests/test_ui_contrast.py is measured, not assumed.
     No orange anywhere in the UI chrome; #f39c12 appears only as the
     "partially paid" status tint. The logo keeps its own colours.
   ========================================================================= */

/* =========================================================================
   1. Tokens — light (the default)
   ========================================================================= */

:root {
  /* ---- brand ---- */
  --baladi-accent: #2980b9;
  --baladi-accent-strong: #21709f;
  --baladi-accent-light: #3498db;
  --baladi-accent-tint: #e8f1f8;
  --baladi-accent-on-tint: #21709f;
  /* Link text. Its own token because the right value differs by mode, and
     one token means one rule below instead of a .dark override that is easy
     to forget. tests/test_ui_contrast.py measures this, not the accent. */
  --baladi-link: #21709f;

  /* ---- neutral base. Slightly cool, slightly off-white: a page of forms
          at 100% white is tiring to read all day. ---- */
  --baladi-page: #f5f7f9;
  --baladi-surface: #ffffff;
  --baladi-surface-2: #f0f3f6;
  --baladi-ink: #1a1d23;
  --baladi-ink-muted: #5a6472;
  --baladi-line: #d8dde4;
  --baladi-line-strong: #c2c9d2;

  /* ---- status: a light tint of the hue, with dark text of the same hue ---- */
  --baladi-paid-bg: #e8f6ee;
  --baladi-paid-fg: #166534;
  --baladi-overdue-bg: #fcebe9;
  --baladi-overdue-fg: #9b2c21;
  --baladi-partial-bg: #fdf3e2;
  --baladi-partial-fg: #8a5200;
  --baladi-draft-bg: #eef0f3;
  --baladi-draft-fg: #44505f;
  --baladi-info-bg: #e8f1f8;
  --baladi-info-fg: #21709f;

  /* ---- type ---- */
  --baladi-font-body: 'IBM Plex Sans Arabic', 'Segoe UI', system-ui, sans-serif;
  --baladi-font-heading: 'Readex Pro', 'IBM Plex Sans Arabic', system-ui, sans-serif;

  --baladi-text-body: 0.9375rem;   /* 15px */
  --baladi-text-form: 1rem;        /* 16px — stops iOS zooming on focus */
  --baladi-text-table: 0.875rem;   /* 14px */
  --baladi-text-small: 0.8125rem;  /* 13px — the floor; nothing smaller */
  --baladi-h1: 1.75rem;            /* 28px */
  --baladi-h2: 1.5rem;             /* 24px */
  --baladi-h3: 1.25rem;            /* 20px */
  --baladi-leading-body: 1.75;
  --baladi-leading-heading: 1.4;

  --baladi-control-height: 44px;

  /* ---- Metronic's own tokens, repointed ---- */
  --background: var(--baladi-page);
  --foreground: var(--baladi-ink);
  --card: var(--baladi-surface);
  --card-foreground: var(--baladi-ink);
  --popover: var(--baladi-surface);
  --popover-foreground: var(--baladi-ink);
  --primary: var(--baladi-accent-strong);
  --primary-foreground: #ffffff;
  --secondary: var(--baladi-surface-2);
  --secondary-foreground: var(--baladi-ink);
  --muted: var(--baladi-surface-2);
  --muted-foreground: var(--baladi-ink-muted);
  --accent: var(--baladi-accent-tint);
  --accent-foreground: var(--baladi-accent-strong);
  --destructive: #c0392b;
  --destructive-foreground: #ffffff;
  --border: var(--baladi-line);
  --input: var(--baladi-line-strong);
  --ring: var(--baladi-accent);
  --radius: 0.375rem;

  /* arabic-rtl.css inflated these for Cairo; IBM Plex Sans Arabic has a
     larger x-height, so they go back to an honest scale. */
  --text-2xs: var(--baladi-text-small);
  --text-xs: var(--baladi-text-small);
  --text-sm: var(--baladi-text-table);
  --text-base: var(--baladi-text-body);
}

/* =========================================================================
   2. Tokens — dark
   =========================================================================
   Not an inversion. The surfaces are a desaturated blue-grey rather than
   Metronic's near-black zinc, the text stops short of pure white (14.5:1 is
   plenty and less harsh), and the links lighten to #5dade2 because #3498db
   on these surfaces is fine but #2980b9 would not be.
   ========================================================================= */

.dark {
  --baladi-accent: #3498db;
  --baladi-accent-strong: #21709f;      /* buttons keep the same solid fill */
  --baladi-accent-light: #5dade2;
  --baladi-accent-tint: #132330;
  --baladi-accent-on-tint: #5dade2;
  --baladi-link: #5dade2;

  --baladi-page: #161920;
  --baladi-surface: #1b1f27;
  --baladi-surface-2: #22262e;
  --baladi-ink: #e6e9ef;
  --baladi-ink-muted: #a3adba;
  --baladi-line: #2d333d;
  --baladi-line-strong: #3a4250;

  --baladi-paid-bg: #13291d;
  --baladi-paid-fg: #6ee7a8;
  --baladi-overdue-bg: #2c1614;
  --baladi-overdue-fg: #f3a79c;
  --baladi-partial-bg: #2b1f0d;
  --baladi-partial-fg: #f8c777;
  --baladi-draft-bg: #22262e;
  --baladi-draft-fg: #b6c0cd;
  --baladi-info-bg: #132330;
  --baladi-info-fg: #8ecbf0;

  --background: var(--baladi-page);
  --foreground: var(--baladi-ink);
  --card: var(--baladi-surface);
  --card-foreground: var(--baladi-ink);
  --popover: var(--baladi-surface);
  --popover-foreground: var(--baladi-ink);
  --primary: var(--baladi-accent-strong);
  --primary-foreground: #ffffff;
  --secondary: var(--baladi-surface-2);
  --secondary-foreground: var(--baladi-ink);
  --muted: var(--baladi-surface-2);
  --muted-foreground: var(--baladi-ink-muted);
  --accent: var(--baladi-accent-tint);
  --accent-foreground: var(--baladi-accent-light);
  --destructive: #e05a4b;
  --destructive-foreground: #1a1d23;
  --border: var(--baladi-line);
  --input: var(--baladi-line-strong);
  --ring: var(--baladi-accent);
}

/* =========================================================================
   3. Type
   ========================================================================= */

html,
body {
  font-family: var(--baladi-font-body);
  font-size: var(--baladi-text-body);
  line-height: var(--baladi-leading-body);
  /* Western digits, always. "locl" off stops the font substituting
     Arabic-Indic forms for lang="ar"; lining-nums keeps them full height. */
  font-feature-settings: 'locl' 0;
  font-variant-numeric: lining-nums;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, h5, h6,
.kt-card-title,
.baladi-heading {
  font-family: var(--baladi-font-heading);
  line-height: var(--baladi-leading-heading);
  font-weight: 600;
  letter-spacing: 0;
}

h1, .baladi-h1 { font-size: var(--baladi-h1); }
h2, .baladi-h2 { font-size: var(--baladi-h2); }
h3, .baladi-h3 { font-size: var(--baladi-h3); }
.kt-card-title { font-size: var(--baladi-h3); font-weight: 500; }

.baladi-small,
small {
  font-size: var(--baladi-text-small);
  line-height: 1.6;
}

/* Numbers: one alignment everywhere, so columns line up.
   `.baladi-num` on any element; `.baladi-num-field` on inputs that hold a
   phone number, a national ID, or a receipt / property number — those read
   left-to-right but sit on the right of an RTL form. */
.baladi-num,
.kt-table td.baladi-num,
.baladi-num-field {
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: 'locl' 0, 'tnum' 1;
}

.baladi-num-field {
  direction: ltr;
  text-align: right;
}

/* Display-only isolation, emitted by the `ltr` template filter. <bdi> already
   isolates the value from the surrounding bidi run; this adds the numerals
   and keeps it inline. Use it for anything read left-to-right inside Arabic
   prose: phone numbers, national IDs, bill / receipt / property numbers,
   IBANs, emails, URLs. */
.baladi-ltr {
  direction: ltr;
  unicode-bidi: isolate;
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: 'locl' 0, 'tnum' 1;
}

/* =========================================================================
   4. Forms
   =========================================================================
   44px controls, labels above the field, a placeholder that shows an example
   rather than repeating the label, a focus ring you can actually see, and
   error text under the field it belongs to.
   ========================================================================= */

.baladi-field {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
}

.baladi-label,
.baladi-field > label {
  font-size: var(--baladi-text-body);
  font-weight: 500;
  color: var(--foreground);
  line-height: 1.5;
}

.baladi-label-required::after {
  content: ' *';
  color: var(--destructive);
  font-weight: 600;
}

.baladi-hint {
  font-size: var(--baladi-text-small);
  color: var(--muted-foreground);
  line-height: 1.6;
}

.baladi-error {
  font-size: var(--baladi-text-small);
  color: var(--destructive);
  line-height: 1.6;
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

.kt-input,
.kt-select,
input[type='text'],
input[type='email'],
input[type='password'],
input[type='number'],
input[type='tel'],
input[type='search'],
input[type='date'],
select,
textarea {
  min-height: var(--baladi-control-height);
  font-family: var(--baladi-font-body);
  font-size: var(--baladi-text-form);
  line-height: 1.5;
  padding: 0.5rem 0.75rem;
  color: var(--foreground);
  background-color: var(--card);
  border: 1px solid var(--input);
  border-radius: var(--radius);
  transition: border-color 120ms ease, box-shadow 120ms ease;
}

textarea {
  min-height: calc(var(--baladi-control-height) * 2);
  line-height: var(--baladi-leading-body);
}

.kt-input::placeholder,
input::placeholder,
textarea::placeholder {
  /* Readable, not decorative: the placeholder carries an example, so it has
     to be legible rather than a hint of grey. */
  color: var(--muted-foreground);
  opacity: 1;
}

.kt-input:focus,
.kt-input:focus-visible,
.kt-select:focus,
input:focus,
input:focus-visible,
select:focus,
textarea:focus,
textarea:focus-visible {
  outline: none;
  border-color: var(--baladi-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--baladi-accent) 28%, transparent);
}

.kt-input[aria-invalid='true'],
.baladi-field-error .kt-input,
.baladi-field-error input,
.baladi-field-error select,
.baladi-field-error textarea {
  border-color: var(--destructive);
}

.baladi-field-error .kt-input:focus,
.baladi-field-error input:focus {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--destructive) 26%, transparent);
}

/* Disabled must stay readable — a greyed-out field whose value cannot be read
   is a field whose value cannot be checked. */
.kt-input:disabled,
input:disabled,
select:disabled,
textarea:disabled {
  background-color: var(--muted);
  color: var(--foreground);
  opacity: 1;
  cursor: not-allowed;
  border-style: dashed;
}

/* Checkbox and radio: big enough to hit, accent-coloured when on. */
input[type='checkbox'],
input[type='radio'],
.kt-checkbox,
.kt-radio {
  width: 1.125rem;
  height: 1.125rem;
  accent-color: var(--baladi-accent-strong);
  cursor: pointer;
}

input[type='checkbox']:focus-visible,
input[type='radio']:focus-visible {
  outline: 2px solid var(--baladi-accent);
  outline-offset: 2px;
}

/* A row of radios, as a group of choices rather than a column of fields.
   Used by the employee form, where the choice is "a person from the register"
   or "a new person" and the two have to read as one question. Plain CSS and
   not `flex items-center gap-2.5`: the Tailwind bundle is prebuilt and
   pruned, so a utility that happens to be compiled in today is not a
   dependency worth taking. */
.baladi-radio-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
  align-items: center;
}

.baladi-radio {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
}

/* "15 مقعداً — 13 مشغولاً": the one line the council page exists for. Larger
   than body text because it is the answer somebody opened the page to read,
   and the two numbers are what the warning under it refers to. */
.baladi-seats {
  font-size: 1.125rem;
  margin-bottom: 0.75rem;
}

.baladi-seats strong {
  font-size: 1.5rem;
  font-weight: 600;
  color: var(--baladi-accent-strong);
}

/* =========================================================================
   5. Buttons
   =========================================================================
   Primary is a solid accent with white text. Secondary is an outline, never
   grey text on a grey fill. Disabled keeps its text legible.
   ========================================================================= */

.kt-btn {
  min-height: var(--baladi-control-height);
  font-family: var(--baladi-font-body);
  font-size: var(--baladi-text-body);
  font-weight: 500;
  line-height: 1.4;
  border-radius: var(--radius);
  padding: 0.5rem 1.125rem;
  gap: 0.5rem;
  transition: background-color 120ms ease, border-color 120ms ease,
    color 120ms ease, box-shadow 120ms ease;
}

.kt-btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--baladi-accent) 36%, transparent);
}

.kt-btn-sm {
  min-height: 36px;
  font-size: var(--baladi-text-small);
  padding: 0.25rem 0.75rem;
}

.kt-btn-primary,
.baladi-btn-primary {
  background-color: var(--baladi-accent-strong);
  border: 1px solid var(--baladi-accent-strong);
  color: #ffffff;
}
.kt-btn-primary:hover,
.baladi-btn-primary:hover {
  background-color: #1d6391;
  border-color: #1d6391;
  color: #ffffff;
}

.kt-btn-outline,
.kt-btn-secondary,
.baladi-btn-secondary {
  background-color: transparent;
  border: 1px solid var(--input);
  color: var(--foreground);
}
.kt-btn-outline:hover,
.kt-btn-secondary:hover,
.baladi-btn-secondary:hover {
  background-color: var(--baladi-accent-tint);
  border-color: var(--baladi-accent);
  color: var(--accent-foreground);
}

.kt-btn-danger,
.baladi-btn-danger {
  background-color: var(--destructive);
  border: 1px solid var(--destructive);
  color: var(--destructive-foreground);
}
.kt-btn-danger:hover,
.baladi-btn-danger:hover {
  background-color: #a53124;
  border-color: #a53124;
}

.kt-btn-light {
  background-color: var(--secondary);
  border: 1px solid var(--border);
  color: var(--foreground);
}
.kt-btn-light:hover {
  background-color: var(--baladi-accent-tint);
  border-color: var(--baladi-accent);
  color: var(--accent-foreground);
}

.kt-btn:disabled,
.kt-btn[disabled],
.kt-btn.disabled {
  /* Still readable: a dimmed label on a dimmed fill tells the user nothing
     about what the button would have done. */
  background-color: var(--muted);
  border-color: var(--border);
  color: var(--muted-foreground);
  opacity: 1;
  cursor: not-allowed;
  box-shadow: none;
}

/* Links */
/* Content links. The :where() wrapper keeps the whole selector at zero
   specificity for the chrome exclusion, so the frame's own rules in §10 win
   without having to out-specify a three-:not() chain — which is exactly the
   bug this replaces: the light link colour was reaching the dark footer at
   3.28:1. */
a:not(.kt-btn):not(.kt-menu-link):not(.kt-tab-toggle):not(:where(
  .kt-sidebar, .kt-sidebar *, .kt-header, .kt-header *, .kt-footer, .kt-footer *
)) {
  color: var(--baladi-link);
  text-underline-offset: 3px;
}
a:not(.kt-btn):not(.kt-menu-link):not(.kt-tab-toggle):not(:where(
  .kt-sidebar, .kt-sidebar *, .kt-header, .kt-header *, .kt-footer, .kt-footer *
)):hover {
  color: var(--baladi-accent);
  text-decoration: underline;
}

/* =========================================================================
   6. Cards, tables, badges, alerts
   ========================================================================= */

.kt-card {
  background-color: var(--card);
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) + 2px);
  box-shadow: none;
}

.kt-card-header {
  /* Metronic hard-codes `background-color: #fafafa` here — a literal, not a
     variable, so .dark could not reach it and every card header in the console
     and the SaaS panel rendered as a near-white bar in dark mode with pale
     text on it. It is the only literal light background in the bundle. */
  background-color: var(--baladi-surface-2);
  border-bottom: 1px solid var(--border);
  padding: 1rem 1.25rem;
  min-height: 0;
}

.kt-card-content { padding: 1.25rem; }
.kt-card-footer {
  border-top: 1px solid var(--border);
  padding: 1rem 1.25rem;
}

/* Tables: hairline rows rather than zebra — zebra plus status tints is two
   background signals fighting each other. */
.kt-table {
  font-size: var(--baladi-text-table);
  width: 100%;
  border-collapse: collapse;
}

.kt-table th {
  font-family: var(--baladi-font-body);
  font-size: var(--baladi-text-small);
  font-weight: 600;
  color: var(--muted-foreground);
  background-color: var(--muted);
  text-align: right;
  padding: 0.75rem 1rem;
  white-space: nowrap;
  border-bottom: 1px solid var(--border);
}

.kt-table td {
  padding: 0.875rem 1rem;      /* comfortable row height */
  color: var(--foreground);
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}

.kt-table tbody tr:last-child td { border-bottom: none; }
.kt-table tbody tr:hover { background-color: var(--baladi-accent-tint); }

/* Numeric columns: tabular, and aligned the same way down the column. */
.kt-table th.baladi-num,
.kt-table td.baladi-num {
  text-align: left;
  direction: ltr;
}

/* Badges — one per status, each a tint of its own hue. */
.baladi-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.1875rem 0.625rem;
  border-radius: 999px;
  font-size: var(--baladi-text-small);
  font-weight: 500;
  line-height: 1.5;
  white-space: nowrap;
  /* A badge is almost always dropped into a flex row or a table cell. As a
     flex item it would otherwise inherit align-items: stretch and grow to the
     row's full height, and flex-shrink: 1 would squeeze it narrower than its
     own text. Both were visible in the first style-guide screenshot. */
  flex: 0 0 auto;
  align-self: center;
  width: fit-content;
}

.baladi-badge-paid    { background: var(--baladi-paid-bg);    color: var(--baladi-paid-fg); }
.baladi-badge-overdue { background: var(--baladi-overdue-bg); color: var(--baladi-overdue-fg); }
.baladi-badge-partial { background: var(--baladi-partial-bg); color: var(--baladi-partial-fg); }
.baladi-badge-draft   { background: var(--baladi-draft-bg);   color: var(--baladi-draft-fg); }
.baladi-badge-issued  { background: var(--baladi-info-bg);    color: var(--baladi-info-fg); }

/* Alerts — same tints, with a bar on the leading edge. In RTL that is the
   right-hand side, which `border-inline-start` handles without a flip. */
.kt-alert,
.baladi-alert {
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
  padding: 0.875rem 1rem;
  border-radius: var(--radius);
  border: 1px solid transparent;
  border-inline-start-width: 3px;
  font-size: var(--baladi-text-body);
  line-height: 1.6;
}

.kt-alert-success, .baladi-alert-success {
  background: var(--baladi-paid-bg);
  color: var(--baladi-paid-fg);
  border-inline-start-color: var(--baladi-paid-fg);
}
.kt-alert-danger, .baladi-alert-danger {
  background: var(--baladi-overdue-bg);
  color: var(--baladi-overdue-fg);
  border-inline-start-color: var(--baladi-overdue-fg);
}
.kt-alert-warning, .baladi-alert-warning {
  background: var(--baladi-partial-bg);
  color: var(--baladi-partial-fg);
  border-inline-start-color: var(--baladi-partial-fg);
}
.kt-alert-info, .baladi-alert-info {
  background: var(--baladi-info-bg);
  color: var(--baladi-info-fg);
  border-inline-start-color: var(--baladi-info-fg);
}

/* Empty state */
.baladi-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  padding: 3rem 1.5rem;
  text-align: center;
  color: var(--muted-foreground);
}
.baladi-empty-icon {
  font-size: 2.5rem;
  color: var(--baladi-accent);
  opacity: 0.6;
}
.baladi-empty-title {
  font-family: var(--baladi-font-heading);
  font-size: var(--baladi-h3);
  font-weight: 500;
  color: var(--foreground);
}

/* Pagination — numbers stay LTR, the row reads RTL. */
.baladi-pagination {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}
.baladi-pagination a,
.baladi-pagination span {
  min-width: var(--baladi-control-height);
  min-height: var(--baladi-control-height);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 0.625rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: var(--baladi-text-body);
  font-variant-numeric: tabular-nums;
  color: var(--foreground);
  text-decoration: none;
}
.baladi-pagination a:hover {
  background: var(--baladi-accent-tint);
  border-color: var(--baladi-accent);
  color: var(--accent-foreground);
  text-decoration: none;
}
.baladi-pagination .baladi-page-current {
  background: var(--baladi-accent-strong);
  border-color: var(--baladi-accent-strong);
  color: #ffffff;
  font-weight: 600;
}
.baladi-pagination .baladi-page-disabled {
  color: var(--muted-foreground);
  background: var(--muted);
  cursor: not-allowed;
}

/* =========================================================================
   7. Directional icons in RTL
   =========================================================================
   Arrows and chevrons point the wrong way when a left-to-right icon set is
   dropped into an RTL page; logos, numbers and status glyphs must not be
   touched.

   Scoped to `.baladi-ui` on purpose. The existing console templates already
   compensate by hand — they write ki-arrow-right where a LTR design would
   write ki-arrow-left — so flipping globally here would double-flip them and
   break every page, which this step is not allowed to do. Pages opt in as
   they are reworked; the style guide opts in now.

   `.baladi-no-flip` is the escape hatch for an icon inside an opted-in
   container that must keep its direction.
   ========================================================================= */

.baladi-ui [class*='ki-arrow-left'],
.baladi-ui [class*='ki-arrow-right'],
.baladi-ui [class*='ki-left'],
.baladi-ui [class*='ki-right'],
.baladi-ui [class*='ki-double-left'],
.baladi-ui [class*='ki-double-right'],
.baladi-ui [class*='ki-exit-left'],
.baladi-ui [class*='ki-exit-right'],
.baladi-ui [class*='ki-entrance-left'],
.baladi-ui [class*='ki-entrance-right'],
.baladi-icon-flip {
  display: inline-block;
  transform: scaleX(-1);
}

.baladi-ui .baladi-no-flip,
.baladi-ui img,
.baladi-ui svg.baladi-no-flip {
  transform: none;
}

/* =========================================================================
   8. Focus visibility, globally
   =========================================================================
   A keyboard user has to be able to see where they are. Metronic's default
   ring is a low-contrast zinc; this is the brand accent at 3:1+ against both
   page backgrounds.
   ========================================================================= */

:where(a, button, [role='button'], summary, [tabindex]):focus-visible {
  outline: 2px solid var(--baladi-accent);
  outline-offset: 2px;
  border-radius: var(--radius);
}

/* =========================================================================
   9. Theme toggle
   ========================================================================= */

.baladi-theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted-foreground);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}
.baladi-theme-toggle:hover {
  background: var(--baladi-accent-tint);
  color: var(--accent-foreground);
  border-color: var(--baladi-accent);
}

/* Only one of the two glyphs shows, whichever matches the current mode. */
.baladi-theme-toggle .baladi-icon-sun { display: none; }
.baladi-theme-toggle .baladi-icon-moon { display: inline-block; }
.dark .baladi-theme-toggle .baladi-icon-sun { display: inline-block; }
.dark .baladi-theme-toggle .baladi-icon-moon { display: none; }

/* =========================================================================
   10. The dark frame — sidebar, header, footer
   =========================================================================
   Owner decision, 2026-10-08: the frame is ALWAYS dark; only the content area
   follows the light/dark toggle. So these tokens are declared once, outside
   both :root and .dark, and nothing redefines them — that is the guarantee
   that the frame cannot drift between modes.

   Every text pair here is measured in tests/test_ui_contrast.py against
   --baladi-chrome-bg: text 4.5:1, icons and the focus ring 3:1.

   The active menu item is marked three ways — a blue tint, white text, and a
   3px accent bar on the leading edge — because the tint alone is only 1.5:1
   against the frame. The bar is #3498db at 5.6:1, so the state is still
   perceivable for someone who cannot see the fill.
   ========================================================================= */

:root {
  --baladi-chrome-bg: #141821;
  --baladi-chrome-raised: #1a1f2a;
  --baladi-chrome-fg: #e8ecf2;
  --baladi-chrome-muted: #99a3b2;
  --baladi-chrome-icon: #aab4c2;
  --baladi-chrome-accent: #3498db;
  --baladi-chrome-hover: rgba(255, 255, 255, 0.06);
  --baladi-chrome-active-bg: #1e3a52;
  --baladi-chrome-active-fg: #ffffff;
  --baladi-chrome-border: #262c38;
  /* ONE height for the whole top of the frame. The rail's logo block and the
     top bar both read it, so their bottom borders are the same line across
     the window instead of two that miss each other by the few pixels the
     logo block happened to grow by. 64px is what the 30px wordmark needs
     with room to breathe, and it is also what the top bar needs now that it
     carries a button and the shortcuts. */
  --baladi-header-height: 64px;
  --baladi-sidebar-width: 260px;
}

/* Metronic drives `.kt-wrapper`'s top padding from its OWN --header-height
   (70px, or 60px above lg) and the header's inset from --sidebar-width, so
   the token above has to be fed back to it or the content sits under the bar
   at one width and below a gap at the other.

   On `body.demo1`, not on :root: Metronic declares --header-height on
   `.demo1`, which IS the body, and a value declared there shadows the :root
   one for every descendant. The same trap §15 documents for --sidebar-width. */
body.demo1 { --header-height: var(--baladi-header-height); }

/* ---- Sidebar ---------------------------------------------------------- */

.kt-sidebar {
  background-color: var(--baladi-chrome-bg);
  border-color: var(--baladi-chrome-border);
  color: var(--baladi-chrome-fg);
  /* Full viewport height, pinned. inset-block rather than top/bottom so it
     does not need flipping in RTL. */
  position: fixed;
  inset-block: 0;
  inset-inline-start: 0;
  height: 100vh;
  height: 100dvh;      /* mobile browsers whose chrome steals viewport height */
  z-index: 30;
}

/* EXACTLY the header height, not a minimum.

   `min-height` let the block grow with whatever was in it, and it was: the
   wordmark, the collapse toggle and the municipality's name and type. The
   rail's border therefore sat ~30px below the top bar's, and the first line
   a reader meets on every page of the console was a step. The identity block
   moved out, below the seam — `shell/_sidebar_identity.html` — so the only
   thing left in here is the mark, which fits in 64px at any width. */
.kt-sidebar .kt-sidebar-header {
  background-color: var(--baladi-chrome-bg);
  border-bottom: 1px solid var(--baladi-chrome-border);
  box-sizing: border-box;
  height: var(--baladi-header-height);
  min-height: var(--baladi-header-height);
  max-height: var(--baladi-header-height);
}

.kt-sidebar .kt-sidebar-content {
  /* The menu scrolls; the logo and the footer of the rail do not. */
  overflow-y: auto;
  overscroll-behavior: contain;
}

.kt-sidebar .kt-menu-link {
  position: relative;
  color: var(--baladi-chrome-fg);
  border-radius: var(--radius);
  min-height: 40px;
}

.kt-sidebar .kt-menu-title { color: var(--baladi-chrome-fg); }
.kt-sidebar .kt-menu-icon,
.kt-sidebar .kt-menu-link i { color: var(--baladi-chrome-icon); }

.kt-sidebar .kt-menu-link:hover {
  background-color: var(--baladi-chrome-hover);
}
.kt-sidebar .kt-menu-link:hover .kt-menu-title,
.kt-sidebar .kt-menu-link:hover .kt-menu-icon,
.kt-sidebar .kt-menu-link:hover i {
  color: var(--baladi-chrome-active-fg);
}

.kt-sidebar .kt-menu-item-active > .kt-menu-link,
.kt-sidebar .kt-menu-link.active,
.kt-sidebar .kt-menu-link[aria-current='page'] {
  background-color: var(--baladi-chrome-active-bg);
}
.kt-sidebar .kt-menu-item-active > .kt-menu-link .kt-menu-title,
.kt-sidebar .kt-menu-link.active .kt-menu-title,
.kt-sidebar .kt-menu-link[aria-current='page'] .kt-menu-title,
.kt-sidebar .kt-menu-item-active > .kt-menu-link .kt-menu-icon,
.kt-sidebar .kt-menu-link.active .kt-menu-icon,
.kt-sidebar .kt-menu-link.active i {
  color: var(--baladi-chrome-active-fg);
}

/* The accent bar, on the leading edge in both directions. */
.kt-sidebar .kt-menu-item-active > .kt-menu-link::before,
.kt-sidebar .kt-menu-link.active::before,
.kt-sidebar .kt-menu-link[aria-current='page']::before {
  content: '';
  position: absolute;
  inset-inline-start: 0;
  inset-block: 6px;
  width: 3px;
  border-radius: 3px;
  background: var(--baladi-chrome-accent);
}

/* Section headings: #99a3b2 is 6.97:1 on the frame. The old #4a4a68 was
   1.6:1 — the "near-invisible grey" this replaces. */
.kt-sidebar .kt-menu-heading {
  color: var(--baladi-chrome-muted);
  font-size: var(--baladi-text-small);
  font-weight: 600;
  letter-spacing: 0.02em;
}

.kt-sidebar a:focus-visible,
.kt-sidebar button:focus-visible {
  outline: 2px solid var(--baladi-chrome-accent);
  outline-offset: -2px;
  border-radius: var(--radius);
}

/* The SaaS panel's rail says which panel it is, next to the logo. */
.baladi-chrome-label {
  font-family: var(--baladi-font-heading);
  font-size: var(--baladi-text-small);
  font-weight: 500;
  color: var(--baladi-chrome-muted);
  letter-spacing: 0.01em;
  white-space: nowrap;
}

/* ---- Header ----------------------------------------------------------- */

/* The other half of the seam. Same token, same box-sizing, same border — so
   the two bottom borders are one line. tests/test_frame_js.py measures it in
   a real browser, expanded and collapsed, because "the same height" is a
   claim about layout and only layout can answer it. */
.kt-header {
  background-color: var(--baladi-chrome-bg);
  border-bottom: 1px solid var(--baladi-chrome-border);
  color: var(--baladi-chrome-fg);
  box-sizing: border-box;
  height: var(--baladi-header-height);
  min-height: var(--baladi-header-height);
  max-height: var(--baladi-header-height);
}

.kt-header,
.kt-header a:not(.kt-btn),
.kt-header .kt-menu-title { color: var(--baladi-chrome-fg); }
.kt-header i { color: var(--baladi-chrome-icon); }
.kt-header .text-muted-foreground,
.kt-header .text-secondary-foreground { color: var(--baladi-chrome-muted); }

/* ---- The user menu ----------------------------------------------------
   The dropdown is anchored in the chrome but it is not chrome: it is a white
   panel, and the three rules above were painting its links #e8ecf2 on #ffffff
   — 1.1:1, which is why the user menu read as an empty box in both shells.
   The rules are right for the bar itself, so the panel opts out rather than
   them being narrowed; everything inside it uses the content tokens.

   Both shells, because the markup and these rules are shared: the console's
   user menu had the same invisible items. */

.kt-header .kt-dropdown-menu {
  background-color: var(--baladi-surface);
  border: 1px solid var(--baladi-line);
  color: var(--baladi-ink);
}

.kt-header .kt-dropdown-menu,
.kt-header .kt-dropdown-menu a:not(.kt-btn),
.kt-header .kt-dropdown-menu .kt-dropdown-menu-link {
  color: var(--baladi-ink);
}

.kt-header .kt-dropdown-menu i { color: var(--baladi-ink-muted); }

.kt-header .kt-dropdown-menu .text-muted-foreground,
.kt-header .kt-dropdown-menu .text-secondary-foreground { color: var(--baladi-ink-muted); }

.kt-header .kt-dropdown-menu .kt-dropdown-menu-link {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  border-radius: var(--radius);
}

.kt-header .kt-dropdown-menu .kt-dropdown-menu-link:hover {
  background-color: var(--baladi-accent-tint);
  color: var(--baladi-accent-on-tint);
}

.kt-header .kt-dropdown-menu .kt-dropdown-menu-link:hover i {
  color: var(--baladi-accent-on-tint);
}

/* Keyboard users get the same ring the rest of the product uses. Metronic
   ships none here, so tabbing through the menu was invisible. */
.kt-header .kt-dropdown-menu a:focus-visible,
.kt-header .kt-dropdown-menu button:focus-visible {
  outline: 2px solid var(--baladi-accent);
  outline-offset: -2px;
  border-radius: var(--radius);
}

.kt-header .kt-dropdown-menu-separator {
  border-top: 1px solid var(--baladi-line);
}

/* The panel used to disappear under the sidebar, and the fix is placement
   rather than z-index.

   It cannot be z-index: `.kt-header` is `position: fixed; z-index: 10`, so it
   opens a stacking context, and the menu's own z-index of 105 only ever
   competes inside it — the sidebar, at 20 in the root context, wins whatever
   the menu asks for. Raising the whole header above the sidebar would work
   and would also paint the header's dark bar across the sidebar's logo strip,
   because the header spans the full width behind it.

   The actual fault was that the menu opened *towards* the sidebar: in RTL the
   toggle sits against the sidebar edge, so a panel aligned to start extends
   straight under it. Opening it the other way, into the page, means it never
   reaches the sidebar and the stacking question never arises. That is in the
   two header templates, as `data-kt-dropdown-placement-rtl`. */

.kt-header .kt-btn-ghost,
.kt-header .kt-btn-icon {
  color: var(--baladi-chrome-icon);
  background: transparent;
  border-color: transparent;
}
.kt-header .kt-btn-ghost:hover,
.kt-header .kt-btn-icon:hover {
  background-color: var(--baladi-chrome-hover);
  color: var(--baladi-chrome-active-fg);
}
.kt-header .kt-btn-ghost:hover i,
.kt-header .kt-btn-icon:hover i { color: var(--baladi-chrome-active-fg); }

.kt-header a:focus-visible,
.kt-header button:focus-visible {
  outline: 2px solid var(--baladi-chrome-accent);
  outline-offset: 2px;
}

/* The theme toggle lives in the header, so it is chrome-coloured there. */
.kt-header .baladi-theme-toggle {
  color: var(--baladi-chrome-icon);
  border-color: var(--baladi-chrome-border);
}
.kt-header .baladi-theme-toggle:hover {
  background: var(--baladi-chrome-hover);
  color: var(--baladi-chrome-active-fg);
  border-color: var(--baladi-chrome-accent);
}

/* ---- What the top bar carries -----------------------------------------
   Where you are on the inline start (the right, in Arabic, which is where a
   reader begins), and three controls on the inline end.

   The bar was empty but for an avatar and the theme toggle: every page in
   the console announced itself only inside the content area, so the one
   strip that is visible at every scroll position said nothing at all.
   ------------------------------------------------------------------------ */

/* The title no longer takes the slack: the middle of the bar does, so the
   quick-add control and the shortcuts sit between where you ARE and the
   three controls at the far end rather than being pushed against them. */
.baladi-topbar-title {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.0625rem;
  min-width: 0;
  flex: 0 1 auto;
  padding-inline-start: 0.25rem;
}

/* The section the page sits in — "الموظفون" above "تسجيل موظف جديد". Text,
   not a link: a section is not a page, there is nothing at /console/hr/ to
   go to, and a crumb that looks like a link and is not is worse than none. */
.baladi-topbar-crumb {
  font-size: var(--baladi-text-small);
  color: var(--baladi-chrome-muted);
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The page's own name, from `{% block page_title %}`. It is an <h1>, and the
   one inside `_page_header.html` further down the page is the heading of the
   content — two h1s on a page is one too many, but changing 84 content
   headers is a separate job from giving the bar a title, and the bar's is
   the one a screen reader meets first. */
.baladi-topbar-heading {
  font-family: var(--baladi-font-heading, inherit);
  font-size: var(--baladi-text-body);
  font-weight: 600;
  line-height: 1.25;
  margin: 0;
  color: var(--baladi-chrome-fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The heading and the «؟» on one line, with the crumb above them. The
   wrapper exists because `.baladi-topbar-title` is a COLUMN — without it the
   help link would stack under the title instead of sitting beside it. */
.baladi-topbar-heading-row {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  min-width: 0;
}

/* «؟» — the link to this page's own guide page. A circle rather than an
   icon: `ki-question` is not in the keenicons bundle this product ships, and
   a question mark is the one glyph that needs no legend in either script. */
.baladi-topbar-help {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 999px;
  border: 1px solid var(--baladi-chrome-border);
  color: var(--baladi-chrome-muted);
  font-size: var(--baladi-text-small);
  line-height: 1;
  text-decoration: none;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.baladi-topbar-help:hover,
.baladi-topbar-help:focus-visible {
  color: var(--baladi-chrome-fg);
  border-color: var(--baladi-chrome-fg);
}

@media (max-width: 1023px) {
  /* Beside the mini logo and the burger, the title has a third of a phone
     to live in. The crumb goes first — the title is the more specific of
     the two and the rail is one tap away for the rest. */
  .baladi-topbar-crumb { display: none; }
  .baladi-topbar-title { padding-inline-start: 0.5rem; }
}

.baladi-topbar-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 0 0 auto;
}

/* ---- The middle: one way to start, four ways to go --------------------
   `console/quicknav.py` decides what is in both lists and whether this user
   may see each entry. See `_header_end.html` for why they are here at all.
   ----------------------------------------------------------------------- */

.baladi-topbar-center {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  flex: 1 1 auto;
  min-width: 0;
  padding-inline: 0.75rem;
}

/* «+ إضافة سريعة». The one accent-coloured thing in a dark bar, because it
   is the one thing in it that does something rather than showing something.
   #3498db is the chrome accent, measured against the bar in
   tests/test_ui_contrast.py. */
.baladi-quick-add {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  flex: 0 0 auto;
  height: 2.25rem;
  padding-inline: 0.875rem;
  border: 1px solid var(--baladi-chrome-accent);
  border-radius: var(--radius);
  background: var(--baladi-chrome-active-bg);
  color: var(--baladi-chrome-active-fg);
  font-family: var(--baladi-font-body);
  font-size: var(--baladi-text-small);
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}

.baladi-quick-add i { color: var(--baladi-chrome-active-fg); }

.baladi-quick-add:hover {
  background: var(--baladi-chrome-accent);
  border-color: var(--baladi-chrome-accent);
}

.baladi-quick-add:focus-visible {
  outline: 2px solid var(--baladi-chrome-accent);
  outline-offset: 2px;
}

.baladi-quick-add-menu {
  min-width: 13rem;
  padding: 0.375rem;
}

.baladi-quick-add-menu__head {
  padding: 0.375rem 0.625rem 0.5rem;
  font-size: var(--baladi-text-small);
  font-weight: 600;
  color: var(--baladi-ink-muted);
}

/* The shortcuts. Quiet by design — they are a hand, not a map, and the rail
   beside them is already marking where you are. */
.baladi-topbar-shortcuts {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  min-width: 0;
}

.baladi-topbar-shortcut {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  height: 2.25rem;
  padding-inline: 0.625rem;
  border-radius: var(--radius);
  color: var(--baladi-chrome-muted);
  font-size: var(--baladi-text-small);
  font-weight: 500;
  white-space: nowrap;
  text-decoration: none;
}

.baladi-topbar-shortcut i { color: var(--baladi-chrome-icon); font-size: 1rem; }

.baladi-topbar-shortcut:hover {
  background: var(--baladi-chrome-hover);
  color: var(--baladi-chrome-active-fg);
}

.baladi-topbar-shortcut:hover i { color: var(--baladi-chrome-active-fg); }

.baladi-topbar-shortcut:focus-visible {
  outline: 2px solid var(--baladi-chrome-accent);
  outline-offset: -2px;
}

/* Below 1280px the shortcuts go and the quick-add stays. Four labelled links
   that have to wrap are not a row anybody can aim at, and every one of them
   is in the rail — which at this width is a tap away. The control that
   STARTS something has no second home, so it keeps its place. */
@media (max-width: 1279px) {
  .baladi-topbar-shortcuts { display: none; }
}

/* On a phone the bar is the mini logo, the burger, the title and three
   controls. The quick-add survives as the icon alone — a 36px target with
   the same menu behind it — because "add a person" from wherever you are is
   worth more on a phone than anywhere else. */
@media (max-width: 767px) {
  .baladi-topbar-center { padding-inline: 0.375rem; flex: 0 0 auto; }
  .baladi-quick-add { padding-inline: 0; width: 2.25rem; justify-content: center; }
  .baladi-quick-add__label { display: none; }
}

/* ---- The bell ---------------------------------------------------------- */

.baladi-topbar-bell {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: transparent;
  color: var(--baladi-chrome-icon);
  cursor: pointer;
}

.baladi-topbar-bell:hover {
  background: var(--baladi-chrome-hover);
  color: var(--baladi-chrome-active-fg);
}

.baladi-topbar-bell:hover i { color: var(--baladi-chrome-active-fg); }

/* The count. `aria-hidden`, because the button's own label already carries
   the number — a screen reader should hear "التنبيهات — 2" once, not a
   stray digit after it. */
.baladi-topbar-bell__count {
  position: absolute;
  top: 0.125rem;
  inset-inline-end: 0.125rem;
  min-width: 1rem;
  height: 1rem;
  padding-inline: 0.1875rem;
  border-radius: 999px;
  /* The one place in the chrome that is a solid status colour rather than
     a tint: a badge has to be read at 10px in the corner of an icon.
     `--baladi-overdue-fg` is the design layer's own red, so the bell's
     count and an overdue bill are the same hue. */
  background: var(--baladi-overdue-fg);
  color: #ffffff;
  font-size: 0.625rem;
  font-weight: 700;
  line-height: 1rem;
  text-align: center;
}

.baladi-topbar-alerts {
  width: 20rem;
  max-width: calc(100vw - 2rem);
  padding: 0.5rem;
}

.baladi-topbar-alerts__head {
  padding: 0.25rem 0.5rem 0.5rem;
  font-size: var(--baladi-text-small);
  font-weight: 700;
  color: var(--baladi-ink-muted);
}

.baladi-topbar-alerts__list {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.baladi-topbar-alert {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  padding: 0.5rem;
  border-radius: var(--radius);
  font-size: var(--baladi-text-small);
  line-height: 1.5;
  color: var(--baladi-ink);
}

.baladi-topbar-alert--danger { background: var(--baladi-overdue-bg); }
.baladi-topbar-alert--danger i { color: var(--baladi-overdue-fg); }
.baladi-topbar-alert--warning { background: var(--baladi-partial-bg); }
.baladi-topbar-alert--warning i { color: var(--baladi-partial-fg); }
.baladi-topbar-alert--info { background: var(--baladi-accent-tint); }
.baladi-topbar-alert--info i { color: var(--baladi-accent-on-tint); }

/* Said rather than hidden. A control that vanishes when it has nothing to
   report is one nobody learns the meaning of, and its absence looks exactly
   like a page that failed to draw it. */
.baladi-topbar-alerts__empty {
  margin: 0;
  padding: 0.5rem;
  font-size: var(--baladi-text-small);
  color: var(--baladi-ink-muted);
}

/* ---- The user menu's own block ---------------------------------------- */

.baladi-topbar-user {
  display: inline-flex;
  align-items: center;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}

.baladi-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 999px;
  border: 2px solid var(--baladi-chrome-accent);
  background: var(--baladi-chrome-raised);
  color: var(--baladi-chrome-fg);
  font-size: var(--baladi-text-small);
  font-weight: 700;
}

.baladi-usermenu {
  width: 17rem;
  max-width: calc(100vw - 2rem);
  padding: 0.5rem;
}

.baladi-usermenu__head {
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
  padding: 0.25rem 0.5rem 0.5rem;
  min-width: 0;
}

/* Inside the white panel, not on the dark bar. */
.baladi-usermenu .baladi-avatar {
  border-color: var(--baladi-accent);
  background: var(--baladi-accent-tint);
  color: var(--baladi-accent-on-tint);
}

.baladi-avatar--lg {
  width: 2.5rem;
  height: 2.5rem;
  font-size: var(--baladi-text-body);
}

.baladi-usermenu__facts {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  min-width: 0;
}

.baladi-usermenu__name {
  font-size: var(--baladi-text-table);
  font-weight: 600;
  color: var(--baladi-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The role, which used to be the literal string "مشرف" for everybody. */
.baladi-usermenu__role {
  align-self: flex-start;
  padding: 0.0625rem 0.375rem;
  border-radius: 999px;
  background: var(--baladi-accent-tint);
  color: var(--baladi-accent-on-tint);
  font-size: var(--baladi-text-small);
  font-weight: 600;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.baladi-usermenu__entity,
.baladi-usermenu__email {
  font-size: var(--baladi-text-small);
  color: var(--baladi-ink-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- Footer ----------------------------------------------------------- */

.kt-footer {
  background-color: var(--baladi-chrome-bg);
  border-top: 1px solid var(--baladi-chrome-border);
  color: var(--baladi-chrome-muted);
  font-size: var(--baladi-text-small);
}
.kt-footer a {
  color: var(--baladi-chrome-muted);
  text-decoration: none;
}
.kt-footer a:hover {
  color: var(--baladi-chrome-active-fg);
  text-decoration: underline;
}
.kt-footer a:focus-visible {
  outline: 2px solid var(--baladi-chrome-accent);
  outline-offset: 2px;
}

/* ---- The drawer below tablet width -------------------------------------
   Metronic already drives this sidebar as an off-canvas drawer below lg
   (data-kt-drawer-activate="{default: true, lg: false}" on the <aside>), with
   its own overlay. A second implementation would fight it over transform and
   position, so there is none here — what was missing is only that the drawer
   surface and its overlay were taking Metronic's light colours. The <aside>
   lost its hard-coded `bg-background` so .kt-sidebar's chrome background
   applies in the drawer too; the overlay is darkened below. */

.kt-drawer-overlay,
.drawer-overlay {
  background-color: rgba(10, 12, 16, 0.6);
  backdrop-filter: blur(1px);
}

@media (max-width: 1023px) {
  /* The drawer is the whole frame on a phone, so it gets the chrome colours
     regardless of which classes Metronic injects. */
  .kt-sidebar {
    background-color: var(--baladi-chrome-bg) !important;
    border-color: var(--baladi-chrome-border);
  }
  /* Metronic's drawer sets its own height; keep it full-bleed. */
  .kt-sidebar.kt-drawer {
    height: 100vh;
    height: 100dvh;
  }
}

/* =========================================================================
   11. Page content — layout primitives
   -------------------------------------------------------------------------
   assets/css/styles.css is a PREBUILT, PRUNED Tailwind bundle and there is no
   build pipeline in this repository, so a Tailwind class is only usable if it
   already happens to be compiled in. `md:grid-cols-3` is not — which is why
   the dashboard's three stat cards stacked full-width at every viewport
   despite carrying `grid grid-cols-1 md:grid-cols-3`.

   Page layout therefore lives here, in plain CSS we own, instead of in
   utility classes we cannot compile. tests/test_css_classes.py keeps the
   step-3 templates honest about this.
   ========================================================================= */

/* Every grid child may shrink below its content. Without this a table, a long
   unbroken number or a wide select widens the whole grid — and with it the
   page — instead of scrolling or wrapping inside its own cell. */
.baladi-stat-grid > *,
.baladi-split-grid > *,
.baladi-filter-grid > *,
.baladi-breakdown > * {
  min-width: 0;
}

/* Stat grid: as many columns as there are tiles, up to what fits.

   It was `repeat(4, 1fr)` above 1024px, which is right for the dashboard's
   four KPI tiles and wrong for every card that has three: the asset card and
   the council card each drew three tiles and a quarter of empty space, which
   reads as a tile that failed to load.

   `auto-fit` + `minmax` is the rule rather than a modifier class per card —
   the grid counts its own children. Three tiles make three columns, four
   make four, and a card that gains a fifth gets a sensible wrap instead of a
   stylesheet change. The `min(100%, …)` is what keeps a single column on a
   390px phone: a bare 15rem minimum would overflow the viewport.

   The ONE media query left is the floor. Below 640px every card is one
   column whatever it holds, because two 160px tiles side by side are two
   numbers nobody can read. */
.baladi-stat-grid {
  display: grid;
  gap: 1.25rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
}

@media (max-width: 639px) {
  .baladi-stat-grid { grid-template-columns: minmax(0, 1fr); }
}

/* A two-up content row (recent payments beside quick actions). */
.baladi-split-grid {
  display: grid;
  gap: 1.25rem;
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 1024px) {
  .baladi-split-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Page header: one line of context on one side, one primary action on the
   other; stacks on a phone.

   **No title.** The page's name is in the top bar and this strip used to
   repeat it forty pixels lower as a large <h1> — every page in the console
   said its own name twice, the second time bigger than the first. What is
   left is what the bar cannot hold, so the strip is a line and a button
   rather than a block, and `center` rather than `flex-start` is what puts
   the two on the same line. */
.baladi-page-header {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-bottom: 1.25rem;
}

@media (min-width: 640px) {
  .baladi-page-header {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
  }
}

.baladi-page-header__text { min-width: 0; }

.baladi-page-header__subtitle {
  font-size: var(--baladi-text-small);
  color: var(--muted-foreground);
}

/* A single statistic. The value is Western digits by inheritance from the
   body rule in section 3. */
.baladi-stat {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  padding: 1.25rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.baladi-stat__icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: var(--radius);
  background: var(--baladi-accent-tint);
  color: var(--baladi-accent-on-tint);
  font-size: 1.25rem;
}

.baladi-stat__body { min-width: 0; }

.baladi-stat__value {
  font-family: var(--baladi-font-heading);
  font-size: 1.5rem;
  font-weight: 600;
  line-height: 1.2;
  color: var(--foreground);
}

.baladi-stat__label {
  margin-top: 0.125rem;
  font-size: var(--baladi-text-small);
  color: var(--muted-foreground);
}

.baladi-stat__hint {
  margin-top: 0.125rem;
  font-size: var(--baladi-text-small);
  color: var(--muted-foreground);
}

/* Status breakdown: a label/count pair per status, on the same rule as the
   stat grid above — as many columns as there are items, up to what fits.
   The minimum is smaller because an item here is a number and a word, not a
   tile with an icon. */
.baladi-breakdown {
  display: grid;
  gap: 0.75rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 9rem), 1fr));
}

@media (max-width: 639px) {
  .baladi-breakdown { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.baladi-breakdown__item {
  padding: 0.875rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-align: center;
}

.baladi-breakdown__value {
  font-family: var(--baladi-font-heading);
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--foreground);
}

.baladi-breakdown__label {
  margin-top: 0.125rem;
  font-size: var(--baladi-text-small);
  color: var(--muted-foreground);
}

/* Quick actions: a stack of full-width buttons, so the row reads as a list of
   things you can do rather than a cramped button bar. */
.baladi-actions {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
}

.baladi-actions > * { width: 100%; justify-content: flex-start; }

/* Empty state, used by the dashboard panels and (from part b) by lists. */
.baladi-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  padding: 2.5rem 1.25rem;
  text-align: center;
}

.baladi-empty__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  border-radius: 999px;
  background: var(--baladi-accent-tint);
  color: var(--baladi-accent-on-tint);
  font-size: 1.5rem;
}

.baladi-empty__text {
  font-size: var(--baladi-text-small);
  color: var(--muted-foreground);
}

/* A thin progress track, used by the dashboard's collection rate. Deliberately
   not Metronic's .kt-progress: that one is sized for a card of its own, and
   this has to sit inside a stat card under the label without changing its
   height. */
.baladi-meter {
  margin-top: 0.5rem;
  height: 4px;
  border-radius: 999px;
  background: var(--baladi-line);
  overflow: hidden;
}

.baladi-meter__fill {
  height: 100%;
  border-radius: 999px;
  background: var(--baladi-accent);
}

/* The rate itself, beside the amount rather than under it. */
.baladi-stat__aside {
  margin-inline-start: 0.5rem;
  font-family: var(--baladi-font-body);
  font-size: var(--baladi-text-small);
  font-weight: 500;
  color: var(--muted-foreground);
}

/* =========================================================================
   12. Lists — the shared pattern
   -------------------------------------------------------------------------
   One page header, one filters card, one table, one pagination bar, one empty
   state, for persons / properties / bills / payments / services / packages /
   users. Markup lives in console/templates/console/includes/.

   Same reason as section 11 for it being here rather than in utility classes:
   every list carried `grid-cols-1 sm:grid-cols-2 lg:grid-cols-4` on its filter
   form and `lg:grid-cols-4` is not in the prebuilt bundle, so the filters were
   two-up at every width above 640px instead of four-up.
   ========================================================================= */

/* Filter fields: 4 up on desktop, 2 on tablet, 1 on a phone. */
.baladi-filter-grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 640px) {
  .baladi-filter-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1024px) {
  .baladi-filter-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* The apply/clear row always spans the whole grid. */
.baladi-filter-actions {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  /* The grid stretches its items, which made Apply and Clear full-width
     buttons on a phone. They size to their labels. */
  align-items: flex-start;
  gap: 0.625rem;
}

.baladi-filter-actions > * { flex: 0 0 auto; }

/* A label stacked above its control — the house form rule, reused by the
   filters so a filter looks like what it is: a field. */
.baladi-field {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  min-width: 0;
}

.baladi-field__label {
  font-size: var(--baladi-text-small);
  font-weight: 500;
  color: var(--foreground);
}

/* A control the form has decided for you — الدولة when the residence status
   already says the country is Lebanon (registry/person_rules.py). It is
   disabled rather than hidden, because the value is the answer and seeing it
   is the point; what the muted ground says is "this is not yours to change
   here", which is different from "this field is empty".

   `[hidden]` is restored on .baladi-field because the reset sets
   `display: flex` on it, which beats the attribute. The residence script
   hides the city field that way. */
.baladi-field-locked select,
.baladi-field-locked input {
  background: var(--muted);
  color: var(--muted-foreground);
  cursor: not-allowed;
}

.baladi-field[hidden] {
  display: none;
}

/* Tables scroll inside their own card rather than widening the page. */
.baladi-table-wrap {
  width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  /* position: relative so the scroll box is the containing block for anything
     absolutely positioned inside it. Without it, the `sr-only` span in the
     actions column header — Tailwind's sr-only is `position: absolute` —
     resolved against the initial containing block instead, escaped the scroll
     box entirely, and landed 82px to the left of the page. At 390px that was
     82px of bare background beside the fixed top bar, which is what made the
     dark bar look like it did not span the full width. */
  position: relative;
}

/* Row actions: icon buttons, never wrapping onto a second line. */
.baladi-row-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.25rem;
  white-space: nowrap;
}

/* Pagination: the count on one side, the page links on the other. */
.baladi-pagination {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  padding: 0.875rem 1.25rem;
  border-top: 1px solid var(--border);
}

@media (min-width: 640px) {
  .baladi-pagination {
    flex-direction: row;
    justify-content: space-between;
  }
}

.baladi-pagination__count {
  font-size: var(--baladi-text-small);
  color: var(--muted-foreground);
}

.baladi-pagination__pages {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

.baladi-pagination__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.25rem;
  height: 2.25rem;
  padding: 0 0.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: var(--baladi-text-small);
  color: var(--foreground);
  background: var(--card);
}

.baladi-pagination__link:hover { background: var(--baladi-surface-2); }

.baladi-pagination__link[aria-current="page"] {
  background: var(--baladi-accent-strong);
  border-color: var(--baladi-accent-strong);
  color: #ffffff;
  font-weight: 600;
}

.baladi-pagination__link[aria-disabled="true"] {
  opacity: 0.45;
  pointer-events: none;
}

/* The gap marker between non-contiguous page numbers. */
.baladi-pagination__gap {
  padding: 0 0.25rem;
  color: var(--muted-foreground);
}

/* A row action that destroys something. Ghost like its neighbours until
   hovered, so a table of twenty rows is not twenty red buttons. */
.baladi-action-danger:hover,
.baladi-action-danger:focus-visible {
  color: var(--baladi-overdue-fg);
  background: var(--baladi-overdue-bg);
}

/* The secondary line inside a table cell — a father's name under a person, a
   second phone number under the first. */
.baladi-cell-sub {
  margin-top: 0.125rem;
  font-size: var(--baladi-text-small);
  color: var(--muted-foreground);
}

/* Sortable column header. The arrow sits after the label and only the active
   column has one, so the header row does not read as a row of arrows. */
.baladi-sort {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  color: inherit;
  text-decoration: none;
  white-space: nowrap;
}

.baladi-sort:hover { color: var(--baladi-link); }

.baladi-sort i {
  font-size: 0.75rem;
  color: var(--baladi-accent-on-tint);
}

/* Per-page links, in the pagination bar beside the count. */
.baladi-pagination__perpage {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  font-size: var(--baladi-text-small);
  color: var(--muted-foreground);
}

.baladi-pagination__perpage-link {
  min-width: 1.75rem;
  padding: 0.125rem 0.375rem;
  border-radius: var(--radius);
  text-align: center;
  color: var(--baladi-link);
}

.baladi-pagination__perpage-link[aria-current="true"] {
  background: var(--baladi-accent-tint);
  color: var(--baladi-accent-on-tint);
  font-weight: 600;
}

/* The actions column takes only the width its buttons need, so they sit beside
   the last data column instead of being pushed to the far edge of the table. */
.baladi-col-actions {
  width: 1%;
  white-space: nowrap;
}

/* Several badges in one cell — a property's services, a user's roles. */
.baladi-badge-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
}

/* =========================================================================
   13. Horizontal overflow
   -------------------------------------------------------------------------
   A flex item's min-width defaults to `auto`, which means "never shrink below
   my content". The console layout is a chain of them — div.flex.grow >
   .kt-wrapper > .kt-main > .kt-container-fixed — so a wide table did not
   scroll inside .baladi-table-wrap; it widened every ancestor instead, and
   the document grew to 655px inside a 390px viewport.

   The visible symptom was the top bar: #header is `fixed`, so it stayed
   390px wide while the page beside it was bare, which read as "the dark bar
   only covers part of the width".
   ========================================================================= */

.baladi-page-shell,
.kt-wrapper,
.kt-main,
.kt-container-fixed {
  min-width: 0;
}

/* No `overflow-x: hidden` safety net on body: it does not stop the page
   being scrolled programmatically, it does not stop a fixed header looking
   narrower than the document, and it turns the next overflow bug into
   something invisible. tests/test_responsive.py asserts the real condition —
   scrollWidth == clientWidth at 390px — instead. */

/* A pair of fields that belong together — a date range — occupying one cell of
   the filter grid and splitting it in two. They stack only on a phone, where
   the grid is one column anyway and two date inputs side by side would be
   about 80px each. */
.baladi-field-range {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  min-width: 0;
}

@media (min-width: 640px) {
  .baladi-field-range {
    flex-direction: row;
    align-items: flex-end;
    gap: 0.5rem;
  }
  .baladi-field-range > * { flex: 1 1 0; min-width: 0; }
}

/* =========================================================================
   14. Forms — page layout
   -------------------------------------------------------------------------
   Section 4 already defines the field itself: label above, 44px control,
   required marker, hint, error text, aria-invalid styling. This is what a
   whole FORM needs around those: a two-column grid that collapses, sections
   for long forms, an error summary, and an action bar that stays reachable.
   ========================================================================= */

/* Fields two-up on anything above a phone. A field that needs the full width
   (an address, a note) carries .baladi-form-grid__wide. */
.baladi-form-grid {
  display: grid;
  gap: 1.25rem;
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 640px) {
  .baladi-form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.baladi-form-grid > * { min-width: 0; }
.baladi-form-grid__wide { grid-column: 1 / -1; }

/* A long form is a stack of sections, each a card with its own heading, so
   "الهوية" and "الاتصال" are findable rather than being 30 fields in a row. */
.baladi-form-sections {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

/* The summary at the top of a form that failed validation. It lists the
   fields, each as a link to the field itself. */
.baladi-error-summary {
  border: 1px solid var(--destructive);
  border-radius: var(--radius);
  background: var(--baladi-overdue-bg);
  padding: 1rem 1.25rem;
  margin-bottom: 1.25rem;
}

.baladi-error-summary__title {
  font-family: var(--baladi-font-heading);
  font-weight: 600;
  color: var(--baladi-overdue-fg);
  margin-bottom: 0.5rem;
}

.baladi-error-summary ul {
  margin: 0;
  padding-inline-start: 1.25rem;
  list-style: disc;
}

.baladi-error-summary li {
  font-size: var(--baladi-text-small);
  color: var(--baladi-overdue-fg);
  line-height: 1.8;
}

.baladi-error-summary a {
  color: inherit;
  text-decoration: underline;
}

/* The action bar. Sticky to the bottom of the viewport so Save is reachable
   from anywhere in a form that is taller than the screen — the persons form
   is about 1,400px tall, and Save used to be below all of it. */
.baladi-form-actions {
  position: sticky;
  bottom: 0;
  z-index: 5;
  display: flex;
  flex-wrap: wrap;
  gap: 0.625rem;
  padding: 0.875rem 1.25rem;
  margin-top: 1.25rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* Lifts the bar off the content scrolling under it. */
  box-shadow: 0 -6px 16px -12px rgba(0, 0, 0, 0.45);
}

.baladi-form-actions > * { flex: 0 0 auto; }

/* A short form inside a card that is one of several. Sticky is for a form
   taller than the screen; for four fields it is a bar hovering over whatever
   is below for as long as the page is open. Without the shadow and the
   border it reads as the last row of its card rather than as a second card
   inside it. */
.baladi-form-actions--inline {
  position: static;
  padding: 0;
  border: 0;
  background: transparent;
  box-shadow: none;
}

/* Cancel sits away from Save so the two are not a coin toss. */
.baladi-form-actions__spacer { flex: 1 1 auto; }

@media (max-width: 639px) {
  /* On a phone the bar is the width of the screen and the buttons split it,
     which is easier to hit than two buttons bunched at one end. */
  .baladi-form-actions > .kt-btn { flex: 1 1 0; justify-content: center; }
  .baladi-form-actions__spacer { display: none; }
}

/* The confirmation step for something destructive. Not a modal: a page, so it
   has a URL, survives a refresh and can be reached with the keyboard. */
.baladi-confirm {
  max-width: 34rem;
  margin-inline: auto;
}

.baladi-confirm__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  border-radius: 999px;
  background: var(--baladi-overdue-bg);
  color: var(--baladi-overdue-fg);
  font-size: 1.5rem;
}

/* The row of facts restating what is about to be destroyed. */
.baladi-confirm__facts {
  display: grid;
  gap: 0.5rem;
  margin: 1.25rem 0;
  padding: 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--baladi-surface-2);
}

.baladi-confirm__fact {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  font-size: var(--baladi-text-small);
}

.baladi-confirm__fact dt { color: var(--muted-foreground); }
.baladi-confirm__fact dd { margin: 0; font-weight: 500; color: var(--foreground); }

/* An input whose value reads left-to-right — phone, national ID, IBAN, an
   amount. The text aligns with the digits rather than with the Arabic label
   above it, and the caret starts where typing starts. */
.baladi-ltr-input {
  direction: ltr;
  text-align: left;
}

/* Checkboxes: label beside the box, not above it — the one exception to the
   label-above rule, because a tick has nothing to sit under. The whole row is
   the label, so the text is part of the hit target. */
.baladi-checkbox {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  min-height: var(--baladi-control-height);
  cursor: pointer;
  font-size: var(--baladi-text-body);
  color: var(--foreground);
}

.baladi-checkbox input {
  width: 1.125rem;
  height: 1.125rem;
  min-height: 0;
  flex: 0 0 auto;
  accent-color: var(--baladi-accent-strong);
  cursor: pointer;
}

.baladi-checkbox-grid {
  display: grid;
  gap: 0.25rem 1.25rem;
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 640px) {
  .baladi-checkbox-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1024px) {
  .baladi-checkbox-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* A service checkbox and the package select it governs, on one row: the two
   are a single decision ("does this property have water, and on what tariff"),
   so splitting them across the form grid would read as two unrelated fields. */
.baladi-service-row {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.875rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

@media (min-width: 640px) {
  .baladi-service-row {
    flex-direction: row;
    align-items: flex-end;
    gap: 1.25rem;
  }
  .baladi-service-row > .baladi-checkbox { flex: 0 0 11rem; align-self: center; }
  .baladi-service-row > .baladi-field { flex: 1 1 auto; min-width: 0; }
}

/* A role, as a checkbox with its description beside it. A multi-select would
   hide the descriptions, and the set is short and fixed. */
.baladi-role-list {
  display: grid;
  gap: 0.5rem;
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 768px) {
  .baladi-role-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.baladi-role {
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
  padding: 0.75rem 0.875rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  min-width: 0;
}

.baladi-role:hover { border-color: var(--baladi-accent); }

.baladi-role input {
  margin-top: 0.2rem;
  width: 1.125rem;
  height: 1.125rem;
  min-height: 0;
  flex: 0 0 auto;
  accent-color: var(--baladi-accent-strong);
}

.baladi-role__body { display: flex; flex-direction: column; gap: 0.125rem; min-width: 0; }
.baladi-role__name { font-weight: 500; color: var(--foreground); }
.baladi-role__desc { font-size: var(--baladi-text-small); color: var(--muted-foreground); }

/* Permissions, grouped by subject. A flat list of twenty-four checkboxes is
   unreadable; "everything to do with bills" is one block you can scan. */
.baladi-permission-groups {
  display: grid;
  gap: 1rem;
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 768px) {
  .baladi-permission-groups { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.baladi-permission-group {
  position: relative;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.875rem 1rem 1rem;
  min-width: 0;
}

.baladi-permission-group__title {
  font-family: var(--baladi-font-heading);
  font-weight: 600;
  font-size: var(--baladi-text-body);
  color: var(--foreground);
  padding-inline: 0.375rem;
}

.baladi-permission-group__toggle {
  position: absolute;
  top: 0.5rem;
  inset-inline-end: 0.5rem;
}

.baladi-permission-group__items {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  margin-top: 0.25rem;
}

/* Confirm page actions. Cancel first in the DOM, so it is first in the tab
   order and a stray Enter does not land on the destructive one. */
.baladi-confirm__actions {
  display: flex;
  gap: 0.625rem;
  margin-top: 1.25rem;
}

.baladi-confirm__actions > * { flex: 1 1 0; justify-content: center; }

/* The only solid red button in the UI, and it only ever appears on a
   confirmation page. #c0392b on white is 5.05:1, so the label is readable. */
.baladi-btn-danger {
  background-color: var(--destructive);
  border-color: var(--destructive);
  color: var(--destructive-foreground);
}

.baladi-btn-danger:hover {
  background-color: #a93226;
  border-color: #a93226;
  color: #ffffff;
}

.baladi-alert-danger {
  background: var(--baladi-overdue-bg);
  border: 1px solid var(--baladi-overdue-fg);
  color: var(--baladi-overdue-fg);
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  padding: 0.875rem 1rem;
  border-radius: var(--radius);
}

/* =========================================================================
   15. Sidebar — collapse to icons
   -------------------------------------------------------------------------
   The state lives in the `baladi_sidebar` cookie and is rendered by the server
   onto <html>, exactly like the theme. Metronic's own sidebar state is in
   localStorage and applied after load, so the rail painted at one width and
   jumped to the other on every navigation.

   Metronic drives the content offset and the header inset from
   --sidebar-width, so collapsing is mostly a matter of redefining that one
   token; the <aside> itself needs its Tailwind w-[250px] overridden too.

   Not built on Metronic's own .kt-sidebar-collapse: that one expands the rail
   again on hover, which is a different design from "icons, with the label as a
   tooltip" and would have to be fought rather than used.
   ========================================================================= */

/* The width transition exists only while the user is toggling.

   Metronic puts `transition: width var(--sidebar-transition-duration)` on
   .kt-sidebar unconditionally, so the rail animates on first paint too — the
   server can render the right width and the browser will still animate TO it
   from the initial value. Turned off by default and switched back on by
   baladiToggleSidebar(), which adds .baladi-sidebar-animating; the server
   never writes that class, so a page load in either state paints once. */
.kt-sidebar,
.kt-wrapper,
.kt-header {
  transition: none;
}

.baladi-sidebar-animating .kt-sidebar,
.baladi-sidebar-animating .kt-wrapper,
.baladi-sidebar-animating .kt-header {
  transition: width 200ms ease, padding-inline-start 200ms ease,
              inset-inline-start 200ms ease;
}

/* ---- The toggle itself ------------------------------------------------- */

/* The sidebar header holds the brand block and, below 1024px, the drawer's
   close button beside it. Centred, not flex-start: the block is exactly the
   header height now, so there is nothing to align to the top of. */
.kt-sidebar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.baladi-sidebar-dismiss { flex: 0 0 auto; margin-inline-end: 0.75rem; }

/* Who is signed in, at the FOOT of the panel's rail.

   It was `px-4 py-4` with a top border and nothing under it, so the row sat
   against the bottom edge of the window — and on a phone the home indicator
   crossed the logout button. The safe-area inset is added to the padding
   rather than replacing it, so the block keeps its breathing room on a
   screen that has no inset to add.

   The border reads the chrome token like the rest of the rail: `border-t
   border-border` took the CONTENT's border colour, which in light mode is a
   pale grey drawn across a near-black rail. */
.baladi-sidebar-user {
  flex: 0 0 auto;
  padding: 0.875rem 1rem calc(0.875rem + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--baladi-chrome-border);
}

/* The brand block at the TOP of the rail — the mark, and nothing else.
   Shared by the console and the SaaS panel:
   templates/shell/_sidebar_brand.html.

   It used to carry the municipality's name and the collapse toggle too,
   which is what made the rail's header taller than the top bar beside it.
   The name is now `shell/_sidebar_identity.html`, below the seam; the
   toggle is on the seam itself. */
.baladi-sidebar-brand {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 0.5rem;
  flex: 1 1 auto;
  height: 100%;
  padding-inline: 1rem;
  min-width: 0;
}

/* ---- Whose console this is -------------------------------------------- */

/* Below the seam, not inside the header block.

   It was in the header, under the wordmark, and it is the reason that block
   was ~30px taller than the top bar it is supposed to line up with. Moving
   it down costs nothing: it was never a header, it is the first fact about
   the rail, and it now reads as one — with the border above it rather than
   around it. */
.baladi-sidebar-identity-block {
  padding: 0.75rem 1rem 0.25rem;
  min-width: 0;
}

/* The one fact on the rail that is never the same twice.

   It was 13px in --baladi-chrome-muted, the colour this layer uses for
   things a reader may skip, which made the municipality's own name the
   least readable thing on its own menu. An operator who supports three
   municipalities has to be able to tell at a glance which one they are
   typing into, so the name is body-sized, in the chrome's full foreground
   colour, and the TYPE below it takes over the muted role. */
.baladi-sidebar-identity {
  display: flex;
  flex-direction: column;
  gap: 0.0625rem;
  min-width: 0;
}

/* One line: names like "بلدية الشياح والغبيري وحارة حريك" do not fit a
   260px rail at any size worth reading, and the full text is on the
   element's title. */
.baladi-sidebar-identity__name {
  font-size: var(--baladi-text-body);
  font-weight: 600;
  line-height: 1.3;
  color: var(--baladi-chrome-fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* بلدية / اتحاد بلديات — shown only when the NAME does not already say it,
   which `entity_caption` decides. See console/templatetags/baladi.py. */
.baladi-sidebar-identity__type {
  font-size: var(--baladi-text-small);
  color: var(--baladi-chrome-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* The municipality's emblem under its name. 40px: large enough to be
   recognised, small enough that the rail is still a menu. `contain`, because
   a seal is rarely square and cropping one is defacing it. */
.baladi-sidebar-logo-mark {
  display: block;
  width: auto;
  max-width: 100%;
  max-height: 40px;
  margin-top: 0.625rem;
  object-fit: contain;
}

/* Support mode: a superuser is looking at someone else's data. Said plainly,
   next to whose data it is. */
.baladi-sidebar-support {
  display: inline-block;
  margin-top: 0.375rem;
  padding: 0.0625rem 0.5rem;
  border-radius: 999px;
  background: var(--baladi-partial-bg);
  color: var(--baladi-partial-fg);
  font-size: var(--baladi-text-small);
  font-weight: 600;
  white-space: nowrap;
}

/* ---- The collapse control, ON the seam --------------------------------- */

/* It sits on the corner where the rail's edge meets the header's border,
   half over each. `position: fixed` reading the two tokens the frame is
   built from — --sidebar-width for where the rail ends,
   --baladi-header-height for where the border runs — so it lands on that
   corner at either rail width and needs no JavaScript to follow the rail.

   The negative margins are half the button, which is what centres the box on
   the point rather than hanging it off one side. Both are logical
   properties, so the control is on the correct side of a LTR page too.

   It used to be inside the rail's header beside the wordmark. That put it at
   one place when the rail was 260px and another when it was 72px: you
   collapsed the menu and then went looking for the way back. */
.baladi-sidebar-toggle {
  display: none;
  position: fixed;
  top: var(--baladi-header-height);
  inset-inline-start: var(--sidebar-width, var(--baladi-sidebar-width));
  margin-top: -14px;
  margin-inline-start: -14px;
  z-index: 40;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid var(--baladi-chrome-border);
  border-radius: 999px;
  background: var(--baladi-chrome-raised);
  color: var(--baladi-chrome-icon);
  font-size: 0.75rem;
  line-height: 1;
  cursor: pointer;
}

@media (min-width: 1024px) {
  /* Below this the rail is an off-canvas drawer with its own open and close
     controls; there is no seam to sit on and nothing to collapse. */
  .baladi-sidebar-toggle { display: inline-flex; }
}

.baladi-sidebar-toggle:hover {
  background: var(--baladi-chrome-active-bg);
  border-color: var(--baladi-chrome-accent);
  color: var(--baladi-chrome-active-fg);
}

.baladi-sidebar-toggle:focus-visible {
  outline: 2px solid var(--baladi-chrome-accent);
  outline-offset: 2px;
}

/* It follows the rail when the rail animates, and only then — same rule as
   the rail itself, so a page load in either state paints once. */
.baladi-sidebar-animating .baladi-sidebar-toggle {
  transition: inset-inline-start 200ms ease;
}

/* One chevron shows at a time, and it points the way the rail is about to
   GO. The rail is pinned to the inline start, so collapsing retreats its
   inner edge towards the start and expanding brings it back out.

   The glyphs are named for a left-to-right page, which is the direction the
   icon set is drawn in; on this RTL page the pair is flipped so that
   "towards the start" still means "towards the rail". */
.baladi-chevron-collapse { display: inline-flex; }
.baladi-chevron-expand { display: none; }
.baladi-sidebar-collapsed .baladi-chevron-collapse { display: none; }
.baladi-sidebar-collapsed .baladi-chevron-expand { display: inline-flex; }

[dir='rtl'] .baladi-chevron-collapse,
[dir='rtl'] .baladi-chevron-expand {
  transform: scaleX(-1);
}

/* ---- The logo ---------------------------------------------------------- */

.baladi-sidebar-logo {
  display: inline-flex;
  align-items: center;
  min-width: 0;
}

/* 30px, not 34px: the block is exactly --baladi-header-height now, and the
   wordmark has to sit inside it with air above and below rather than fill
   it edge to edge. */
.baladi-logo-full {
  height: 30px;
  width: auto;
  max-width: 100%;
}

.baladi-logo-mark {
  display: none;
  height: 30px;
  width: auto;
}

.baladi-sidebar-collapsed .baladi-logo-full { display: none; }
.baladi-sidebar-collapsed .baladi-logo-mark { display: block; }

/* ---- Collapsed, desktop only ------------------------------------------ */

@media (min-width: 1024px) {

  /* Metronic reads this for .kt-wrapper's padding and .kt-header's inset, so
     the content area widens by itself.

     It has to be declared on BODY, not on :root. Metronic puts
     `--sidebar-width: 280px` on `.demo1`, which is the body element, and a
     value set there shadows the :root one for every descendant — so setting it
     on html alone collapsed the rail and left the content offset at 280px. */
  html.baladi-sidebar-collapsed body.demo1 { --sidebar-width: 72px; }

  /* The <aside> carries Tailwind's w-[250px] 2xl:w-[280px], which a custom
     property cannot override on its own. */
  .baladi-sidebar-collapsed .kt-sidebar {
    width: 72px !important;
    min-width: 72px;
  }

  .baladi-sidebar-collapsed .kt-sidebar-header {
    padding-inline: 0;
  }

  /* Collapsed: the mark centred in the same 64px block, and the identity
     gone — the mark's title carries the municipality instead. The seam does
     not move, which is the point of the 72px rail keeping the same header
     height as the 260px one. */
  .baladi-sidebar-collapsed .baladi-sidebar-brand {
    justify-content: center;
    padding-inline: 0.5rem;
  }

  .baladi-sidebar-collapsed .baladi-sidebar-identity-block {
    display: none;
  }

  .baladi-sidebar-collapsed .kt-sidebar-content {
    padding-inline: 0;
  }

  .baladi-sidebar-collapsed #sidebar_scrollable {
    padding-inline: 0.5rem;
  }

  /* Section headings have no room and no meaning without the items under them
     being readable. Hidden visually, kept for a screen reader: the rail is
     `fixed`, so it is a containing block and an absolutely positioned span
     inside it cannot escape to widen the page. */
  .baladi-sidebar-collapsed .kt-sidebar .kt-menu-heading {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .baladi-sidebar-collapsed .kt-sidebar .kt-menu-item {
    padding-top: 0;
    padding-bottom: 0;
  }

  .baladi-sidebar-collapsed .kt-sidebar .kt-menu-link {
    position: relative;
    justify-content: center;
    gap: 0;
    padding-inline: 0;
    height: 44px;
  }

  .baladi-sidebar-collapsed .kt-sidebar .kt-menu-icon {
    width: auto;
    justify-content: center;
  }

  /* The label becomes the tooltip. It is already in the DOM with the right
     Arabic text, so nothing has to be duplicated into a data- attribute and
     the two can never disagree. */
  .baladi-sidebar-collapsed .kt-sidebar .kt-menu-title {
    position: absolute;
    inset-inline-start: calc(100% + 0.5rem);
    top: 50%;
    transform: translateY(-50%);
    z-index: 60;
    padding: 0.375rem 0.625rem;
    border-radius: var(--radius);
    background: var(--baladi-chrome-active-bg);
    color: var(--baladi-chrome-active-fg);
    border: 1px solid var(--baladi-chrome-border);
    font-size: var(--baladi-text-small);
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity 120ms ease;
  }

  .baladi-sidebar-collapsed .kt-sidebar .kt-menu-link:hover .kt-menu-title,
  .baladi-sidebar-collapsed .kt-sidebar .kt-menu-link:focus-visible .kt-menu-title {
    opacity: 1;
  }

  /* The active item has to stay obvious with no text to carry it: an accent
     bar on the rail's inner edge plus a tinted icon. */
  .baladi-sidebar-collapsed .kt-sidebar .kt-menu-link[aria-current='page'] {
    background: var(--baladi-chrome-active-bg);
    border-radius: var(--radius);
  }

  .baladi-sidebar-collapsed .kt-sidebar .kt-menu-link[aria-current='page']::before {
    content: '';
    position: absolute;
    inset-inline-end: 0;
    top: 0.375rem;
    bottom: 0.375rem;
    width: 3px;
    border-radius: 3px;
    background: var(--baladi-chrome-accent);
  }

  .baladi-sidebar-collapsed .kt-sidebar .kt-menu-link[aria-current='page'] i {
    color: var(--baladi-chrome-active-fg);
  }
}

/* Anything that only makes sense beside a label — an "opens in a new tab"
   marker, a badge — is dropped when the rail is icons only. Without this the
   Django Admin row put two icons side by side in a 72px rail. */
@media (min-width: 1024px) {
  .baladi-sidebar-collapsed .kt-sidebar .baladi-hide-collapsed { display: none; }
}

/* =========================================================================
   16. Detail pages
   -------------------------------------------------------------------------
   A header saying what the record is and what state it is in, a strip of key
   facts, then sections of field/value pairs and related lists.
   ========================================================================= */

.baladi-detail-header {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-bottom: 1rem;
}

@media (min-width: 640px) {
  .baladi-detail-header {
    flex-direction: row;
    align-items: flex-start;
    justify-content: space-between;
  }
}

.baladi-detail-header__main {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  min-width: 0;
}

.baladi-detail-header__eyebrow {
  font-size: var(--baladi-text-small);
  color: var(--muted-foreground);
}

.baladi-detail-header__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

/* Separates a destructive action from the ordinary ones, so Delete is not
   adjacent to Edit at the same size and weight. */
.baladi-detail-header__gap {
  width: 1px;
  align-self: stretch;
  min-height: 1.5rem;
  margin-inline: 0.25rem;
  background: var(--border);
}

/* The facts strip: the handful of things you opened the page to check. */
.baladi-facts {
  display: grid;
  gap: 0.75rem 1.25rem;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin: 0 0 1.25rem;
  padding: 1rem 1.25rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

@media (min-width: 640px) {
  .baladi-facts { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (min-width: 1024px) {
  .baladi-facts { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.baladi-facts__item { min-width: 0; }

.baladi-facts__item dt {
  font-size: var(--baladi-text-small);
  color: var(--muted-foreground);
  margin-bottom: 0.125rem;
}

.baladi-facts__item dd {
  margin: 0;
  font-weight: 500;
  color: var(--foreground);
  overflow-wrap: anywhere;
}

/* Field/value pairs inside a section. */
.baladi-detail-grid {
  display: grid;
  gap: 1rem 1.25rem;
  grid-template-columns: minmax(0, 1fr);
  margin: 0;
}

@media (min-width: 640px) {
  .baladi-detail-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1024px) {
  .baladi-detail-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.baladi-detail-grid__item { min-width: 0; }

.baladi-detail-grid__item dt {
  font-size: var(--baladi-text-small);
  color: var(--muted-foreground);
  margin-bottom: 0.125rem;
}

.baladi-detail-grid__item dd {
  margin: 0;
  color: var(--foreground);
  overflow-wrap: anywhere;
}

/* Sections stack with the same rhythm as a form's. */
.baladi-detail-sections {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

/* A permission this user actually has, on their detail page. Only the granted
   ones are listed — a wall of ticks and crosses is harder to read than a short
   list of what someone can do. */
.baladi-permission-granted {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  font-size: var(--baladi-text-small);
  color: var(--foreground);
}

.baladi-permission-granted i {
  font-size: 0.875rem;
  color: var(--baladi-paid-fg);
}

/* State-changing actions on a detail page. They are POST forms, not links, so
   they cannot live in the header's action row beside the <a>s — they sit under
   it as their own row. */
.baladi-detail-state-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

/* A caution, not a danger: archiving hides a record and can be undone, so it
   does not get the red treatment a real deletion would. */
.baladi-alert-caution {
  background: var(--baladi-partial-bg);
  border: 1px solid var(--baladi-partial-fg);
  color: var(--baladi-partial-fg);
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  padding: 0.875rem 1rem;
  border-radius: var(--radius);
}

/* The banner on an archived record. The restore button sits at the end of the
   row rather than under the text, so the state and the way out of it read as
   one thing. */
.baladi-archived-banner {
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem;
}

.baladi-archived-banner form { margin: 0; }

/* The municipality's emblem, shown on both surfaces it has to work on.

   The stored file keeps its transparency (common/images.py) precisely so it
   can sit on the always-dark rail and on a white printed page. Showing it
   against one of them would be showing half the answer. */
.baladi-logo-preview {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}

.baladi-logo-preview__swatch {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 4.5rem;
  height: 4.5rem;
  padding: 0.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.baladi-logo-preview__swatch img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

.baladi-logo-preview__swatch--light { background: #ffffff; }
.baladi-logo-preview__swatch--dark  { background: var(--baladi-chrome-bg); }

/* A file input the page can label in its own language. The native control's
   button and its "no file chosen" are written by the browser in the
   browser's locale and cannot be changed, so the input is moved out of
   sight — not removed: it keeps its place in the tab order and its name for
   a screen reader — and a <label> stands in for it, which is what a label
   is for. */
.baladi-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* A control that belongs to the group above it rather than starting a new
   one — the "remove the logo" tick under the "choose a file" button. */
.baladi-field-group__inline { margin-top: 0.625rem; }

.baladi-file-field {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.625rem;
}

/* The label is the control, so it has to answer the keyboard the way one
   does: focusing the hidden input rings the thing the user can see. */
.baladi-visually-hidden:focus-visible + label {
  outline: 2px solid var(--baladi-accent);
  outline-offset: 2px;
}

/* The dollar approximation beside a lira figure. Quieter than the figure it
   accompanies, because it is a courtesy to a reader who thinks in dollars
   and never the number anybody is owed — see core/money.py. */
.baladi-approx {
  font-size: var(--baladi-text-small);
  color: var(--muted-foreground);
  white-space: nowrap;
}

/* A figure the page worked out rather than one somebody typed. It reads as a
   value, not as a field: no box, no border, nothing that invites a click. */
.baladi-computed {
  display: block;
  font-family: var(--baladi-font-heading);
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 2.75rem;
  color: var(--foreground);
}

/* The note under the permissions list. It follows a grid, so it needs the
   same separation from it that a field group does. */
.baladi-permission-note { margin-top: 1.25rem; }

/* A labelled group inside a card that is NOT a field/value pair — a row of
   badges, a short list. The label matches `.baladi-detail-grid__item dt` so
   it reads as one more line of the same card rather than a second idea.

   The top rule separates it from the grid above it. Without one, a row of
   badges under a row of values reads as another value. */
.baladi-field-group {
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
}

.baladi-field-group__label {
  font-size: var(--baladi-text-small);
  color: var(--muted-foreground);
  margin-bottom: 0.5rem;
}

/* The group is the whole card's content on a page that has nothing above it
   to be separated from. */
.kt-card-content > .baladi-field-group:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}


/* =========================================================================
   17. Prose
   -------------------------------------------------------------------------
   Running text inside a card. The only page with any is the privacy notice,
   which is several hundred words of paragraphs and lists rather than the
   field/value pairs every other page is made of.

   It exists because the reset strips margins from <p> and Tailwind's
   preflight removes list markers, so an unstyled paragraph and an unstyled
   <ul> inside .kt-card-content come out as one undifferentiated block of
   text with no bullets. Nothing in the pruned bundle restores them —
   `prose` is a Tailwind plugin class and is not compiled in (see §11).

   Margins are logical (block/inline) rather than top/left, so the list
   indent sits on the correct side under RTL without a direction-specific
   rule.
   ========================================================================= */

.baladi-prose {
  line-height: 1.75;
  color: var(--baladi-text, inherit);
}

.baladi-prose > * + * {
  margin-block-start: 0.85rem;
}

.baladi-prose ul,
.baladi-prose ol {
  margin-inline-start: 1.25rem;
  list-style-position: outside;
}

.baladi-prose ul { list-style-type: disc; }
.baladi-prose ol { list-style-type: decimal; }

.baladi-prose li + li {
  margin-block-start: 0.4rem;
}

.baladi-prose strong {
  font-weight: 600;
}

/* ---------------------------------------------------------------------------
   §16 Family links on the person detail page

   The add row under the table: a search box, the name it resolved to, the
   relationship, and the button. Plain CSS and not `md:grid-cols-4`, for the
   reason §11 gives — that class is not in the pruned Tailwind bundle and
   would silently do nothing.
   --------------------------------------------------------------------------- */
.baladi-family-add {
  padding: 1.25rem;
  border-top: 1px solid var(--border);
}

.baladi-family-add__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1rem;
  align-items: start;
}

@media (min-width: 768px) {
  .baladi-family-add__grid {
    /* The search box earns the extra width: it is the field somebody types
       a name into, and the other three hold a picked value. */
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 1fr) auto;
  }
}

.baladi-family-add__grid > * { min-width: 0; }

/* The button sits on the baseline of the inputs, not of their labels. */
.baladi-family-add__action {
  display: flex;
  align-items: flex-end;
  height: 100%;
}

@media (min-width: 768px) {
  .baladi-family-add__action { padding-block-start: 1.6rem; }
}

/* The actions column of a table inside a detail card. */
.baladi-table-actions-head {
  width: 1%;
  white-space: nowrap;
  text-align: end;
}

/* ---------------------------------------------------------------------------
   §17 The public shell — /privacy/, /terms/, /support/

   The pages a visitor who is not logged in can read. Not the console frame
   (no sidebar, no municipality) and not the 440px auth card (the privacy
   notice is a long document and would read through a letterbox).
   --------------------------------------------------------------------------- */
.baladi-public-page {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--baladi-page);
}

.baladi-public-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem 1.25rem;
  background: var(--baladi-chrome-bg);
  border-block-end: 1px solid var(--baladi-chrome-border);
}

.baladi-public-brand {
  display: inline-flex;
  align-items: center;
}

.baladi-public-logo {
  height: 34px;
  width: auto;
}

.baladi-public-main {
  flex: 1 1 auto;
  width: 100%;
  max-width: 860px;
  margin-inline: auto;
  padding: 1.75rem 1.25rem 2.5rem;
}

.baladi-public-footer {
  padding: 1rem 1.25rem;
  background: var(--baladi-chrome-bg);
  border-block-start: 1px solid var(--baladi-chrome-border);
  color: var(--baladi-chrome-muted);
}

/* The links themselves, shared by all three footers. Wrapping is on purpose:
   four items plus separators do not fit one line on a phone, and a footer
   that overflows horizontally is what the body-must-never-scroll rule is
   about. */
.baladi-public-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  font-size: 0.8125rem;
  line-height: 1.6;
}

/* ---------------------------------------------------------------------------
   §18 Printable documents

   The first print rules in this project — a grep for `@media print` found
   nothing before the جرد sheet (docs/ASSETS.md §8.6). Written to be reused by
   every later printable document (bills, receipts, certificates), which is
   why they are here and not inline on one page.

   Two audiences: the screen, where the sheet sits inside the console frame
   and is previewed; and paper, where it is A4 portrait RTL and is walked
   around a building with a pen.
   --------------------------------------------------------------------------- */

/* The bare print shell (console/layout/print.html) — no frame at all. */
.baladi-print-body {
  margin: 0;
  padding: 0;
  /* Light on paper whatever the viewer's theme. The dark palette prints as a
     black page, and a browser that honours it wastes a cartridge. */
  background: #ffffff;
  color: #18181b;
}

.baladi-print-bar {
  display: flex;
  gap: 0.5rem;
  justify-content: flex-end;
  padding: 0.75rem 1rem;
  border-block-end: 1px solid #e4e4e7;
  background: #fafafa;
}

.baladi-print-sheet {
  max-width: 190mm;
  margin-inline: auto;
  padding: 1rem;
  color: #18181b;
}

.baladi-print-head {
  text-align: center;
  margin-block-end: 1.25rem;
  padding-block-end: 0.75rem;
  border-block-end: 2px solid #18181b;
}

/* The emblem at the top of a printed document, above the title. 72px is
   about what a letterhead gives one, and `contain` keeps a seal that is not
   square from being cropped into one. */
.baladi-print-logo {
  display: block;
  max-height: 72px;
  max-width: 180px;
  margin: 0 auto 0.5rem;
  object-fit: contain;
}

.baladi-print-head h1 {
  font-size: 1.25rem;
  font-weight: 700;
  margin: 0;
}

.baladi-print-meta {
  margin: 0.35rem 0 0;
  font-size: 0.8125rem;
  color: #52525b;
}

.baladi-print-section { margin-block-end: 1.25rem; }

.baladi-print-section__title {
  font-size: 0.9375rem;
  font-weight: 700;
  margin: 0 0 0.4rem;
  padding: 0.3rem 0.5rem;
  background: #f4f4f5;
  border-inline-start: 3px solid #18181b;
}

/* The tree depth, as an indent rather than a number. `attr()` in content is
   not reliable across print engines, so the template emits the depth and this
   turns it into space — one step per level, and the digit itself is hidden. */
.baladi-print-indent {
  display: inline-block;
  overflow: hidden;
  width: 1.1em;
  text-indent: -99em;
}

.baladi-print-code {
  font-size: 0.75rem;
  font-weight: 400;
  color: #52525b;
}

/* Seven columns do not fit a phone, and the body must never scroll sideways
   (CLAUDE.md) — so the table scrolls inside this instead. Caught by the
   screenshot harness, which refuses to photograph a page that overflows: the
   جرد sheet was pushing the viewport 73px wide at 390px. */
.baladi-print-table-wrap {
  overflow-x: auto;
  max-width: 100%;
}

.baladi-print-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.75rem;
}

.baladi-print-table th,
.baladi-print-table td {
  border: 1px solid #d4d4d8;
  padding: 0.3rem 0.4rem;
  text-align: start;
  vertical-align: top;
}

.baladi-print-table thead th {
  background: #f4f4f5;
  font-weight: 700;
  white-space: nowrap;
}

/* The موجود / غير موجود column: two boxes, ticked with a pen. */
.baladi-print-tick {
  width: 24mm;
  white-space: nowrap;
  text-align: center;
}

.baladi-print-box {
  display: inline-block;
  width: 4mm;
  height: 4mm;
  margin-inline: 1mm;
  border: 1px solid #52525b;
}

.baladi-print-subtotal {
  margin: 0.35rem 0 0;
  font-size: 0.75rem;
  color: #52525b;
}

.baladi-print-empty {
  margin: 0;
  font-size: 0.75rem;
  color: #71717a;
  font-style: italic;
}

.baladi-print-totals,
.baladi-print-signatures {
  margin-block-start: 1.5rem;
  padding-block-start: 0.75rem;
  border-block-start: 2px solid #18181b;
  font-size: 0.8125rem;
}

.baladi-print-note {
  font-size: 0.75rem;
  color: #52525b;
}

.baladi-print-signatures__table tbody td { height: 12mm; }

@media print {
  /* A4 portrait with a margin a stapler and a hole punch can both live
     with. `size` is honoured by Chrome and Firefox; a browser that ignores
     it still gets the right page because the sheet is sized in mm. */
  @page {
    size: A4 portrait;
    margin: 14mm 12mm;
  }

  html, body {
    background: #ffffff !important;
    color: #000000 !important;
  }

  /* The frame, and anything that is a control rather than content. On the
     screen shell the sheet sits inside the console layout, so printing from
     there has to drop the chrome too — which is what makes the console
     version printable at all, rather than only the ?print=1 one. */
  .baladi-print-bar,
  .kt-sidebar,
  aside,
  .kt-header,
  header.kt-header,
  .kt-footer,
  footer.kt-footer,
  .baladi-page-header,
  .kt-btn,
  .baladi-public-header,
  .baladi-public-footer {
    display: none !important;
  }

  /* The console layout offsets the content for a fixed sidebar and header.
     On paper there is neither. */
  .baladi-print-sheet {
    max-width: none;
    margin: 0;
    padding: 0;
  }

  .kt-wrapper,
  .kt-content,
  .kt-container-fixed,
  main {
    margin: 0 !important;
    padding: 0 !important;
    width: auto !important;
    max-width: none !important;
  }

  /* Backgrounds off, borders kept: a header band that prints as grey ink is
     a waste, and the grid is what makes the sheet readable. */
  .baladi-print-section__title,
  .baladi-print-table thead th {
    background: transparent !important;
    border-block-end: 1px solid #000000;
  }

  .baladi-print-table th,
  .baladi-print-table td {
    border-color: #000000;
  }

  /* The header row repeats on every page — a twelve-page جرد whose columns
     are named only on page one is unusable in a room. */
  .baladi-print-table thead { display: table-header-group; }
  .baladi-print-table tfoot { display: table-footer-group; }

  /* Never split a row, and keep a location's heading with at least the start
     of its table. */
  .baladi-print-table tr,
  .baladi-print-box {
    break-inside: avoid;
    page-break-inside: avoid;
  }

  .baladi-print-section__title {
    break-after: avoid;
    page-break-after: avoid;
  }

  .baladi-print-signatures {
    break-inside: avoid;
    page-break-inside: avoid;
  }

  /* On paper there is no viewport to scroll, and a clipping container would
     cut the table off at the page edge instead of flowing it. */
  .baladi-print-table-wrap {
    overflow: visible !important;
    max-width: none !important;
  }

  a[href]::after { content: ""; }
}

/* ---------------------------------------------------------------------------
   §19 The movement timeline (asset detail page)

   docs/ASSETS.md §8.3. An ordered list with a rule down the start edge and a
   marker per entry — the history a municipality reads, as opposed to
   core.AuditLog, which the vendor reads.

   `border-inline-start` and `inset-inline-start`, not `left`: the rule is on
   the right in RTL and the whole console is RTL.
   --------------------------------------------------------------------------- */
.baladi-timeline {
  list-style: none;
  margin: 0;
  padding: 0;
}

.baladi-timeline__item {
  position: relative;
  padding-inline-start: 1.25rem;
  padding-block-end: 1.25rem;
  border-inline-start: 2px solid var(--border);
}

.baladi-timeline__item:last-child {
  border-inline-start-color: transparent;
  padding-block-end: 0;
}

.baladi-timeline__item::before {
  content: "";
  position: absolute;
  inset-inline-start: -5px;
  top: 0.35rem;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--baladi-accent-text, #21709f);
}

.baladi-timeline__date {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--foreground);
}

.baladi-timeline__body { margin-block-start: 0.25rem; }

.baladi-timeline__changes {
  list-style: none;
  margin: 0 0 0.4rem;
  padding: 0;
  font-size: 0.875rem;
  line-height: 1.8;
}

.baladi-timeline__reason {
  margin: 0 0 0.35rem;
  font-size: 0.875rem;
  line-height: 1.7;
}

.baladi-timeline__decision {
  margin: 0 0 0.35rem;
  font-size: 0.8125rem;
  color: var(--foreground);
}

.baladi-timeline__who {
  margin: 0;
  font-size: 0.75rem;
  color: var(--muted-foreground);
}

/* ---------------------------------------------------------------------------
   §20 Asset photographs

   Thumbnails in the list, a main image and a gallery on the detail page.
   Every one of these is an authenticated request through the download view
   (there is no public URL for a private file), so the sizes are small and
   the markup carries `loading="lazy"`.
   --------------------------------------------------------------------------- */
.baladi-thumb-col {
  width: 56px;
  padding-inline-end: 0;
}

.baladi-thumb {
  display: block;
  width: 40px;
  height: 40px;
  object-fit: cover;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--muted);
}

/* The placeholder for an asset with no photograph. Same box, so a column of
   mixed rows does not jump. */
.baladi-thumb--empty {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted-foreground);
  font-size: 1rem;
}

.baladi-photo-main img {
  display: block;
  max-width: 100%;
  max-height: 320px;
  width: auto;
  border-radius: calc(var(--radius) + 2px);
  border: 1px solid var(--border);
}

.baladi-photo-gallery {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-block-start: 0.875rem;
}

.baladi-photo-thumb {
  margin: 0;
  width: 120px;
}

.baladi-photo-thumb img {
  display: block;
  width: 120px;
  height: 90px;
  object-fit: cover;
  border-radius: var(--radius);
  border: 1px solid var(--border);
}

.baladi-photo-thumb figcaption {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.25rem;
  margin-block-start: 0.25rem;
}

.baladi-photo-add {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-block-start: 1rem;
  padding-block-start: 0.875rem;
  border-block-start: 1px solid var(--border);
}

.baladi-photo-add .kt-input { flex: 1 1 220px; }

/* The document form under the attachments table. */
.baladi-attachment-add {
  padding: 1.25rem;
  border-block-start: 1px solid var(--border);
}

.baladi-attachment-add__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1rem;
  align-items: start;
}

@media (min-width: 768px) {
  .baladi-attachment-add__grid {
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr) minmax(0, 1fr) auto;
  }
  .baladi-attachment-add__action { padding-block-start: 1.6rem; }
}

.baladi-attachment-add__grid > * { min-width: 0; }

@media print {
  /* The جرد sheet and the asset reports carry no photographs — §8.6. */
  .baladi-thumb-col,
  .baladi-photo-main,
  .baladi-photo-gallery,
  .baladi-photo-add,
  .baladi-attachment-add {
    display: none !important;
  }
}

/* ---------------------------------------------------------------------------
   §21 Sidebar — the single-open accordion

   The console menu grew past a laptop screen: eight sections, thirty-odd
   items, and on a 13" display you scrolled to reach التقارير. So the rail
   is an accordion, and **at most one section is open**.

   Which one is not stored anywhere: the SERVER opens the section holding the
   current page and closes the rest, and renders that into the markup — the
   same arrangement as `baladi_theme` and `baladi_sidebar` and for the same
   reason. State applied by script after load means the menu paints at one
   height and jumps to another on every navigation.

   It used to persist what you folded in a `baladi_nav` cookie, with the
   current page's section forced open over the top of it. Both are gone: a
   remembered fold is a guess about where you want to be that is wrong the
   moment you navigate, and the lock meant the one section a user most wants
   out of the way was the one they could not move.
   --------------------------------------------------------------------------- */

.baladi-nav-section {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* The heading. A <button>, so Enter and Space work with no key handling of
   our own, and so aria-expanded carries the state to a screen reader. */
.baladi-nav-section__toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  width: 100%;
  margin-block-start: 0.625rem;
  padding: 0.25rem 0.625rem;
  background: transparent;
  border: 0;
  border-radius: var(--radius);
  text-align: start;
  cursor: pointer;
}

.baladi-nav-section__toggle:hover {
  background: var(--baladi-chrome-hover);
}

.baladi-nav-section__toggle:hover .baladi-nav-section__label,
.baladi-nav-section__toggle:hover .baladi-nav-section__chevron {
  color: var(--baladi-chrome-fg);
}

/* The label is `.kt-menu-heading` as well, which is what already colours and
   sizes a sidebar heading in both this rail and the SaaS panel's; this only
   stops it shrinking the chevron off the row. */
.baladi-nav-section__label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Down closed, up open. Up and down need no mirroring between LTR and RTL —
   what IS mirrored is where the chevron sits, and `space-between` above puts
   it on the inline END, which in Arabic is the left. */
.baladi-nav-section__chevron {
  flex: 0 0 auto;
  font-size: 0.6875rem;
  color: var(--baladi-chrome-muted);
  transition: transform 180ms ease;
}

.baladi-nav-section__toggle[aria-expanded='true'] .baladi-nav-section__chevron {
  transform: rotate(180deg);
}

/* The slide.

   `grid-template-rows: 1fr → 0fr` animates a height nobody has to measure, so
   there is no JavaScript holding a pixel value and nothing to go stale when a
   permission adds an item. A browser that cannot interpolate it (Safari below
   17.4) snaps instead of sliding, which is the whole of the degradation.

   A transition never runs on first paint, so the server rendering a section
   closed costs nothing — this needs no counterpart to
   `.baladi-sidebar-animating`. */
.baladi-nav-section__items {
  display: grid;
  grid-template-rows: 1fr;
  overflow: hidden;
  transition: grid-template-rows 180ms ease;
}

.baladi-nav-section__items[data-baladi-nav-open='false'] {
  grid-template-rows: 0fr;
}

/* The items keep the rail's own rhythm — `gap-1` on the <nav> stops applying
   between them once they are a grid cell deep. */
.baladi-nav-section__inner {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-height: 0;
  overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
  .baladi-nav-section__items,
  .baladi-nav-section__chevron {
    transition: none;
  }
}

/* ---- What is still missing on a record --------------------------------
   The employee page's «الخطوات التالية»: a fact, and the button that
   settles it. Facts with buttons rather than a warning strip — a register
   entry that is simply not finished yet is not an error.
   ------------------------------------------------------------------------ */

.baladi-next-steps {
  margin-bottom: 1rem;
  border-color: var(--baladi-accent);
}

.baladi-next-steps__list {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.baladi-next-steps__item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

.baladi-next-steps__fact {
  min-width: 0;
  font-size: var(--baladi-text-body);
  color: var(--baladi-ink);
}

/* =========================================================================
   The person combobox
   -------------------------------------------------------------------------
   One search-as-you-type control for choosing a person out of the register.
   It replaced a text box and a <select> standing side by side: two controls
   for one choice, the second of them empty until the first had been used.

   Markup: console/templates/console/widgets/person_combobox.html
   Behaviour: static/assets/js/person-combobox.js
   ========================================================================= */

.baladi-combobox {
  min-width: 0;
}

/* The box, its clear button and the list it drops. Relative HERE and not on
   the wrapper: the list is positioned at `top: 100%`, and on the wrapper
   that 100% would include the live region below, dropping the list a line
   away from the control it belongs to. */
.baladi-combobox__field {
  position: relative;
  min-width: 0;
}

/* Out of the flow, still announced. `clip-path` rather than `display: none`
   or `visibility: hidden`, either of which takes the live region out of the
   accessibility tree and silences it. */
.baladi-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.baladi-combobox__input {
  width: 100%;
  /* Room for the clear button, on the inline end, which mirrors by itself. */
  padding-inline-end: 2.25rem;
}

.baladi-combobox__clear {
  position: absolute;
  inset-inline-end: 0.375rem;
  top: 0.375rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--baladi-ink-muted);
  font-size: 0.75rem;
  cursor: pointer;
}

.baladi-combobox__clear:hover {
  background: var(--baladi-surface-2);
  color: var(--baladi-ink);
}

/* The listbox floats over the fields under it rather than pushing them
   down: a form that reflows while you are typing into it moves the control
   out from under the cursor. */
.baladi-combobox__list {
  position: absolute;
  inset-inline: 0;
  top: calc(100% + 0.25rem);
  z-index: 40;
  max-height: 16rem;
  overflow-y: auto;
  margin: 0;
  padding: 0.25rem;
  list-style: none;
  background: var(--baladi-surface);
  border: 1px solid var(--baladi-line);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}

.baladi-combobox__option {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.4375rem 0.5rem;
  border-radius: var(--radius);
  cursor: pointer;
  color: var(--baladi-ink);
  font-size: var(--baladi-text-table);
}

/* `.is-active` is the arrow keys' position, not the mouse's — focus never
   leaves the input, so :focus cannot carry this. Hover paints the same way
   so the two input methods look alike. */
.baladi-combobox__option:hover,
.baladi-combobox__option.is-active {
  background: var(--baladi-accent-tint);
  color: var(--baladi-accent-on-tint);
}

.baladi-combobox__option-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Present only when the caller may see identifiers — the endpoint omits the
   key rather than sending an empty one. */
.baladi-combobox__option-id {
  flex: 0 0 auto;
  font-size: var(--baladi-text-small);
  color: var(--baladi-ink-muted);
}

.baladi-combobox__option.is-active .baladi-combobox__option-id,
.baladi-combobox__option:hover .baladi-combobox__option-id {
  color: var(--baladi-accent-on-tint);
}

/* "لا نتائج" as a row of the list rather than a separate line under it.
   `role="presentation"`, because it is not an option and arrowing onto it
   would offer the user something they cannot choose. */
.baladi-combobox__empty {
  padding: 0.4375rem 0.5rem;
  font-size: var(--baladi-text-table);
  color: var(--baladi-ink-muted);
}

/* ---- A named group INSIDE a section ------------------------------------
   The module's reports, the module's settings. One rule for every module:
   the register at the top of the section, then «تقارير», then «إعدادات».

   A label and an indent, NOT a second accordion — see
   console/includes/_nav_group.html for why. The label is quieter and
   smaller than a section heading because it is a level down, and the items
   under it carry a hairline on the inline-start edge, which is what makes
   the indent read as containment rather than as four links that drifted.
   ------------------------------------------------------------------------ */

.baladi-nav-group {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin-block-start: 0.375rem;
}

.baladi-nav-group__label {
  padding-inline: 0.625rem;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--baladi-chrome-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.baladi-nav-group__items {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding-inline-start: 0.5rem;
  margin-inline-start: 0.625rem;
  border-inline-start: 1px solid var(--baladi-chrome-border);
}

/* ---- The collapsed rail ------------------------------------------------- */

@media (min-width: 1024px) {
  /* Sections mean nothing in a 72px rail of icons: there is no heading to
     click and no label to group by. So the headings go, and **every section
     is forced open** — otherwise collapsing the rail would hide the icons of
     the seven closed sections with no control left anywhere to get them
     back. That matters more under a single-open accordion than it did
     before: in the expanded rail exactly one section is open, so without
     this rule the 72px rail would show one module's icons and nothing else.

     The heading's text survives the hiding for a screen reader: the items
     wrapper names it through `aria-labelledby`, and a referenced node still
     supplies an accessible name when it is display: none.

     `overflow: visible` matters as much: §15 renders each label as a tooltip
     positioned outside its link, and a wrapper clipping its own overflow
     would cut every one of them off at the rail's edge. */
  .baladi-sidebar-collapsed .baladi-nav-section__toggle,
  .baladi-sidebar-collapsed .baladi-nav-group__label {
    display: none;
  }

  /* A 72px rail of icons has nothing to indent FROM: the hairline and the
     inset would push the icons off their own centre line and leave the
     group's items looking like a different, narrower menu. The group's
     label is gone above, so there is nothing left for the indent to mean —
     the items wrapper still names it through `aria-labelledby`, which works
     on a display: none node. */
  .baladi-sidebar-collapsed .baladi-nav-group__items {
    padding-inline-start: 0;
    margin-inline-start: 0;
    border-inline-start: 0;
  }

  .baladi-sidebar-collapsed .baladi-nav-group {
    margin-block-start: 0;
  }

  .baladi-sidebar-collapsed .baladi-nav-section__items,
  .baladi-sidebar-collapsed .baladi-nav-section__items[data-baladi-nav-open='false'] {
    grid-template-rows: 1fr;
    overflow: visible;
    transition: none;
  }

  .baladi-sidebar-collapsed .baladi-nav-section__inner {
    overflow: visible;
  }
}

/* ===========================================================================
   §24  دليل الاستخدام — the staff guide

   Two pages: an index of cards with a search over it, and one rendered
   Markdown file. The body styles are here rather than reaching for
   `.baladi-prose` because a guide page is longer and more structured than a
   note field — it has h2s, ordered lists, tables and screenshots, and a
   `> * + *` margin rule is not a reading rhythm.
   ======================================================================== */

.baladi-guide-search {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

.baladi-guide-search .kt-input {
  flex: 1 1 auto;
  min-width: 0;
}

/* auto-fit, like every other card grid here: thirteen cards should not draw
   a column of empty space on a narrow window. */
.baladi-guide-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 0.75rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* The <li> itself carries no visual of its own — the link fills it — but
   it is named so the grid item can stretch, and `tests/test_css_classes.py`
   requires every class a template uses to be defined somewhere. */
.baladi-guide-card {
  display: flex;
}

.baladi-guide-card__link {
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
  height: 100%;
  padding: 0.875rem;
  border: 1px solid var(--baladi-border);
  border-radius: var(--baladi-radius);
  background: var(--baladi-surface);
  text-decoration: none;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.baladi-guide-card__link:hover,
.baladi-guide-card__link:focus-visible {
  border-color: var(--baladi-accent);
  background: var(--baladi-surface-raised);
}

.baladi-guide-card__icon {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: var(--baladi-radius-sm);
  background: var(--baladi-accent-soft);
  color: var(--baladi-accent-text);
}

.baladi-guide-card__text {
  display: flex;
  flex-direction: column;
  gap: 0.1875rem;
  min-width: 0;
}

.baladi-guide-card__title {
  font-weight: 600;
  color: var(--baladi-fg);
}

.baladi-guide-card__summary {
  font-size: var(--baladi-text-small);
  color: var(--baladi-fg-muted);
  line-height: 1.5;
}

.baladi-guide-hits {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
}

.baladi-guide-hit {
  padding-inline-start: 0.125rem;
}

.baladi-guide-hit__title {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  font-weight: 600;
  color: var(--baladi-link);
  text-decoration: none;
}

.baladi-guide-hit__excerpt {
  margin: 0.25rem 0 0;
  font-size: var(--baladi-text-small);
  color: var(--baladi-fg-muted);
  line-height: 1.6;
}

/* The hit itself, inside the excerpt. `mark`'s browser default is a yellow
   that belongs to neither theme. */
.baladi-guide-hit__excerpt mark {
  background: var(--baladi-accent-soft);
  color: var(--baladi-accent-text);
  border-radius: 0.1875rem;
  padding: 0 0.125rem;
}

/* --- the rendered Markdown ---------------------------------------------- */

.baladi-guide-body {
  display: block;
  max-width: 54rem;
  color: var(--baladi-fg);
  line-height: 1.75;
}

.baladi-guide-body h2 {
  font-family: var(--baladi-font-heading, inherit);
  font-size: var(--baladi-text-h3);
  font-weight: 600;
  margin: 2rem 0 0.75rem;
  padding-bottom: 0.375rem;
  border-bottom: 1px solid var(--baladi-border);
}

.baladi-guide-body h3 {
  font-family: var(--baladi-font-heading, inherit);
  font-size: var(--baladi-text-body);
  font-weight: 600;
  margin: 1.5rem 0 0.5rem;
}

/* The file's own `# عنوان` is dropped: the page already says its name in
   the top bar and in the page header, and a third copy at the top of the
   body would be the duplicate-title problem the frame pass removed. */
.baladi-guide-body h1 {
  display: none;
}

.baladi-guide-body p {
  margin: 0 0 0.875rem;
}

/* The markers have to be asked for: Tailwind's preflight sets
   `list-style: none` on every ul and ol, so a numbered list of steps
   renders as a column of sentences with nothing to count them by. The same
   reason `.baladi-prose` names them. */
.baladi-guide-body ul,
.baladi-guide-body ol {
  margin: 0 0 0.875rem;
  padding-inline-start: 1.5rem;
  list-style-position: outside;
}

.baladi-guide-body ul { list-style-type: disc; }
.baladi-guide-body ol { list-style-type: decimal; }

/* A nested list inside a step — the import page has one — keeps its own
   indent and drops the bottom margin its parent item already provides. */
.baladi-guide-body li > ul,
.baladi-guide-body li > ol {
  margin-block: 0.375rem 0;
}

.baladi-guide-body li {
  margin-bottom: 0.375rem;
}

.baladi-guide-body code {
  font-family: var(--baladi-font-mono, monospace);
  font-size: 0.875em;
  background: var(--baladi-surface-raised);
  border: 1px solid var(--baladi-border);
  border-radius: 0.1875rem;
  padding: 0.0625rem 0.25rem;
  /* A permission code or a column key reads left to right inside Arabic. */
  direction: ltr;
  unicode-bidi: isolate;
  display: inline-block;
}

.baladi-guide-body a {
  color: var(--baladi-link);
}

.baladi-guide-body img {
  max-width: 100%;
  height: auto;
  margin: 0.5rem 0 1rem;
  border: 1px solid var(--baladi-border);
  border-radius: var(--baladi-radius);
}

/* Wide content scrolls inside itself; the page body never scrolls sideways. */
.baladi-guide-body table {
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 1rem;
  font-size: var(--baladi-text-small);
}

.baladi-guide-body th,
.baladi-guide-body td {
  border: 1px solid var(--baladi-border);
  padding: 0.375rem 0.625rem;
  text-align: start;
  vertical-align: top;
}

.baladi-guide-body th {
  background: var(--baladi-surface-raised);
  font-weight: 600;
}

.baladi-guide-nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  margin-top: 1.25rem;
  flex-wrap: wrap;
}

/* ---------------------------------------------------------------------------
   §22 The public portal — docs/CITIZEN_PORTAL.md §18 step 0

   A municipality's own site on its own host. Not the console: a visitor has
   no sidebar, no user menu and no theme cookie of ours, so this is a header,
   a main and a footer.

   **Light only, on purpose.** The console's dark mode is a preference a
   staff user sets and carries; somebody arriving from a search result has
   nothing to toggle, and a half-implemented dark mode on a public page reads
   as a bug. When a municipality can choose (step 1, the CMS), the choice
   lands here.

   Plain CSS, not Tailwind: `styles.css` is a prebuilt, pruned bundle with no
   build step in this repository, so a utility class only works if it happens
   to be compiled in already (CLAUDE.md). Everything below is ours.
   --------------------------------------------------------------------------- */

.baladi-portal {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--baladi-surface-2, #f5f7f9);
  color: var(--baladi-ink, #18181b);
  font-family: var(--baladi-font-body, system-ui), sans-serif;
  font-feature-settings: 'locl' 0;
}

.baladi-portal__header {
  background: var(--baladi-chrome-bg, #141821);
  color: var(--baladi-chrome-fg, #e8ecf2);
  padding: 2.5rem 1.25rem;
  text-align: center;
}

.baladi-portal__logo {
  display: block;
  margin: 0 auto 1rem;
  max-height: 96px;
  max-width: 100%;
  height: auto;
}

.baladi-portal__title {
  font-family: var(--baladi-font-heading, inherit);
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1.3;
  margin: 0;
  color: var(--baladi-chrome-fg, #e8ecf2);
}

.baladi-portal__subtitle {
  margin: 0.375rem 0 0;
  font-size: var(--baladi-text-small, 0.8125rem);
  color: var(--baladi-chrome-muted, #99a3b2);
}

.baladi-portal__main {
  flex: 1 1 auto;
  width: 100%;
  max-width: 48rem;
  margin: 0 auto;
  padding: 2rem 1.25rem;
}

.baladi-portal__card {
  background: var(--baladi-surface, #ffffff);
  border: 1px solid var(--baladi-border, #e4e6ef);
  border-radius: 0.75rem;
  padding: 1.5rem;
}

.baladi-portal__heading {
  font-family: var(--baladi-font-heading, inherit);
  font-size: 1.125rem;
  font-weight: 600;
  margin: 0 0 1rem;
}

/* One column on a phone, two where there is room. `auto-fit` + `minmax`
   rather than a fixed count, for the reason the stat grids use it: a card
   with one fact should not draw a column of empty space beside it. */
.baladi-portal__facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 1rem 1.5rem;
  margin: 0;
}

.baladi-portal__facts dt {
  font-size: var(--baladi-text-small, 0.8125rem);
  color: var(--baladi-ink-muted, #6b7280);
  margin-bottom: 0.125rem;
}

.baladi-portal__facts dd {
  margin: 0;
  font-weight: 500;
}

.baladi-portal__facts a {
  color: var(--baladi-accent-text, #21709f);
}

.baladi-portal__empty {
  margin: 0;
  color: var(--baladi-ink-muted, #6b7280);
}

.baladi-portal__footer {
  background: var(--baladi-chrome-bg, #141821);
  color: var(--baladi-chrome-muted, #99a3b2);
  padding: 1.25rem;
  text-align: center;
  font-size: var(--baladi-text-small, 0.8125rem);
}

.baladi-portal__footer p { margin: 0; }

.baladi-portal__credit { margin-top: 0.25rem; opacity: 0.75; }

/* The municipality's public addresses, on its settings page — read-only,
   beside the code and the plan. A list rather than a fact grid: the number
   of rows is one or two today and unbounded in principle, and a grid of one
   draws a column of empty space beside it. */
.baladi-domain-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.baladi-domain {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.baladi-domain__host {
  font-weight: 500;
}

.baladi-detail-subheading {
  font-family: var(--baladi-font-heading, inherit);
  font-size: var(--baladi-text-small);
  font-weight: 600;
  color: var(--baladi-ink-muted);
  margin: 1.25rem 0 0.625rem;
}

/* ---- The body editor (Quill 2) ----------------------------------------
   docs/CITIZEN_PORTAL.md §14.1. The textarea is the field and Quill is a
   skin on it, so the textarea is HIDDEN rather than removed: the form
   posts it, and a browser with the script blocked gets it back visible
   because the class is added by the script itself.
   ----------------------------------------------------------------------- */

.baladi-editor__source {
  display: none;
}

.baladi-editor .ql-toolbar,
.baladi-editor .ql-container {
  border-color: var(--baladi-border);
  background: var(--baladi-surface);
}

.baladi-editor .ql-toolbar {
  border-start-start-radius: 0.5rem;
  border-start-end-radius: 0.5rem;
}

.baladi-editor .ql-container {
  border-end-start-radius: 0.5rem;
  border-end-end-radius: 0.5rem;
  font-family: var(--baladi-font-body, inherit);
  font-size: var(--baladi-text-body);
}

/* Quill sizes its own editing surface to the content and collapses to two
   lines on an empty one, which reads as a broken field rather than as an
   empty one. */
.baladi-editor .ql-editor {
  min-height: 18rem;
  color: var(--baladi-ink);
}

.baladi-editor .ql-editor.ql-blank::before {
  color: var(--baladi-ink-muted);
  font-style: normal;
  inset-inline-start: 1rem;
  inset-inline-end: auto;
}

/* The cover image, above the file input that replaces it. */
.baladi-cover-preview {
  display: block;
  max-height: 180px;
  max-width: 100%;
  width: auto;
  border-radius: 0.5rem;
  margin-bottom: 1rem;
}

/* ---- The portal's content: the feed, a post, a page ------------------- */

.baladi-portal__nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.25rem 1.25rem;
  margin-top: 1.25rem;
  font-size: var(--baladi-text-small, 0.8125rem);
}

.baladi-portal__nav a {
  color: var(--baladi-chrome-muted, #99a3b2);
  text-decoration: none;
  padding: 0.25rem 0;
  border-bottom: 2px solid transparent;
}

.baladi-portal__nav a:hover,
.baladi-portal__nav a:focus-visible {
  color: var(--baladi-chrome-fg, #e8ecf2);
  border-bottom-color: var(--baladi-chrome-accent, #3498db);
}

/* A card in the feed. The cover is first in the source and first on a
   phone; above 40rem it sits beside the text, which is the one place this
   stylesheet needs a breakpoint. */
.baladi-post-card {
  margin-bottom: 1rem;
  padding: 0;
  overflow: hidden;
  display: grid;
  gap: 0;
}

.baladi-post-card__cover img {
  display: block;
  width: 100%;
  height: 100%;
  max-height: 220px;
  object-fit: cover;
}

.baladi-post-card__body { padding: 1.25rem; }

.baladi-post-card__title {
  font-family: var(--baladi-font-heading, inherit);
  font-size: 1.0625rem;
  font-weight: 600;
  margin: 0 0 0.375rem;
}

.baladi-post-card__title a {
  color: var(--baladi-ink, #18181b);
  text-decoration: none;
}

.baladi-post-card__title a:hover { text-decoration: underline; }

.baladi-post-card__meta {
  margin: 0 0 0.5rem;
  font-size: var(--baladi-text-small, 0.8125rem);
  color: var(--baladi-ink-muted, #6b7280);
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
}

.baladi-post-card__meta a { color: var(--baladi-accent-text, #21709f); }

.baladi-post-card__excerpt { margin: 0; }

@media (min-width: 40rem) {
  .baladi-post-card:has(.baladi-post-card__cover) {
    grid-template-columns: 220px 1fr;
  }
}

.baladi-portal__heading--section { margin: 2rem 0 1rem; }

.baladi-portal__filter {
  margin: 1rem 0 0;
  font-size: var(--baladi-text-small, 0.8125rem);
}

.baladi-portal__filter a { color: var(--baladi-accent-text, #21709f); }

.baladi-portal__pages {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  margin-top: 1.5rem;
  font-size: var(--baladi-text-small, 0.8125rem);
}

.baladi-portal__pages a { color: var(--baladi-accent-text, #21709f); }

.baladi-portal__hero {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 0.5rem;
  margin-bottom: 1.25rem;
}

/* The body, which is the one place in this product that renders
   user-written HTML. `cms/sanitise.py` decides what can be in it; this
   decides how those few tags look. */
.baladi-portal__article { line-height: 1.8; }
.baladi-portal__article h2,
.baladi-portal__article h3,
.baladi-portal__article h4 {
  font-family: var(--baladi-font-heading, inherit);
  margin: 1.5rem 0 0.75rem;
  line-height: 1.35;
}
.baladi-portal__article h2 { font-size: 1.25rem; }
.baladi-portal__article h3 { font-size: 1.125rem; }
.baladi-portal__article h4 { font-size: 1rem; }
.baladi-portal__article p { margin: 0 0 1rem; }
.baladi-portal__article ul,
.baladi-portal__article ol { margin: 0 0 1rem; padding-inline-start: 1.5rem; }
.baladi-portal__article li { margin-bottom: 0.375rem; }
.baladi-portal__article a { color: var(--baladi-accent-text, #21709f); }
.baladi-portal__article img { max-width: 100%; height: auto; border-radius: 0.5rem; }
.baladi-portal__article blockquote {
  margin: 0 0 1rem;
  padding-inline-start: 1rem;
  border-inline-start: 3px solid var(--baladi-border, #e4e6ef);
  color: var(--baladi-ink-muted, #6b7280);
}

/* The panel's rail: the menu scrolls, the brand block above it and the user
   block below it stay put.

   It did not. The wrapper carried `grow shrink-0 … overflow-auto`, and
   `shrink-0` pins a flex item to the height of its content — so on a
   1440×600 window the menu grew past the bottom of the screen, pushed the
   user block out of sight, and `overflow-auto` had nothing to overflow:
   النظام was simply unreachable.

   `min-height: 0` is what lets a flex child be shorter than its content.
   `overflow-y: auto` alone would also do it (a scroll container's automatic
   minimum size is zero), and both are written out because the next person
   to read this should not have to know that rule.

   Plain CSS and not `min-h-0 overflow-y-auto`: neither of those utilities
   is in the pruned Tailwind bundle, so both would have been inert. */
.baladi-rail-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}
