/* ============================================================
   The shape language, applied to Serenity's own chrome.

   legacy-shapes.css deliberately stopped at `.s-WglPage` / `.wgl-dlg`, on the
   reasoning that restyling the framework's CRUD pages would fight the theme on
   every upgrade. That call was reversed on request: eight hand-written screens
   in the shop's own language and twenty CRUD grids in Serenity's default read
   as two different products, and staff move between them all day.

   This file is that reversal, kept in ONE place so an upgrade has one file to
   check rather than a diff smeared through legacy-shapes.css.

   THREE RULES IT KEEPS TO:

   1. Tokens only, never brand literals. Everything here resolves through the
      --s-wgl-* surface set or Bootstrap's --bs-*, so both themes follow. Naming
      --wgl-white here would pin the whole admin to paper, which is exactly the
      bug legacy-shapes.css was refactored out of.

   2. Shape, not layout. Borders, radii, shadows, type. No display, no flex, no
      width, no position — SlickGrid measures its own boxes in JavaScript and
      writes inline pixel heights, so moving its geometry from CSS desynchronises
      the virtual scroller from what is painted.

   3. Lower specificity than legacy-shapes.css, and loaded after it. A rule here
      is `.s-Toolbar .tool-button`; the WGL screens' is
      `.s-WglPage .s-Toolbar .tool-button`. The hand-written screens therefore
      keep their own values wherever the two disagree, and this file only
      reaches the pages that had nothing.
   ============================================================ */

/* ── The page frame ──────────────────────────────────────────
   A CRUD page was a title in space above an edge-to-edge grid. It becomes the
   same card every WGL panel is: one surface, one hairline, one radius.

   --wgl-frame-inset is the single left edge everything in the frame hangs off.
   Without it the parts drifted apart: the titlebar padded its text in by 20px,
   the tabs landed at 21, and the toolbar and the category rules sat flush at 0 —
   so a form had three different left margins stacked down it, and the category
   hairlines reached further left than the button above them. Each band states
   its inset in terms of this value, so they cannot disagree again. */

.s-DataGrid,
.s-Panel,
.ui-dialog {
  --wgl-frame-inset: 20px;
}

/* Two more single-sourced measurements, for editors only. --wgl-caption-w is
   the label column (common-theme's own 150px, one pixel narrower so the
   uppercase micro-label below sits on a round number with its gap).
   --wgl-editor-w is the cap every editor in a half-width field takes, so a
   column has ONE right edge instead of one per editor type — a date's picker
   button and a validation icon used to leave the date box 53px shorter than
   the amount directly beneath it. */
.s-Panel,
.ui-dialog {
  --wgl-caption-w: 168px;
  --wgl-editor-w: 296px;
}

.s-DataGrid {
  background: var(--s-wgl-surface);
  border: var(--wgl-edge-thin);
  border-radius: var(--wgl-r-card);
}

/* No overflow:hidden to clip the band's corners — the quick-filter dropdowns and
   the column-picker open inside this box and would be cut off. The band rounds
   its own two corners instead. */
.s-DataGrid > .grid-title {
  background: var(--wgl-black);
  border-bottom: 6px solid var(--wgl-yellow);
  border-radius: calc(var(--wgl-r-card) - 2px) calc(var(--wgl-r-card) - 2px) 0 0;
  padding: 14px var(--wgl-frame-inset);
}

/* Theme-fixed on purpose: the legacy's topbar is black in a lit shop too. It is
   brand chrome, not a surface that follows the page. */
.s-DataGrid > .grid-title .title-text {
  font-family: var(--header-font), 'Space Grotesk', sans-serif;
  font-weight: 700;
  font-size: 19px;
  letter-spacing: -0.01em;
  color: var(--wgl-white);
}

/* ── Toolbar ─────────────────────────────────────────────────
   Serenity's toolbar is a SEGMENTED control: `.tool-button` carries
   `margin-left: -1px` so neighbours overlap by their 1px border and share one
   hairline, and only the first and last of a group get a radius. A 2px border
   plus a hard offset breaks every premise of that — the next button's rounded
   edge lands on the previous one's outline AND its shadow, and a group reads as
   one welded lump. The overlap becomes a real gap, and proximity carries the
   grouping instead. Same fix, same reasoning, as the WGL screens. */

/* On a GRID page the toolbar, the quick-filter bar and the quick-search box
   deliberately do NOT take --wgl-frame-inset. They keep the framework's own
   flush alignment; only the title band, the tabs and the category rules sit on
   the inset.

   An EDITOR is the exception, and the direct-child selector is what keeps the
   two apart (a master-detail grid's own toolbar sits deeper and is untouched).
   A dialog had four different left margins stacked down it — the title band at
   20, the toolbar at 0, the tabs at 21 and the category cards at 48 — and it
   reads as four unrelated strips rather than one document.

   The toolbar and the tab strip share one white band that ends on the tab
   strip's hairline, and the form's tinted ground begins under it. Two zones:
   what you DO to the record, then the record. */
/* No bottom padding: the tab strip below has its own 0.7rem above its labels, so
   a second gap here just pushed the two apart inside a band that is supposed to
   read as one. */
.panel-body > .s-Toolbar,
.ui-dialog-content > .s-Toolbar {
  background: var(--s-wgl-surface);
  padding: 11px var(--wgl-frame-inset) 0;
}

.s-Toolbar .tool-group {
  column-gap: 0.4rem;
}

.s-Toolbar .tool-button {
  margin-left: 0;
  font-family: var(--header-font), 'Space Grotesk', sans-serif;
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.04em;
  border: var(--wgl-edge);
  border-radius: var(--wgl-r-btn);
  background: var(--s-wgl-surface);
  color: var(--s-wgl-ink);
  box-shadow: var(--wgl-pop);
  transition: transform 0.08s ease, box-shadow 0.08s ease;
}

.s-Toolbar .tool-button:hover:not(.disabled) {
  transform: translate(-1px, -1px);
  box-shadow: var(--wgl-pop-hover);
}

.s-Toolbar .tool-button:active:not(.disabled) {
  transform: translate(1px, 1px);
  box-shadow: var(--wgl-pop-active);
}

.s-Toolbar .tool-button:focus-visible {
  outline: 3px solid var(--wgl-yellow);
  outline-offset: 2px;
}

/* The primary action on every grid — New X — is the yellow one, as it is on the
   hand-written screens. */
.s-Toolbar .tool-button.add-button {
  background: var(--wgl-yellow);
  color: var(--wgl-black);
  border-color: var(--wgl-black);
}

.s-Toolbar .tool-button.disabled {
  opacity: 0.4;
  box-shadow: none;
  transform: none;
}

/* Sergen colours the add icon green and the delete icon red inline. On a yellow
   fill the green is 2.1:1 and reads as a smudge; the button already says what it
   does in words. */
.s-Toolbar .tool-button.add-button .fa,
.s-Toolbar .tool-button.add-button i {
  color: var(--wgl-black) !important;
}

/* ── Quick search ────────────────────────────────────────────
   Same box as every other input in the app. */

.s-QuickSearchBar input,
.s-DataGrid .s-QuickSearchBar input {
  font-size: 13.5px;
  border: var(--wgl-edge-thin);
  border-radius: var(--wgl-r-input);
  background: var(--s-wgl-surface);
  color: var(--s-wgl-ink);
}

.s-QuickSearchBar input:focus {
  outline: none;
  border-color: var(--s-wgl-edge);
  box-shadow: 0 0 0 3px var(--s-wgl-focus-tint);
}

.s-QuickSearchBar input::placeholder {
  color: var(--s-wgl-ink-mute);
}

/* ── Quick filters ───────────────────────────────────────────
   The label takes the brand's micro-label geometry — 9.5px, .16em, uppercase —
   which is what every field label in the app is set at. Sentence-case labels
   over the same controls were the loudest tell that these pages belonged to a
   different product. */

.quick-filter-label {
  font-family: var(--header-font);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--s-wgl-ink-dim);
}

.quick-filter-item > input,
.quick-filter-item > select,
.quick-filter-item > .select2-container .select2-choice,
.quick-filter-item > .s-DateEditor input {
  font-size: 13.5px;
  border: var(--wgl-edge-thin);
  border-radius: var(--wgl-r-input);
  background: var(--s-wgl-surface);
  color: var(--s-wgl-ink);
}

.quick-filter-item > .select2-container .select2-choice {
  box-shadow: none;
  background-image: none;
}

.quick-filter-item > input:focus,
.quick-filter-item > select:focus,
.quick-filter-item > .select2-container-active .select2-choice {
  outline: none;
  border-color: var(--s-wgl-edge);
  box-shadow: 0 0 0 3px var(--s-wgl-focus-tint);
}

/* ── Column headers ──────────────────────────────────────────
   The same micro-label as the hand-written lists' header row, so a column
   caption reads identically whether the list is a SlickGrid or a stack of
   cards. Set in grid.css, which owns header typography. */

/* ── Pager ───────────────────────────────────────────────────
   Quiet. It is navigation for a list you are already looking at, not an action.
   No offsets here: eight little popped buttons in a footer strip is noise. */

.slick-pg-in {
  border-top: var(--wgl-edge-thin);
  background: transparent;
}

.slick-pg-in .slick-pg-btn {
  border: var(--wgl-edge-thin);
  border-radius: var(--wgl-r-input);
  background: var(--s-wgl-surface);
  color: var(--s-wgl-ink-dim);
  box-shadow: none;
  transition: background var(--s-transition), color var(--s-transition),
              border-color var(--s-transition);
}

.slick-pg-in .slick-pg-btn:hover {
  background: var(--s-wgl-surface-2);
  border-color: var(--s-wgl-edge);
  color: var(--s-wgl-ink);
}

.slick-pg-in input,
.slick-pg-in select {
  font-size: 13px;
  border: var(--wgl-edge-thin);
  border-radius: var(--wgl-r-input);
  background: var(--s-wgl-surface);
  color: var(--s-wgl-ink);
}

.slick-pg-in .slick-pg-stat {
  font-size: 12px;
  color: var(--s-wgl-ink-dim);
  font-variant-numeric: tabular-nums;
}

/* ── Entity dialogs ──────────────────────────────────────────
   You open one of these from every grid, so a framework-shaped editor over a
   WGL-shaped grid is the seam staff would notice most.

   This build renders them as `.s-Panel` — an inline page-level panel with a
   `.panel-titlebar`, not a floating `.ui-dialog`. Both are covered: the panel is
   what actually appears today, and the floating form is what Serenity falls back
   to for lookups and in dialog mode. */

.s-Panel,
.ui-dialog,
.s-Dialog {
  background: var(--s-wgl-surface);
  border: var(--wgl-edge-thin);
  border-radius: var(--wgl-r-card);
}

/* A floating dialog is lifted off the page, so it gets the offset. The inline
   panel is the page, so it does not — a hard shadow on something with nothing
   behind it reads as a misprint. */
.ui-dialog,
.s-Dialog {
  border: var(--wgl-edge);
  box-shadow: var(--wgl-pop-lg);
}

/* The inline panel IS a card, and common-theme flattens it.
   `.full-height-page section.content > .s-Panel { border: none; box-shadow: none }`
   is the framework's own rule for an editor that fills the page — and with the
   editor now painting a tinted ground of its own, "fills the page" turned it into
   a grey slab bleeding into the page behind it, with no edge anywhere.

   Restated at the same specificity and later in the cascade, so the panel keeps
   the hairline and gains the soft lift every other card in the app has. The
   `padding: 0` common-theme sets alongside them is deliberately NOT restated —
   the titlebar, toolbar and categories carry their own insets. */
.full-height-page section.content > .s-Panel,
section.content > .s-Panel {
  border: var(--wgl-edge-thin);
  border-radius: var(--wgl-r-card);
  box-shadow: var(--s-shadow-lg);
  overflow: hidden;
}

.s-Panel > .panel-titlebar,
.ui-dialog .ui-dialog-titlebar {
  background: var(--wgl-black);
  border-bottom: 6px solid var(--wgl-yellow);
  border-radius: calc(var(--wgl-r-card) - 2px) calc(var(--wgl-r-card) - 2px) 0 0;
  padding: 14px var(--wgl-frame-inset);
}

/* Theme-fixed, like the grid's band and the legacy's topbar. */
.s-Panel > .panel-titlebar .panel-titlebar-text,
.ui-dialog .ui-dialog-title {
  font-family: var(--header-font), 'Space Grotesk', sans-serif;
  font-weight: 700;
  font-size: 19px;
  letter-spacing: -0.01em;
  color: var(--wgl-white);
}

.s-Panel > .panel-titlebar .panel-titlebar-close,
.ui-dialog .ui-dialog-titlebar-close {
  color: var(--wgl-white);
  opacity: 0.7;
  transition: opacity var(--s-transition);
}

.s-Panel > .panel-titlebar .panel-titlebar-close:hover,
.ui-dialog .ui-dialog-titlebar-close:hover {
  opacity: 1;
}

/* The footer Serenity builds and never fills.
   `createPanel` appends `<div class="panel-footer">` whenever the dialog options
   carry a `buttons` array, and an EntityDialog in panel mode puts Save / Apply /
   Delete in the TOOLBAR instead — so the array is there, the footer is created,
   and nothing is ever put in it. common-theme then gives it 11.25px of padding
   all round, which is a 22.5px empty strip across the bottom of every editor in
   the app, and now that the panel is a card it reads as a blank band under the
   last section.

   `:empty` rather than a blanket hide: a dialog that DOES supply footer buttons
   gets its footer back the moment it has something in it. Serenity's own
   `.panel-footer` styling is otherwise untouched. */
.s-Panel > .panel-footer:empty,
.ui-dialog .panel-footer:empty {
  display: none;
}

/* Save is the primary action on an editor, exactly as New X is on a grid, so it
   takes the same yellow. Delete keeps the danger colour in its edge and its
   offset rather than as a fill — a solid red button is the loudest thing on a
   form whose usual outcome is Save. */
.s-Toolbar .tool-button.save-and-close-button {
  background: var(--wgl-yellow);
  color: var(--wgl-black);
  border-color: var(--wgl-black);
}

.s-Toolbar .tool-button.save-and-close-button .fa,
.s-Toolbar .tool-button.save-and-close-button i {
  color: var(--wgl-black) !important;
}

.s-Toolbar .tool-button.delete-button {
  color: var(--wgl-red);
  border-color: var(--wgl-red);
  box-shadow: 2px 2px 0 var(--wgl-red);
}

.s-Toolbar .tool-button.delete-button:hover:not(.disabled) {
  box-shadow: 3px 3px 0 var(--wgl-red);
}

.s-Toolbar .tool-button.delete-button .fa,
.s-Toolbar .tool-button.delete-button i {
  color: var(--wgl-red) !important;
}

/* ── Forms inside a dialog ───────────────────────────────────
   PropertyGrid renders `.categories > .category > .category-title` plus a
   `.field > label + .editor` per row. The caption becomes the same micro-label
   the WGL panels use; the editor becomes the same box. */

/* Categories are NOT restyled here.

   `components/form.css` already owns them, and as of this pass what it gives
   them is `.wgl-panel` — the identical section container the eight
   hand-written screens use, with `.category-title` set like
   `.wgl-panel-title`. One owner per component; this file does not get a vote
   on that one. It has tried twice — once duplicating the card with the 2px
   edge, once flattening it to a bare label and hairline — and both were
   reverted.

   The only thing left is the ground they sit on and the inset, so the cards
   line up with the title band above them rather than sitting 20px to its left.

   THE GROUND is --s-wgl-surface: the SAME white the cards are, which is what
   the quotation builder does — its page is white, its panels are white, and a
   hairline is all that separates them.

   It was a tinted band for a while, on the reasoning that white-on-white is
   why an editor read as flat. That reasoning was about a card with nothing to
   distinguish it; the fix belonged to the card. Once the section carries a
   1.05rem heading of its own, the tint had no work left to do and was just a
   warm cast under the whole lower half of every dialog — an area far larger
   than the quick-filter strip whose value it borrowed. This is also the one
   move that makes a dialog and a builder look like the same product, which
   they are.

   The rule stays rather than being deleted: --s-wgl-surface is not what
   `.panel-body` resolves to on every theme and every Serenity skin, and this
   is the guarantee that the ground under a form matches the cards on it.

   Scoped to a dialog's own form: a PropertyGrid rendered inline on a page has
   no panel behind it and would be painting a band across whatever it sits on.
   Not a direct-child selector — Serenity nests it
   `.panel-body > .s-Form > form > .s-PropertyGrid`, and `> .s-PropertyGrid`
   silently matched nothing at all. */
.panel-body .s-Form .s-PropertyGrid,
.ui-dialog-content .s-Form .s-PropertyGrid {
  background: var(--s-wgl-surface);
}

.s-PropertyGrid .categories {
  padding: 1rem var(--wgl-frame-inset) 1.4rem;
}

/* The pane's own padding would double the block padding above; `.categories` is
   the one that has it, because a tabless form has no pane at all. */
.s-PropertyGrid .property-panes .tab-pane {
  padding-block: 0;
}

/* ── The field row ───────────────────────────────────────────
   `flex-wrap: wrap` is load-bearing, not cosmetic: it is what lets `.vx` drop
   to a line of its own under the editor (see the validation block below).
   common-theme sets nowrap, and every editor here carries `min-width: 0`, so
   nothing else moves. */
.s-PropertyGrid .field {
  flex-wrap: wrap;
  padding-block: 0.3rem;
}

/* The trailing `<div class="clear">` is a float-era leftover. As a flex item it
   is a zero-width sibling that does nothing; with wrapping on it is one more
   thing that can take a line. */
.s-PropertyGrid .field > .clear {
  display: none;
}

/* The caption becomes the micro-label every quick-filter, column header and chip
   in this app already is. 12px sentence case beside a 9.5px uppercase column
   header was the loudest tell that a dialog and a grid were different products.

   Stays a plain block, deliberately. Making it a flex row to reorder the
   required marker with `order: 2` works — Serenity uses that trick itself in
   `.s-vertical-form` — but a `<sup>` in a flex row is a flex ITEM, and a flex
   item ignores `vertical-align`. Every way of placing it back on the line
   (align-self, matched line-heights) put the mark a few pixels adrift, because
   the flex line's cross size is not the label's height. */
.s-PropertyGrid .field > label.caption {
  width: var(--wgl-caption-w);
  font-size: 10.5px;
  font-weight: 700;
  /* .07em, not the .16em the quick-filter labels take. A filter caption is two
     words; a field caption is "Total Supplier Payout USD", and at .16em that is
     185px of label to keep on one line. The column is 168px and nowrap, so the
     tracking is what decides whether the longest names in the app fit or wear an
     ellipsis. `title` is on the label either way, so the rare one that does not
     fit still says what it is on hover. */
  letter-spacing: 0.07em;
  text-transform: uppercase;
  line-height: calc(var(--s-input-inner-height) + 2px);
  color: var(--s-wgl-ink-dim);
  text-align: end;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* So the marker is drawn AFTER the words instead of moved there. PropertyGrid
   emits `<sup>*</sup>` first and there is no way to reorder inline content, but
   there is no need to: the element is decorative — required-ness lives on the
   input, as `required` and `.required`, which is what the validator and a screen
   reader read. Hidden here and restated as `::after`, at a size that reads as a
   marker (sup's own 0.83em of a 10.5px label is 8.7px, and lands as a full stop
   after the words). The label then keeps ONE clean right edge down the column
   instead of the ragged left one a leading asterisk gave it. */
.s-PropertyGrid .field > label.caption sup {
  display: none;
}

.s-PropertyGrid .field > label.caption:has(sup)::after {
  content: "*";
  color: var(--s-danger-text);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0;
  margin-inline-start: 3px;
}

/* Labels on anything that is NOT a PropertyGrid caption keep the old size —
   `.field > label` also reaches the filter panel and a few hand-rolled forms. */
.s-PropertyGrid .field > label:not(.caption) {
  font-size: 12px;
  font-weight: 600;
  color: var(--s-wgl-ink-dim);
}

/* `:not([type=checkbox])` for the same reason it appears in legacy-shapes.css —
   a checkbox reaches `.editor` too, and a text-field box on one turns it into an
   empty full-width rectangle. */
.s-PropertyGrid .field input.editor:not([type="checkbox"]),
.s-PropertyGrid .field select.editor,
.s-PropertyGrid .field textarea.editor {
  font-size: 13.5px;
  border: var(--wgl-edge-thin);
  border-radius: var(--wgl-r-input);
  background: var(--s-wgl-surface);
  color: var(--s-wgl-ink);
}

.s-PropertyGrid .field input.editor:not([type="checkbox"]):focus,
.s-PropertyGrid .field select.editor:focus,
.s-PropertyGrid .field textarea.editor:focus {
  outline: none;
  border-color: var(--s-wgl-edge);
  box-shadow: 0 0 0 3px var(--s-wgl-focus-tint);
}

/* ── One editor width per column ─────────────────────────────
   A field that carries a `col-*` class is one the form author declared as HALF
   (or a third) of a row — they had already said it does not need the whole
   line. Stretching its editor to fill that half anyway is what produced a
   column of 560px boxes holding eight characters, and made every difference in
   editor construction show up as a different right edge.

   Capping them at one width gives each column a single straight right edge,
   which is what "the alignment is off" actually was. A FULL-width field is left
   alone: it is full width because somebody wanted the room.

   `.select2-container` is named because a LookupEditor paints into a sibling
   DIV, not into its own `input.editor` — miss it and every lookup in the form is
   the one editor still stretching to the column's full width. */
.s-PropertyGrid .field[class*="col-"] input.editor:not([type="checkbox"]),
.s-PropertyGrid .field[class*="col-"] select.editor,
.s-PropertyGrid .field[class*="col-"] .widget-wrapper,
.s-PropertyGrid .field[class*="col-"] > .select2-container {
  max-width: var(--wgl-editor-w);
}

/* A textarea is the deliberate exception — it is chosen when the content is
   long, and half a row of it is already the constraint. */
.s-PropertyGrid .field[class*="col-"] textarea.editor {
  max-width: none;
}

/* Numbers right-align on their figures and set in tabular figures, so decimal
   points stack down a column and a total cannot drift by a glyph width.
   `.decimalQ` / `.integerQ` are corelib's own validation classes, on the input
   itself — no form attribute is needed to opt in. */
.s-PropertyGrid .field input.editor.decimalQ,
.s-PropertyGrid .field input.editor.integerQ {
  text-align: end;
  font-variant-numeric: tabular-nums;
}

/* ── Read-only is a value, not an empty box ──────────────────
   Every column a rule derives is `[Insertable(false), Updatable(false)]`, so a
   dialog is full of fields the service writes and nobody types: Opened By,
   Expected USD, Over/Short, the audit stamps. Drawn as input boxes they read as
   work the user has not done yet — on the cash session that is five empty boxes
   a cashier can reasonably try to fill.

   They become a value on a rule: no fill, no box, the bottom hairline only, and
   ink at full strength because this is real data. Distinct from disabled, which
   is dimmed. The side borders stay at their own width in `transparent` so
   nothing shifts by a pixel when a field flips state. */
.s-PropertyGrid .field input.editor[readonly]:not([type="checkbox"]),
.s-PropertyGrid .field input.editor.readonly:not([type="checkbox"]),
.s-PropertyGrid .field textarea.editor[readonly] {
  background: transparent;
  border-color: transparent transparent var(--s-wgl-hairline);
  border-radius: 0;
  padding-inline: 2px;
  box-shadow: none;
  cursor: default;
}

.s-PropertyGrid .field input.editor[readonly]:focus,
.s-PropertyGrid .field input.editor.readonly:focus,
.s-PropertyGrid .field textarea.editor[readonly]:focus {
  box-shadow: none;
  border-color: transparent transparent var(--s-wgl-edge);
}

/* ── Validation says what is wrong ───────────────────────────
   common-theme renders the message with `color: transparent` and paints a red
   `\f06a` glyph in its place, 1.5rem wide, as a flex item beside the editor. Two
   things go wrong with that. The message itself survives only in a `title`
   tooltip, which no touch device shows and no screen reader announces from a
   glyph. And the glyph is a flex item, so an invalid field is narrower than the
   valid one beneath it — the error moves the layout.

   `.field` wraps (see the field row block above), so `.vx` takes a line of its
   own under the editor, indented to the editor column, with the words visible
   and the icon beside them rather than instead of them. */
.s-PropertyGrid .field .error ~ .vx {
  flex: 1 0 100%;
  width: auto;
  height: auto;
  overflow: visible;
  margin-top: 4px;
  margin-inline-start: calc(var(--wgl-caption-w) + var(--s-col-gap));
}

.s-PropertyGrid .field .vx label.error {
  color: var(--s-danger-text);
  font-size: 12px;
  font-weight: 600;
  white-space: normal;
  margin: 0;
  gap: 5px;
}

.s-PropertyGrid .field .vx label.error::before {
  font-size: 1.05em;
  width: auto;
}

/* ── Date editors: one field shape, not three ────────────────
   Serenity renders a DateEditor as `<input class="s-DateEditor"> + <button
   class="ui-datepicker-trigger">`, and `.field` is a flex row. The button is a
   flex item, so it takes its ~24px out of the INPUT — every date field ended
   24px short of the text field directly above it, with the trigger sitting
   outside the box as a loose glyph. Three visual styles down one column, on a
   form where they are all just fields.

   The trigger sits inside the box instead, the way a native picker indicator
   does. The input then measures the same as every other editor in the same
   column, which is the whole point.

   IT IS PULLED BACK, NOT ABSOLUTELY POSITIONED. An earlier pass pinned it to
   `inset-inline-end` of `.field`, which is correct only while the editor fills
   the field — it does not, since the width cap above. Three margins that sum to
   zero park it over the input's own right edge whatever that edge happens to
   be, and leave the next sibling (a DateTimeEditor's time `<select>`, which
   corelib inserts AFTER the trigger) exactly where it would otherwise land.

   `:has(+ .ui-datepicker-trigger)` rather than a class, because the trigger is
   attached by DateEditor AND DateTimeEditor and they do not share one.

   Nothing here restyles the editor itself — the border, radius, background and
   focus ring are the rules above, unchanged. Scoped to `.s-PropertyGrid` so the
   grid quick-filters keep the treatment they already have. */
.s-PropertyGrid .field {
  position: relative;
}

.s-PropertyGrid .field > .ui-datepicker-trigger {
  margin: 0 0.25rem 0 -2.25rem;
  width: 2rem;
  flex: 0 0 auto;
  align-self: center;
  z-index: 2;

  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: var(--wgl-r-chip);
  background: none;
  font-size: 1.05rem;
  line-height: 1;
  color: var(--s-wgl-ink-dim);
  transition: color var(--s-transition), background var(--s-transition);
}

html[dir="rtl"] .s-PropertyGrid .field > .ui-datepicker-trigger {
  margin: 0 -2.25rem 0 0.25rem;
}

.s-PropertyGrid .field > .ui-datepicker-trigger:hover {
  color: var(--s-wgl-ink);
  background: var(--s-wgl-surface-2);
}

.s-PropertyGrid .field > .ui-datepicker-trigger:focus-visible {
  outline: 2px solid var(--wgl-yellow);
  outline-offset: 1px;
}

.s-PropertyGrid .field > input.editor:has(+ .ui-datepicker-trigger) {
  padding-inline-end: 2.4rem;
}

/* A read-only date has no picker to open, so the trigger is a control that does
   nothing. Audit stamps — Created At, Updated At — are the common case. Hidden
   rather than dimmed, which leaves them looking exactly like the plain text
   field beside them, because that is exactly what they are. */
.s-PropertyGrid .field > input.editor.readonly ~ .ui-datepicker-trigger,
.s-PropertyGrid .field > input.editor[readonly] ~ .ui-datepicker-trigger {
  display: none;
}

.s-PropertyGrid .field > input.editor.readonly:has(+ .ui-datepicker-trigger),
.s-PropertyGrid .field > input.editor[readonly]:has(+ .ui-datepicker-trigger) {
  padding-inline-end: 2px;
}

/* ── Over / Short is the answer, so it reads like one ────────
   The ONE field-name rule in this file, and it is here rather than in a Cufex
   stylesheet because there is no Cufex stylesheet — `components/screens.css`
   belongs to the hand-written pages, and this is a Serenity CRUD dialog.

   Over/Short is the bottom line of a drawer reconciliation: Counted minus
   Expected, written by CashSessionService. As the fifth of five half-width
   fields it sat in a column with a hole beside it and the same weight as the
   two numbers it is derived from. It takes the row, above a rule, at the size
   the conclusion of a page is set at. */
.s-PropertyGrid .field.OverShortUsd {
  flex: 0 1 100%;
  margin-top: 0.5rem;
  padding-top: 0.85rem;
  border-top: var(--wgl-edge-thin);
}

.s-PropertyGrid .field.OverShortUsd > label.caption {
  color: var(--s-wgl-ink);
}

/* The `[readonly]` twin is not redundant: the read-only rule above is one class
   more specific than a plain `.OverShortUsd input.editor`, and would win the
   border back. Same specificity, declared later. */
.s-PropertyGrid .field.OverShortUsd input.editor,
.s-PropertyGrid .field.OverShortUsd input.editor[readonly] {
  max-width: var(--wgl-editor-w);
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.01em;
  border-bottom-width: 2px;
  border-bottom-color: var(--s-wgl-edge);
}

/* ── Checkboxes ──────────────────────────────────────────────
   One vocabulary across the whole admin: the 2px edge, the 5px radius, and a
   fill on check whose tick contrasts with it in either theme.

   Serenity draws grid checkboxes as `<span class="check-box">`, not inputs —
   the select-row column, the select-all header, and every bool cell. They take
   the same treatment so a tick in a grid and a tick in a dialog are one thing. */
.s-PropertyGrid .field input[type="checkbox"],
.slick-cell input[type="checkbox"],
.slick-cell .check-box,
.slick-header-columns .check-box,
.slick-header-column input[type="checkbox"] {
  width: 17px;
  height: 17px;
  border: var(--wgl-edge);
  border-radius: 5px;
  background-color: var(--s-wgl-surface);
  background-image: none;
  background-repeat: no-repeat;
  background-position: center;
  background-size: 74%;
  cursor: pointer;
}

/* `:not(.grant):not(.revoke)` leaves the permission dialog's own green/red
   colourways alone — those carry the framework's white tick on a dark fill and
   are already readable. Only the default yellow state was the problem. */
.s-PropertyGrid .field input[type="checkbox"]:checked,
.slick-cell input[type="checkbox"]:checked,
.slick-cell .check-box.checked:not(.grant):not(.revoke),
.slick-header-columns .check-box.checked:not(.grant):not(.revoke),
.slick-header-column input[type="checkbox"]:checked {
  background-color: var(--s-wgl-edge);
  border-color: var(--s-wgl-edge);
  background-image: var(--s-check-tick);
}

/* ── Tabs ────────────────────────────────────────────────────
   The active tab takes the brand rule underneath rather than a box, which is the
   same device the sidebar's active parent and the title band both use. */

/* PropertyGrid builds `ul.nav.nav-underline.property-tabs` — Bootstrap's
   underline variant, not `.nav-tabs`. Both are named: `.property-tabs` is what
   a form actually renders, `.nav-tabs` is the boxed variant a dialog can use. */
.property-tabs {
  border-bottom: var(--wgl-edge-thin);
  margin-bottom: 0;
  gap: 1.6rem;
  /* The strip continues the toolbar's surface, so the two read as one band under
     the title rather than two floating strips. Its hairline is where that band
     ends and the form's own ground begins. */
  background: var(--s-wgl-surface);
  /* No horizontal padding on the link below, so the first LABEL lands on the
     inset itself — the label is what the eye lines up, and backing out a hit
     area with a negative calc left the two disagreeing by a pixel or two
     whenever the link padding changed. */
  padding-inline: var(--wgl-frame-inset);
}

.property-tabs .nav-link,
.s-PropertyGrid .nav-tabs .nav-link,
.ui-dialog .nav-tabs .nav-link {
  font-family: var(--header-font);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--s-wgl-ink-dim);
  background: transparent;
  border: none;
  border-bottom: 3px solid transparent;
  border-radius: 0;
  /* Taller than it was: this is a control on a screen opened a hundred times a
     day, and 22px of hit height on an 11.5px label was mean. The gap on the
     strip carries the separation the horizontal padding used to. */
  padding: 0.7rem 0 0.65rem;
  margin-bottom: -1.5px;   /* sit the marker on the strip's own rule */
  transition: color var(--s-transition), border-color var(--s-transition);
}

/* `.nav-tabs` is the boxed variant, and it is NOT on the strip above — it keeps
   its own padding so its links do not collapse onto each other. */
.s-PropertyGrid .nav-tabs .nav-link,
.ui-dialog .nav-tabs .nav-link {
  padding-inline: 0.85rem;
}

.property-tabs .nav-link:hover,
.s-PropertyGrid .nav-tabs .nav-link:hover,
.ui-dialog .nav-tabs .nav-link:hover {
  color: var(--s-wgl-ink);
  border-bottom-color: var(--s-wgl-hairline);
}

/* The brand rule under the current tab — the same device the sidebar's active
   parent and the title band use. */
.property-tabs .nav-link.active,
.s-PropertyGrid .nav-tabs .nav-link.active,
.ui-dialog .nav-tabs .nav-link.active {
  color: var(--s-wgl-ink);
  background: transparent;
  border-bottom-color: var(--wgl-yellow);
}

.property-tabs .nav-link:focus-visible {
  outline: 3px solid var(--wgl-yellow);
  outline-offset: -3px;
}
