/* LTP-specific additions on top of jui/dash26 -- tokens only, no hardcoded hex. */

/* Phase X cleanup: the brand padlock mark used to carry stroke="#fff" inline
   in four separate templates (sidebar, login, accept-invite, forced password
   change). It genuinely has to be white in BOTH themes -- it sits on the
   brand's own coloured fill (`.brand-logo{background:var(--primary)}`, and
   the auth aside's gradient), not on the page background -- so it is the one
   mark that must NOT follow the theme. jui ships no "always white" token
   (`--t-inverse` inverts with the theme, which is exactly wrong here), so the
   literal is defined ONCE here and the templates now use
   stroke="currentColor" against this class. One defined value, not four
   scattered inline ones. */
.brand-mark { color: var(--brand-mark); }
:root { --brand-mark: #fff; }

/* Phase GA: root-cause fix for a sitewide defect, not a one-off. jui's
   `.btn--icon` (assets/jui/style.css) only ever sizes its svg when wrapped
   in `.data-cell-actions` (`.data-cell-actions .btn--icon svg{width:13px;
   height:13px;...}`) -- a plain `.btn--icon` outside that context gets only
   `.btn--icon svg{margin:0}`, no width/height at all. Combined with the
   sitewide `svg{display:block;max-width:100%}` reset (no fallback size),
   the icon renders at 0x0: the BUTTON box is present, sized, clickable and
   on top of the stack (confirmed live -- elementFromPoint at its center
   returns the button itself), but nothing is painted inside it. This is the
   exact same mechanism a prior phase already diagnosed and fixed narrowly
   for one single button (`.docs-trigger svg`, Phase EC) -- never
   generalized. It explains the modal close X reportedly "not present":
   the markup was there the whole time, present in every panel.php and
   doc_list.php, just invisible. Fixing it here, once, for every current AND
   future plain `.btn--icon` instance instead of patching this one button --
   `.data-cell-actions .btn--icon svg`'s higher specificity (2 classes + 1
   type vs. 1 class + 1 type here) means it's unaffected, still wins where it
   already applied. Same 13px value as `.docs-trigger svg` and
   `.data-cell-actions .btn--icon svg`, for one consistent icon-button size
   app-wide rather than a new arbitrary number. */
.btn--icon svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 1.8; }

/* Phase GA: root-cause fix for mis-centred button labels (Cancel specifically
   reported, but this is a component-level gap, not a one-off -- fixed here,
   not with a `.panel-foot .btn` override, per the brief's own instruction).
   jui's base `.btn{display:inline-flex;...}` never sets `justify-content`,
   so it defaults to `normal` (behaves as flex-start: content packed to the
   LEFT of the flex box). Invisible everywhere a `.btn` auto-sizes to its own
   content -- there's no leftover space to misplace -- but the moment a
   button gets a wider fixed/min-width box than its content needs (e.g.
   `.panel-foot .btn{min-width:120px}`), the label sits left-packed instead
   of centered. Measured live: Cancel's own CSS padding was already
   symmetric (14px/14px) while the RENDERED gap around the text was 15px
   left vs 61px right -- proving this is a flex-alignment defect, not a
   padding one, before writing this rule. `.btn` already correctly has
   `align-items:center` for the cross axis; this adds the matching
   `justify-content:center` for the main axis, wherever a fixed-width `.btn`
   exists anywhere in the app, not just this footer. */
.btn { justify-content: center; }

/* Phase GB: "both eye icons render side by side" traced to the sitewide
   `svg{display:block;...}` reset (assets/jui/style.css, several variants) --
   an AUTHOR rule, which always beats the browser's UA-stylesheet
   `[hidden]{display:none}` regardless of specificity. This is the same
   conflict class already hit four times this project for OTHER elements
   (`.grid` vs. a tab pane, `.dropzone-file`/`[data-dropzone-empty]` vs.
   their own `display:flex`) -- the password toggle is the first place
   `hidden` was ever put directly ON an `<svg>` rather than a wrapping div,
   so it's the first place this exact variant of the conflict surfaced.
   Confirmed live before writing this: `[data-icon-hide]` measured
   `display:block` with a real 16x16 rendered box despite carrying the
   `hidden` attribute. General fix, not scoped to `.password-toggle`, since
   the underlying conflict (any `svg{display:...}` rule vs. `[hidden]` on an
   svg) isn't specific to this one component. */
svg[hidden] { display: none; }

/* Phase S: record panels are centered modals, not right-hand slide-overs.
   The old .slide-over/.panel-backdrop pair is gone entirely -- everything
   now rides the same .modal-overlay shell the Phase F document preview
   already used, so there is one modal implementation in this app, not two.

   Layout note: the panel fragments render .panel-head, then an optional
   .tabs row, then one .panel-body per tab (all but the active one carrying
   `hidden`). Making the modal a flex column and letting the single VISIBLE
   .panel-body be the flex-grow item means the head and tabs stay fixed and
   the body scrolls internally with no restructuring of any panel.php --
   hidden panes are display:none so they never participate in the flex sizing. */
.panel-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 22px;
  border-bottom: 1px solid var(--border-soft);
  background: var(--bg-card);
}
.panel-title {
  font-family: "Inter Tight", sans-serif;
  font-weight: 700;
  font-size: 17px;
  color: var(--t-base);
  /* long station names must ellipsize, not shove the close button off-modal */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.panel-body {
  flex: 1 1 auto;
  min-height: 0;          /* load-bearing: without it a flex item refuses to shrink below content height and the PAGE scrolls instead of the modal */
  overflow-y: auto;
  /* Phase DC: bottom padding was 0 -- harmless back when the Cancel/Save
     footer lived inside this same scrolling element (its own sticky rule
     supplied the visual gap, see the .panel-body > form > .form-actions
     rule below). Phase CA moved that footer OUT to .panel-foot, a true
     sibling outside this scroll region -- with nothing left to supply a
     gap, the last element (often the Actions block) sat flush against the
     footer's divider. 24px matches the .card/.panel-head padding scale
     already used throughout this file, not an invented value. */
  padding: 20px 22px 24px;
}
.modal-demo--record > .tabs { flex: 0 0 auto; margin: 0 22px 0; }

/* Phase CA: the modal's primary Cancel/Save Changes footer is now a real
   flex sibling of .panel-body (a direct child of #slideOverContent, same as
   .panel-head/.tabs), not part of any one tab's scrolling content -- it
   never moves, regardless of which tab is active or how long that tab's
   content is. The Save button lives OUTSIDE its <form> and references it via
   the standard HTML `form="<id>"` attribute; that still fires the form's
   native submit event (bubbles to the existing delegated data-ajax-form
   listener in ltp.js), so no JS change was needed for the submit mechanism
   itself -- only for the disabled-while-saving lookup (see ltp.js). */
.panel-foot {
  flex: 0 0 auto;
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  align-items: center;
  padding: 14px 22px;
  padding-bottom: max(14px, env(safe-area-inset-bottom));
  border-top: 1px solid var(--border-soft);
  background: var(--bg-card);
}
.panel-foot .btn { min-width: 120px; }

/* A tab-LOCAL secondary action (Documents tab's own Upload button) is not
   the modal's primary footer -- it stays inside that tab's own scrolling
   body, stuck to the bottom of ITS content the same way the whole record's
   footer used to work before this phase. */
.panel-body > form > .form-actions,
.panel-body > .form-actions {
  position: sticky;
  bottom: 0;
  background: var(--bg-card);
  margin-top: 18px;
  padding-bottom: 18px;
}

/* Phase DC: the destructive Actions zone (Terminate/Archive) reads as a
   distinct area, not the tail of the form -- divider + space above (space
   below now comes from .panel-body's own bottom padding, immediately
   above). Shared class, not a one-off -- both Network's and Dealership's
   panel.php use it identically. */
.panel-actions-zone {
  border-top: 1px solid var(--border-soft);
  margin-top: 24px;
  padding-top: 20px;
}

.activity-entry { padding: 12px 0; border-bottom: 1px solid var(--border-soft); }
.activity-entry:last-child { border-bottom: none; }
.activity-summary { font-size: 13px; color: var(--t-base); }
.activity-lines { margin: 6px 0 0; padding-left: 18px; font-size: 12px; color: var(--t-muted); }
.activity-lines li { margin-bottom: 2px; }

/* Print (Reports module): jui ships no print stylesheet of its own, so this
   is built from its existing var(--token)s, not new colors. */
.report-print-header { display: none; }

/* Phase H fix: the block below used to reference var(--bg-body) etc.
   directly, which resolves against whatever data-theme is on <html> at
   print time -- a user with dark mode on got dark-mode light-on-dark
   colors printed with no background fill (browsers don't print
   backgrounds by default), i.e. unreadable light text on white paper.
   Fix: redefine the FULL jui light token set on :root inside @media print
   (overriding whatever :root[data-theme="dark"] set), so every component
   that already references var(--token) -- .tag, .badge, borders, etc. --
   prints correctly with no per-component patching needed, plus an explicit
   #fff/#000 fallback on html/body for browsers that don't print CSS custom
   properties from a media-query override at all. */
@media print {
  /* Must match/exceed :root[data-theme="dark"]'s specificity ((0,2,0) via
     the attribute selector) or its declarations win regardless of source
     order -- a plain `:root { ... }` here (0,1,0) loses to it every time.
     Covering both attribute values (and bare :root, for a first-time
     visitor with no stored preference at all) guarantees this wins no
     matter what's on <html> when Print is pressed. */
  :root, :root[data-theme="dark"], :root[data-theme="light"] {
    --bg-body: #f0f4f8; --bg-card: #fff; --bg-sidebar: #fff; --bg-hover: #f8fafc; --bg-muted: #f1f5f9;
    --t-base: #1e293b; --t-muted: #64748b; --t-light: #94a3b8; --t-inverse: #fff;
    --border: #e4e8ef; --border-soft: #eef1f5;
    --primary: #f97316; --primary-light: #fb923c; --primary-dark: #c2410c; --primary-soft: #fff7ed; --primary-ring: rgba(249,115,22,.18);
    --success: #10b981; --success-soft: #ecfdf5;
    --warning: #f59e0b; --warning-soft: #fffbeb;
    --danger: #ef4444; --danger-soft: #fef2f2;
    --info: #0ea5e9; --info-soft: #f0f9ff;
    --purple: #8b5cf6; --purple-soft: #f5f3ff;
    --pink: #ec4899; --pink-soft: #fdf2f8;
    --teal: #14b8a6; --teal-soft: #f0fdfa;
    --orange: #f97316; --orange-soft: #fff7ed;
    --shadow-sm: none; --shadow-card: none; --shadow-lg: none;
  }
  /* Brief-mandated explicit fallback (not just the var() overrides above):
     some print engines don't honor a @media print override of a custom
     property at all, so html/body get a literal, non-token safety net on
     top of the token redefinition -- the one deliberate exception to "no
     hardcoded hex," required by this phase's own fix instructions. */
  html, body {
    background: #fff !important;
    color: #000 !important;
  }
  /* Sidebar, topbar, filters, and every button -- nothing interactive prints. */
  .no-print, .d-sidebar, .d-topbar, .hero-actions,
  .modal-overlay, .data-toolbar, button, .btn, .pager, .data-foot { display: none !important; }
  /* Flatten the app shell's screen-only CSS Grid (.shell{grid-template-
     columns:248px 1fr; min-height:100vh}) to plain block flow. Headless
     Chrome's print/PDF layout pass was observed collapsing this grid to
     zero height with `min-height:100vh` still in play (a `vh`-in-print
     quirk, not a jui rule -- confirmed no @media print exists in jui's own
     style.css), producing a blank printed page even though every color/
     visibility value was independently confirmed correct. Print never
     needs the sidebar grid track anyway once the sidebar itself is hidden
     above, so this is a correct simplification regardless of the quirk's
     exact cause, not just a workaround. */
  .shell, .main { display: block !important; min-height: 0 !important; height: auto !important; }
  body, .content, .card, #reportCard { background: var(--bg-card); box-shadow: none; margin: 0; padding: 0; }
  .report-print-header { display: block; margin-bottom: 16px; }
  .report-print-header .card-title { font-size: 18px; margin: 0 0 4px; color: var(--t-base); }
  .report-print-header .report-meta { font-size: 11px; color: var(--t-muted); } /* "as of" date + generating user, kept visible */
  #reportTable { width: 100%; border-collapse: collapse; }
  #reportTable thead { display: table-header-group; } /* repeat header on every printed page */
  #reportTable tr { page-break-inside: avoid; break-inside: avoid; }
  #reportTable th, #reportTable td { border: 1px solid var(--border); padding: 6px 8px; font-size: 11px; color: var(--t-base); }
  #reportTable thead th { background: var(--bg-muted); -webkit-print-color-adjust: exact; print-color-adjust: exact; }
  @page { margin: 14mm; }
}

/* Phase E: 5-across KPI card row (jui's own .kpi-grid defaults to 4 columns
   and already collapses to 2/1 below tablet -- this modifier keeps that
   same collapse behavior, just starting from 5 instead of 4, and reuses
   .kpi-grid's own gap/margin, doesn't redeclare them). */
.kpi-grid-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
@media (max-width: 1200px) { .kpi-grid-5 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 600px) { .kpi-grid-5 { grid-template-columns: 1fr; } }

/* Phase E: dense table variant. jui's .data-table ships no compact/dense
   modifier (checked style.css before adding this), so this trims its
   12px/14px cell padding down proportionally rather than inventing an
   unrelated spacing value. jui's base th/td are `white-space:nowrap` with
   no overflow handling, which is fine at the table's natural (auto) width
   but bleeds text across cell boundaries once table-layout:fixed forces
   narrow, percentage-wide columns -- overflow:hidden here is load-bearing,
   not cosmetic, for every cell in this variant, not just ones explicitly
   marked .cell-truncate. */
.data-table--dense thead th,
.data-table--dense tbody td {
  padding: 8px 10px;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Phase E: whole-row click opens the detail panel; interactive elements
   inside a row (checkboxes, links) opt out via data-no-row-click so their
   own click handler fires instead of the row's. */
.data-table--dense tbody tr[data-row-click] { cursor: pointer; }
.data-table--dense tbody tr[data-row-click]:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: -2px;
}
.data-table--dense .cell-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 0; /* forces the cell to respect its <th> % width under table-layout:fixed */
}
.data-table--dense .cell-num { text-align: right; white-space: nowrap; }

/* Phase T: Docs column. The icon carries a count badge only when there is
   more than one attachment (a "1" on every single-document row is noise),
   and a row with no attachments gets a muted placeholder instead of an icon
   that would open an empty modal and read as broken. */
.docs-cell { text-align: center; }
.docs-cell--empty { color: var(--t-light); }
/* Phase NB: generic muted em-dash placeholder for a list cell with no value
   (e.g. Dealership's Station Code, Lease's Locator count) -- same tone as
   the empty Docs cell above so every "nothing here" reads the same way. */
.cell-empty,
.data-table tbody td.cell-empty { color: var(--t-light); }
/* Phase EC: `.data-table--dense tbody td{overflow:hidden}` exists for TEXT
   cells (so unclipped nowrap text can't bleed across a fixed-layout column
   boundary) but was clipping this cell's icon BUTTON too -- there's no text
   here to truncate, so the ellipsis behavior was never wanted on this cell in
   the first place. `.docs-trigger{overflow:visible}` below (pre-existing)
   only affects the button itself; the actual clip happens on this cell (its
   ANCESTOR), which is why that earlier fix never worked. Needs the doubled
   class + tbody/td chain to out-specificity the dense rule it's overriding. */
.data-table--dense tbody td.docs-cell { overflow: visible; }
/* Phase EC, in three parts, each proven live (headless Chrome measurement)
   before being layered on top of the last, not assumed from reading CSS alone:
   1. `position:relative;overflow:visible` (pre-existing) only affects the
      button itself -- the actual clip was happening on `.docs-cell` (its
      ANCESTOR, via `.data-table--dense tbody td{overflow:hidden}` above,
      correctly meant for text cells), which a child's own overflow:visible
      cannot undo. That's the override right above this rule.
   2. The button box then measured fine (18x18px, unclipped, on top of the
      stack) but its <svg> child measured 0x0 -- it has only a viewBox, no
      width/height attribute, and the only rule elsewhere in the app that
      sizes an icon svg (`.data-cell-actions .btn--icon svg{width:13px;...}`)
      doesn't match here (this button isn't inside `.data-cell-actions`).
   3. With an explicit svg size added, 1366px measured correctly (31x31
      button, 13x13 svg) but 390px still measured a 0-width svg: the button's
      shrink-to-fit width is negotiated against its containing block, which
      collapses small enough on a phone-width Docs column (6% of an already-
      narrow table) that the svg's `max-width:100%` resolves against ~0. A
      definite (non-shrink-to-fit) box size on the button removes that
      negotiation; overflow:visible (both here and on `.docs-cell` above)
      lets the now fixed-size button spill past its too-narrow column on tiny
      screens instead of collapsing. */
.docs-trigger {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  overflow: visible;
  flex-shrink: 0;
}
.docs-trigger svg { width: 13px; height: 13px; flex-shrink: 0; }
.docs-count {
  position: absolute;
  top: -2px;
  right: -4px;
  min-width: 15px;
  height: 15px;
  padding: 0 3px;
  border-radius: 999px;
  background: var(--primary);
  color: var(--t-inverse);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 9.5px;
  line-height: 15px;
  font-weight: 500;
  text-align: center;
  pointer-events: none;
}

/* Phase T: uploader attribution under a filename in the Documents list --
   the "who attached this" the brief asked to be visible without opening the
   Activity tab. */
.doc-meta { display: block; margin-top: 2px; font-size: 11.5px; color: var(--t-muted); }

/* Live-filtered list: subtle loading state + empty state, no layout jump. */
.list-loading { opacity: .5; pointer-events: none; transition: opacity .12s; }
.list-empty-row td { text-align: center; color: var(--t-light); padding: 40px 0; }

/* Phase F: document preview modal. jui ships .modal-demo/.modal-head/
   .modal-body/.modal-foot (a static card look) but "no open/close JS --
   bring your own trigger logic" per its own component docs -- this is that
   positioning/backdrop layer, same show/hide pattern as .panel-backdrop
   above, just centered instead of a right-hand slide-in. */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: rgba(15, 23, 42, .5);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  display: none;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.modal-overlay.is-open { display: flex; }
/* Phase FB: matches .panel-title's existing protection (same reasoning --
   a long station name must ellipsize, not shove the close X off the card). */
.modal-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.modal-head .btn--icon { flex-shrink: 0; }
/* Phase FB: document viewer, enlarged. A definite height (not just a
   max-height cap, unlike .modal-demo--record below) is deliberate here --
   the brief wants the PDF/image to fill the body and grow WITH the modal,
   which needs the modal's own box to have a stable, known size for a flexed
   child to fill via height:100%/flex:1, not one that shrinks to whatever
   the file list's natural content height happens to be. */
.modal-demo--lg {
  width: 90vw;
  max-width: 1400px;
  height: 90vh;
  max-height: 1000px;
  display: flex;
  flex-direction: column;
}
.modal-demo--lg .modal-body { flex: 1; overflow-y: auto; display: flex; flex-direction: column; min-height: 0; }
/* Empty/hidden by default; once a preview is open (JS adds .is-previewing to
   #docModalContent, see assets/ltp.js) this becomes the flex-grow item that
   claims the body's remaining height, and the file list below it shrinks to
   a small scrollable strip instead of continuing to claim its natural
   (potentially tall) height. */
[data-doc-preview-area]:not([hidden]) { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
#docModalContent.is-previewing [data-doc-list-table] { flex: 0 1 auto; max-height: 140px; overflow-y: auto; }

/* Phase S: while any centered modal is open the page behind it must not
   scroll -- otherwise a scroll that runs past the end of the modal's own
   body chains through to the list underneath. Set/cleared by ltpScrollLock
   in ltp.js, which also compensates for the reclaimed scrollbar width so
   nothing shifts sideways when a modal opens.

   This must sit on <html>: measured live, document.scrollingElement is
   <html> and body's clientHeight equals its full content height, so body is
   not the scroll container and `body{overflow:hidden}` alone left the page
   fully scrollable. Body is covered too, for the case where a future layout
   change makes body the scroller instead. */
html.has-modal-open,
html.has-modal-open body { overflow: hidden; }

/* Phase S, resized Phase CA: record-editor modal. Sized by content, per the
   brief -- .narrow stays small for the Role editor (a genuinely short,
   3-field form -- a 1000px frame around that reads as broken) and every
   confirm dialog (untouched, those don't use .modal-demo--record at all).
   Lease, Dealership AND User (Phase CA widened User out of .narrow -- it
   used to share the Role editor's small size, but the brief's acceptance
   criteria calls it out by name alongside Lease/Dealership) get the large
   size. Vertically centered by .modal-overlay's own align-items:center;
   height is capped so the BODY scrolls internally (see .panel-body above)
   rather than the modal growing past the viewport. */
.modal-demo--record {
  width: 100%;
  max-width: 960px;
  max-height: 85vh;
  display: flex;
  flex-direction: column;
}
.modal-demo--record.is-narrow { max-width: 520px; }

/* At phone width a centered box with side padding reads as cramped, so the
   modal goes effectively full-bleed with a small margin instead. */
@media (max-width: 480px) {
  .modal-overlay { padding: 10px; align-items: flex-start; }
  .modal-demo--record,
  .modal-demo--record.is-narrow {
    max-width: 100%;
    max-height: calc(100vh - 20px);
  }
  /* Phase FB: "full-screen at 390px" -- the desktop 90vw/90vh sizing above
     still reads as a framed modal even on a phone (a visible gap of
     backdrop all around); true full-screen needs the overlay's own padding
     gone too, not just a wider card. Targeted by id (#docModalOverlay is the
     only .modal-overlay that ever holds .modal-demo--lg, per FA3's inventory)
     rather than :has() so this doesn't depend on relatively newer CSS support. */
  #docModalOverlay { padding: 0; }
  .modal-demo--lg { width: 100vw; max-width: 100vw; height: 100vh; max-height: 100vh; }
  .panel-head { padding: 14px 16px; }
  .panel-body { padding: 16px 16px 0; }
  .modal-demo--record > .tabs { margin: 0 16px 0; overflow-x: auto; }
  .panel-foot { padding: 12px 16px; padding-bottom: max(12px, env(safe-area-inset-bottom)); flex-wrap: wrap; }
  .panel-foot .btn { flex: 1 1 auto; }
}

/* ---- Phase S: two app-wide .alert defects, both fixed on the component ----

   1. `hidden` did nothing. jui's `.alert{display:grid}` and the browser's UA
      `[hidden]{display:none}` have EQUAL specificity (0,1,0); authored CSS
      loads later, so it won. Every `<div class="alert danger" data-form-error
      hidden>` in the app therefore rendered as a permanently visible empty
      red box -- which is exactly the "empty red input" reported at the top of
      the Role edit form. It was never the `code` field (that input only
      renders for a NEW role). Raising this to (0,2,0) makes `hidden` work
      again everywhere, with no !important and no per-page override.
   2. Text wrapped one word per line. `.alert` is a 3-column grid
      (icon / text / close). An alert authored with only a `.body` and no
      `<span class="ico">` -- which is most of them -- had that lone child
      AUTO-PLACED into column 1, the 24px icon track, so a full sentence wrapped
      inside 24px. Placing each child in its column explicitly makes the text
      land in the 1fr track whether or not an icon is present. */
.alert[hidden] { display: none; }
.alert { grid-template-columns: auto minmax(0, 1fr) auto; }
.alert > .ico   { grid-column: 1; }
.alert > .body  { grid-column: 2; min-width: 0; }
.alert > .close { grid-column: 3; }
/* Phase FB: was a fixed 55vh regardless of the modal's own size -- a small
   box inside the now-much-larger modal shell. flex:1 on both (their parent,
   [data-doc-preview-area], is now the flex-grow item above) means each fills
   whatever height the enlarged modal actually has, growing and shrinking
   with it instead of a hardcoded viewport fraction. */
.doc-preview-frame { width: 100%; flex: 1 1 auto; min-height: 0; border: 1px solid var(--border); border-radius: 8px; margin-bottom: 16px; }
.doc-preview-img { max-width: 100%; flex: 1 1 auto; min-height: 0; object-fit: contain; display: block; margin: 0 auto 16px; border-radius: 8px; }

/* Phase M: District Access typable multi-select. jui ships the chip's
   visual style (a .badge with an inline remove button, see messaging.md's
   recipient-chips pattern) but no combobox/tag-input behavior -- the
   dropdown/filter/keyboard-nav shell below is this project's own, built to
   match jui's token system. */
.combo { position: relative; }
.combo-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.combo-chips:empty { margin-bottom: 0; }
.combo-chip-remove {
  background: none; border: 0; cursor: pointer; padding: 0 2px; line-height: 1;
  color: inherit; opacity: .7; font-size: 13px;
}
.combo-chip-remove:hover { opacity: 1; }
.combo-dropdown {
  position: absolute; left: 0; right: 0; top: 100%; margin-top: 4px; z-index: 40;
  background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: var(--shadow-lg); max-height: 220px; overflow-y: auto; padding: 4px;
}
.combo-option {
  padding: 8px 10px; border-radius: 6px; cursor: pointer; font-size: 13px; color: var(--t-base);
}
.combo-option:hover, .combo-option.is-active { background: var(--bg-hover); }
.combo-empty { padding: 8px 10px; font-size: 12.5px; color: var(--t-light); }

/* Phase DA: `.col-4` does not exist anywhere in the base jui stylesheet --
   confirmed by grepping the compiled style.css directly, which defines only
   `.col-6`/`.col-12` (the dash26 template's own pages just never happened to
   use a third-width section, so the rule was never generated). This was the
   ROOT CAUSE of the Notifications six-cards-in-one-row defect: a `<section
   class="col-4 card">` with no matching CSS rule gets no `grid-column` span
   at all, so CSS Grid auto-places it into a single 1-of-12 track -- six
   ~110px slivers with six empty tracks beside them. Added as a project
   extension on top of jui, same precedent as `.modal-demo--record`/
   `.panel-foot` elsewhere in this file. `.grid`'s own `gap` token (20px,
   16px at the existing card breakpoint) already spaces the three per row. */
.col-4 { grid-column: span 4; }
@media (max-width: 900px) { .col-4 { grid-column: span 6; } }
@media (max-width: 600px) { .col-4 { grid-column: span 12; } }

/* Notifications cards (Phase CB, sizing/overflow fixed Phase DA). CSS Grid
   items stretch to the row's tallest item by default (the container sets no
   align-items override), so "equal height within a row" falls out of the
   col-4 fix above for free -- .card is already a flex column, so its own
   height fills that stretched box.

   .card-head's base rule is `align-items:flex-end` (jui's own default,
   correct for its usual single-line-title layout elsewhere in this app) --
   with a two-line title+subtitle stack next to it, flex-end pulls the count
   badge down toward the subtitle instead of pinning it to the top beside
   the title. Overridden here, scoped to .notif-card only, not globally. */
.notif-card .card-head { align-items: flex-start; }
.notif-card .card-title {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.notif-card-subtitle {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; margin: 4px 0 0; color: var(--t-light);
  font-family: "JetBrains Mono", monospace; font-size: 10px; font-weight: 500;
  letter-spacing: .04em;
}
.notif-card .card-head .badge { flex: 0 0 auto; margin-left: 0; }

/* Vertical-only scroll, capped height so a 54-entry card never grows the
   page (Phase CB) -- overflow-x:hidden plus ellipsis/clamp on every text
   element below is what actually stops the sideways scrollbar Phase DA
   found (the col-4 fix alone gives each card real width, but a single
   over-long address could still force it without these). */
.notif-list { max-height: 340px; overflow-y: auto; overflow-x: hidden; padding-right: 2px; margin-top: 18px; }
/* Phase FC: was an <a href> to the owning module's list page; now a <button>
   that opens the record modal in place (data-open-panel-url), so the usual
   button UA defaults (background, full border, system font, no pointer
   cursor, no full-width) need resetting to keep the exact same look. */
.notif-entry {
  display: block; width: 100%; text-align: left; background: none; cursor: pointer;
  font: inherit; text-decoration: none; color: inherit; min-width: 0;
  padding: 10px 0; border: 0; border-bottom: 1px solid var(--border-soft);
}
.notif-entry:last-child { border-bottom: none; }
.notif-entry-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; min-width: 0; }
.notif-entry-name {
  font-weight: 600; font-size: 13px; color: var(--t-base); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.notif-entry-address {
  font-size: 12px; color: var(--t-muted); margin-top: 2px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.notif-entry-meta { font-size: 12px; color: var(--t-muted); margin-top: 2px; }

/* Phase EF: page-level tabs (Bulk Upload's Network Leases / Dealership).
   Deliberately separate attribute names from the record modal's own
   data-tab-trigger/data-tab-pane (assets/ltp.js): that handler always
   toggles panes inside the modal's #slideOverContent specifically, not
   whichever tab group was actually clicked, so reusing its attributes for
   an on-page tab group would silently try to operate on the (closed)
   modal instead of this page. */
/* jui's own `.grid{display:grid}` is an AUTHOR rule, which always beats the
   browser's UA-stylesheet `[hidden]{display:none}` regardless of specificity
   -- found live (a tab-pane still rendered fully visible, side by side with
   its sibling, despite `hidden` being correctly set/read as true in the
   DOM). Needs its own, more specific override wherever a `[hidden]`
   pane also carries `.grid` (or any class that sets `display`). */
[data-page-pane][hidden] { display: none; }
.page-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border-soft); margin-bottom: 20px; }
.page-tab {
  padding: 10px 4px; margin-right: 22px; font-size: 13px; font-weight: 600;
  color: var(--t-muted); text-decoration: none; border-bottom: 2px solid transparent;
  cursor: pointer; background: none; border-left: 0; border-right: 0; border-top: 0;
}
.page-tab.is-active { color: var(--primary); border-bottom-color: var(--primary); }
.page-tab .badge { margin-left: 6px; }

/* Dropzone: drag-and-drop CSV upload panel, one per Bulk Upload tab. Three
   states, exactly one visible at a time -- empty (drop/browse prompt),
   file (name + size + Remove), error (inline rejection message; no
   window.alert() per this phase's brief -- a rejected non-CSV file reads
   as a message in the panel itself, not a popup). */
/* Phase FD: `.dropzone` only ever had `text-align:center`, which centers
   inline TEXT but does nothing for the icon -- the global `svg{display:
   block}` reset (assets/jui/style.css) makes the icon a block-level box that
   `text-align` cannot affect at all, so it sat flush against the left edge
   while the text below it centered correctly (measured live: icon center
   339px against a 550px zone center, a real, confirmed 211px miss, not a
   guess). Now an explicit flex column on the zone itself, centering
   whichever state (`[data-dropzone-empty]`/`[data-dropzone-file]`) is
   currently visible on BOTH axes across the zone's full height (min-height
   keeps that height meaningful -- matches this zone's own previous natural
   rendered height, ~185px, so this isn't a new visual size, just now-correct
   centering within it). `.dropzone-icon` gets its own flex centering too,
   since it's a block div wrapping a block svg -- centering the OUTER flex
   item alone would only center the (auto-width, i.e. already-fluid) icon
   DIV's box, not the icon graphic sitting inside it. */
.dropzone {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 180px;
  border: 2px dashed var(--border);
  border-radius: 10px;
  padding: 28px 20px;
  text-align: center;
  cursor: pointer;
  transition: border-color .14s, background .14s;
}
.dropzone:hover { border-color: var(--primary); background: var(--bg-hover); }
.dropzone.is-dragover { border-color: var(--primary); background: var(--primary-soft); }
.dropzone.has-error { border-color: var(--danger); }
.dropzone [data-dropzone-empty] {
  display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%;
}
/* Same [hidden]-vs-authored-display conflict as .dropzone-file[hidden] below
   -- found by the same live test, one state later (with a file selected,
   the EMPTY state's own now-flex display was winning over its own [hidden]
   attribute this time). Higher specificity than the rule above so it wins
   regardless of source order. */
.dropzone [data-dropzone-empty][hidden] { display: none; }
.dropzone-icon { display: flex; justify-content: center; color: var(--t-light); margin-bottom: 10px; }
.dropzone-icon svg { width: 28px; height: 28px; fill: none; stroke: currentColor; stroke-width: 1.6; }
.dropzone-hint { font-size: 13px; color: var(--t-muted); }
.dropzone-hint .link { color: var(--primary); font-weight: 600; text-decoration: underline; }
.dropzone-hint-sub { font-size: 11.5px; color: var(--t-light); margin-top: 4px; }
.dropzone-file {
  display: flex; align-items: center; gap: 10px; text-align: left; width: 100%; max-width: 100%;
}
/* Phase FD, found while measuring the centering fix above: `display:flex` is
   an AUTHOR rule and beats the browser's UA-stylesheet `[hidden]{display:
   none}` regardless of specificity -- the exact same conflict already fixed
   once this session for `.grid`/`[data-page-pane]` (Phase EF). Without this,
   the "hidden" file-preview row still rendered (empty, so not visibly
   obvious) and occupied real height in the now-flex `.dropzone` column,
   which is what the vertical-centering measurement below caught: topGap=30
   vs bottomGap=52, a 22px gap that exactly matched this row's own stray
   offsetHeight. */
.dropzone-file[hidden] { display: none; }
.dropzone-file-icon { color: var(--primary); flex: 0 0 auto; }
.dropzone-file-icon svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.6; }
.dropzone-file-info { min-width: 0; flex: 1 1 auto; }
.dropzone-file-name {
  font-size: 13px; font-weight: 600; color: var(--t-base);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dropzone-file-size { font-size: 11.5px; color: var(--t-muted); margin-top: 2px; }
.dropzone-error-msg { font-size: 12.5px; color: var(--danger); margin-top: 12px; }

/* Phase FE: eye show/hide toggle, every type="password" field in the app.
   jui ships a LEADING-icon input pattern (.input-icon, padding-left) but
   nothing for a trailing one, so this is a minimal LTP extension on the
   same model -- tokens only, no hardcoded hex, matching the project's
   existing precedent for filling small gaps in the base stylesheet
   (.col-4, .dropzone, etc. all followed the same "checked first, extended
   minimally" pattern). */
.password-field { position: relative; }
.password-field .input { padding-right: 38px; }
.password-toggle {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  background: none; border: 0; padding: 6px; border-radius: 6px;
  color: var(--t-muted); cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.password-toggle:hover { background: var(--bg-hover); color: var(--t-base); }
.password-toggle svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; }

/* Generate-password affordance (User Management create/edit only -- "shown
   in plain text so it can be passed on" only makes sense when an admin is
   issuing a credential to someone else, not a self-service password change). */
.generate-password-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.generate-password-feedback { font-size: 12px; color: var(--success); }
.generate-password-feedback.is-error { color: var(--danger); }

/* ---- Lease module (Phase ND): inline Locators editor in the record modal.
   Built on jui's own .table/.input/.select/.alert/.badge -- this only sizes
   the cells so six inputs sit on one line inside the 960px modal, and
   restacks each row as a small labelled card at phone width so the table
   never needs sideways scrolling. Tokens only, no hex. */
.locator-block { margin-bottom: 8px; }
.locator-dup { margin-bottom: 10px; }
.locator-table { width: 100%; table-layout: fixed; }
/* Phase RD -- the overlap. `.locator-table td { padding: 6px }` never applied:
   jui's `.table tbody td { padding: 11px 0 }` (specificity 0,1,2) outranks it
   (0,1,1), so every cell had 0px side padding and the full-width controls sat
   edge to edge, reading as one overlapping strip (and the Remaining text ran
   into the End Date box). Now matched at 0,2,2 with `.table.locator-table`.
   Placed BEFORE the <=640px block below, whose same-specificity rules must
   still win on phones. Spacing values follow jui's own scale (jui has no
   --space-* tokens): 8px cell gutter / row padding, 18px section gap. */
.table.locator-table tbody td { padding: 8px 6px; vertical-align: middle; }
/* Column headers in jui's .field-label style (12px, 600, --t-base), not the
   mono uppercase data-table header -- this is a form, not a report. */
.table.locator-table thead th {
  padding: 0 6px 8px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: -.005em;
  text-transform: none;
  color: var(--t-base);
  white-space: nowrap;
}
/* Phase RD: explicit widths -- Name wide; Area / dates / Status narrow;
   remove button fixed. table-layout:fixed makes them binding whatever the
   content. Name | Area | Start | End | Remaining (read-only) | Status | x */
.locator-table th:nth-child(1) { width: 24%; }
.locator-table th:nth-child(2) { width: 11%; text-align: right; }
.locator-table th:nth-child(3),
.locator-table th:nth-child(4) { width: 17%; }
.locator-table th:nth-child(5) { width: 13%; }
.locator-table th:nth-child(6) { width: 14%; }
.locator-table th:nth-child(7) { width: 44px; }
.locator-table td.locator-remaining { font-size: 12px; line-height: 1.35; color: var(--t-base); }
/* One height for every control in the row (text, number, date and select
   were 33 / 33 / 35 / 29 px) so they share a baseline. */
.locator-table .input,
.locator-table .select {
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
  height: 34px;
  padding-top: 0;
  padding-bottom: 0;
  padding-left: 8px;
  font-size: 12px;
  line-height: 32px;
}
.locator-table .input { padding-right: 8px; }
.locator-table [data-locator-area] { text-align: right; }
.locator-table .list-empty-row td { color: var(--t-muted); text-align: center; padding: 16px 8px; }
.locator-remove-cell { text-align: center; }
.locator-remove-cell .btn--icon { vertical-align: middle; }
.locator-remove-cell .btn--icon:hover { color: var(--danger); }
.locator-actions { margin-top: 10px; }
.locator-actions .btn svg { width: 14px; height: 14px; }
.locator-total { margin: 12px 0 0; font-size: 12px; color: var(--t-muted); font-family: "JetBrains Mono", monospace; }
.locator-total.is-over { color: var(--warning); font-weight: 600; }
.locator-total.is-over::before { content: "\26A0\00A0"; }

@media (max-width: 640px) {
  .locator-table,
  .locator-table tbody,
  .locator-table tr,
  .locator-table td { display: block; width: 100%; }
  .locator-table thead { display: none; }
  .locator-table tr[data-locator-row] {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px 10px;
    padding: 10px 0;
    border-bottom: 1px solid var(--border-soft);
    position: relative;
  }
  .locator-table tr[data-locator-row] td { padding: 0; border: 0; }
  .locator-table tr[data-locator-row] td::before {
    content: attr(data-label);
    display: block;
    font-size: 10px;
    color: var(--t-light);
    margin-bottom: 2px;
  }
  .locator-table tr[data-locator-row] td:first-child { grid-column: 1 / -1; padding-right: 40px; }
  .locator-table td.locator-remove-cell { position: absolute; top: 10px; right: 0; width: auto; }
}

/* ---- Full-bleed list tables (Phase NE). The Network/Dealership/Lease/
   Station Master lists and the Roles matrix run their table edge-to-edge
   across the card by cancelling the card's padding with a negative margin.
   That margin used to be an inline `margin:0 -22px` -- right for jui's
   desktop .card padding (22px) but 4px too wide once jui drops .card to
   18px at <=720px, so the wrapper stuck out past the card on phones.
   One class, matched to the card's padding at each breakpoint. The table
   width is set here too: the Roles matrix table was width:100% of the
   wrapper PLUS its 22px bleed margins, so it always overflowed by 44px and
   scrolled sideways even at 1366px. */
.table-bleed { overflow-x: auto; margin: 0 -22px; }
.table-bleed > table { margin: 0 22px; width: calc(100% - 44px); }
@media (max-width: 720px) {
  .table-bleed { margin: 0 -18px; }
  .table-bleed > table { margin: 0 18px; width: calc(100% - 36px); }
}
/* A .table-bleed nested inside some other box (the Roles matrix sits inside
   its <form>, not directly in the card) would poke past THAT box's edges.
   The host re-creates the card's padding inside itself and cancels it with a
   matching negative margin -- visually identical, but the bleed now stays
   within the host's own padding box. */
.table-bleed-host { margin: 0 -22px; padding: 0 22px; }
@media (max-width: 720px) {
  .table-bleed-host { margin: 0 -18px; padding: 0 18px; }
}

/* ---- Phase OC: Locators allocation indicator ("Allocated 300 of 500 sqm ·
   200 remaining") above the Locators table -- warning tone at exactly zero
   remaining, danger once over. Tokens only. */
.locator-alloc { margin: 0 0 10px; }
.locator-alloc-line { margin: 0; font-size: 12px; font-weight: 600; color: var(--t-base); font-family: "JetBrains Mono", monospace; }
.locator-alloc-line.is-warning { color: var(--warning); }
.locator-alloc-line.is-danger { color: var(--danger); }
.locator-alloc-line.is-warning::before,
.locator-alloc-line.is-danger::before { content: "\26A0\00A0"; }
p.locator-alloc-note { margin: 0 0 4px; font-size: 12px; color: var(--t-muted); }
.alert.locator-alloc-note { margin-bottom: 8px; font-size: 12px; }
.locator-alloc-unset { margin: 4px 0 0; font-size: 11px; color: var(--t-light); }
/* The allocation rejection is several lines (what's requested, what's left,
   and one line per locator using the budget) -- keep its line breaks. */
[data-form-error] { white-space: pre-line; }

/* ---- Phase OD: Leasing list -- eye toggles + read-only locator detail rows.
   Closed eye while collapsed, open eye once expanded (both SVGs are in the
   button; the state class picks one). A station with no locators shows a
   muted, inert eye. Tokens only. */
.locator-eye { width: 22px; height: 22px; padding: 0; vertical-align: middle; color: var(--t-muted); }
.locator-eye svg { width: 15px; height: 15px; }
.locator-eye .eye-open { display: none; }
.locator-eye.is-open .eye-open { display: inline; }
.locator-eye.is-open .eye-closed { display: none; }
.locator-eye.is-open,
button.locator-eye:hover { color: var(--primary); }
.locator-eye.is-muted { display: inline-flex; align-items: center; justify-content: center; color: var(--t-light); opacity: .55; cursor: default; }
/* Phase PB: the eye lives in its own first column (44px, fixed) -- centred,
   tight padding so the 22px button fits without clipping at any width. */
.data-table th.locator-expander-head,
.data-table tbody td.locator-expander-cell { padding-left: 4px; padding-right: 4px; text-align: center; overflow: visible; }

.data-table tbody tr.locator-detail-row > td {
  background: var(--bg-muted);
  padding: 10px 14px 12px;
  white-space: normal;
  overflow: visible;
}
.locator-detail { display: grid; gap: 4px; font-size: 12px; min-width: 0; }
.locator-detail-head,
.locator-detail-item {
  display: grid;
  /* Name | Area | Start | End | Remaining (PC) | Status */
  grid-template-columns: minmax(0, 2.6fr) minmax(0, 1.1fr) minmax(0, 1.2fr) minmax(0, 1.2fr) minmax(0, 1.5fr) minmax(0, 1.3fr);
  gap: 10px;
  align-items: center;
}
.locator-detail-head { color: var(--t-light); font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em; padding-bottom: 2px; border-bottom: 1px solid var(--border-soft); }
.locator-detail-item > span { min-width: 0; overflow-wrap: anywhere; }
.locator-detail-name { font-weight: 600; color: var(--t-base); }

@media (max-width: 720px) {
  .locator-detail-head { display: none; }
  .locator-detail-item {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    padding: 6px 0;
    border-bottom: 1px solid var(--border-soft);
  }
  .locator-detail-item > span::before {
    content: attr(data-label);
    display: block;
    font-size: 10px;
    color: var(--t-light);
  }
  .locator-detail-item > .locator-detail-name { grid-column: 1 / -1; }
}

/* ---- Phase OE: Station Master "select all visible" header checkbox. jui's
   .check has no indeterminate look (some-but-not-all rows ticked) and, since
   its real input is visually hidden, no keyboard focus ring -- both added
   here on jui's own .box, tokens only. */
.check input:indeterminate + .box { background: var(--primary); border-color: var(--primary); }
.check input:indeterminate + .box::after { content: ""; width: 8px; height: 0; border-top: 2px solid var(--t-inverse); }
.check input:focus-visible + .box { box-shadow: 0 0 0 3px var(--primary-ring); }

/* ---- Phase PD: PDF-only document uploads. jui's .field-error sets
   display:flex, which beats the UA's [hidden] rule -- without this the empty
   inline error would still take up space. */
.field-error[hidden] { display: none; }
.input[type="file"].is-invalid { border-color: var(--danger); }

/* Phase QC: Start / End of a lease with no dates at all show a neutral
   "COMPANY OWNED" badge. The date columns are ~9% wide, narrower than the
   badge on one line, so it wraps to two ("COMPANY" / "OWNED") and never
   extends past its cell; at phone width it clips inside the cell like every
   other dense-table value (the cell's title carries the full text). */
.data-table--dense td.cell-company-owned { white-space: normal; }
.data-table--dense td.cell-company-owned .badge {
  max-width: 100%;
  white-space: normal;
  overflow: hidden;
  text-align: center;
  line-height: 1.2;
  font-size: 10px;
  letter-spacing: .02em;
  box-sizing: border-box;
  /* side padding scales down with the cell (percent of its width), so in a
     phone-width cell the badge still fits inside it instead of being cut */
  padding-left: min(8px, 10%);
  padding-right: min(8px, 10%);
}

/* Phase QE: clickable KPI cards and the card-list modal. A card with a zero
   count stays a plain, non-interactive card. */
.kpi-card--clickable { cursor: pointer; transition: box-shadow .15s ease, transform .15s ease; }
.kpi-card--clickable:hover { box-shadow: var(--shadow-lg); transform: translateY(-1px); }
.kpi-card--clickable:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.modal-demo--list {
  width: 920px;
  max-width: 100%;
  max-height: 80vh;
  display: flex;
  flex-direction: column;
}
.modal-demo--list .modal-body { flex: 1; min-height: 0; overflow-y: auto; }
.card-list-definition { color: var(--t-muted); font-size: 12px; margin: 0 0 10px; }
.card-list-table tbody tr[data-card-open] { cursor: pointer; }
.card-list-table tbody tr[data-card-open]:hover td { background: var(--bg-hover); }
.card-list-table tbody tr[data-card-open]:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.card-list-table td { white-space: nowrap; }
.card-list-table td.cell-name { white-space: normal; }
/* Phase QD: Leasing's "Leases are added in Network Development." in place of New Lease. */
.hero-hint { font-size: 12px; color: var(--t-muted); align-self: center; }
@media (max-width: 640px) {
  #cardModalOverlay { padding: 0; }
  .modal-demo--list { width: 100vw; max-height: 100vh; height: 100vh; border-radius: 0; }
  .card-list-scroll { overflow-x: auto; }
}
/* Right-aligned number column (Area (sqm)) -- keep it clear of the next, left-aligned column. */
.card-list-table td.cell-num,
.card-list-table th[style*="text-align:right"] { padding-right: 28px; }
/* Phase RB: read-only Contract Status line in the ND / Leasing record modal. */
.contract-status-line { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; min-width: 0; }
/* Phase RC: Bulk Upload check-all in a table head -- readable body text, not the mono column-label style. */
.create-all { white-space: nowrap; font-family: inherit; font-size: 12px; text-transform: none; letter-spacing: 0; color: var(--t-base); }

/* Phase RD: section headings in every record modal (#slideOverContent: Network,
   Leasing, Dealership, Station Master, Users, Roles) -- clear space beneath the
   heading, and a larger gap above one that follows content (e.g. Locators under
   the Holdover switch, which used to sit ~8px below it). jui's scale: 18px is its
   .form-grid row gap; 28px sets a new section apart from the last field. Hidden
   inputs before the first heading are <input>s, so they don't trigger the gap. */
#slideOverContent .section-h { margin-bottom: 18px; }
#slideOverContent :is(div, p, table, form, section) + .section-h { margin-top: 28px; }
