/* =============================================================================
 * ui.css — the ui/ component layer's own stylesheet.
 *
 * Minimal polish where Trezo's BASE component styling falls short. This is NOT a
 * second design system: every value comes from Trezo's own Bootstrap tokens
 * (--bs-*). Keep it tiny — most styling still lives in Trezo's classes.
 * ===========================================================================*/

/* --- App shell: stable scrollbar gutter -------------------------------------
 * The one intentional root-level (non-component) rule in this file. Reserve the
 * vertical scrollbar's space permanently on the document scroller so the layout
 * never reflows when a scrollbar appears/disappears (e.g. expanding an accordion
 * or a tall form). Without this, the viewport narrows by the scrollbar width the
 * moment content overflows and the whole content area jumps sideways — jarring
 * on platforms with classic (space-taking) scrollbars. `stable` is inline-end
 * and direction-aware, so the .rtl layout is handled too. Loaded last, so no
 * !important needed. */
html {
  scrollbar-gutter: stable;
}

/* --- App shell: mobile sidebar drawer (below xl) ----------------------------
 * Below 1200px Trezo parks the fixed .sidebar-area off-canvas (left:-350px) and
 * zeroes the .main-content gutter, so content goes full-width — but the only nav
 * trigger Trezo ships is dormant: its custom.js toggles body[sidebar-data-theme]
 * between sidebar-hide / sidebar-show, and on mobile `sidebar-hide` already slides
 * the panel in (left:0) and reveals the in-sidebar × — but the BUTTON that fires
 * it (#header-burger-menu) was never in our header, so the nav was unreachable.
 * We add that button (re-activating Trezo's own handler) and complete the drawer
 * with the chrome Trezo omits: a dimming backdrop, scroll-lock, and a z-index that
 * lifts the panel above the backdrop (Trezo's own is only 13). All SCOPED to
 * ≤1199.98px, so the desktop sidebar is untouched. Backdrop/Esc/nav-link close +
 * aria sync live in partials/common/js_files.html. Tokens only; z-indexes follow
 * Bootstrap's offcanvas scale (panel 1045 over backdrop 1040). */
.header-burger-menu {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;                                  /* WCAG 2.5.5 tap target */
  height: 44px;
  color: var(--bs-secondary-color);
  transition: color 0.15s ease-in-out;
}
.header-burger-menu:hover {
  color: var(--bs-primary);
}

@media (max-width: 1199.98px) {
  /* Lift the open drawer above the backdrop. Trezo already gives it left:0 + the
   * slide via [sidebar-data-theme=sidebar-hide], but only z-index:13; this more
   * specific selector (body + attr + class) wins and raises it over the backdrop. */
  body[sidebar-data-theme="sidebar-hide"] .sidebar-area {
    z-index: 1045;
    box-shadow: 0 0 1.5rem rgba(var(--bs-body-color-rgb, 33, 37, 41), 0.18);
  }
  /* Dimming backdrop — created by the drawer JS, shown whenever the drawer is
   * open. Driven by the same attribute as the panel, so it can't desync from it. */
  .sidebar-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1040;
    background: rgba(var(--bs-body-color-rgb, 33, 37, 41), 0.5);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s ease, visibility 0.3s ease;
  }
  body[sidebar-data-theme="sidebar-hide"] .sidebar-backdrop {
    opacity: 1;
    visibility: visible;
  }
  /* Lock the page behind the open drawer so only the menu scrolls. */
  body[sidebar-data-theme="sidebar-hide"] {
    overflow: hidden;
  }
}

/* --- Editor / form shell ----------------------------------------------------
 * The canonical wrapper for single-screen editors and forms (invoice editor,
 * product form/detail). Caps the width so content doesn't sprawl edge-to-edge on
 * wide screens. LEFT-aligned (margin-inline: 0) so the card's left edge always
 * tracks the full-width page header's title (§1.5) — the header and card share a
 * left edge on every screen size. Pair with Bootstrap padding, e.g. `px-3 pb-5`. */
.ui-editor-shell {
  max-width: 1080px;
  margin-inline: 0;
}
/* Wide variant — forms that embed a data grid (e.g. the invoice line items)
 * need more room than the 1080px reading width. Same left-align, wider cap. */
.ui-editor-shell--wide {
  max-width: 1280px;
  margin-inline: 0;
}

/* --- Form action bar (the c-ui form sticky footer) --------------------------
 * The canonical action row for forms: pinned to the bottom of the shell so the
 * primary action is never below the fold on a long form (Form Design Standard
 * §5). Right-aligned cluster, one filled primary. All values from --bs-* tokens. */
.ui-form-actions {
  position: sticky;
  bottom: 1rem;                               /* float just off the bottom — a save-bar, not a welded strip */
  z-index: 1020;                              /* = Bootstrap $zindex-sticky */
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  justify-content: flex-end;
  padding: 0.625rem 1rem;
  margin-top: 1rem;
  /* WHITE surface (#fff), NOT --bs-body-bg (grey page) and NOT --bs-card-bg
   * (component-scoped to .card AND = grey body bg in this build). Cards get white
   * from the bg-white utility; match it. Border + rounded corners give it the
   * card language; FLAT (no shadow) to match c-ui.card for strict visual
   * consistency — it's still sticky, just without the lift. */
  background: var(--bs-white);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg, 0.5rem);
}

/* --- Form grid vertical rhythm — no double spacing --------------------------
 * `c-ui.form_field` (and `c-ui.combobox`) always carry `mb-3`. Inside a Bootstrap
 * grid, the row's own vertical gutter (`g-3`) already spaces wrapped fields, so
 * the field's bottom margin doubles the gap. Drop it when the field is the sole
 * child of a grid column; standalone fields (not in `.row > .col`) keep `mb-3`. */
.row > [class*="col"] > .js-field:only-child,
.row > [class*="col"] > .ui-combobox:only-child {
  margin-bottom: 0 !important;
}

/* --- Fieldset/legend for radio & checkbox groups ----------------------------
 * c-ui.form_field wraps option groups in <fieldset><legend> for a11y (§4.1).
 * Reset Bootstrap's oversized, full-width <legend> back to a plain field label
 * so it matches every other `.label`. */
.ui-fieldset {
  border: 0;
  padding: 0;
  margin: 0;
  min-width: 0;
}
.ui-fieldset > legend.label {
  float: none;
  width: auto;
  font-size: inherit;
  line-height: inherit;
  margin-bottom: 0.25rem;
}

/* --- Native select: white surface app-wide (Trezo base override) ------------
 * Trezo's .form-select sits on --bs-body-bg (the grey page bg) and draws a dark
 * #343a40 chevron, so every native dropdown reads as a grey island next to the
 * white .form-control inputs (which Trezo overrides to #fff) and the combobox.
 * Make enabled selects white and recolour the chevron to the muted combobox-caret
 * tone — one consistent dropdown look everywhere. Lower specificity than both
 * `[data-theme=dark] .form-select` (dark bg) and `.form-select:disabled` (grey),
 * so dark mode and disabled selects keep their own (correct) surfaces. */
.form-select {
  background-color: var(--bs-white);
  --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%238b97a8' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}

/* --- Tables (c-ui.table / c-ui.data_table, marker class .ui-table) ----------
 * The single, canonical table for the whole app. Goals:
 *   • header shares the BODY typography — same Inter, same size as the rows,
 *     just heavier (600). No smaller/uppercase/washed-out header.
 *   • rows are compact (not the tall default) and use the body text colour.
 *   • white surface (Trezo's .table defaults rows to the grey body bg). */
.ui-table {
  --bs-table-bg: transparent;                 /* white card shows through rows */
  --bs-table-color: var(--bs-secondary);      /* Trezo .default-table-area cell colour (#3A4252) */
  --bs-table-border-color: var(--bs-border-color);
  font-size: 0.875rem;                        /* ~14px body cells (≈ Trezo base font) */
  margin-bottom: 0;
}
/* Opt-in stable columns (add .ui-table-fixed via config.fixed_layout). With
 * table-layout:fixed the widths come from the header's width hints (config
 * col.width) instead of content, so columns DON'T reshuffle when a filter /
 * sort / page swap changes which rows are shown. Long values wrap (taller row)
 * rather than widening the column. */
.ui-table-fixed {
  table-layout: fixed;
}
.ui-table-fixed td {
  overflow-wrap: anywhere;
}
/* Per-column "clear filter": a small × shown in a filtered column's header (only
 * when that column is active). Muted, tints to danger on hover; clicking clears
 * just that column (handled in ui-datatable-filters.js, which also stops the
 * header's sort from firing). */
.ui-col-clear {
  position: absolute;            /* pinned to the right of the header cell */
  right: 0.5rem;
  top: 50%;
  transform: translateY(-50%);
  border: 0;
  background: transparent;
  padding: 0;
  color: var(--bs-secondary-color);
  cursor: pointer;
  line-height: 1;
  transition: color 0.15s ease;  /* same hover behaviour as the dropdown chevron */
}
.ui-col-clear > i {
  font-size: 1.05rem;
  vertical-align: middle;
}
.ui-col-clear:hover {
  color: var(--bs-primary);
}
/* The filtered header cell hosts the absolutely-placed clear icon: make it the
 * positioning context and reserve room so the title never runs under the icon. */
.ui-table thead tr:first-child th.ui-col-filtered {
  position: relative;
  padding-right: 1.9rem;
}

/* The toolbar "Clear" button animates in/out instead of popping: it persists in
 * the card-header (outside the HTMX swap), and ui-datatable-filters.js toggles
 * .is-hidden from the server's active-filter count. Collapsing max-width + the
 * flex gap makes the neighbouring buttons slide over smoothly; the icon-first
 * reveal reads as wiping in from the left. */
.ui-clear-all {
  overflow: hidden;
  white-space: nowrap;
  max-width: 7rem;
  transition: max-width 0.3s ease, opacity 0.25s ease, padding 0.3s ease,
    margin 0.3s ease, border-color 0.3s ease;
}
.ui-clear-all.is-hidden {
  max-width: 0;
  opacity: 0;
  /* !important to beat Bootstrap's .px-4 padding utilities (themselves
   * !important) that the full-size c-ui.button applies — otherwise the 1.5rem
   * side padding keeps the collapsed button ~3rem wide and it leaves a ghost gap. */
  padding-left: 0 !important;
  padding-right: 0 !important;
  margin-left: -0.5rem; /* swallow the container's gap-2 so nothing peeks through */
  border-color: transparent;
  pointer-events: none;
}
/* Header shares the BODY typography — same Inter, same size as the rows — but is
 * clearly accented: weight 600 (vs the 400 body) and the darker secondary colour.
 * On our clean, band-less structure that weight+colour contrast IS the header,
 * so it can't fall back on Trezo's grey band. (Trezo's own .default-table-area
 * leans the other way — 12px/500/#445164 ON a #F6F7F9 band; without the band
 * those values read smaller-and-washed-out, which is what this replaces.) */
.ui-table > thead > tr > th {
  padding: 0.7rem 1rem;
  font-size: 0.875rem;                  /* = body cells; no longer smaller than the rows */
  font-weight: 600;                     /* clearly heavier than the 400 body */
  color: var(--bs-secondary);           /* #3A4252 — the darker body-text slate */
  border-bottom: 1px solid var(--bs-border-color);
  white-space: nowrap;
  vertical-align: middle;
}
/* Compact rows, Trezo cell colour. Low specificity (0,1,1) on purpose, so a denser
 * table — e.g. the invoice line grid (table.invoice-lines td) — keeps its padding. */
.ui-table td {
  padding: 0.6rem 1rem;
  color: var(--bs-secondary);                 /* Trezo #3A4252 */
  vertical-align: middle;
}
/* Drop the last row's bottom border so the straight line doesn't spill past the
 * card's rounded-3 corners (table is flush in a p-0 card-body). */
.ui-table > tbody > tr:last-child > td {
  border-bottom: 0;
}

/* --- Interactive data table (c-ui.data_table) ------------------------------
 * Sortable headers show a sort affordance; the active column shows the
 * direction. The filter row sits under the header with compact inputs. */
.ui-table thead th.ui-sortable {
  cursor: pointer;
  user-select: none;
}
.ui-table thead th.ui-sortable::after {
  content: "unfold_more";
  font-family: "Material Symbols Outlined";
  font-size: 1rem;
  font-weight: normal;
  vertical-align: middle;
  margin-left: 0.2rem;
  opacity: 0.35;
}
.ui-table thead th.ui-sort-asc::after { content: "arrow_upward"; opacity: 0.9; color: var(--bs-primary); }
.ui-table thead th.ui-sort-desc::after { content: "arrow_downward"; opacity: 0.9; color: var(--bs-primary); }
.ui-table thead th.ui-sortable:hover { color: var(--bs-primary); }

/* Filter row = just another (compact) table row, with inline inputs that fill
 * their column and never widen it. Cell padding matches the header so each input
 * sits under its column; inputs are forced small (the theme's .form-control is
 * tall and would otherwise tower over the rows). */
.ui-table thead tr.ui-filter-row > th {
  /* Horizontal padding only, pulled in by the input's border (1px) + left
     padding (0.5rem) so the input's TEXT aligns with the column text. Vertical
     space lives on .ui-filter-cell so the row can animate open/closed. */
  padding: 0 calc(1rem - 0.5rem - 1px);
  background-color: var(--bs-tertiary-bg);
  border: 0;
  vertical-align: middle;
}
.ui-table thead tr.ui-filter-row.ui-filter-open > th {
  border-bottom: 1px solid var(--bs-border-color);
}
.ui-table thead tr.ui-filter-row :is(.form-control, .form-select) {
  width: 100%;
  min-width: 0;                        /* never let the input widen its column */
  font-size: 0.8125rem;
  line-height: 1.4;
}
/* Trezo's ::placeholder is 14px (bigger than our 13px text), so an EMPTY field
 * would read larger than a filled one — and a placeholder'd input next to a
 * value'd combobox would look mismatched. Match the placeholder to the text. */
.ui-table thead tr.ui-filter-row :is(.form-control, .form-select)::placeholder {
  font-size: 0.8125rem;
}
.ui-table thead tr.ui-filter-row .form-control {
  height: auto;
  padding: 0.25rem 0.5rem;             /* 0.5rem left padding ↔ compensated above */
}
/* Selects in the filter row inherit the white surface + muted chevron from the
 * global .form-select rule above; here just the compact tweaks (shorter, chevron
 * pulled in a touch to suit the filter height). */
.ui-table thead tr.ui-filter-row .form-select {
  padding-block: 0.25rem;
  background-position: right 0.5rem center;
}
/* Rich filter widgets (c-ui.combobox) inside a filter cell: drop the form margin
 * and shrink the control to the compact filter height. The component carries
 * Bootstrap's .mb-3 (margin-bottom !important) — without !important here that
 * 16px stays, making this cell taller and nudging the input out of line with
 * the plain text-filter inputs. */
.ui-table thead tr.ui-filter-row .ui-combobox {
  margin-bottom: 0 !important;
}
/* The datepicker wraps itself in Bootstrap's .mb-3 — drop it inside a filter cell
 * so the control sits flush like the plain inputs. */
.ui-table thead tr.ui-filter-row .ui-filter-cell .mb-3 {
  margin-bottom: 0 !important;
}
/* Datepicker filter: match the plain text inputs and the combobox chevron. The
 * component's leading .ps-5 icon pad wastes the column and shifts the value, and
 * the calendar glyph sits on the left — both inconsistent with the other filters.
 * Reclaim the width (text uses the full cell) and re-place the glyph on the right
 * as a chevron-style affordance: same size/colour, pointer cursor, hover tint,
 * click-through opens the calendar (the icon stays pointer-events:none). */
.ui-table thead tr.ui-filter-row .ui-datepicker .form-control {
  padding-left: 0.5rem !important;   /* override .ps-5 (the leading-icon pad) */
  padding-right: 2rem !important;    /* clear the right-hand icon zone */
  cursor: pointer;                   /* it's a picker, not a typing field */
}
.ui-table thead tr.ui-filter-row .ui-datepicker .ui-field-icon {
  left: auto;
  right: 0;
  top: 0;
  bottom: 0;
  transform: none;
  width: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.15rem;                            /* same weight as the chevron */
  color: var(--bs-secondary-color) !important;   /* same colour as the chevron */
}
.ui-table thead tr.ui-filter-row .ui-datepicker:hover .ui-field-icon {
  color: var(--bs-primary) !important;           /* hover tint, like the chevron */
}
.ui-table thead tr.ui-filter-row .ui-combobox .form-control {
  height: auto;
  padding: 0.25rem 2rem 0.25rem 0.5rem; /* right pad clears the 2rem caret zone */
  font-size: 0.8125rem;
}
/* When OPEN, let the cell overflow so floating dropdowns/calendars escape (the
 * cell is overflow:hidden only for the collapse animation). */
.ui-filter-row.ui-filter-open .ui-filter-cell {
  overflow: visible;
}

/* The animatable filter cell: closed = zero-height + faded; open = slides down.
 * NB: no `transform` here — a transformed ancestor becomes the containing block
 * for position:fixed descendants, which would break the combobox's floating menu
 * (it must resolve against the viewport). Height + opacity give the same feel. */
.ui-filter-cell {
  max-height: 0;
  padding-block: 0;
  opacity: 0;
  overflow: hidden;
  transition:
    max-height 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
    padding-block 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
    opacity 0.18s ease;
}
.ui-filter-row.ui-filter-open .ui-filter-cell {
  max-height: 3rem;
  padding-block: 0.4rem;
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .ui-filter-cell { transition: none; }
}

/* --- Filtered column highlight --------------------------------------------
 * A very light primary wash across a column whose filter is active (header
 * title, the filter cell, and the body cells), so it's obvious at a glance
 * which columns are narrowing the list. Kept deliberately faint. */
.ui-table td.ui-col-filtered {
  background-color: rgba(96, 93, 255, 0.05);   /* brand primary @ 5% */
}
.ui-table thead th.ui-col-filtered {
  background-color: rgba(96, 93, 255, 0.08);
}
.ui-table thead tr.ui-filter-row > th.ui-col-filtered {
  background-color: rgba(96, 93, 255, 0.08);
}

/* --- Editable / formset table (.ui-table.ui-table-editable) -----------------
 * Looks exactly like a normal .ui-table (same header, standard row height), but
 * every body cell holds a compact inline input styled like the filter row — on a
 * WHITE row. Use it for row-per-row data entry (invoice/order line items, etc.). */
.ui-table-editable > tbody > tr > td {
  /* Standard row height; horizontal padding pulled in by the input's border (1px)
     + left padding (0.5rem) so the input's TEXT lines up under the column header. */
  padding: 0.45rem calc(1rem - 0.5rem - 1px);
  background-color: #fff;
}
.ui-table-editable td .form-control,
.ui-table-editable td .form-select {
  width: 100%;
  min-width: 0;
  height: auto;
  padding: 0.3rem 0.5rem;
  font-size: 0.875rem;   /* match .ui-table body cells / the read-only columns */
  line-height: 1.4;
}
/* Cells that are display-only (e.g. a computed Amount) keep normal cell padding. */
.ui-table-editable td.is-readonly {
  padding: 0.6rem 1rem;
}

/* --- Ghost row (Excel-style, driven by ui-formset.js) -----------------------
 * The trailing inert row: faded inputs, a + in the narrow left "gutter" column,
 * and no remove control. Active rows show the remove (✕) and hide the +. */
.ui-formset-gutter {
  width: 2.75rem;
}
.ui-table-editable [data-ui-add] {
  display: none;
}
.ui-table-editable tr.ui-ghost-row [data-ui-add] {
  display: inline-flex;
}
.ui-table-editable tr.ui-ghost-row [data-ui-remove] {
  visibility: hidden;
}
.ui-table-editable tr.ui-ghost-row .form-control:disabled {
  background-color: #fff;   /* white, not the default disabled grey */
  opacity: 0.4;             /* "ghosted" — signals press + / Enter to edit */
}

/* Circular row action buttons: solid filled circle + white glyph (no underline).
   Add = primary, Remove = danger. */
.ui-rowbtn {
  width: 1.65rem;
  height: 1.65rem;
  padding: 0;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  text-decoration: none;
}
.ui-rowbtn i {
  font-size: 1rem;
}

/* ---------------------------------------------------------------------------
   Accordion (c-ui.accordion) — collapsible white cards.
   Bootstrap accordion restyled to the c-ui.card surface. The header TEXT stays
   the readable dark (secondary) colour in both states; the OPEN panel is
   accented on its border + header background instead, and that accent persists
   for as long as the panel stays open (state-driven, not focus-driven). Tokens
   only. (`:has()` is supported in the Chromium/Edge browsers this app targets.)
--------------------------------------------------------------------------- */
.ui-accordion .accordion-item {
  background-color: var(--bs-white);                 /* white surface (matches c-ui.card's bg-white;
                                                        NB --bs-card-bg is grey body-bg in Trezo) */
  border: 1px solid transparent;                     /* reserved → primary when open */
}
.ui-accordion .accordion-button {
  background-color: transparent;                     /* show the item surface */
  color: var(--bs-secondary);                        /* always dark & readable */
}
.ui-accordion .accordion-button:focus {
  box-shadow: none;
}
/* OPEN — accent the panel border + header background; persists until collapsed */
.ui-accordion .accordion-item:has(.accordion-button:not(.collapsed)) {
  border-color: var(--bs-primary);
}
.ui-accordion .accordion-button:not(.collapsed) {
  box-shadow: none;
  color: var(--bs-secondary);                        /* keep text readable, not low-contrast */
  background-color: rgba(var(--bs-primary-rgb), 0.06);   /* tinted open header */
  border-bottom: 1px solid var(--bs-border-color);   /* fold divider under header */
}

/* ---------------------------------------------------------------------------
   Collapse chevron — a generic rotating chevron for Bootstrap collapse toggles
   (e.g. the invoice editor's "Advanced" expander). Put on the chevron <i>;
   it points down when collapsed and flips up when the toggle is expanded.
--------------------------------------------------------------------------- */
.ui-collapse-chevron {
  transition: transform 0.2s ease;
}
[data-bs-toggle="collapse"][aria-expanded="true"] .ui-collapse-chevron {
  transform: rotate(180deg);
}

/* ---------------------------------------------------------------------------
   Editable formset table (.ui-table-editable) — ledger polish that applies to
   every consumer of the formset standard. People type figures, they don't click
   the spinner, so suppress the type=number steppers. (Numeric columns are
   right-aligned and Amount is read-only at the markup level via `text-end`.)
--------------------------------------------------------------------------- */
.ui-table-editable input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}
.ui-table-editable input[type="number"]::-webkit-outer-spin-button,
.ui-table-editable input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* ---------------------------------------------------------------------------
   Combobox (c-ui.combobox) — FUNCTIONAL LAYOUT ONLY. The control is a stock
   Trezo `form-control` <input> (idle state mimics a text input) with a small
   dropdown chevron overlaid on the right so it still reads as openable. The rest
   is: anchor the floating menu under it (Bootstrap normally leans on Popper for
   this, which we don't load) and give keyboard nav the same tint as a hover.
--------------------------------------------------------------------------- */
/* Room on the right for the chevron (≈ what form-select reserves), so typed text
   never runs under it. */
.ui-combobox [data-combobox-input] {
  padding-right: 2.25rem;
}
/* The multiselect trigger is a readonly dropdown (you can't type into it), so it
   shows the pointer cursor — not the text I-beam — and reserves chevron room. */
.ui-multiselect [data-multiselect-display] {
  cursor: pointer;
  padding-right: 2.25rem;
}
/* Number filter: the read-only trigger reads as a dropdown; the floating panel
   stacks an operator select + value input(s). Reuses the .ui-combobox-menu chrome
   (white bg, shadow, rounding); just give it padding + a sensible min width. */
.ui-number-filter [data-number-display] {
  cursor: pointer;
  padding-right: 2.25rem;
}
.ui-number-filter-menu {
  padding: 0.5rem;
  min-width: 11rem;
}
/* The theme's checkbox border is very faint; make the empty multiselect boxes
   clearly visible (checked ones are brand-filled, so they don't need it). */
.ui-multiselect [data-multiselect-menu] .form-check-input:not(:checked) {
  border-color: rgba(0, 0, 0, 0.3);
}
/* The chevron: muted, centred, and an affordance that it opens the menu — pointer
   cursor + a primary tint on hover (its click is wired in the component JS). It
   flips when the menu is open. */
.ui-combobox-caret {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  width: 2rem;                 /* bigger hit area: the whole right zone, full height */
  display: flex;
  align-items: center;
  justify-content: center;     /* glyph centred in the zone, so it reads as balanced */
  cursor: pointer;
  color: var(--bs-secondary-color);
  font-size: 1.15rem;
  line-height: 1;
  transition: transform 0.15s ease, color 0.15s ease;
}
.ui-combobox-caret:hover {
  color: var(--bs-primary);
}
/* Flip on open — matches both the combobox input and the multiselect display
   (both set aria-expanded). Glyph is centred → it flips in place. */
.ui-combobox [aria-expanded="true"] ~ .ui-combobox-caret {
  transform: rotate(180deg);
}
.ui-combobox [data-combobox-input]:disabled ~ .ui-combobox-caret {
  opacity: 0.5;
}

/* Same open/close caret flip for every Bootstrap dropdown (c-ui.dropdown and the
   stock .dropdown-toggle used in toolbars/headers) — one consistent affordance
   across all dropdowns app-wide. Bootstrap toggles aria-expanded for us. */
.dropdown-toggle::after {
  transition: transform 0.15s ease;
}
.dropdown-toggle[aria-expanded="true"]::after {
  transform: rotate(180deg);
}

.ui-combobox-menu {
  top: 100%;
  left: 0;
  width: 100%;
  /* Show ~8 items, then scroll; the half-item peek hints there's more below.
   * Shared by combobox, multiselect and the number-filter panel. */
  max-height: 15.5rem;
  overflow-y: auto;
  /* Reserve the scrollbar's track so (a) it never sits over the option text and
   * (b) the content-sized (max-content) width stays correct when the list scrolls
   * — otherwise the appearing scrollbar would steal width and force a horizontal
   * scrollbar. No-op on overlay-scrollbar platforms (where nothing is obscured). */
  scrollbar-gutter: stable;
  margin-top: 0.25rem;
  background-color: #fff;   /* a dropdown is white, not the grey --bs-dropdown-bg */
}
/* Keyboard highlight mirrors Bootstrap's own .dropdown-item hover. */
.ui-combobox-menu .dropdown-item.ui-combobox-active:not(.active) {
  background-color: var(--bs-tertiary-bg);
  color: var(--bs-body-color);
}

/* ---------------------------------------------------------------------------
   Datepicker (c-ui.datepicker) — a Trezo-themed flatpickr calendar.

   Two parts:
   1. FUNCTIONAL LAYOUT — a leading glyph sits inside the input's left pad (the
      input carries `ps-5`), vertically centred and click-through so the input
      still opens the picker. Shared by the `c-ui.datepicker` component and the
      `icon` prop on `c-ui.form_field` (both put `.ui-field-icon` on the glyph and
      `ps-5` on the control). (The kind of absolute positioning the rule allows:
      layout Bootstrap utilities can't express, kept minimal.)
   2. THEME — flatpickr's popup restyled to Trezo's look. Scoped to the
      `.ui-datepicker-calendar` class that ui-datepicker.js adds to the calendar,
      so it never touches the datatable filter's flatpickr.
      The extra class also lifts specificity above flatpickr's base CSS, so the
      lazily-injected base stylesheet can't win on cascade order. Tokens only.
--------------------------------------------------------------------------- */
.position-relative > .ui-field-icon {
  position: absolute;
  top: 50%;
  left: 0.85rem;
  transform: translateY(-50%);
  pointer-events: none;             /* clicks fall through to the input → opens it */
  z-index: 2;
}

.flatpickr-calendar.ui-datepicker-calendar {
  font-family: var(--bs-body-font-family);
  border-radius: 0.5rem;            /* matches the c-ui.card rounding */
  border: 1px solid var(--bs-border-color);
  box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.12);
  background-color: var(--bs-white);
  color: var(--bs-secondary);
}
/* The little pointer arrow → match the calendar's border + surface. */
.flatpickr-calendar.ui-datepicker-calendar.arrowTop::before { border-bottom-color: var(--bs-border-color); }
.flatpickr-calendar.ui-datepicker-calendar.arrowTop::after { border-bottom-color: var(--bs-white); }
.flatpickr-calendar.ui-datepicker-calendar.arrowBottom::before { border-top-color: var(--bs-border-color); }
.flatpickr-calendar.ui-datepicker-calendar.arrowBottom::after { border-top-color: var(--bs-white); }

/* Month / year header + prev/next nav. */
.ui-datepicker-calendar .flatpickr-current-month,
.ui-datepicker-calendar .flatpickr-monthDropdown-months,
.ui-datepicker-calendar .numInputWrapper input.cur-year {
  font-weight: 600;
  color: var(--bs-body-color);
}
.ui-datepicker-calendar .flatpickr-prev-month svg,
.ui-datepicker-calendar .flatpickr-next-month svg { fill: var(--bs-secondary); }
.ui-datepicker-calendar .flatpickr-prev-month:hover svg,
.ui-datepicker-calendar .flatpickr-next-month:hover svg { fill: var(--bs-primary); }

/* Weekday header row. */
.ui-datepicker-calendar .flatpickr-weekday {
  color: var(--bs-tertiary-color, #6c757d);
  font-weight: 600;
  font-size: 0.75rem;
}

/* Day cells. */
.ui-datepicker-calendar .flatpickr-day {
  color: var(--bs-secondary);
  border-radius: 0.375rem;
  border: 1px solid transparent;
}
.ui-datepicker-calendar .flatpickr-day:hover,
.ui-datepicker-calendar .flatpickr-day:focus {
  background: rgba(var(--bs-primary-rgb), 0.1);
  border-color: transparent;
}
.ui-datepicker-calendar .flatpickr-day.today {
  border-color: var(--bs-primary);
  color: var(--bs-primary);
}
.ui-datepicker-calendar .flatpickr-day.today:hover {
  background: rgba(var(--bs-primary-rgb), 0.1);
  color: var(--bs-primary);
}
.ui-datepicker-calendar .flatpickr-day.selected,
.ui-datepicker-calendar .flatpickr-day.selected:hover,
.ui-datepicker-calendar .flatpickr-day.startRange,
.ui-datepicker-calendar .flatpickr-day.endRange,
.ui-datepicker-calendar .flatpickr-day.startRange:hover,
.ui-datepicker-calendar .flatpickr-day.endRange:hover {
  background: var(--bs-primary);
  border-color: var(--bs-primary);
  color: #fff;
  box-shadow: none;
}
/* Range fill between the two endpoints (side shadows close the inter-cell gaps). */
.ui-datepicker-calendar .flatpickr-day.inRange {
  background: rgba(var(--bs-primary-rgb), 0.12);
  border-color: transparent;
  box-shadow: -5px 0 0 rgba(var(--bs-primary-rgb), 0.12), 5px 0 0 rgba(var(--bs-primary-rgb), 0.12);
}
.ui-datepicker-calendar .flatpickr-day.flatpickr-disabled,
.ui-datepicker-calendar .flatpickr-day.prevMonthDay,
.ui-datepicker-calendar .flatpickr-day.nextMonthDay {
  color: var(--bs-tertiary-color, #adb5bd);
}

/* --- ui.empty_state — the calm "nothing here" moment -------------------------
 * A faint oversized icon over a short line. Bootstrap utilities can't express
 * "big AND faint", so the size + muted tint live here; the colour is a --bs-*
 * token (no new palette). Everything else is utilities on the component. */
.ui-empty-state-icon {
  font-size: 2.5rem;
  line-height: 1;
  color: var(--bs-secondary-color);
  opacity: 0.5;
}

/* --- Datatable HTMX loading indicator ---------------------------------------
 * The calm inline spinner shown while a list request is in flight. Hidden at
 * rest so it reserves NO space (no layout jump), revealed only for the request.
 * htmx tags every hx-indicator target with .htmx-request for the request's
 * duration; we toggle DISPLAY off that. (htmx's own default style only animates
 * opacity, which leaves the element in flow holding space even when "hidden" —
 * hence the old inline display:none, which in turn defeated the reveal entirely.
 * The htmx-indicator class is kept so the reveal still fades in via opacity.) */
.datatable-indicator {
  display: none;
}
.datatable-indicator.htmx-request {
  display: block;
}

/* --- Datatable row-action icon links: never underline -----------------------
 * Trezo's reboot sets `a { text-decoration: underline }` (style.css ~ln 396),
 * and the row-action links in the datatable actions group (View/Edit/Delete)
 * carry only utility classes (border-0 bg-transparent p-1), so they inherit it
 * — a stray underline under each icon, most visible in the mobile card layout.
 * They are icon buttons, not text links. Scoped to the actions group via its
 * role="group" so genuine in-cell text links keep their underline. */
.datatable-component [role="group"] a {
  text-decoration: none;
}

/* --- Table: mobile card layout (≤ sm), opt-in via .ui-table-cards ------------
 * A multi-column table is unreadable below ~576px — columns collapse to slivers
 * and text wraps character-by-character. OPT IN by adding `ui-table-cards` to a
 * table: below Bootstrap's `sm` breakpoint each ROW becomes a stacked CARD (the
 * header row hides; every cell becomes a "Label  value" line, label from the
 * td's data-label). The server datatable carries it by default
 * (_datatable_htmx_fragment.html); a static c-ui.table opts in via
 * table_class="… ui-table-cards" (and its cells need data-label). Small
 * key/value c-ui.tables that read fine as two columns simply don't opt in.
 * Values from --bs-* tokens. Trade-off: hiding thead drops header-click sorting
 * on mobile — the datatable filter toolbar still works; sorting is desktop. */
@media (max-width: 575.98px) {
  /* No horizontal scroller needed once rows are cards. */
  .table-responsive:has(table.ui-table-cards) { overflow-x: visible; }

  table.ui-table-cards thead { display: none; }

  table.ui-table-cards,
  table.ui-table-cards tbody,
  table.ui-table-cards tr,
  table.ui-table-cards td {
    display: block;
    width: 100%;
  }

  /* Each row → a soft-elevated card. */
  table.ui-table-cards tr {
    background: var(--bs-white);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius-lg, 0.5rem);
    box-shadow: 0 1px 2px rgba(var(--bs-body-color-rgb, 33, 37, 41), 0.04);
    padding: 0.25rem 0.9rem;
    margin-bottom: 0.75rem;
  }
  /* Zebra/hover backgrounds don't belong on cards. */
  table.ui-table-cards tbody tr:hover { background: var(--bs-white); }

  /* Each cell → a label/value row, touch-comfortable height. */
  table.ui-table-cards td {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    text-align: right;
    min-height: 44px;
    padding: 0.55rem 0;
    border: 0;
    border-bottom: 1px solid var(--bs-border-color);
    white-space: normal;
  }
  table.ui-table-cards td:last-child { border-bottom: 0; }

  /* Inline label from the column name. Short column names stay on one line; the
   * VALUE (the cell's text node) wraps instead. */
  table.ui-table-cards td::before {
    content: attr(data-label);
    margin-right: auto;
    text-align: left;
    font-weight: 600;
    color: var(--bs-secondary);
    white-space: nowrap;
    flex-shrink: 0;
  }

  /* Actions cell: no label, controls spread left across the full width and sized
   * for touch (≥44px hit area each). */
  table.ui-table-cards td.ui-actions-cell {
    justify-content: flex-start;
    gap: 0.25rem;
  }
  table.ui-table-cards td.ui-actions-cell::before { content: none; }
  table.ui-table-cards td.ui-actions-cell a,
  table.ui-table-cards td.ui-actions-cell button {
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  /* The empty-state cell shouldn't render as a label/value row. */
  table.ui-table-cards td[colspan] {
    justify-content: center;
    min-height: 0;
  }
  table.ui-table-cards td[colspan]::before { content: none; }

  /* Sticky form save-bar: on a short phone viewport the bottom-sticky bar floats
   * UP over the form fields (a tall 2-row cluster covers what you're editing).
   * Let it flow at the end of the form instead — the standard mobile pattern. */
  .ui-form-actions {
    position: static;
  }
}

/* --- Touch targets (WCAG 2.5.5) ---------------------------------------------
 * On COARSE (touch) pointers, interactive controls meet the 44px minimum tap
 * height. Keyed on pointer type, not viewport width, so a touch laptop/tablet
 * benefits and — crucially — fine-pointer (mouse) desktop is completely
 * unaffected (every desktop surface stays exactly as designed). Buttons become
 * inline-flex so the taller box centres its label; .btn-sm and the round icon
 * row-buttons keep their compact size (still ≥40px, fine for their context).
 * Full-width .d-grid/.d-block buttons keep their display (Bootstrap utilities
 * carry !important). */
@media (pointer: coarse) {
  .btn:not(.btn-sm):not(.ui-rowbtn) {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .form-control:not(.form-control-sm),
  .form-select:not(.form-select-sm),
  .page-link,
  .dropdown-item {
    min-height: 44px;
  }
}

/* --- Modal: soft scrim ------------------------------------------------------
 * Bootstrap's default backdrop is pure black at 0.5 opacity (--bs-backdrop-bg
 * #000 / --bs-backdrop-opacity .5), which reads as a hard, heavy dim over a
 * light Trezo page. Ease it to a calmer 0.4 so the dialog still separates from
 * the page without the jarring blackout. Token-driven: we only retune the
 * Bootstrap backdrop CSS variable (the canonical theming hook) — no new colour,
 * and the fade transition Bootstrap already applies stays intact. */
.modal-backdrop {
  --bs-backdrop-opacity: 0.4;
}

/* --- Icon-button anti-jank (functional, not a style choice) -----------------
 * Ported verbatim off the removed luppis-tokens.css overlay — this is an
 * app-wide rule, NOT scoped to any legacy class, so it must live in the ui/
 * layer now that the overlay is gone. Material Symbols renders by ligature:
 * until the icon font loads, "save"/"check_circle" are normal text. Paired with
 * font-display:block (the &display=block on the Google Fonts link) the text
 * stays invisible during load — but the invisible word still reserves its full
 * width, so the button would shrink (jump) once the glyph collapses. Clipping
 * the glyph box to 1em pins the button at its final width, so icon buttons paint
 * smoothly. */
.btn .material-symbols-outlined {
  /* Size the glyph relative to the button text (default .material-symbols-outlined
     is a fixed 24px, ~1.7x the label, so its strokes read much heavier). 1.2em
     keeps the icon proportional to the label so their weights look consistent. */
  font-size: 1.2em;
  width: 1em;
  overflow: hidden;
  /* Match the button's own colour transition (style.css: color .15s ease-in-out)
     so the icon and the label change colour together on hover. Without this the
     icon (transition: all 0s) snapped instantly while the text eased — the desync. */
  transition: color 0.15s ease-in-out;
}

/* --- Form / section card (ported off the removed luppis-tokens.css overlay) --
 * The legacy .luppis-card gave the system-admin permission cards and the
 * payments card_form base a 0.75rem corner radius (larger than Trezo's
 * --bs-border-radius-lg of 0.5rem), and card-styles.css constrained a
 * form-bearing card to ~8/12 of the row on desktop (the old
 * `.luppis-card.card:has(> .card-body > form)` rule). Reproduced here on an
 * explicit opt-in marker so those pages keep their EXACT width + radius after
 * the overlay is deleted. The 0.75rem is a verbatim-preserved legacy value (no
 * --bs-* token matches it), kept only to avoid a visual shift. The 1.5rem bottom
 * margin is also from the old .luppis-card (a card with a `mb-*` utility still
 * overrides it, so the form pages that carry `mb-4` are unaffected; the list and
 * card_form cards that had no utility keep their gap). */
.ui-form-card {
  border-radius: 0.75rem;
  margin-bottom: 1.5rem;
  /* .luppis-card used an opaque --bs-border-color border and, loading after the
     theme, won over Bootstrap's translucent --bs-card-border-color. Reproduce it
     so bordered cards (the list + card_form cards) keep the exact same border;
     the `border-0` form cards strip the border entirely either way. */
  border-color: var(--bs-border-color);
}
@media (min-width: 992px) {
  .ui-form-card:has(> .card-body > form) {
    max-width: 67%;
  }
}

/* ---------------------------------------------------------------------------
   Validation feedback — error-only, no "valid" green (Form Design Standard §1, §2.2)

   On submit, both the validation module (form-validation.js, via `is-valid` +
   the form's `was-validated`) and the invoice/quote editor's own JS paint a
   GREEN border + ✓ on every field that PASSES — including untouched OPTIONAL
   fields. An empty optional field then reads as "completed correctly" when the
   user did nothing: positive noise that is the opposite of the anxiety-free goal.
   The signal that matters is the ERROR state; keep that (red invalid) and reset
   the VALID state back to a normal field. ui.css loads after style.css, so these
   matched selectors win on source order without !important. Tokens only.

   We do NOT touch the `.is-valid` class itself (JS keeps applying it) — only its
   appearance — so no validation/submit behaviour changes. --------------------- */

/* Text inputs / textareas: drop the green border + ✓ glyph; focus reads as the
   normal brand focus, not a green one. The right-padding the ✓ glyph reserved is
   reclaimed for plain controls but LEFT for the combobox (its caret needs it). */
.was-validated .form-control:valid,
.form-control.is-valid {
  border-color: var(--bs-border-color);
  background-image: none;
}
.was-validated .form-control:valid:not([data-combobox-input]),
.form-control.is-valid:not([data-combobox-input]) {
  padding-right: 0.75rem;
}
.was-validated .form-control:valid:focus,
.form-control.is-valid:focus {
  border-color: var(--bs-primary);
  box-shadow: 0 0 0 0.25rem rgba(var(--bs-primary-rgb), 0.25);
}

/* Selects: drop the green ✓ overlay + green border, keep the muted chevron and
   the normal single-glyph padding/positioning. */
.was-validated .form-select:valid,
.form-select.is-valid {
  border-color: var(--bs-border-color);
}
.was-validated .form-select:valid:not([multiple]):not([size]),
.was-validated .form-select:valid:not([multiple])[size="1"],
.form-select.is-valid:not([multiple]):not([size]),
.form-select.is-valid:not([multiple])[size="1"] {
  --bs-form-select-bg-icon: none;
  padding-right: 2.25rem;
  background-position: right 0.75rem center;
  background-size: 16px 12px;
}
.was-validated .form-select:valid:focus,
.form-select.is-valid:focus {
  border-color: var(--bs-primary);
  box-shadow: 0 0 0 0.25rem rgba(var(--bs-primary-rgb), 0.25);
}

/* Checkboxes / radios: a passing control keeps the BRAND fill when checked (not
   green), a neutral border when unchecked, and its label stays normal text — no
   green tick, no green label. */
.was-validated .form-check-input:valid,
.form-check-input.is-valid {
  border-color: var(--bs-border-color);
}
.was-validated .form-check-input:valid:checked,
.form-check-input.is-valid:checked {
  background-color: var(--bs-primary);
  border-color: var(--bs-primary);
}
.was-validated .form-check-input:valid:focus,
.form-check-input.is-valid:focus {
  box-shadow: 0 0 0 0.25rem rgba(var(--bs-primary-rgb), 0.25);
}
.was-validated .form-check-input:valid ~ .form-check-label,
.form-check-input.is-valid ~ .form-check-label {
  color: inherit;
}
