/* ============================================================================
   PropEdge Design System - COMPONENTS
   File: design/css/propedge-components.css
   LIVE COPY at PropEdge_UI/css/ — Mahenoor, 27 Sep 2026, with the Login
   .aspx (D29). Byte-for-byte the approved design file below this header;
   the live site cannot reference design/. Change the design file first,
   then re-copy — never edit this copy on its own, or the two drift.
   Re-copied 5 Oct (Ayaz): adds 4b SWITCH (contract 11).
   Load order:  bootstrap(.rtl).min.css  ->  propedge-tokens.css  ->  this file
   (signin.aspx loads NO Bootstrap: the approved Login preview never did.)

   HOW THIS SITS ON BOOTSTRAP 5.3
   ------------------------------
   Bootstrap keeps doing what it is good at: the grid, spacing utilities,
   display/flex utilities, and the JS behaviours (modal, offcanvas, toast,
   dropdown, collapse). This file supplies the *visual* component layer as
   `pe-`-prefixed classes so nothing here fights a Bootstrap selector.
   Stock Bootstrap components still look right because propedge-tokens.css
   re-points the --bs-* variables.

   RTL
   ---
   Everything uses CSS logical properties (padding-inline, margin-inline-start,
   border-inline-start, inset-inline-start, text-align: start). The layout
   therefore mirrors for `dir="rtl"` on its own - it is not a separate
   stylesheet with left/right swapped by hand. Only genuinely directional
   glyphs need flipping; see `.pe-icon--directional`.
   ========================================================================= */

/* ==========================================================================
   0. BASE
   ========================================================================== */
.pe-root,
body.pe-root {
  font-family: var(--pe-font-body);
  font-size: var(--pe-fs-base);
  line-height: var(--pe-lh-base);
  color: var(--pe-text);
  background: var(--pe-canvas);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Visible focus for keyboard users on every interactive element.
   :focus-visible only, so a mouse click does not leave a ring behind.
   The ring is 3px of --pe-brand-500 (4.90:1 on white, 4.52:1 on the
   #F4F6F8 canvas) plus a 2px white offset so it reads on dark fills too. */
.pe-root :focus-visible,
.pe-focus-ring {
  outline: 3px solid var(--pe-border-focus);
  outline-offset: 2px;
  border-radius: var(--pe-radius-xs);
}
.pe-root :focus:not(:focus-visible) { outline: none; }

.pe-sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ==========================================================================
   1. TYPOGRAPHY HELPERS
   ========================================================================== */
.pe-display { font-size: var(--pe-fs-display); line-height: var(--pe-lh-tight);
              font-weight: var(--pe-fw-bold);     letter-spacing: var(--pe-ls-tight);
              color: var(--pe-text-strong); margin: 0; }
.pe-h1 { font-size: var(--pe-fs-h1); line-height: var(--pe-lh-tight);
         font-weight: var(--pe-fw-bold); letter-spacing: var(--pe-ls-tight);
         color: var(--pe-text-strong); margin: 0; }
.pe-h2 { font-size: var(--pe-fs-h2); line-height: var(--pe-lh-heading);
         font-weight: var(--pe-fw-semibold); color: var(--pe-text-strong); margin: 0; }
.pe-h3 { font-size: var(--pe-fs-h3); line-height: var(--pe-lh-heading);
         font-weight: var(--pe-fw-semibold); color: var(--pe-text-strong); margin: 0; }
.pe-h4 { font-size: var(--pe-fs-h4); line-height: var(--pe-lh-snug);
         font-weight: var(--pe-fw-semibold); color: var(--pe-text-strong); margin: 0; }
.pe-lead    { font-size: var(--pe-fs-lg); line-height: var(--pe-lh-relaxed); color: var(--pe-text); margin: 0; }
.pe-body    { font-size: var(--pe-fs-base); line-height: var(--pe-lh-base); color: var(--pe-text); margin: 0; }
.pe-body-sm { font-size: var(--pe-fs-sm); line-height: var(--pe-lh-base); color: var(--pe-text); margin: 0; }
.pe-caption { font-size: var(--pe-fs-caption); line-height: var(--pe-lh-snug); color: var(--pe-text-muted); margin: 0; }
.pe-overline{ font-size: var(--pe-fs-overline); line-height: var(--pe-lh-snug);
              font-weight: var(--pe-fw-semibold); letter-spacing: var(--pe-ls-wide);
              text-transform: uppercase; color: var(--pe-text-muted); margin: 0; }
.pe-muted { color: var(--pe-text-muted); }

/* Arabic never gets uppercase or letter-spacing: it has no case, and
   letter-spacing breaks the cursive joins. */
[dir="rtl"] .pe-overline, [lang="ar"] .pe-overline { text-transform: none; letter-spacing: 0; }

/* ==========================================================================
   NUMERALS AND BIDIRECTIONAL TEXT
   Decision closed 15 Sep 2026: Western digits 0-9 in BOTH languages, because
   Omani users cross-check against cheques, bank statements and Civil ID
   cards, which all use Western digits.

   VERIFIED against the real IBM Plex Sans Arabic, not assumed
   (design/propedge-verification.html, 15 Sep 2026):
     - the Arabic face ships its OWN ASCII digits, metrically identical to
       IBM Plex Sans (advance 156.00 vs 156.00 at 26px; serif fallback
       would be 130.00, so it is not falling back)
     - ASCII digits under lang="ar" render IDENTICALLY to lang="en"
     - the font performs NO locale digit substitution, even with
       font-feature-settings:"locl" 1 forced on

   So there is nothing to "fix" in CSS, and nothing in CSS could fix it:
   a digit system is a property of the CHARACTERS, not of the rendering.
   The guarantee is that we emit U+0030-U+0039.

   The real risk is the FORMATTER. Intl.NumberFormat('ar-OM') returns
   Arabic-Indic (measured: numberingSystem resolves to "arab"). Every locale
   tag in PropEdge must carry -u-nu-latn. See tokens/typography.json.
   ========================================================================== */

/* Tabular figures everywhere a number can be compared down a column:
   ticket ids, OMR amounts, dates, counts. This is ALIGNMENT ONLY - it does
   not and cannot change which digit system is used. */
.pe-num, .pe-table td, .pe-table th { font-variant-numeric: tabular-nums; }
.pe-amount { font-variant-numeric: tabular-nums; font-weight: var(--pe-fw-semibold);
             white-space: nowrap; }

/* --------------------------------------------------------------------------
   Bidirectional isolation.

   A Western number inside RTL Arabic is where layout actually breaks.
   MEASURED painted order, read from getBoundingClientRect() and sorted by
   left edge:

     الهاتف: <span>+</span><span>968</span> <span>9123 4567</span>
       paints as   9123 4567 968 +          <-- the "968+" bug, for real
     الفترة: <span>01/09/2026</span> - <span>30/09/2026</span>
       paints as   30/09/2026 01/09/2026    <-- start and end date SWAP
     الزيارة: <span>09:00</span> - <span>17:00</span>
       paints as   17:00 09:00              <-- the visit window inverts
     المبلغ: <span>1,426.750</span> <span>OMR</span>
       paints as   OMR 1,426.750            <-- code jumps in front
     الفرق: <span>-</span><span>45.500</span>
       paints as   45.500 -                 <-- minus reads as a dash

   Each one is correct when the WHOLE run sits in ONE isolate:
     <bdi dir="ltr">+968 9123 4567</bdi>      -> +968 9123 4567
     <bdi dir="ltr">01/09/2026 - 30/09/2026</bdi> -> 01/09/2026 - 30/09/2026

   Rule: isolate the whole run, never split a number across elements, never
   leave a leading + or - outside the isolate.

   A single bare integer in an Arabic sentence needs NO isolation - the
   Unicode Bidi Algorithm already places it correctly. Measured:
   "الوحدة 12 في المبنى 774" paints 774 then 12, which is correct RTL.

   Prefer <bdi>: it carries unicode-bidi:isolate in the HTML spec itself, so
   a stray CSS rule cannot switch the isolation off, and it is self-
   documenting in the markup. dir="ltr" alone measured correct in Chromium
   only because the UA stylesheet supplies the isolation for us.
   -------------------------------------------------------------------------- */
.pe-ltr { direction: ltr; unicode-bidi: isolate; text-align: start; }
.pe-rtl { direction: rtl; unicode-bidi: isolate; text-align: start; }

/* --------------------------------------------------------------------------
   BRAND LOCKUP - and the rule that stops "EdgeProp". Added 18 Sep 2026.

   MEASURED 18 Sep, twenty cases under dir=rtl: the bidi algorithm gets plain
   text right EVERY time, including multi-word Latin phrases and brackets.
   The reversal is a CSS box-layout bug, not a text bug. The moment "Prop" and
   "Edge" become atomic boxes - display:inline-block, inline-flex, flex or
   grid - CSS lays them out in the CONTAINER's direction and the bidi
   algorithm never gets to correct them. Measured painted order in that case:
   "EdgeProp".

   Measured on the CURRENT live markup (UserControl/header.ascx): the letters
   are fine because .pe-brand-text wraps them in one inline box, but .pe-brand
   is display:flex, so the mark paints to the RIGHT of the word. Painted order
   "Prop | Edge | mark". With the isolate below it is "mark | Prop | Edge",
   and the lockup still sits at the RTL inline-start of the header.

   NEVER give .pe-brand__prop or .pe-brand__edge a display value.
   -------------------------------------------------------------------------- */
.pe-brand {
  direction: ltr; unicode-bidi: isolate;      /* the whole rule, in two words */
  display: inline-flex; align-items: center; gap: var(--pe-space-8);
  text-decoration: none;
}
.pe-brand__mark { flex: 0 0 auto; }            /* the mark never mirrors       */
.pe-brand__text { font-weight: var(--pe-fw-bold); line-height: 1;
                  letter-spacing: -0.01em; }
.pe-brand__prop { color: var(--pe-brand-900); }   /* 14.75:1 on white          */
.pe-brand__edge { color: var(--pe-brand-600); }   /*  6.91:1 on white          */
[dir="rtl"] .pe-brand__text { letter-spacing: 0; } /* never letter-space Arabic;
                                                      the wordmark is Latin, but
                                                      the reset keeps the rule
                                                      true everywhere.         */

/* Values that are ALWAYS Latin-directional regardless of UI language:
   phone numbers, Civil IDs, passport numbers, ticket references, amounts,
   date and time ranges. Applied to the cell or the <bdi>, not to a wrapper
   that also contains Arabic words. */
.pe-cell-id, .pe-phone, .pe-civilid, .pe-passport, .pe-daterange, .pe-timerange {
  direction: ltr; unicode-bidi: isolate; text-align: start;
  font-variant-numeric: tabular-nums;
}

/* Currency. OMR always shows exactly 3 decimals, padded, never trimmed -
   the rial has 1000 baisa, so 45.5 must render as 45.500. This pairs with
   the DECIMAL(12,3) rule in CLAUDE.md section 7.
     English : OMR 1,426.750   (code before the figure)
     Arabic  : 1,426.750 ر.ع.  (Arabic currency word stays in the RTL flow)
   The FIGURE is isolated; the Arabic currency word is not. */
.pe-currency { font-weight: var(--pe-fw-regular); color: var(--pe-text-muted);
               white-space: nowrap; }
.pe-money { display: inline-flex; align-items: baseline; gap: var(--pe-space-4); }

/* Numeric inputs type left-to-right in BOTH languages. A right-aligned
   amount field in an RTL form still fills from the left. */
.pe-input[type="tel"], .pe-input[inputmode="numeric"], .pe-input[inputmode="decimal"],
.pe-input.pe-input--num {
  direction: ltr; unicode-bidi: isolate; text-align: start;
  font-variant-numeric: tabular-nums;
}
[dir="rtl"] .pe-input[type="tel"],
[dir="rtl"] .pe-input[inputmode="numeric"],
[dir="rtl"] .pe-input[inputmode="decimal"],
[dir="rtl"] .pe-input.pe-input--num { text-align: right; }
/* ...except inside an LTR prefix group ("+968" then the number): there the digits
   sit against the prefix, as they do in English (Mahenoor, 5 Oct). */
[dir="rtl"] .pe-input-group[dir="ltr"] .pe-input { text-align: left; }

/* The digit-normalisation notice. NOT an error: the user did nothing wrong,
   their keyboard did. Polite, temporary, announced to screen readers via an
   aria-live="polite" region. Never sets aria-invalid, never blocks submit. */
.pe-normalised-note {
  display: flex; align-items: flex-start; gap: var(--pe-space-4);
  margin-block-start: var(--pe-space-4);
  font-size: var(--pe-fs-caption); color: var(--pe-info-fg);
}
.pe-normalised-note .pe-icon { flex: none; width: 14px; height: 14px;
                               margin-block-start: 2px; }
@media (prefers-reduced-motion: no-preference) {
  .pe-normalised-note { animation: pe-fade-in var(--pe-dur-base) var(--pe-ease); }
}
@keyframes pe-fade-in { from { opacity: 0 } to { opacity: 1 } }

/* Hijri date, shown read-only beside the Gregorian date in the Arabic UI.
   Never bold, never the only date on screen. */
.pe-date-hijri { font-size: var(--pe-fs-caption); color: var(--pe-text-muted); }

/* Fluid heads at the 375px reference width. */
@media (max-width: 767.98px) {
  .pe-display { font-size: 2rem; }
  .pe-h1 { font-size: 1.75rem; }
  .pe-h2 { font-size: 1.375rem; }
  .pe-h3 { font-size: 1.1875rem; }
}

/* ==========================================================================
   2. ICON
   ========================================================================== */
.pe-icon { width: var(--pe-icon-md); height: var(--pe-icon-md);
           display: inline-block; vertical-align: -0.2em; flex: 0 0 auto;
           fill: none; stroke: currentColor; }
.pe-icon--sm { width: var(--pe-icon-sm); height: var(--pe-icon-sm); }
.pe-icon--lg { width: var(--pe-icon-lg); height: var(--pe-icon-lg); }
.pe-icon--xl { width: 32px; height: 32px; }
/* Add to chevrons, arrows, back/forward - anything that points along the
   reading direction. Never add it to a wrench, a clock or a building. */
[dir="rtl"] .pe-icon--directional { transform: scaleX(-1); }

.pe-icon-tile { width: 48px; height: 48px; border-radius: var(--pe-radius-lg);
                display: inline-flex; align-items: center; justify-content: center;
                background: var(--pe-brand-50); color: var(--pe-brand-700); }

/* ==========================================================================
   3. BUTTON
   Variants: primary | secondary | tertiary | danger | ghost
   Sizes:    sm (36) | md (44, default) | lg (52)
   States:   :hover :active :focus-visible [disabled] .is-loading
   ========================================================================== */
.pe-btn {
  --_bg: var(--pe-brand-600);
  --_bg-hover: var(--pe-brand-700);
  --_fg: var(--pe-text-on-brand);
  --_bd: var(--pe-brand-600);
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--pe-space-8);
  min-height: var(--pe-control-h-md);
  padding-inline: var(--pe-space-20);
  padding-block: var(--pe-space-8);
  font-family: inherit;
  font-size: var(--pe-fs-base);
  font-weight: var(--pe-fw-semibold);
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  border: 1px solid var(--_bd);
  border-radius: var(--pe-radius-md);
  background: var(--_bg);
  color: var(--_fg);
  cursor: pointer;
  position: relative;
  transition: background var(--pe-dur-fast) var(--pe-ease),
              border-color var(--pe-dur-fast) var(--pe-ease),
              box-shadow var(--pe-dur-fast) var(--pe-ease),
              transform var(--pe-dur-fast) var(--pe-ease);
}
.pe-btn:hover  { background: var(--_bg-hover); border-color: var(--_bg-hover); color: var(--_fg); }
.pe-btn:active { transform: translateY(1px); }

.pe-btn--secondary {
  --_bg: var(--pe-surface); --_bg-hover: var(--pe-brand-50);
  --_fg: var(--pe-brand-700); --_bd: var(--pe-brand-600);
}
.pe-btn--secondary:hover { color: var(--pe-brand-700); border-color: var(--pe-brand-700); }

.pe-btn--tertiary {
  --_bg: var(--pe-surface); --_bg-hover: var(--pe-neutral-50);
  --_fg: var(--pe-text); --_bd: var(--pe-border-input);
}
.pe-btn--tertiary:hover { color: var(--pe-text-strong); border-color: var(--pe-neutral-500); }

.pe-btn--danger {
  --_bg: var(--pe-danger-solid); --_bg-hover: #8E1D17;
  --_fg: var(--pe-danger-on-solid); --_bd: var(--pe-danger-solid);
}

.pe-btn--ghost {
  --_bg: transparent; --_bg-hover: var(--pe-neutral-100);
  --_fg: var(--pe-brand-700); --_bd: transparent;
}
.pe-btn--ghost:hover { color: var(--pe-brand-700); }

.pe-btn--sm { min-height: var(--pe-control-h-sm); padding-inline: var(--pe-space-12);
              font-size: var(--pe-fs-sm); }
.pe-btn--lg { min-height: var(--pe-control-h-lg); padding-inline: var(--pe-space-32);
              font-size: var(--pe-fs-lg); }
.pe-btn--block { display: flex; width: 100%; }

/* Icon-only button keeps the 44px tap target even at --sm. */
.pe-btn--icon { padding-inline: 0; width: var(--pe-control-h-md);
                min-width: var(--pe-tap-min); min-height: var(--pe-tap-min); }

.pe-btn[disabled], .pe-btn.is-disabled, .pe-btn[aria-disabled="true"] {
  background: var(--pe-surface-disabled);
  border-color: var(--pe-neutral-200);
  color: var(--pe-text-disabled);
  cursor: not-allowed; transform: none; box-shadow: none;
}

/* Loading: the spinner replaces the label visually, but the label stays in
   the DOM for screen readers and the button gets aria-busy="true". */
.pe-btn.is-loading { pointer-events: none; }
.pe-btn.is-loading .pe-btn__label { visibility: hidden; }
.pe-btn.is-loading .pe-icon { visibility: hidden; }
.pe-btn.is-loading::after {
  content: ""; position: absolute; width: 18px; height: 18px;
  border: 2.5px solid currentColor; border-top-color: transparent;
  border-radius: 50%; animation: pe-spin .7s linear infinite;
}
@keyframes pe-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .pe-btn, .pe-btn::after, .pe-skeleton, .pe-toast {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .pe-btn.is-loading::after { border-top-color: currentColor; opacity: .5; }
}

/* ==========================================================================
   4. FORM FIELD
   ========================================================================== */
.pe-field { display: flex; flex-direction: column; gap: var(--pe-space-4);
            margin-block-end: var(--pe-space-20); }
.pe-label { font-size: var(--pe-fs-sm); font-weight: var(--pe-fw-semibold);
            color: var(--pe-text-strong); text-align: start; }
.pe-label .pe-req { color: var(--pe-danger-fg); margin-inline-start: 2px; }
.pe-label__optional { font-weight: var(--pe-fw-regular); color: var(--pe-text-muted); }

.pe-input, .pe-select, .pe-textarea {
  width: 100%;
  min-height: var(--pe-field-h);
  padding-inline: var(--pe-space-12);
  padding-block: var(--pe-space-8);
  font-family: inherit;
  font-size: var(--pe-fs-base);
  line-height: var(--pe-lh-snug);
  color: var(--pe-text-strong);
  background: var(--pe-surface);
  border: 1px solid var(--pe-border-input);
  border-radius: var(--pe-radius-md);
  text-align: start;
  transition: border-color var(--pe-dur-fast) var(--pe-ease),
              box-shadow var(--pe-dur-fast) var(--pe-ease);
}
.pe-input::placeholder, .pe-textarea::placeholder { color: var(--pe-text-placeholder); opacity: 1; }
.pe-input:hover, .pe-select:hover, .pe-textarea:hover { border-color: var(--pe-neutral-500); }
.pe-input:focus, .pe-select:focus, .pe-textarea:focus {
  border-color: var(--pe-brand-600); box-shadow: var(--pe-focus-ring); outline: none;
}
.pe-textarea { min-height: 120px; resize: vertical; line-height: var(--pe-lh-base); }

/* Native select: the caret is drawn with a background image so it mirrors
   correctly by swapping background-position for RTL. */
.pe-select {
  appearance: none; -webkit-appearance: none;
  padding-inline-end: var(--pe-space-40);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%233B4552' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5.6 9.2 6.4 6.4 6.4-6.4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-size: 18px 18px;
  background-position: right var(--pe-space-12) center;
}
[dir="rtl"] .pe-select { background-position: left var(--pe-space-12) center; }

/* Input with a leading icon (search, amount prefix ...) */
.pe-input-group { position: relative; display: flex; align-items: center; }
.pe-input-group .pe-input { padding-inline-start: var(--pe-space-40); }
.pe-input-group__icon { position: absolute; inset-inline-start: var(--pe-space-12);
                        color: var(--pe-text-muted); pointer-events: none; }
.pe-input-group__suffix { position: absolute; inset-inline-end: var(--pe-space-12);
                          color: var(--pe-text-muted); font-size: var(--pe-fs-sm); }

.pe-help { font-size: var(--pe-fs-caption); color: var(--pe-text-muted); text-align: start; }

/* --- validation error state (founders, 8 Oct 2026) ---
   The field is MARKED, never captioned: its message is shown once, in the
   form's error panel (.pe-errsum__box below), and reaches a screen reader
   through aria-describedby + aria-invalid. Never colour alone (WCAG 1.4.1):
   the border doubles in width and the label carries the panel's warning
   shape - an icon, not text. */
.pe-field.is-invalid .pe-input,
.pe-field.is-invalid .pe-select,
.pe-field.is-invalid .pe-textarea { border-color: var(--pe-danger-solid); border-width: 2px; }
.pe-field.is-invalid .pe-input:focus,
.pe-field.is-invalid .pe-select:focus,
.pe-field.is-invalid .pe-textarea:focus { box-shadow: 0 0 0 3px rgba(179, 38, 30, .30); }
.pe-field.is-invalid .pe-label,
fieldset.is-invalid > legend { color: var(--pe-danger-fg); }
.pe-field.is-invalid .pe-label::before,
fieldset.is-invalid > legend::before {
  content: ""; display: inline-block; inline-size: 1em; block-size: 1em;
  margin-inline-end: var(--pe-space-4); vertical-align: -0.125em; background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 4.5 2.8 20h18.4L12 4.5Z'/%3E%3Cpath d='M12 10v4.2'/%3E%3Cpath d='M12 17.2h.01'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 4.5 2.8 20h18.4L12 4.5Z'/%3E%3Cpath d='M12 10v4.2'/%3E%3Cpath d='M12 17.2h.01'/%3E%3C/svg%3E") center / contain no-repeat;
}
/* a group of option cards in error: every card's edge turns red */
fieldset.is-invalid .pe-choice-card { border-color: var(--pe-danger-solid); border-width: 2px; }
/* a single control marked on its own (a row of the unit generator, a photo picker) */
.pe-input[aria-invalid="true"],
.pe-select[aria-invalid="true"],
.pe-textarea[aria-invalid="true"] { border-color: var(--pe-danger-solid); border-width: 2px; }

.pe-field.is-valid .pe-input { border-color: var(--pe-success-solid); }

/* --- checkbox and radio ---
   The control is 22px but the clickable label block is at least 44px tall. */
.pe-choice { display: flex; align-items: flex-start; gap: var(--pe-space-12);
             min-height: var(--pe-tap-min); padding-block: var(--pe-space-8);
             cursor: pointer; }
.pe-choice input[type="checkbox"], .pe-choice input[type="radio"] {
  appearance: none; -webkit-appearance: none;
  width: 22px; height: 22px; flex: 0 0 auto; margin: 0; margin-block-start: 1px;
  border: 2px solid var(--pe-border-input); background: var(--pe-surface);
  cursor: pointer; transition: all var(--pe-dur-fast) var(--pe-ease);
}
.pe-choice input[type="checkbox"] { border-radius: var(--pe-radius-xs); }
.pe-choice input[type="radio"]    { border-radius: 50%; }
.pe-choice input:hover { border-color: var(--pe-brand-600); }
.pe-choice input:checked { background: var(--pe-brand-600); border-color: var(--pe-brand-600); }
.pe-choice input[type="checkbox"]:checked {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4.8 12.6 4.8 4.8 9.6-10.8'/%3E%3C/svg%3E");
  background-size: 15px 15px; background-position: center; background-repeat: no-repeat;
}
.pe-choice input[type="radio"]:checked { box-shadow: inset 0 0 0 4px var(--pe-surface); }
.pe-choice input:disabled { background: var(--pe-surface-disabled);
                            border-color: var(--pe-neutral-300); cursor: not-allowed; }
.pe-choice__text { font-size: var(--pe-fs-base); color: var(--pe-text); text-align: start; }
.pe-choice__hint { display: block; font-size: var(--pe-fs-caption); color: var(--pe-text-muted); }

/* Radio card - used for the single "unit-level / building-level" choice on
   the maintenance ticket form (one radio button, per the locked decision). */
.pe-choice-card { border: 1px solid var(--pe-border-input); border-radius: var(--pe-radius-md);
                  padding: var(--pe-space-12) var(--pe-space-16); background: var(--pe-surface); }
.pe-choice-card:has(input:checked) { border-color: var(--pe-brand-600); background: var(--pe-brand-50);
                                     box-shadow: inset 0 0 0 1px var(--pe-brand-600); }

/* ==========================================================================
   4b. SWITCH - every yes/no control in the product (founders 5 Oct;
       demo-contracts.md §11, Sameera's ruling). Ayaz, 5 Oct 2026.

   MARKUP: exactly Bootstrap 5.3's switch, so it keeps working unchanged if
   Bootstrap is ever loaded (delete this block on that day):
     <div class="form-check form-switch">
       <input class="form-check-input" type="checkbox" role="switch" id="chk_X">
       <label class="form-check-label" for="chk_X" data-i18n="lblX">X</label>
     </div>
   Optional, inside the same .form-check:
     <span class="form-text" id="sp_XHint">hint</span>   + aria-describedby on the input
     .pe-switch-state  "On"/"Off" words after the label (aria-hidden: the
                       switch role already announces on/off)
     .pe-switch--end   label first, switch at the inline end (settings rows)

   Bootstrap's CSS is NOT loaded (§11.1). Every selector is .form-check.form-switch
   + one class, specificity 0,3,0+, so it would also beat Bootstrap's 0,2,0.
   No aria-checked by hand: the native checked state is announced (§11.2.4).

   MEASURED (WCAG relative luminance, on #FFFFFF):
     off track border --pe-border-input #7F8A99    3.50:1  (1.4.11 needs 3:1)
     off knob        --pe-neutral-600 #515C6B      6.78:1
     on track        --pe-brand-600   #1B5E8E      6.91:1  (white knob on it 6.91:1)
     focus ring      --pe-border-focus #2A76AC     4.90:1
     label           --pe-text #3B4552             9.72:1
   ========================================================================== */
.form-check.form-switch {
  display: flex; align-items: center; flex-wrap: wrap;
  column-gap: var(--pe-space-12); row-gap: 0;
  min-height: var(--pe-tap-min);                /* the whole row is the target (§11.2.1) */
  margin: 0; padding: var(--pe-space-4) 0;     /* Bootstrap's padding-left:2.5em is NOT wanted */
  text-align: start;
}
.form-check.form-switch .form-check-input {
  appearance: none; -webkit-appearance: none;
  flex: 0 0 auto; float: none; margin: 0;       /* Bootstrap floats it and pulls it -2.5em */
  width: 2.75rem; height: 1.5rem;               /* 44 x 24 */
  border: 2px solid var(--pe-border-input);
  border-radius: var(--pe-radius-pill);
  background-color: var(--pe-surface);
  /* The knob is a background image: an <input> takes no pseudo-element. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='4' fill='%23515C6B'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-size: 1rem 1rem;
  background-position: left 0.125rem center;   /* off = inline START (LTR: left) */
  cursor: pointer;
  transition: background-position var(--pe-dur-base) var(--pe-ease),
              background-color var(--pe-dur-fast) var(--pe-ease),
              border-color var(--pe-dur-fast) var(--pe-ease);
  print-color-adjust: exact; -webkit-print-color-adjust: exact;
}
[dir="rtl"] .form-check.form-switch .form-check-input { background-position: right 0.125rem center; }

.form-check.form-switch .form-check-input:hover { border-color: var(--pe-brand-600); }
.form-check.form-switch .form-check-input:focus { outline: none; box-shadow: none; }
.form-check.form-switch .form-check-input:focus-visible {
  outline: 3px solid var(--pe-border-focus); outline-offset: 2px;
  border-radius: var(--pe-radius-pill);         /* .pe-root :focus-visible would square it */
}
/* ON: brand fill AND the knob at the inline END - two signals (§11.2.3). */
.form-check.form-switch .form-check-input:checked {
  background-color: var(--pe-brand-600); border-color: var(--pe-brand-600);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='4' fill='%23FFFFFF'/%3E%3C/svg%3E");
  background-position: right 0.125rem center;
}
[dir="rtl"] .form-check.form-switch .form-check-input:checked { background-position: left 0.125rem center; }
.form-check.form-switch .form-check-input:checked:hover { background-color: var(--pe-brand-700); border-color: var(--pe-brand-700); }

.form-check.form-switch .form-check-input:disabled {
  cursor: not-allowed; opacity: 1;
  background-color: var(--pe-surface-disabled); border-color: var(--pe-neutral-300);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='4' fill='%23B6BFCB'/%3E%3C/svg%3E");
}
.form-check.form-switch .form-check-input:disabled:checked { background-color: var(--pe-brand-200); border-color: var(--pe-brand-200); }
.form-check.form-switch .form-check-input:disabled ~ .form-check-label { color: var(--pe-text-disabled); cursor: not-allowed; }

.form-check.form-switch .form-check-label {
  flex: 1 1 0; min-width: 0;
  font-size: var(--pe-fs-base); line-height: var(--pe-lh-snug); color: var(--pe-text);
  cursor: pointer; padding-block: var(--pe-space-8);   /* label height counts toward the 44px row */
}
.form-check.form-switch .form-text {
  flex: 1 0 100%; margin: calc(-1 * var(--pe-space-8)) 0 var(--pe-space-4);
  padding-inline-start: calc(2.75rem + var(--pe-space-12));   /* aligns under the label */
  font-size: var(--pe-fs-caption); color: var(--pe-text-muted);     /* 6.78:1 */
}
/* "On" / "Off" words. Visual only (aria-hidden); the switch role announces state. */
.form-check.form-switch .pe-switch-state {
  flex: 0 0 auto; font-size: var(--pe-fs-sm); font-weight: var(--pe-fw-semibold);
  color: var(--pe-text-muted);                                    /* 6.78:1 */
}
.form-check.form-switch .form-check-input:checked ~ .pe-switch-state { color: var(--pe-brand-700); }  /* 9.31:1 */
.form-check.form-switch .form-check-input:checked ~ .pe-switch-state .pe-switch-state__off,
.form-check.form-switch .form-check-input:not(:checked) ~ .pe-switch-state .pe-switch-state__on { display: none; }

/* Label first, switch at the end: a settings-style row. Visual order only;
   the DOM (and so the reading order) stays input-then-label. */
.form-check.form-switch.pe-switch--end { flex-direction: row-reverse; }
.form-check.form-switch.pe-switch--end .form-text { order: 5; padding-inline-start: 0; }

/* Forced colours / Windows high contrast (§11.2.5): the track keeps a
   visible border and the ON state stays distinguishable without colour
   (the knob position) because the system colours replace ours. */
@media (forced-colors: active) {
  .form-check.form-switch .form-check-input { border: 2px solid CanvasText; forced-color-adjust: none;
    background-color: Canvas;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='4' fill='%23808080'/%3E%3C/svg%3E"); }
  .form-check.form-switch .form-check-input:checked { background-color: Highlight; border-color: Highlight; }
  .form-check.form-switch .form-check-input:focus-visible { outline-color: Highlight; }
}
@media (prefers-reduced-motion: reduce) {
  .form-check.form-switch .form-check-input { transition: none; }
}

/* ==========================================================================
   5. CARD
   ========================================================================== */
.pe-card { background: var(--pe-surface); border: 1px solid var(--pe-border);
           border-radius: var(--pe-radius-lg); box-shadow: var(--pe-shadow-sm);
           overflow: hidden; }
.pe-card__header { display: flex; align-items: center; justify-content: space-between;
                   gap: var(--pe-space-12);
                   padding: var(--pe-space-16) var(--pe-space-20);
                   border-block-end: 1px solid var(--pe-border); }
.pe-card__body   { padding: var(--pe-space-20); }
.pe-card__footer { padding: var(--pe-space-16) var(--pe-space-20);
                   border-block-start: 1px solid var(--pe-border);
                   background: var(--pe-surface-sunken);
                   display: flex; gap: var(--pe-space-12); justify-content: flex-end; }
.pe-card--interactive { cursor: pointer; transition: box-shadow var(--pe-dur-base) var(--pe-ease),
                                                     transform var(--pe-dur-base) var(--pe-ease); }
.pe-card--interactive:hover { box-shadow: var(--pe-shadow-lg); transform: translateY(-2px); }

/* Stat card for the dashboard. The value is the largest thing in it. */
.pe-stat { display: flex; align-items: center; gap: var(--pe-space-16);
           padding: var(--pe-space-20); }
.pe-stat__value { font-size: var(--pe-fs-h2); font-weight: var(--pe-fw-bold);
                  color: var(--pe-text-strong); line-height: 1.1;
                  font-variant-numeric: tabular-nums; }
.pe-stat__label { font-size: var(--pe-fs-sm); color: var(--pe-text-muted); }

/* ==========================================================================
   6. BADGE / STATUS PILL / PRIORITY PILL
   Colour is a reinforcement, never the message. Every pill has an icon with
   a distinct silhouette and a visible text label.
   ========================================================================== */
.pe-badge {
  display: inline-flex; align-items: center; gap: var(--pe-space-4);
  padding: 3px var(--pe-space-8) 3px var(--pe-space-8);
  font-size: var(--pe-fs-caption); font-weight: var(--pe-fw-semibold);
  line-height: 1.4; white-space: nowrap;
  border: 1px solid transparent; border-radius: var(--pe-radius-pill);
  background: var(--pe-neutral-100); color: var(--pe-neutral-700);
}
.pe-badge .pe-icon { width: 14px; height: 14px; stroke-width: 2.1; }
.pe-badge--count { min-width: 22px; justify-content: center;
                   background: var(--pe-danger-solid); color: #fff; }

/* Status and priority pills are `.pe-badge` PLUS a modifier:
   <span class="pe-badge pe-status--overdue"> ... </span>                 */
.pe-status--new       { background: var(--pe-status-new-bg);       color: var(--pe-status-new-fg);       border-color: var(--pe-status-new-border); }
.pe-status--assigned  { background: var(--pe-status-assigned-bg);  color: var(--pe-status-assigned-fg);  border-color: var(--pe-status-assigned-border); }
.pe-status--progress  { background: var(--pe-status-progress-bg);  color: var(--pe-status-progress-fg);  border-color: var(--pe-status-progress-border); }
.pe-status--hold      { background: var(--pe-status-hold-bg);      color: var(--pe-status-hold-fg);      border-color: var(--pe-status-hold-border); }
.pe-status--resolved  { background: var(--pe-status-resolved-bg);  color: var(--pe-status-resolved-fg);  border-color: var(--pe-status-resolved-border); }
.pe-status--closed    { background: var(--pe-status-closed-bg);    color: var(--pe-status-closed-fg);    border-color: var(--pe-status-closed-border); }
.pe-status--cancelled { background: var(--pe-status-cancelled-bg); color: var(--pe-status-cancelled-fg); border-color: var(--pe-status-cancelled-border); border-style: dashed; }
.pe-status--reopened  { background: var(--pe-status-reopened-bg);  color: var(--pe-status-reopened-fg);  border-color: var(--pe-status-reopened-border); }
.pe-status--overdue   { background: var(--pe-status-overdue-bg);   color: var(--pe-status-overdue-fg);   border-color: var(--pe-status-overdue-border); border-width: 1.5px; }

/* SLA-BREACHED is deliberately NOT a pill. PROPOSED, revised 18 Sep 2026.
   Urgent priority owns the solid #8E1D17 pill (approved D6), so a second
   solid red pill would be indistinguishable from it. Measured: the darkest
   same-hue red still readable as red separates from the Urgent fill by only
   1.66:1. The fix is form, not hue - square corners, a white body, a 2px
   rule, a solid cap, and it ALWAYS carries the overrun figure. A priority
   pill never contains a number, and that is the signal that survives
   greyscale and every form of colour blindness.
   See docs/design-open-questions.md Q3a.                                   */
/* Look-and-feel #7 (Ayaz's audit 14 Oct, Kashif's list; "badge overload"): most late rows
   carried a solid red Urgent pill AND a solid red-capped "N days late" box. Solid red is now
   for Urgent ONLY. "Late" is red TEXT with its icon: no box, no solid cap. Still told apart
   from Urgent by form (no pill, no fill) and by its overrun figure; same colour token
   (#8E1D17, 8.98:1 on white). Markup and script unchanged
   (Home, the bell, the desk and the ticket page all build the same cap + body). */
.pe-sla-chip {
  display: inline-flex; align-items: center; gap: var(--pe-space-4);
  border: 0; background: transparent;
  color: var(--pe-status-breached-fg);          /* 8.98:1 on white */
  font-size: var(--pe-fs-caption); font-weight: var(--pe-fw-semibold);
  line-height: 1.4; white-space: nowrap;
}
.pe-sla-chip__cap {                              /* the icon; mirrors under [dir=rtl] */
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto; color: inherit;
}
.pe-sla-chip__cap .pe-icon { width: 16px; height: 16px; stroke-width: 2.1; }
.pe-sla-chip__body { display: inline-flex; align-items: center; gap: var(--pe-space-4); padding: 0; }
/* The overrun figure: the SIGNED NUMBER is its own <bdi dir="ltr"> ("+11"), and the unit
   ("h" / "س") follows in the page direction (BUG-G5-7, 6 Oct). Forcing the whole figure LTR
   put "س" on the wrong side in Arabic. typography.json > bidi. */
.pe-sla-chip__figure { font-variant-numeric: tabular-nums; unicode-bidi: isolate; }
/* A figure still written as plain text ("+11 h", the landing mock-up) keeps the old whole-run
   LTR, so it never reads "11+". Only the figures that isolate their own number are freed. */
.pe-sla-chip__figure:not(:has(bdi)) { direction: ltr; }

/* Kept for the design-system comparison row only. Overdue stays a pill; it
   is the lesser flag and must not compete with the breached chip.          */
.pe-status--breached  { background: var(--pe-status-breached-bg);  color: var(--pe-status-breached-fg);  border-color: var(--pe-status-breached-border); border-width: 2px; border-radius: var(--pe-radius-xs); }

.pe-priority--low   { background: var(--pe-priority-low-bg);    color: var(--pe-priority-low-fg);    border-color: var(--pe-neutral-300); }
.pe-priority--medium  { background: var(--pe-priority-medium-bg); color: var(--pe-priority-medium-fg); border-color: var(--pe-info-border); }
.pe-priority--high    { background: var(--pe-priority-high-bg);   color: var(--pe-priority-high-fg);   border-color: var(--pe-warning-border); }
.pe-priority--urgent  { background: var(--pe-priority-urgent-bg); color: var(--pe-priority-urgent-fg); border-color: var(--pe-priority-urgent-bg); }

/* ==========================================================================
   7. DATA TABLE + PAGINATION
   ========================================================================== */
.pe-table-wrap { border: 1px solid var(--pe-border); border-radius: var(--pe-radius-lg);
                 background: var(--pe-surface); overflow: hidden; }
.pe-table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.pe-table { width: 100%; border-collapse: collapse; font-size: var(--pe-fs-sm); }
.pe-table caption { text-align: start; padding: var(--pe-space-16) var(--pe-space-16) 0;
                    font-size: var(--pe-fs-caption); color: var(--pe-text-muted); }
.pe-table thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--pe-surface-sunken);
  padding: var(--pe-space-12) var(--pe-space-16);
  font-size: var(--pe-fs-caption); font-weight: var(--pe-fw-semibold);
  color: var(--pe-text-muted); text-align: start; white-space: nowrap;
  border-block-end: 1px solid var(--pe-border);
}
.pe-table tbody td { padding: var(--pe-space-12) var(--pe-space-16);
                     border-block-end: 1px solid var(--pe-border);
                     color: var(--pe-text); vertical-align: middle; text-align: start; }
.pe-table tbody tr:last-child td { border-block-end: 0; }
.pe-table tbody tr:hover { background: var(--pe-brand-50); }
.pe-table tbody tr.is-selected { background: var(--pe-brand-50);
                                 box-shadow: inset 3px 0 0 0 var(--pe-brand-600); }
[dir="rtl"] .pe-table tbody tr.is-selected { box-shadow: inset -3px 0 0 0 var(--pe-brand-600); }
/* An overdue row is flagged on the row itself as well as in the pill, so it
   is findable while scanning a long list. */
.pe-table tbody tr.is-overdue { background: var(--pe-danger-bg); }
.pe-table tbody tr.is-overdue:hover { background: #FBE2DF; }
.pe-table .pe-cell-id { font-family: var(--pe-font-mono); font-size: var(--pe-fs-caption);
                        color: var(--pe-text-muted); white-space: nowrap; }
.pe-table .pe-cell-actions { text-align: end; white-space: nowrap; }
.pe-table th[aria-sort] { cursor: pointer; }
.pe-table th[aria-sort="ascending"]  .pe-sort-ind::after { content: "\2191"; }
.pe-table th[aria-sort="descending"] .pe-sort-ind::after { content: "\2193"; }

.pe-table-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--pe-space-12);
                    padding: var(--pe-space-16); border-block-end: 1px solid var(--pe-border); }
.pe-table-toolbar .pe-input-group { flex: 1 1 240px; }

.pe-pagination { display: flex; flex-wrap: wrap; align-items: center;
                 justify-content: space-between; gap: var(--pe-space-12);
                 padding: var(--pe-space-12) var(--pe-space-16);
                 border-block-start: 1px solid var(--pe-border);
                 background: var(--pe-surface-sunken); }
.pe-pagination__info { font-size: var(--pe-fs-caption); color: var(--pe-text-muted); }
.pe-pagination__pages { display: flex; flex-wrap: wrap; align-items: center; gap: var(--pe-space-4); }   /* BUG-PG-1: wraps, never pushes the page sideways */
.pe-page {
  min-width: var(--pe-tap-min); min-height: var(--pe-tap-min);
  display: inline-flex; align-items: center; justify-content: center;
  padding-inline: var(--pe-space-8);
  font-size: var(--pe-fs-sm); font-weight: var(--pe-fw-medium);
  color: var(--pe-text); background: var(--pe-surface);
  border: 1px solid var(--pe-border); border-radius: var(--pe-radius-sm);
  text-decoration: none; cursor: pointer;
}
.pe-page:hover { background: var(--pe-brand-50); color: var(--pe-brand-700); }
.pe-page[aria-current="page"] { background: var(--pe-brand-600); border-color: var(--pe-brand-600);
                                color: var(--pe-text-on-brand); }
.pe-page[aria-disabled="true"], .pe-page[disabled] {
  color: var(--pe-text-disabled); background: var(--pe-surface-disabled);
  cursor: not-allowed; pointer-events: none;
}

/* Below md the table becomes a stack of cards. Each cell carries its own
   label via data-label, so nothing is lost off-screen. */
@media (max-width: 767.98px) {
  .pe-table--stack thead { position: absolute; width: 1px; height: 1px;
                           overflow: hidden; clip: rect(0,0,0,0); }
  .pe-table--stack tbody tr { display: block; border: 1px solid var(--pe-border);
                              border-radius: var(--pe-radius-md);
                              margin: var(--pe-space-12); padding: var(--pe-space-8); }
  .pe-table--stack tbody td { display: flex; justify-content: space-between;
                              gap: var(--pe-space-12); border: 0;
                              padding: var(--pe-space-8); }
  .pe-table--stack tbody td::before { content: attr(data-label);
                                      font-size: var(--pe-fs-caption);
                                      font-weight: var(--pe-fw-semibold);
                                      color: var(--pe-text-muted); }
  .pe-table--stack .pe-cell-actions { justify-content: flex-end; }
  /* BUG-TENPAY-1 (Rahil-2, d3a7f60; Mahenoor-2): as a table, one long unbroken value (a cheque number) set the
     width of EVERY card, wider than the phone, and the box clipped them ("OMR 13"). As blocks the cards keep the
     box's width; a value too long for its line moves below its label, and long text breaks. Money never breaks:
     the whole figure moves down instead. */
  .pe-table--stack, .pe-table--stack tbody { display: block; inline-size: 100%; }
  .pe-table--stack tbody td { flex-wrap: wrap; overflow-wrap: anywhere; }
  .pe-table--stack tbody td .pe-money { white-space: nowrap; overflow-wrap: normal; }
}

/* ==========================================================================
   8. MODAL
   ========================================================================== */
.pe-modal-backdrop { position: fixed; inset: 0; background: rgba(12, 42, 65, .55);
                     z-index: var(--pe-z-modal); display: flex; align-items: center;
                     justify-content: center; padding: var(--pe-space-16); }
.pe-modal { background: var(--pe-surface); border-radius: var(--pe-radius-xl);
            box-shadow: var(--pe-shadow-xl); width: 100%; max-width: 520px;
            max-height: calc(100vh - var(--pe-space-48));
            display: flex; flex-direction: column; overflow: hidden; }
.pe-modal--lg { max-width: 760px; }
.pe-modal__header { display: flex; align-items: flex-start; gap: var(--pe-space-12);
                    padding: var(--pe-space-20) var(--pe-space-24);
                    border-block-end: 1px solid var(--pe-border); }
.pe-modal__title { flex: 1; font-size: var(--pe-fs-h3); font-weight: var(--pe-fw-semibold);
                   color: var(--pe-text-strong); margin: 0; text-align: start; }
.pe-modal__close { flex: 0 0 auto; }
.pe-modal__body { padding: var(--pe-space-24); overflow-y: auto; text-align: start; }
.pe-modal__footer { display: flex; gap: var(--pe-space-12); justify-content: flex-end;
                    padding: var(--pe-space-16) var(--pe-space-24);
                    border-block-start: 1px solid var(--pe-border);
                    background: var(--pe-surface-sunken); }
.pe-modal__icon { width: 44px; height: 44px; border-radius: 50%;
                  display: flex; align-items: center; justify-content: center; flex: 0 0 auto; }
.pe-modal__icon--danger { background: var(--pe-danger-bg); color: var(--pe-danger-fg); }
.pe-modal__icon--warning{ background: var(--pe-warning-bg); color: var(--pe-warning-fg); }

/* On phones a modal becomes a bottom sheet: thumb-reachable buttons. */
@media (max-width: 575.98px) {
  .pe-modal-backdrop { align-items: flex-end; padding: 0; }
  .pe-modal { max-width: none; border-radius: var(--pe-radius-xl) var(--pe-radius-xl) 0 0;
              max-height: 92vh; }
  .pe-modal__footer { flex-direction: column-reverse; }
  .pe-modal__footer .pe-btn { width: 100%; }
}

/* ==========================================================================
   9. TOAST
   Container is the ARIA live region; individual toasts are inserted into it.
   ========================================================================== */
.pe-toast-region { position: fixed; z-index: var(--pe-z-toast);
                   inset-block-start: var(--pe-space-16);
                   inset-inline-end: var(--pe-space-16);
                   display: flex; flex-direction: column; gap: var(--pe-space-12);
                   width: min(380px, calc(100vw - var(--pe-space-32))); }
.pe-toast { display: flex; align-items: flex-start; gap: var(--pe-space-12);
            padding: var(--pe-space-12) var(--pe-space-16);
            background: var(--pe-surface); border: 1px solid var(--pe-border);
            border-radius: var(--pe-radius-md); box-shadow: var(--pe-shadow-lg);
            border-inline-start: 4px solid var(--pe-neutral-400);
            animation: pe-toast-in var(--pe-dur-slow) var(--pe-ease); text-align: start; }
@keyframes pe-toast-in { from { opacity: 0; transform: translateY(-8px); } }
.pe-toast__body { flex: 1; min-width: 0; }
.pe-toast__title { font-size: var(--pe-fs-sm); font-weight: var(--pe-fw-semibold);
                   color: var(--pe-text-strong); }
.pe-toast__text { font-size: var(--pe-fs-caption); color: var(--pe-text-muted); }
.pe-toast--success { border-inline-start-color: var(--pe-success-solid); }
.pe-toast--success .pe-toast__icon { color: var(--pe-success-fg); }
.pe-toast--warning { border-inline-start-color: var(--pe-warning-solid); }
.pe-toast--warning .pe-toast__icon { color: var(--pe-warning-fg); }
.pe-toast--danger  { border-inline-start-color: var(--pe-danger-solid); }
.pe-toast--danger  .pe-toast__icon { color: var(--pe-danger-fg); }
.pe-toast--info    { border-inline-start-color: var(--pe-brand-600); }
.pe-toast--info    .pe-toast__icon { color: var(--pe-info-fg); }

@media (max-width: 575.98px) {
  .pe-toast-region { inset-inline: var(--pe-space-16); width: auto; }
}
/* Founders' observation 1 (26 Oct, D87): every form's success or failure is also shown as a toast
   (PropEdge_CommonFunctionShowToast). The message text is the body copy, not the muted caption. */
.pe-toast__icon { flex: 0 0 auto; inline-size: 20px; block-size: 20px; margin-block-start: 1px; }
.pe-toast .pe-toast__message { font-size: var(--pe-fs-sm); color: var(--pe-text-strong); overflow-wrap: anywhere; }
.pe-toast__close { flex: 0 0 auto; border: 0; background: transparent; color: var(--pe-text-muted); cursor: pointer;
                   inline-size: 28px; block-size: 28px; margin: -4px; border-radius: var(--pe-radius-sm); line-height: 1; font-size: 20px; }
.pe-toast__close:hover { color: var(--pe-text-strong); background: var(--pe-neutral-100, rgba(0,0,0,.05)); }
.pe-toast__close:focus-visible { outline: none; box-shadow: var(--pe-focus-ring); }

/* ==========================================================================
   10. WIZARD STEP INDICATOR
   Current step is marked with aria-current="step"; completed steps carry a
   check icon so progress is not conveyed by colour alone.
   ========================================================================== */
.pe-steps { display: flex; align-items: flex-start; gap: 0; list-style: none;
            margin: 0; padding: 0; counter-reset: pe-step; }
.pe-step { flex: 1; display: flex; flex-direction: column; align-items: center;
           gap: var(--pe-space-8); position: relative; text-align: center;
           padding-inline: var(--pe-space-4); min-width: 0; }
.pe-step::before { content: ""; position: absolute; inset-block-start: 17px;
                   inset-inline-start: -50%; width: 100%; height: 2px;
                   background: var(--pe-neutral-200); }
.pe-step:first-child::before { display: none; }
.pe-step__marker { width: 36px; height: 36px; border-radius: 50%; z-index: 1;
                   display: flex; align-items: center; justify-content: center;
                   font-size: var(--pe-fs-sm); font-weight: var(--pe-fw-bold);
                   background: var(--pe-surface); color: var(--pe-text-muted);
                   border: 2px solid var(--pe-neutral-300); }
.pe-step__label { font-size: var(--pe-fs-caption); color: var(--pe-text-muted);
                  font-weight: var(--pe-fw-medium); }
.pe-step.is-complete .pe-step__marker { background: var(--pe-success-solid);
                                        border-color: var(--pe-success-solid); color: #fff; }
.pe-step.is-complete::before { background: var(--pe-success-solid); }
.pe-step.is-complete .pe-step__label { color: var(--pe-text); }
.pe-step.is-current .pe-step__marker { background: var(--pe-brand-600);
                                       border-color: var(--pe-brand-600); color: #fff;
                                       box-shadow: 0 0 0 4px var(--pe-brand-100); }
.pe-step.is-current .pe-step__label { color: var(--pe-brand-700); font-weight: var(--pe-fw-semibold); }

/* At 375px the horizontal rail cannot hold five labels. Collapse it to a
   "Step 2 of 5" line plus a progress bar. */
.pe-steps--compact { display: none; }
@media (max-width: 575.98px) {
  .pe-steps { display: none; }
  .pe-steps--compact { display: block; }
  .pe-steps-progress { height: 8px; border-radius: var(--pe-radius-pill);
                       background: var(--pe-neutral-200); overflow: hidden; }
  .pe-steps-progress__fill { height: 100%; background: var(--pe-brand-600);
                             border-radius: var(--pe-radius-pill); }
}

/* ==========================================================================
   11. EMPTY STATE
   ========================================================================== */
.pe-empty { display: flex; flex-direction: column; align-items: center;
            text-align: center; gap: var(--pe-space-12);
            padding: var(--pe-space-48) var(--pe-space-24); }
.pe-empty__art { width: 72px; height: 72px; border-radius: 50%;
                 display: flex; align-items: center; justify-content: center;
                 background: var(--pe-brand-50); color: var(--pe-brand-600); }
.pe-empty__title { font-size: var(--pe-fs-h4); font-weight: var(--pe-fw-semibold);
                   color: var(--pe-text-strong); margin: 0; }
.pe-empty__text { font-size: var(--pe-fs-sm); color: var(--pe-text-muted);
                  max-width: 42ch; margin: 0; }

/* ==========================================================================
   12. LOADING SKELETON
   The container carries aria-busy="true" and a visually hidden "Loading"
   message, so a screen reader is told something is happening.
   ========================================================================== */
.pe-skeleton { display: block; position: relative; overflow: hidden; border-radius: var(--pe-radius-sm);
               background: var(--pe-neutral-100); }
.pe-skeleton::after { content: ""; position: absolute; inset: 0;
                      transform: translateX(-100%);
                      background: linear-gradient(90deg, transparent,
                                  rgba(255,255,255,.75), transparent);
                      animation: pe-shimmer 1.4s infinite; }
[dir="rtl"] .pe-skeleton::after { animation-name: pe-shimmer-rtl; }
@keyframes pe-shimmer     { 100% { transform: translateX(100%); } }
@keyframes pe-shimmer-rtl { 100% { transform: translateX(-200%); } }
.pe-skeleton--text  { height: 12px; margin-block: 6px; }
.pe-skeleton--title { height: 20px; width: 45%; margin-block: 8px; }
.pe-skeleton--pill  { height: 22px; width: 88px; border-radius: var(--pe-radius-pill); }
.pe-skeleton--avatar{ height: 40px; width: 40px; border-radius: 50%; }
.pe-skeleton--block { height: 120px; border-radius: var(--pe-radius-md); }
.pe-skeleton--w40 { width: 40%; } .pe-skeleton--w60 { width: 60%; }
.pe-skeleton--w80 { width: 80%; }
/* Founders, 27 Oct: every page shows these while its data loads, as Home does (PropEdge_CommonFunctionSkeletonStart). */
.pe-skeleton--field { height: 44px; border-radius: var(--pe-radius-md); margin-block-start: var(--pe-space-8); }
.pe-skeleton-card { padding: var(--pe-space-16); }
.pe-skeleton-grid { display: grid; gap: var(--pe-space-12); grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); padding: var(--pe-space-12) 0; }
.pe-skeleton-page { display: flex; flex-direction: column; gap: var(--pe-space-16); }
.pe-skeleton-page .pe-skeleton-grid { padding: 0; }
tr[data-pe-skeleton] > td { padding-block: var(--pe-space-12); }
.pe-table--stack tr[data-pe-skeleton] > td::before { content: none; }
/* a page heading the record fills in is a placeholder bar until it arrives */
.pe-app__content .pe-page-head h1:empty::before { content: ""; display: block; inline-size: min(320px, 60%); block-size: 28px;
  border-radius: var(--pe-radius-sm); background: var(--pe-neutral-100); }
/* a form still loading its record (aria-busy="true"): its own labels and fields are the placeholders, so nothing moves when it arrives */
.pe-app__content form[aria-busy="true"] :is(.pe-label, .pe-legend, .pe-help, .pe-input, .pe-select, .pe-textarea, .pe-choice, .pe-choice-card) {
  color: transparent !important; border-color: transparent !important; box-shadow: none !important; pointer-events: none;
  background: linear-gradient(90deg, var(--pe-neutral-100) 0%, var(--pe-neutral-50, #f6f7f9) 50%, var(--pe-neutral-100) 100%) 0 0 / 200% 100% !important;
  animation: pe-skeleton-sweep 1.4s linear infinite; border-radius: var(--pe-radius-sm); }
.pe-app__content form[aria-busy="true"] :is(.pe-label, .pe-legend, .pe-help) { inline-size: fit-content; max-inline-size: 100%; }
.pe-app__content form[aria-busy="true"] :is(.pe-label, .pe-legend, .pe-help, .pe-choice, .pe-choice-card) * { visibility: hidden; }
.pe-app__content form[aria-busy="true"] :is(.pe-input, .pe-select, .pe-textarea)::placeholder { color: transparent; }
/* the setup checklist while its progress loads: the same placeholder look, no step marked current yet */
.pe-frsteps[aria-busy="true"] :is(.pe-step-row__n, .pe-step-row__title, .pe-step-row__text, .pe-step-row__time, .pe-step-row__act) {
  color: transparent !important; border-color: transparent !important; box-shadow: none !important; pointer-events: none;
  background: linear-gradient(90deg, var(--pe-neutral-100) 0%, var(--pe-neutral-50, #f6f7f9) 50%, var(--pe-neutral-100) 100%) 0 0 / 200% 100% !important;
  animation: pe-skeleton-sweep 1.4s linear infinite; border-radius: var(--pe-radius-sm); }
.pe-frsteps[aria-busy="true"] :is(.pe-step-row__title, .pe-step-row__text, .pe-step-row__time) { inline-size: fit-content; max-inline-size: 100%; }
.pe-frsteps[aria-busy="true"] :is(.pe-step-row__n, .pe-step-row__title, .pe-step-row__text, .pe-step-row__time, .pe-step-row__act) * { visibility: hidden; }
.pe-frsteps[aria-busy="true"] .pe-step-row { background: transparent !important; box-shadow: none !important; }
@keyframes pe-skeleton-sweep { from { background-position: 100% 0; } to { background-position: -100% 0; } }
@media (prefers-reduced-motion: reduce) { .pe-app__content form[aria-busy="true"] *, .pe-frsteps[aria-busy="true"] * { animation: none !important; } }

/* Founders, 27 Oct: the toast is the old PropEdge layout's SweetAlert2 toast; it sits above the app header and drawers */
.swal2-container.pe-swal-toast { z-index: var(--pe-z-toast); }
.pe-swal-toast .swal2-popup.swal2-toast { font-family: inherit; max-inline-size: min(420px, calc(100vw - var(--pe-space-32))); }
.pe-swal-toast .swal2-title { overflow-wrap: anywhere; }
.pe-swal-toast .pe-toast__list { margin: 0 1em .5em; }

/* ==========================================================================
   13. ALERT (inline, page-level)
   ========================================================================== */
.pe-alert { display: flex; gap: var(--pe-space-12); align-items: flex-start;
            padding: var(--pe-space-12) var(--pe-space-16);
            border: 1px solid var(--pe-border); border-radius: var(--pe-radius-md);
            font-size: var(--pe-fs-sm); text-align: start; }
.pe-alert--info    { background: var(--pe-info-bg);    border-color: var(--pe-info-border);    color: var(--pe-info-fg); }
.pe-alert--success { background: var(--pe-success-bg); border-color: var(--pe-success-border); color: var(--pe-success-fg); }
.pe-alert--warning { background: var(--pe-warning-bg); border-color: var(--pe-warning-border); color: var(--pe-warning-fg); }
.pe-alert--danger  { background: var(--pe-danger-bg);  border-color: var(--pe-danger-border);  color: var(--pe-danger-fg); }

/* The ONE error panel of a form (founders, 8 Oct 2026; common.js
   PropEdge_CommonFunctionRenderErrorSummary). No heading: the messages ARE
   the content. One message is a single line; two or more are a list. Each is
   a link that moves focus to its field. */
.pe-alert.pe-errsum__box { align-items: flex-start; }
/* the panel takes focus on submit: never let the sticky app header cover it */
[role="alert"][tabindex="-1"] { scroll-margin-block-start: 88px; }
.pe-alert.pe-errsum__box > .pe-icon { flex: none; margin-block-start: 2px; }
.pe-alert .pe-errsum__list { margin: 0; padding-inline-start: var(--pe-space-20); }
.pe-alert .pe-errsum__list li + li { margin-block-start: var(--pe-space-4); }
.pe-alert .pe-errsum__list li,
.pe-alert .pe-errsum__one { text-wrap: pretty; }
.pe-alert .pe-errsum__one { margin: 0; }
.pe-alert.pe-errsum__box a { color: var(--pe-danger-fg); font-weight: var(--pe-fw-semibold);
                             text-decoration: underline; text-underline-offset: 2px; }
.pe-alert.pe-errsum__box a:focus-visible { outline: 3px solid var(--pe-border-focus); outline-offset: 2px; border-radius: 2px; }

/* --------------------------------------------------------------------------
   Directional icon helpers.
   .pe-icon--directional  forward chevrons/arrows: mirrors in RTL.
   .pe-icon--back         back/previous: already mirrored in LTR, so it
                          mirrors BACK to normal in RTL. Use this instead of
                          an inline transform, which would beat the RTL rule.
   -------------------------------------------------------------------------- */
.pe-icon--back { transform: scaleX(-1); }
[dir="rtl"] .pe-icon--back { transform: scaleX(1); }

/* ==========================================================================
   14. FILE UPLOADER - THE drop zone for every file input (founders, 11 Oct
   2026). Built by common.js PropEdge_CommonFunctionFileUploader; markup:
     div.pe-upl                      the drop target (dashed edge)
       svg.pe-upl__icon
       span.pe-upl__prompt           "Drag and drop photos here, or"
       span.pe-upl__actions
         label.pe-upl__choose        for the real input: the native picker
         button.pe-upl__camera       phones and tablets only (capture=)
       input.pe-upl__input           sr-only; the keyboard control
   States: .is-dragover (files over it), .is-disabled (limit reached), and
   the focus ring round the whole zone while its input has keyboard focus.
   Never colour alone: drag-over also turns the edge solid and changes the
   words. MEASURED on #FAFBFC: prompt #515C6B 6.55:1; edge #7F8A99 3.38:1
   (1.4.11); Choose #154A72 8.99:1. On #EFF6FB (drag-over): text #3B4552
   8.91:1, edge #2A76AC 4.49:1, icon #1B5E8E 6.33:1.
   Logical properties only, so it mirrors in RTL with no extra rule.
   ========================================================================== */
.pe-upl {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--pe-space-8);
  min-height: 132px; padding: var(--pe-space-16);
  text-align: center;
  border: 2px dashed var(--pe-border-input); border-radius: var(--pe-radius-md);
  background: var(--pe-neutral-25); color: var(--pe-text-muted);
  font-size: var(--pe-fs-sm);
  cursor: pointer;
  transition: background var(--pe-dur-fast) var(--pe-ease), border-color var(--pe-dur-fast) var(--pe-ease);
}
.pe-upl:hover { border-color: var(--pe-border-focus); }
.pe-upl__icon { width: 32px; height: 32px; color: var(--pe-neutral-600); }
.pe-upl__prompt { max-inline-size: 36ch; text-wrap: balance; }
.pe-upl__actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--pe-space-8); }
.pe-upl__actions .pe-btn { cursor: pointer; }
/* the input is the zone's keyboard control: its focus ring is drawn round the zone */
.pe-upl:has(.pe-upl__input:focus-visible) { outline: 3px solid var(--pe-border-focus); outline-offset: 2px; }
.pe-upl.is-dragover {
  border-style: solid; border-color: var(--pe-border-focus);
  background: var(--pe-brand-50); color: var(--pe-text);
}
.pe-upl.is-dragover .pe-upl__icon { color: var(--pe-brand-600); }
.pe-upl.is-dragover .pe-upl__prompt { font-weight: var(--pe-fw-semibold); }
.pe-upl.is-disabled { cursor: not-allowed; border-color: var(--pe-neutral-200); }
.pe-upl.is-disabled .pe-upl__choose {
  background: var(--pe-surface-disabled); border-color: var(--pe-neutral-200);
  color: var(--pe-text-disabled); cursor: not-allowed; pointer-events: none;
}
/* a finger needs the full 44px target, even on the small buttons */
@media (pointer: coarse) {
  .pe-upl__actions .pe-btn { min-height: var(--pe-tap-min); padding-inline: var(--pe-space-16); }
}
@media (prefers-reduced-motion: reduce) {
  .pe-upl { transition-duration: .001ms; }
}

/* Look-and-feel #9 (14 Oct): on a touch screen a switch's row (its label is the tap target)
   is at least 44 px tall: 22 px line + 2 x 11 px. Mouse layouts keep 8 px. */
@media (pointer: coarse) {
  .form-check.form-switch .form-check-label { padding-block: 11px; }
}
/* A toast listing several field messages (founders, 26 Oct): a short title, then one line each. */
.pe-toast .pe-toast__title { margin: 0 0 var(--pe-space-4); }
.pe-toast__list { margin: 0; padding-inline-start: var(--pe-space-16); font-size: var(--pe-fs-sm); color: var(--pe-text-strong); }
.pe-toast__list li { overflow-wrap: anywhere; }
.pe-toast .pe-toast__message { margin: 0; }
