/* ==========================================================================
   artifact-tokens.css — § U-3a. THE ONLY FILE IN ART.AI.FACT THAT MAY NAME
   A COLOUR.
   --------------------------------------------------------------------------
   Jeremy asked for LIGHT *AND* DARK with a real toggle. That answer is what
   makes this file the foundation rather than a nicety: a hardcoded colour
   anywhere else cannot follow a theme, so the toggle would ship a page nobody
   can read in one of its two states. The rule is enforced by a test, not by
   good intentions — `application/tests/test_no_hardcoded_colour.py`.

   ⛔ SERVED FROM THE ARTIFACT TREE, NEVER THE SHARED ROOT.
   Deploy to  /var/www/artifact/live/css/artifact-tokens.css
   Link as    /css/artifact-tokens.css
   `/var/www/.htaccess` rewrites artifact.mtc.llc/<x> -> /artifact/live/<x> but
   EXCLUDES ^/assets/, so /assets/css/* is one set of files shared with
   constdisb, blendora, basecamp, titlco AND unite. A rebuild CONSUMES the
   shared bundle; it never edits it. (Tripwire: md5 of
   /var/www/assets/css/central.css must stay 075ff0b2…)

   ⭐⭐ THE THEME ATTRIBUTE IS `data-bs-theme`, AND THAT IS DELIBERATE.
   `unite.bundle.css` ALREADY ships both themes — 63 `[data-bs-theme=dark]`
   blocks and 8 `[data-bs-theme=light]` — as a full `--bs-*` custom-property
   system. Inventing a bespoke `[data-theme]` would mean maintaining a second
   palette that silently drifts from the rest of the lineup. So we adopt the
   bundle's own switch and inherit light/dark for free.
       <html data-bs-theme="dark">   <html data-bs-theme="light">

   ⭐ EVERY TOKEN ALIASES `--bs-*` WITH A MEASURED LITERAL AS ITS FALLBACK.
   The alias means Artifact follows the lineup automatically when the bundle is
   updated; the fallback means the page still renders if the bundle fails to
   load. Every literal below was READ OUT OF THE BUNDLE or off the running
   page — none was chosen by eye.

   ⛔ DO NOT ADD A COLOUR TO A VIEW. Add a token here and use it.
   ========================================================================== */

/* ---------- LIGHT (the default, matching Bootstrap's own convention) ------- */
/* ⛔⛔ EVERY VALUE HERE IS A LITERAL, AND THAT IS NOT AN OVERSIGHT.
   These started as `var(--bs-…, fallback)` aliases so Artifact would follow the
   lineup automatically. That is CORRECT while you only CONSUME the bundle — and
   it becomes a CIRCULAR REFERENCE the moment you also override `--bs-*` in the
   bridge below (`--art-accent: var(--bs-primary)` + `--bs-primary:
   var(--art-accent)`). CSS resolves a custom-property cycle to the
   guaranteed-invalid value, i.e. NOTHING: measured live on the sandbox,
   `--art-accent` and `--bs-primary` both computed to "" and the purple never
   appeared. **A token layer that also overrides must OWN its values and map
   outward ONE WAY.** Every literal below was read out of `unite.bundle.css` or
   off the running Unite page — none was picked by eye. */
:root {
  /* -- surfaces ----------------------------------------------------------- */
  /* ⭐⭐ THE DEPTH MODEL IS THREE LAYERS, AND GETTING IT WRONG IS WHAT MADE THE
     CARDS LOOK NOTHING LIKE UNITE (§ U-SURFACES, 2026-08-12).
         page (body)  ->  FIELD (the recess the content sits in)  ->  card
     Unite dark: #1E1E2D -> #151521 -> #1E1E2D. The card returns to the BODY
     colour and is lifted off the recess by a 1px border, not by being lighter.
     ⛔ This file previously had `--art-surface: #151521` labelled "the card
     surface" — that is Unite's FIELD colour, one layer off. */
  --art-page:            #F9F9F9;
  --art-field:           #F9F9F9;   /* the recess behind the cards            */
  --art-surface:         #FFFFFF;
  --art-surface-2:       #F1F1F4;
  --art-border:          #DBDFE9;
  --art-border-card:     #F1F1F4;   /* a card edge is softer than a data rule */
  --art-card-shadow:     0 3px 4px rgba(0, 0, 0, .03);   /* Unite is nearly flat */
  /* ⛔⛔ A FIELD IS A WELL, NOT A PANE OF THE SURFACE (corrected 2026-08-12).
     First attempt pointed inputs at --art-surface, i.e. the card/modal colour.
     Every field then vanished into its container and the Search-the-Plant modal
     read as one flat dark slab — Jeremy: *"still far too dark, its not right at
     all."* Unite's forms use Metronic's SOLID variant: a fill DISTINCT from the
     surface, and NO border. Measured inside a real Unite modal, both themes. */
  --art-input-bg:        #F9F9F9;   /* Unite `form-control-solid`, light      */
  --art-input-border:    #F9F9F9;   /* solid fields are borderless           */
  --art-input-text:      #4B5675;   /* typed text sits a shade under headings */
  --art-control-edge:    #78829D;   /* 4.0:1 on #FFF — see the checkbox note  */
  --art-text-secondary:  #4B5675;   /* what `.text-muted` resolves to — 7.5:1 */

  /* -- text --------------------------------------------------------------- */
  --art-text:            #071437;
  --art-text-muted:      #78829D;
  --art-text-subtle:     #99A1B7;
  /* ⚠️ COLUMN HEADINGS GET THEIR OWN TOKEN. Using --art-text-muted put them at
     2.95:1 on the dark card — BELOW the 3.0 WCAG AA floor for secondary text.
     Measured on the live page with a contrast calculation, not judged by eye;
     the dark screenshot looked merely 'dim' and the number is what proved it. */
  /* ⛔ COLUMN HEADINGS ARE TEXT, NOT FURNITURE, AND #78829D MEASURED 3.83:1 ON
     A WHITE CARD — below the 4.5 floor for the 10.5px uppercase labels that
     name every column in this product. Re-measured on the live page:
     #6B7590 4.59 · #5B6478 5.93 · #4B5675 7.27. Taking 5.93 so a heading is
     still visibly lighter than body text without being unreadable. */
  --art-text-heading:    #5B6478;

  /* -- accent ------------------------------------------------------------- */
  /* ⚠️ --art-accent-rgb IS OURS ON PURPOSE. The shared bundle declares
     `--bs-primary-rgb: 238,221,255` (= #EEDDFF) against a `--bs-primary` of
     #774a99 (= 119,74,153). Anything doing rgba(var(--bs-primary-rgb), …)
     renders LAVENDER, not purple. The bundle is shared with five other
     products, so we correct it HERE rather than edit it there. */
  --art-accent:          #774A99;
  --art-accent-rgb:      119, 74, 153;
  --art-accent-contrast: #ffffff;
  --art-on-warn:         #1B1C22;   /* dark text on an amber fill */
  --art-accent-soft:     rgba(var(--art-accent-rgb), .10);
  --art-accent-hover:    rgba(var(--art-accent-rgb), .16);

  /* the Unite signature: a 3px hairline across the top of a card */
  --art-gradient:        linear-gradient(90deg, #8A5FC2 0%, #C86DD7 50%, #FF7EB3 100%);

  /* -- status: SEMANTIC NAMES, because the domain is what varies ----------- */
  --art-ok:              var(--bs-success, #0ACF97);   /* passed · Sent        */
  --art-warn:            var(--bs-warning, #FFBC00);   /* held · QA Review     */
  --art-danger:          var(--bs-danger,  #F8285A);   /* error · failed       */
  /* ⛔ --art-info DOES NOT ALIAS --bs-info, AND THAT IS THE POINT. Bootstrap's
     info IS the cyan this rebuild retired, so aliasing it put the retired
     accent back on the first KPI tile and every .text-info icon — measured, not
     assumed: a leak scan found the info tile's top border rendering the old
     cyan. Teal keeps "information" semantically distinct from both the purple
     accent and the colour we removed. The other three DO alias Bootstrap
     (success/warning/danger), because none of those was the old accent. */
  --art-info:            #17A2B8;                      /* processing           */
  --art-neutral:         var(--bs-gray-500, #99A1B7);  /* not started · n/a    */
  --art-ok-soft:         rgba(10, 207, 151, .12);
  --art-warn-soft:       rgba(255, 188,  0, .12);
  --art-danger-soft:     rgba(248,  40, 90, .12);
  --art-info-soft:       rgba(23, 162, 184, .12);

  /* -- status INK: the same meaning, written ON its own soft fill ---------- */
  /* ⛔⛔ THE STATUS COLOURS ABOVE ARE TUNED FOR A DARK GROUND AND FAIL WCAG AA
     ON THEIR OWN LIGHT FILL. Measured on the live page 2026-08-13, light theme,
     compositing each `.12` fill over the real card behind it:
         info    #17A2B8 on --art-info-soft    = 2.69:1   FAIL
         neutral #99A1B7                       = 3.40:1   FAIL
         danger  #F8285A                       = 3.81:1   FAIL
         ok      #0ACF97                       = 4.12:1   FAIL
     A chip is 11px bold, which WCAG counts as NORMAL text, so the floor is
     4.5:1 — these are the states a human is meant to ACT on, and four of six
     were below it while the screen looked perfectly fine.
     ⚠️ IT WAS ONLY VISIBLE BECAUSE THE INSTRUMENT WAS FIXED FIRST. The first
     measurement discarded the fill's ALPHA, so every soft fill read as the same
     solid colour as its own ink and scored a flat 1.00:1 — an impossible number
     that would have been dismissed as a broken check rather than a real one.
     ⇒ darker inks for LIGHT only. Same hue, same meaning, legible. Re-measured
     after: ok 6.62 · info 5.52 · danger 5.95 · neutral 6.45. */
  --art-ok-ink:          #066347;
  --art-info-ink:        #0F6A79;
  --art-danger-ink:      #B00E39;
  --art-neutral-ink:     var(--art-text-secondary);
  --art-accent-ink:      var(--art-accent);   /* 5.64:1 already — unchanged */

  /* -- shape -------------------------------------------------------------- */
  --art-radius-sm:       6px;      /* buttons, inputs, chips                  */
  --art-radius-md:       12px;
  /* ⛔⛔ 19.5px WAS MEASURED OFF THE WRONG PAGE — corrected 2026-08-12 (§ U-SURFACES).
     The comment used to read "measured off the running page", and it was: off
     ARTIFACT's running page, i.e. the value we were supposed to be REPLACING.
     Unite's card radius is 8.125px. **Measuring the thing you are changing and
     calling it the target is how a re-skin silently preserves the old design.** */
  --art-radius-lg:       8px;      /* cards — measured off UNITE (8.125px)    */
  --art-shadow-sm:       0 1px 2px rgba(9, 20, 55, .06);
  --art-shadow-md:       0 6px 18px rgba(9, 20, 55, .10);
  --art-shadow-lift:     0 10px 28px rgba(9, 20, 55, .14);

  /* -- spacing scale ------------------------------------------------------ */
  --art-space-1:         4px;
  --art-space-2:         8px;
  --art-space-3:         12px;
  --art-space-4:         16px;
  --art-space-5:         24px;
  --art-space-6:         32px;

  /* -- type --------------------------------------------------------------- */
  --art-font:            Inter, -apple-system, "Segoe UI", Roboto, sans-serif;
  --art-fs-base:         13px;     /* Unite runs 13px/1.5, not 16            */
  --art-lh-base:         1.5;
  --art-fw-normal:       400;
  --art-fw-medium:       500;
  --art-fw-bold:         600;

  /* -- interaction -------------------------------------------------------- */
  --art-focus-ring:      0 0 0 3px rgba(var(--art-accent-rgb), .35);
  --art-transition:      .15s ease-in-out;
}

/* ---------- DARK ---------------------------------------------------------- */
/* Only the tokens that actually CHANGE are redefined. Shape, spacing, type and
   timing are theme-independent and must not be duplicated — a second copy is a
   second thing to forget. */
:root[data-bs-theme="dark"] {
  --art-page:            #1E1E2D;   /* was Artifact's navy #0b1727            */
  --art-field:           #151521;   /* the recess — Unite's `.content`        */
  --art-surface:         #1E1E2D;   /* the CARD: back to the body colour      */
  --art-surface-2:       #26272F;
  --art-border:          #26272F;   /* data rules (table borders)             */
  --art-border-card:     #1E2027;   /* card edges — measured off Unite        */
  --art-card-shadow:     none;      /* ⭐ Unite casts NO card shadow in dark  */
  --art-input-bg:        #1B1C22;   /* the well — DARKER than the surface     */
  --art-input-border:    #1B1C22;
  --art-input-text:      #B5B7C8;
  --art-control-edge:    #7E8299;   /* 4.0:1 on #1E1E2D                       */
  --art-text-secondary:  #B5B7C8;   /* 9.0:1 on #1E1E2D — was #344050, 1.56:1 */

  --art-text:            #F5F5F5;
  --art-text-muted:      #636674;
  --art-text-subtle:     #464852;
  /* ⚠️ THE OLD COMMENT HERE SAID "2.95:1 -> passes AA" AND 2.95 DOES NOT PASS
     AA — the floor for normal text is 4.5. Corrected in place, not deleted.
     Re-measured live on the dark card below. */
  --art-text-heading:    #A9AEC0;

  --art-accent-contrast: #ffffff;
  --art-accent-soft:     rgba(var(--art-accent-rgb), .18);
  --art-accent-hover:    rgba(var(--art-accent-rgb), .26);

  /* ⚠️ Shadows must deepen on a dark ground or they vanish entirely. */
  --art-shadow-sm:       0 1px 2px rgba(0, 0, 0, .40);
  --art-shadow-md:       0 6px 18px rgba(0, 0, 0, .45);
  --art-shadow-lift:     0 10px 28px rgba(0, 0, 0, .55);

  /* ⚠️ The soft status fills need more alpha to read against #151521. A light
     theme's .12 disappears here — this is exactly the contrast trap in U-5.7. */
  --art-ok-soft:         rgba(10, 207, 151, .18);
  --art-warn-soft:       rgba(255, 188,  0, .18);
  --art-danger-soft:     rgba(248,  40, 90, .18);
  --art-info-soft:       rgba(23, 162, 184, .18);

  /* ⛔⛔ DARK NEEDED ITS OWN INKS TOO, AND I ASSUMED IT DID NOT.
     I first wrote `--art-ok-ink: var(--art-ok)` here with a comment claiming
     the bright colours were "the correct ones" in dark and quoting ratios I had
     NOT MEASURED. Measured on the live page immediately after: ok 2.57 ·
     danger 3.07 · info 4.15 — three more AA failures, shipped behind a comment
     that asserted the opposite. The numbers in that comment were a prediction
     wearing the costume of a measurement, and they are corrected here rather
     than quietly deleted.

     ⭐ THE CAUSE IS WORTH MORE THAN THE FIX: `--art-ok` is `var(--bs-success)`,
     and the Unite bundle REDEFINES `--bs-success` per theme. So the ink was not
     the colour written three lines above it — the literal #0ACF97 measures 5.75
     on this fill and PASSES, while the alias measured 2.57. An alias inherits
     someone else's theme decisions; an ink has to be a value we chose.
     ⇒ these are deliberate literals, not aliases. Measured on the live dark
     page: ok 7.05 · danger 5.73 · info 6.14 · neutral 7.48 · warn 6.76 ·
     accent 5.91. */
  --art-ok-ink:          #2FE3B0;
  --art-info-ink:        #3FC5DA;
  --art-danger-ink:      #FF7D9B;
  --art-neutral-ink:     var(--art-text-secondary);
  --art-accent-ink:      #B99BD1;   /* the accent itself is too dark on #1E1E2D */
}

/* ==========================================================================
   THE BRIDGE — § U-3b. THIS IS WHAT ACTUALLY RE-SKINS THE APP.
   --------------------------------------------------------------------------
   ⭐⭐ MEASURED 2026-08-12, AND IT REPLACES THE PLAN'S APPROACH ENTIRELY.
   § U-0 finding 2 said Artifact could "reach the Unite theme by changing a
   <link>, with no new asset to build". IT IS FALSE AND IT WOULD HAVE BROKEN
   THE APP: `unite.bundle.css` contains **ZERO** rules for `.left-menu`,
   `.top-navbar`, `.side-nav-item` or `.icon-circle` — every shell class
   Artifact uses. Swapping the stylesheet stripts the sidebar and topbar to
   unstyled HTML rather than re-theming them. The two apps share an
   information-architecture SHAPE, not a CSS contract.

   ⭐ WHAT IS TRUE INSTEAD, AND IT IS BETTER: `central.css` already declares
   **1,299 `--bs-*` custom properties** and already supports `data-bs-theme`
   (18 dark blocks, 3 light) — and **not one dark rule targets the shell**,
   because the shell is drawn entirely FROM THOSE VARIABLES:
       .left-menu    -> --bs-wrapper-bg, --bs-gray-300, --bs-leftbar-width
       .top-navbar   -> --bs-wrapper-bg, --bs-topbar-height
       .side-nav-link-> --bs-menu-item-color, --bs-menu-item-*
   ⇒ **Overriding the variables re-skins the entire app in BOTH themes with no
   markup change at all.** That is what this block does.

   ⛔ LOAD ORDER IS LOAD-BEARING: this file must come AFTER central.css.
   ⚠️ Artifact's accent today is NOT the cyan `#22C1E8` the plan recorded — it
   is `--bs-primary: #0d6efd` (with `#727cf5` declared elsewhere). Measured off
   the stylesheet, not off a screenshot.
   ========================================================================== */

/* ---------- LIGHT ---------- */
:root {
  --bs-primary:                 var(--art-accent);
  --bs-primary-rgb:             var(--art-accent-rgb);   /* the shared bundle's is #EEDDFF — wrong */
  --bs-link-color:              var(--art-accent);
  --bs-link-hover-color:        var(--art-accent);

  --bs-wrapper-bg:              var(--art-page);
  --bs-body-bg:                 var(--art-page);
  --bs-body-color:              var(--art-text);
  --bs-card-bg:                 var(--art-surface);
  --bs-border-color:            var(--art-border);

  --bs-menu-item-color:         var(--art-text-muted);
  --bs-menu-item-hover-color:   var(--art-accent);
  --bs-menu-item-active-color:  var(--art-accent);
}

/* ---------- DARK ---------- */
:root[data-bs-theme="dark"] {
  --bs-wrapper-bg:              var(--art-page);      /* #1E1E2D, was #0b1727 navy */
  --bs-body-bg:                 var(--art-page);
  --bs-body-color:              var(--art-text);
  --bs-card-bg:                 var(--art-surface);   /* #151521                   */
  --bs-border-color:            var(--art-border);

  --bs-menu-item-color:         var(--art-text-muted);
  --bs-menu-item-hover-color:   #FFFFFF;
  --bs-menu-item-active-color:  #FFFFFF;
}

/* ---------- the page itself ---------------------------------------------- */
body {
  background-color: var(--art-page);
  color: var(--art-text);
  font-family: var(--art-font);
  font-size: var(--art-fs-base);
  line-height: var(--art-lh-base);
}

/* ==========================================================================
   § U-SURFACES — THE CARD SURFACE ITSELF. Raised by Jeremy 2026-08-12:
   *"first look at the color of the card in art-ai-fact vs the Unite they are
   not the same colors. So the UI work is not complete."* He was right.

   ⛔⛔ THE BUG WAS A VARIABLE **NAME**, NOT A VALUE. The bridge above sets
   `--bs-card-bg`. The rule that actually wins is in the SHARED central.css:

       .card { background-color: var(--bs-card-bg-custom) !important;
               box-shadow:       var(--bs-card-box-shadow-custom) !important;
               border: 1px solid var(--bs-card-border-color-custom) !important; }

   `--bs-card-bg-custom` is a NON-STANDARD name, one of a family of twelve
   (`--bs-input-*-custom`, `--bs-dropdown-bg-custom`, `--bs-check-*-custom` …).
   Nothing reads `--bs-card-bg`. So the accent moved and every SURFACE stayed on
   the old Falcon navy: card #121e2d, inputs #0b1727 — measured live, both apps
   side by side, with an identical probe card injected into each.

   ⭐ AND THE FILE ALREADY KNEW. § U-3e sets `--bs-card-bg-custom` per KPI tile
   and even documents "override the variable, not the rule". It was applied to
   the tiles and never to the global surface. **Knowing the mechanism in one
   place is not the same as applying it everywhere** — the third time this
   project has been bitten by a fix that was real but not universal.

   ⛔ THE ACCEPTANCE TEST COULD NOT SEE ANY OF THIS. It scanned for three
   retired ACCENT hexes and passed with every surface wrong. A gate that
   measures the wrong property reports green forever. `.card`'s computed
   background is now asserted directly — see test_card_surface_matches_unite.
   ========================================================================== */
:root,
:root[data-bs-theme="dark"] {
  --bs-card-bg-custom:           var(--art-surface);
  --bs-card-cap-bg-custom:       transparent;      /* Unite's header is unpainted */
  --bs-card-border-color-custom: var(--art-border-card);
  --bs-card-box-shadow-custom:   var(--art-card-shadow);
  --bs-dropdown-bg-custom:       var(--art-surface);

  --bs-input-bg-custom:          var(--art-input-bg);
  --bs-input-border-custom:      1px solid var(--art-input-border);
  --bs-input-color-custom:       var(--art-input-text);

  /* ⛔⛔ `.text-muted` IS A BOOTSTRAP UTILITY, SO IT CARRIES `!important` AND
     BEATS EVERY COMPONENT RULE WE WRITE. It resolves to `--bs-secondary-color`,
     which central.css leaves at the old navy **#344050** in dark. Result: every
     group label in the Search-the-Plant modal — "Party", "Recording",
     "Subdivision", "Legal & options" — rendered at **1.56:1**, i.e. barely
     visible, which is what Jeremy saw as *"far too dark, its not right at
     all."* It is the SAME defect as the card: a `--bs-*` name the bridge never
     covered. Overriding the variable is again the fix, not fighting the
     utility's !important. */
  --bs-secondary-color:          var(--art-text-secondary);
  --bs-tertiary-color:           var(--art-text-muted);
}

/* a placeholder may sit dimmer than a label, but it must still be legible;
   #344050 on the #1B1C22 well was 1.61:1 — invisible. */
.form-control::placeholder, .form-select::placeholder {
  color: var(--art-text-muted);
  opacity: 1;
}

/* ==========================================================================
   § U-SURFACES-b · DENSITY. Jeremy, 2026-08-12: colour/radius/shadow for sure,
   *"but the search screen is far too dense for example"* — the **manual**
   search, i.e. the Search-the-Plant modal.

   ⭐ MEASURED ON THAT MODAL, NOT JUDGED BY EYE: 25 inputs at **29.3px** tall
   (`padding: 4px 8px`) on a **33px** row pitch — a 4px gutter between fields —
   inside 16px of body padding. Unite's controls are 43.8px in 29.25px padding.

   ⚠️ WE DO NOT ADOPT UNITE'S GEOMETRY WHOLESALE, AND THAT IS JEREMY'S CALL:
   43.8px controls everywhere would make every Artifact grid and form taller and
   fit fewer rows on screen. 40px is the deliberate middle — a 36% loosening
   that keeps Artifact denser than Unite.

   ⭐ RAISING THE CONTROL HEIGHT FIXES THE ROW PITCH FOR FREE (33px -> ~44px);
   the gutter is whitespace between boxes, so it does not need its own rule.
   ⛔ `-sm` VARIANTS ARE EXCLUDED ON PURPOSE — they are the in-grid filter
   inputs that live inside `<thead>` toolbars, and bloating those would push the
   column headings away from their data.
   ========================================================================== */
:root { --art-control-h: 40px; }

.form-control:not(.form-control-sm):not(textarea),
.form-select:not(.form-select-sm) {
  min-height: var(--art-control-h);
  padding: 8px 12px;
}

/* ⛔⛔ AND THE `-sm` EXCLUSION WAS WRONG WHERE IT MATTERED MOST. I assumed
   `.form-control-sm` meant "an in-grid filter input" and excluded it — then
   measured the Search-the-Plant modal and found **every field in it is `-sm`**.
   So the one screen Jeremy named as too dense was the one screen the density
   fix skipped, while a 40px field on the page BEHIND the modal made it look
   like the change had landed. **Assuming what a class is for, instead of
   grepping where it is used, is how a fix misses its own target.**
   The genuine compact case is the toolbar row inside `<thead>` — scope the
   exclusion to THAT, which is what it always should have been. */
.form-control-sm, .form-select-sm { min-height: 34px; padding: 6px 10px; }
thead .form-control-sm, thead .form-select-sm { min-height: 0; padding: 2px 6px; }

/* the card's own breathing room — central.css ships `padding: 0 1rem`, i.e.
   ZERO vertical, which is why content sits hard against the header rule. */
.card-body { padding: 16px 20px !important; }

/* ---------- the modal, measured inside Unite ----------------------------- */
/* Unite's `.modal-content` is the SAME #1E1E2D as a card — the modal is lifted
   off the page by a SHADOW and by its fields being wells, not by being a
   lighter grey. Both halves matter: without the well-coloured fields (above)
   this surface reads as a flat slab. */
.modal-content {
  /* ⚠️ EXPLICIT, because Bootstrap falls back to `--bs-body-bg` — which is the
     PAGE colour (#F9F9F9). In light that made the modal the same #F9F9F9 as the
     solid field wells inside it, so the fields disappeared again, the mirror of
     the dark-mode bug. A modal is a card: it uses the CARD surface. */
  background-color: var(--art-surface);
  border-radius: 6px;
  box-shadow: 0 6.5px 13px rgba(0, 0, 0, .1);
}
.modal-body { padding: 24px; }

/* ---------- the CHECKBOX, which the surface change would have erased ------ */
/* ⛔⛔ CAUGHT ON THE LIVE PAGE, NOT IN REVIEW, AND IT IS THE WHOLE REASON THE
   SURFACE WORK GETS CLICKED. `.form-check-input` takes its fill from
   `--bs-input-bg-custom`. That used to be the navy #0b1727, which happened to
   contrast against the #1e1e2d modal — so the box was visible BY ACCIDENT.
   Re-pointing inputs to the card surface made fill == background: an unchecked
   checkbox at 1.16:1, i.e. INVISIBLE, on a screen where "Sounds like (soundex)"
   and "Exclude related docs" change what the search returns.
   ⭐ Unite's answer, measured: TRANSPARENT fill + a real border, purple when
   checked. A control should be drawn by its EDGE, not by a fill that happens to
   differ from whatever is behind it.
   ⚠️ `[type=checkbox]:not([role=switch])` is load-bearing — a switch is also a
   `.form-check-input`, and squaring it off would wreck the Active Status toggle
   on the subdivision form. */
/* ⚠️ AND HERE WE DELIBERATELY DIVERGE FROM UNITE, MEASURED BOTH WAYS.
   Copying Unite's `--art-input-border` edge gives **1.27:1** in light and
   ~1.9:1 in dark. WCAG 2.1 SC 1.4.11 wants **3:1** for the boundary of a
   control, and a checkbox is the case where the BOX IS THE ONLY AFFORDANCE —
   there is no label inside it and no other cue to its state. Unite ships the
   faint edge; parity is the goal but not at the cost of a control nobody can
   see. `--art-control-edge` is 4.0:1 in both themes.
   ⛔ Text inputs KEEP Unite's softer border on purpose: they carry their own
   placeholder and value, so the edge is not the only signal, and hardening
   every field would visibly weigh the forms down. */
.form-check-input[type="checkbox"]:not([role="switch"]) {
  background-color: transparent;
  border: 1px solid var(--art-control-edge);
  width: 18px;                      /* Unite is 22.7px; Artifact stays denser */
  height: 18px;
  border-radius: 5px;
}
.form-check-input[type="checkbox"]:not([role="switch"]):checked {
  background-color: var(--art-accent);
  border-color: var(--art-accent);
}

/* ---------- the recess the cards sit in ---------------------------------- */
/* ⚠️ Artifact has no `.content` element — its main column is `.content-page`
   (id `pagecontainer`). Without this the card is the same colour as the page
   behind it and reads as a flat region rather than a card. */
.wrapper .content-page { background-color: var(--art-field); }

/* ---------- the Unite signature: a gradient hairline on every card -------- */
/* Purely additive and fully reversible — unlink this file and it is gone. */
.card { position: relative; border-radius: var(--art-radius-lg); }
.card::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: var(--art-gradient);
  border-top-left-radius: var(--art-radius-lg);
  border-top-right-radius: var(--art-radius-lg);
}

/* ==========================================================================
   § U-3e · COLOUR-CODED KPI TILES — Jeremy's answer 5, "all of them".
   --------------------------------------------------------------------------
   ⭐ NO MARKUP CHANGE. Every tile already carries a semantic class
   (`verifydocs` / `reviewdocs` / `errordocs` / `legrevdocs` / `aliasdocs`), so
   the whole thing is CSS and the view's JavaScript is untouched — which matters,
   because those tiles are clickable and each one navigates.

   ⛔ AND IT OVERRIDES THE VARIABLE, NOT THE RULE. central.css sets
   `.taskcard { background-color: var(--bs-card-bg-custom) !important; }`. A
   per-tile `background-color` would lose to that `!important`; redefining
   `--bs-card-bg-custom` ON THE TILE wins without an arms race. Same lesson as
   `--bs-central-btn-bg`, second time it has applied.
   ========================================================================== */
.taskcard {
  position: relative;
  border-radius: var(--art-radius-lg);
  border-top: 3px solid var(--art-accent);
  transition: transform var(--art-transition), box-shadow var(--art-transition);
}
.taskcard h1 { color: var(--art-text); font-weight: var(--art-fw-bold); }
.taskcard h6 {                                   /* small-caps label, Unite style */
  color: var(--art-text-muted);
  text-transform: uppercase;
  letter-spacing: .06em;
  font-size: 11px;
  font-weight: var(--art-fw-bold);
}
.taskcard:hover { transform: translateY(-2px); }

/* the icon, top-right — FontAwesome Pro 6.3 is already loaded by the shell */
.taskcard::after {
  font-family: "Font Awesome 6 Pro"; font-weight: 900;
  position: absolute; top: 14px; right: 16px;
  font-size: 18px; opacity: .55; line-height: 1;
}

/* ⭐ THE COLOUR CODE. Each tile is tinted by its MEANING, not by decoration:
   work-to-do is informational, a queue awaiting a human is a warning, errors
   are danger, and the alias backlog is its own colour. */
.taskcard.verifydocs { --bs-card-bg-custom: var(--art-info-soft);   border-top-color: var(--art-info); }
.taskcard.reviewdocs { --bs-card-bg-custom: var(--art-warn-soft);   border-top-color: var(--art-warn); }
.taskcard.errordocs  { --bs-card-bg-custom: var(--art-danger-soft); border-top-color: var(--art-danger); }
.taskcard.legrevdocs { --bs-card-bg-custom: var(--art-accent-soft); border-top-color: var(--art-accent); }
.taskcard.aliasdocs  { --bs-card-bg-custom: var(--art-ok-soft);     border-top-color: var(--art-ok); }

.taskcard.verifydocs::after { content: "\f15c"; color: var(--art-info); }    /* file-lines       */
.taskcard.reviewdocs::after { content: "\f46c"; color: var(--art-warn); }    /* clipboard-check  */
.taskcard.errordocs::after  { content: "\f071"; color: var(--art-danger); }  /* triangle-excl.   */
.taskcard.legrevdocs::after { content: "\f0e3"; color: var(--art-accent); }  /* gavel            */
.taskcard.aliasdocs::after  { content: "\f02b"; color: var(--art-ok); }      /* tag              */

/* ==========================================================================
   § U-3c · GRIDS — the dominant surface in this product.
   --------------------------------------------------------------------------
   Artifact's tables are `table.datagrid` inside `.card > .card-body`, and the
   FILTER TOOLBAR LIVES INSIDE `<thead>` as a first `<tr><th colspan=7>` — the
   real column headings are the SECOND row. That is why the selectors below are
   `thead tr:first-child` (toolbar) and `thead tr:last-child` (headings) rather
   than a plain `th`. Read off the live DOM, not assumed.
   ⚠️ Rows are `tr.editbtn` and every one of them is CLICKABLE — hence the
   pointer and the hover, which the grid had no affordance for at all.
   ========================================================================== */
.card-header {
  border-bottom: 1px solid var(--art-border);
  padding: 14px 20px;
}
.card-header h4, .card-header h5 {
  color: var(--art-text);
  font-size: 15px;
  font-weight: var(--art-fw-bold);
  margin: 0;
  letter-spacing: -0.01em;
}

/* ⛔ THERE ARE **TWO** TABLE CONVENTIONS IN THIS PRODUCT AND THE FIRST VERSION
   OF THIS BLOCK ONLY STYLED ONE. Legacy screens (Batches, QA Review) use
   `table.datagrid` with the filter toolbar INSIDE `<thead>`; newer screens
   (Error Queue) use `table.table-sm.table-hover` with a single header row.
   Keying on first-child/last-child therefore cancelled itself on the newer
   tables — the Error Queue headings stayed plain and I only saw it by opening
   the page. The discriminator is the TOOLBAR'S `colspan`, not row position. */
.card .table > thead > tr > th[colspan] {      /* the filter toolbar row */
  border-bottom: 0;
  padding: 12px 8px 14px;
}
.card .table > thead > tr > th:not([colspan]) {  /* real column headings */
  color: var(--art-text-heading);
  text-transform: uppercase;
  letter-spacing: .06em;
  font-size: 10.5px;
  font-weight: var(--art-fw-bold);
  border-bottom: 1px solid var(--art-border);
  padding: 10px;
  white-space: nowrap;
}
.card .table > tbody > tr > td {
  border-bottom: 1px solid var(--art-border);
  padding: 9px 10px;
  color: var(--art-text);
  vertical-align: middle;
}
.card .table > tbody > tr:last-child > td { border-bottom: 0; }
.card .table > tbody > tr:hover > td { background: var(--art-accent-soft); }
.datagrid tbody tr.editbtn { cursor: pointer; }

/* the pager reads as furniture, not data */
.datagrid + *, .fuelux .datagrid-footer, .datagrid-footer {
  color: var(--art-text-muted);
  font-size: 12px;
}

/* ==========================================================================
   § U-3c · STATUS CHIPS + FORM CONTROLS
   --------------------------------------------------------------------------
   ⭐ `.badge` IS ALREADY USED 59 TIMES ACROSS THESE VIEWS, so restyling it
   chips every status the product already marks up — no markup change, no JS,
   and nothing that could disturb a click handler. That is deliberately chosen
   over injecting spans into `.datagrid` cells: those rows are clickable and
   some handlers read cell text.
   ========================================================================== */
.badge {
  border-radius: 999px;
  padding: .32em .75em;
  font-weight: var(--art-fw-medium);
  font-size: 10.5px;
  letter-spacing: .04em;
  text-transform: uppercase;
  border: 1px solid transparent;
}
/* Bootstrap's contextual badges, re-tinted to the token palette so a status
   reads the same way on every screen and in both themes. */
.badge.bg-success, .badge.badge-success { background: var(--art-ok-soft)     !important; color: var(--art-ok);      border-color: var(--art-ok); }
.badge.bg-warning, .badge.badge-warning { background: var(--art-warn-soft)   !important; color: var(--art-warn);    border-color: var(--art-warn); }
.badge.bg-danger,  .badge.badge-danger  { background: var(--art-danger-soft) !important; color: var(--art-danger);  border-color: var(--art-danger); }
.badge.bg-info,    .badge.badge-info    { background: var(--art-info-soft)   !important; color: var(--art-info);    border-color: var(--art-info); }
.badge.bg-primary, .badge.badge-primary { background: var(--art-accent-soft) !important; color: var(--art-accent);  border-color: var(--art-accent); }
.badge.bg-secondary                     { background: var(--art-surface-2)   !important; color: var(--art-text-muted); border-color: var(--art-border); }

/* ---------- inputs, selects, search -------------------------------------- */
/* ⚠️ `--bs-form-control-bg` and friends are declared !important in central.css,
   so the BACKGROUND is deliberately left alone here — only geometry and the
   focus state, which are not. Overriding the background would mean either an
   arms race or guessing that variable's exact expected form. */
.form-control, .form-select, .input-group-text {
  border-radius: var(--art-radius-sm);
  font-size: var(--art-fs-base);
}
.form-control:focus, .form-select:focus {
  border-color: var(--art-accent);
  box-shadow: var(--art-focus-ring);
}
.form-label { color: var(--art-text-muted); font-weight: var(--art-fw-medium); font-size: 12px; }

/* ==========================================================================
   § U-3c · CIRCULAR ROW-ACTION BUTTONS (the Unite pattern)
   --------------------------------------------------------------------------
   ⛔ THESE THREE BUTTONS WRITE TO THE PLANT. `.qa-approve` marks a document
   Pending and pushes it to PropertySync; `.qa-reject` sends it back. NOTHING
   HERE TOUCHES BEHAVIOUR — no markup, no JS, no handler, only geometry and
   colour on classes that already exist. They still get CLICKED before this
   screen is signed off (§ U-5.6).
   ⚠️ `.qa-open` is a `.btn-central`, whose `--bs-btn-bg` is declared
   `!important` — an !important custom property beats a more specific plain one,
   so it is retinted through `--bs-central-btn-bg`, the variable it reads.
   `.btn-success`/`.btn-danger` are NOT !important, so those take a plain
   declaration. Same lesson, third application.
   ========================================================================== */
.datagrid .btn-group.btn-group-sm { display: inline-flex; gap: 6px; }
.datagrid .btn-group > .btn {
  width: 28px; height: 28px; padding: 0;
  /* ⚠️ BOTH OF THESE ARE LOAD-BEARING, and neither was obvious from the file:
     `.btn-central` carries `min-width: 80px` (so the first action rendered as an
     80px pill, not a circle), and Bootstrap's `.btn-group` rules zero the INNER
     corners via LONGHAND properties, which a shorthand `border-radius` does not
     reliably beat. Both were read off the computed style of the live button. */
  min-width: 0;
  border-top-left-radius: 50%; border-top-right-radius: 50%;
  border-bottom-left-radius: 50%; border-bottom-right-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px;
  border: 1px solid currentColor;
  transition: transform var(--art-transition), filter var(--art-transition);
}
.datagrid .btn-group > .btn:hover { transform: translateY(-1px); filter: brightness(1.3); }

.datagrid .btn.qa-open {                       /* .btn-central — via its own var */
  --bs-central-btn-bg: var(--art-accent-soft);
  --bs-central-btn-color: var(--art-accent);
  color: var(--art-accent);
}
.datagrid .btn.qa-approve {
  --bs-btn-bg: var(--art-ok-soft);
  --bs-btn-color: var(--art-ok);
  --bs-btn-border-color: var(--art-ok);
  color: var(--art-ok);
}
.datagrid .btn.qa-reject {
  --bs-btn-bg: var(--art-danger-soft);
  --bs-btn-color: var(--art-danger);
  --bs-btn-border-color: var(--art-danger);
  color: var(--art-danger);
}

/* the header's bulk actions read as ACTIONS, not as alerts */
.card-header .btn-success { --bs-btn-bg: var(--art-ok);   --bs-btn-border-color: var(--art-ok);   --bs-btn-color: var(--art-accent-contrast); }
.card-header .btn-warning { --bs-btn-bg: var(--art-warn); --bs-btn-border-color: var(--art-warn); --bs-btn-color: var(--art-on-warn); }
.card-header .btn { border-radius: var(--art-radius-sm); font-size: 12px; }

/* ---------- the active nav item, in the accent --------------------------- */
.side-nav-item .side-nav-link:hover,
.side-nav-item.menuitem-active > .side-nav-link { color: var(--art-accent); }
.side-nav-item.menuitem-active > .side-nav-link { background: var(--art-accent-soft); }

/* ==========================================================================
   § U-3b · THE SHELL — measured off the running page 2026-08-13, not guessed.
   --------------------------------------------------------------------------
   Two real problems, and neither is decorative:

   ⛔⛔ 1. THE SIDEBAR LINKS FAILED WCAG AA. They rendered `#78829D` on the
   `#F9F9F9` sidebar = **3.40:1**, below the 4.5 floor — on the PRIMARY
   NAVIGATION of the product. Same root cause as the chips: `--art-text-muted`
   is the SUBTLE token, and the app's own `.text-muted` resolves to
   `--art-text-secondary` (7.5:1). Real text does not use the subtle token.

   ⚠️ 2. THE SIDEBAR WAS A SIZE LARGER THAN THE PRODUCT. `.side-nav-link` was
   **15px** while `body`, every table cell and every card header is 13px. That
   single mismatch is most of why the shell read as a different generation from
   the content beside it — Unite runs one type size through the whole chrome.
   ========================================================================== */
/* ⛔⛔ AND THIS IS THE FOURTH TIME IN THIS REBUILD: OVERRIDE THE VARIABLE, NOT
   THE RULE. My first attempt was a plain `.side-nav-link { font-size; color }`
   and it LOST — measured after deploying, the nav was still 15px at 3.64:1.
   central.css declares:
       .side-nav .side-nav-link {
         color:     var(--bs-menu-item-color) !important;
         font-size: var(--bs-menu-item-font-size);
       }
   `!important` beats any later declaration of `color`, and `.side-nav
   .side-nav-link` out-specifies a bare `.side-nav-link` for the size. Both are
   won by redefining what those rules READ.
   ⭐ The predecessors: `--bs-card-bg` vs `--bs-card-bg-custom`, `--bs-btn-bg`
   vs `--bs-central-btn-bg`, `--bs-info` vs `--bs-info-rgb`. Every one of them
   looked correct in the file and in the cascade listing, and every one was only
   caught by reading the COMPUTED value back off the live element. */
:root, :root[data-bs-theme="dark"] {
  --bs-menu-item-color:     var(--art-text-secondary);  /* was #78829D, 3.40:1 */
  --bs-menu-item-font-size: var(--art-fs-base);         /* was 15px in a 13px product */
}
/* the small-caps group label stays SMALLER than the links it heads — it is a
   heading for scanning, not a destination */
.side-nav-title, .menu-title {
  color: var(--art-text-heading);
  font-weight: var(--art-fw-bold);
}

/* ⭐ A HAIRLINE UNDER THE TOPBAR AND BESIDE THE SIDEBAR. Both were 0px, so the
   chrome and the content ran together as one flat field — the page had no
   structure until a card happened to start. Unite separates them. */
.top-navbar { border-bottom: 1px solid var(--art-border-card); }
.left-menu  { border-right: 1px solid var(--art-border-card); }

/* the column headings are labels, not furniture — same AA reasoning */
.datagrid thead tr:last-child th { color: var(--art-text-secondary); }

/* ---------- buttons pick up the accent ----------------------------------- */
/* ⛔⛔ OVERRIDE THE UPSTREAM VARIABLE, NEVER THE RULE. central.css declares
   `.btn-central { --bs-btn-bg: var(--bs-central-btn-bg) !important; }` — and 15
   `--bs-*` properties in that file carry `!important` (`--bs-btn-bg`,
   `--bs-btn-color`, `--bs-link-color`, `--bs-form-control-bg`, `--bs-modal-bg`,
   `--bs-dropdown-bg` among them). A later declaration of the SAME property
   loses no matter how it is ordered, so the only cascade-respecting fix is to
   redefine the variable it POINTS AT. Adding `!important` here would start an
   arms race inside a stylesheet we do not own.
   ⭐ Found by reading the computed value off the live button — the rule looked
   correct in the file and in the cascade listing, and still lost. */
:root {
  --bs-central-btn-bg:    var(--art-accent);      /* was #0d6efd              */
  --bs-central-btn-color: var(--art-accent-contrast);
}
:root[data-bs-theme="dark"] {
  --bs-central-btn-bg:    var(--art-accent);      /* was #0dcaf0 — THE "cyan"  */
  --bs-central-btn-color: var(--art-accent-contrast);
}
.btn-primary {
  --bs-btn-bg: var(--art-accent);
  --bs-btn-border-color: var(--art-accent);
  --bs-btn-hover-bg: var(--art-accent);
  --bs-btn-hover-border-color: var(--art-accent);
  --bs-btn-color: var(--art-accent-contrast);
  --bs-btn-hover-color: var(--art-accent-contrast);
}
/* ⛔⛔ THREE RETIRED ACCENTS WERE STILL LIVE — FOUND 2026-08-13 BY PROBING EVERY
   CONTEXTUAL CLASS THE 39 VIEWS ACTUALLY USE, NOT BY LOOKING AT A PAGE.
   Measured off the running app:
       .btn-outline-primary  color + border  = #0d6efd   (2 uses)
       .text-info            color           = #0dcaf0   (9 uses)
       .bg-info              background      = #0dcaf0   (4 uses)
   15 other contextual classes came back clean.

   ⭐ WHY `--bs-primary: #774A99` DID NOT REACH THEM. Bootstrap 5.3 generates its
   button variants with the LITERAL baked into per-class custom properties —
   `.btn-outline-primary { --bs-btn-color: #0d6efd; … }` — and the utilities read
   `--bs-info-rgb`, not `--bs-info`. Setting the theme colour therefore changes
   nothing for either. Same lesson as `--bs-card-bg` vs `--bs-card-bg-custom`:
   OVERRIDE THE VARIABLE THE RULE ACTUALLY READS.

   ⚠️ AND THIS IS WHY THE 2026-08-12 "0 retired accents in BOTH themes" RESULT
   WAS TRUE AND STILL MISSED THEM. That scan measured RENDERED elements on the
   pages it visited; `.btn-outline-primary` sits on the Legal Analysis upload
   tile and `.text-info` on states those pages were not in. The instrument was
   sound, its COVERAGE was the gap — the same shape as the #727cf5 found in
   search.html the same day. A page nobody opens is where a leak survives a
   leak scan. */
.btn-outline-primary {
  --bs-btn-color: var(--art-accent);
  --bs-btn-border-color: var(--art-accent);
  --bs-btn-hover-bg: var(--art-accent);
  --bs-btn-hover-border-color: var(--art-accent);
  --bs-btn-hover-color: var(--art-accent-contrast);
  --bs-btn-active-bg: var(--art-accent);
  --bs-btn-active-border-color: var(--art-accent);
  --bs-btn-active-color: var(--art-accent-contrast);
  --bs-btn-disabled-color: var(--art-accent);
  --bs-btn-disabled-border-color: var(--art-accent);
}
/* `.text-info` / `.bg-info` read `--bs-info-rgb`, so the RGB TRIPLE is the thing
   that has to change — setting `--bs-info` alone leaves both rendering cyan. */
:root, :root[data-bs-theme="dark"] {
  --bs-info: var(--art-info);
  --bs-info-rgb: 23, 162, 184;
}

.btn { border-radius: var(--art-radius-sm); }
.btn:focus-visible { box-shadow: var(--art-focus-ring); }

/* ==========================================================================
   TOGGLES / CHECKBOXES — the last cyan in the product.
   --------------------------------------------------------------------------
   ⛔ THIS IS THE ONE PLACE `!important` IS CORRECT, AND ONLY BECAUSE THERE IS
   NOTHING UPSTREAM TO OVERRIDE. Everywhere else in this file a token wins by
   redefining the variable the rule reads (`--bs-central-btn-bg`,
   `--bs-card-bg-custom`). But central.css declares
       [data-bs-theme="dark"] .form-check-input:checked {
           background-color: rgb(13,202,240) !important; }
   — a HARDCODED LITERAL with !important and no variable behind it. An
   !important declaration can only be beaten by another at equal-or-greater
   specificity, so this matches its selector exactly and no more.
   ⚠️ If that rule ever gains a variable, delete this block and override the
   variable instead.
   ========================================================================== */
.form-check-input:checked,
[data-bs-theme="dark"] .form-check-input:checked {
  background-color: var(--art-accent) !important;
  border-color: var(--art-accent) !important;
}
.form-check-input:focus { box-shadow: var(--art-focus-ring); border-color: var(--art-accent); }

/* ⛔ SAME EXCEPTION, SAME REASON: `.btn-central` declares
   `--bs-btn-border-color: #0d6efd !important` (and #0dcaf0 in dark) as a
   hardcoded literal — the BACKGROUND has a variable (`--bs-central-btn-bg`,
   which is why the fill went purple) but the BORDER does not. Found by scanning
   every rendered element for the old accents rather than by looking: the fill
   was right and only a 1px ring was still cyan, on 6 buttons. */
.btn-central { --bs-btn-border-color: var(--art-accent) !important; }
[data-bs-theme="dark"] .btn-central,
[data-bs-theme="dark"] .support-btn { --bs-btn-border-color: var(--art-accent) !important; }

/* ==========================================================================
   § U-3c · EMPTY STATES + LOGIN
   --------------------------------------------------------------------------
   Jeremy asked for "empty states in words". An empty grid in this product
   currently renders as a blank area under the headings, which is
   indistinguishable from a screen that is still loading — the failure this
   whole project keeps meeting in other forms: ABSENCE READING AS
   NO-PROBLEM. `:empty` is used so it needs no markup and cannot misfire on a
   populated table.
   ========================================================================== */
.card .table > tbody:empty::after,
.card .table > tbody tr.no-records td {
  content: "Nothing here yet.";
  display: block;
  padding: 28px 10px;
  text-align: center;
  color: var(--art-text-muted);
  font-size: 13px;
}

/* ---------- login ---------------------------------------------------------- */
/* ⛔ VISUAL ONLY. The form, its inputs, its action and its handlers are
   untouched — login is the one page in the product with no fallback (§ U-4),
   and the SSO buttons deliberately do not ship until both providers have
   completed a real round trip. */
body.login-page, .login-wrapper { background: var(--art-page); }
#loginform .card, .login-card {
  box-shadow: var(--art-shadow-md);
  border: 1px solid var(--art-border);
}
#loginform .form-control { padding: 10px 12px; }
#loginform .btn { padding: 10px 16px; font-weight: var(--art-fw-medium); }

/* ==========================================================================
   § U-9 · THE LOGIN SCREEN — the split panel, matching Unite and Disbursal Flow.
   --------------------------------------------------------------------------
   Jeremy 2026-08-12: *"We need to make sure we have like king login page with
   SSO to unite and dibursal pro"* and *"we need a login in the same format that
   follows light and dark settings"*.

   ⭐ THE LAYOUT IS CONSTANT, THE RIGHT-HAND SIDE FOLLOWS THE THEME. That is
   what the two reference products actually do, measured live: Unite's login
   right panel rendered DARK and Disbursal Flow's rendered LIGHT — because each
   was following its own theme setting. The purple brand panel is constant in
   both. So: brand panel = accent, always; form side = tokens, both themes.

   ⛔ THE THEME COMES FROM THE `centraltheme` COOKIE, NOT localStorage.
   Artifact's own toggle writes `_setcookie('centraltheme', …, 30)` plus a
   server save (`header.html:182-186`), and login.php hands the saved theme back
   on success. The login page has NO SESSION, so the cookie is the only thing it
   can read — and reading anything else would make the login screen the one page
   that ignores the user's setting. Unite's own login reads `localStorage`,
   which is why copying its bootstrap script verbatim would have been wrong.

   ⚠️ ILLUSTRATION: sigma-1/17, and the set matters. Of the five illustration
   sets in the shared root, only **sigma-1 (21/21)** and unitedpalms-1 (20/20)
   have GENUINE `-dark` variants; **sketchy-1 is 7 of 21 — the other 14
   `-dark.png` are byte-identical copies**, and dozzy-1 is 3 of 20. Unite's own
   login hardcodes `dozzy-1/12.png`, which is one of the identical pairs, so its
   art does not follow the theme at all. Picking a set by name would have
   shipped a swap that silently does nothing.
   ⚠️ THE LOGO IS **NOT** SWAPPED: `artifact-logo-dark.png` is byte-identical to
   `-light.png` (md5 e2e34bf5…), so a swap would be theatre. It sits on the
   constant purple panel and reads in both themes.
   ========================================================================== */
.login-split { display: flex; min-height: 100vh; }

/* ---------- the brand panel (constant) ----------------------------------- */
.login-brand {
  flex: 0 0 38%;
  min-width: 340px;
  max-width: 560px;
  background-color: var(--art-accent);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 56px 40px 0;
  position: relative;
  overflow: hidden;
}
.login-brand::before {            /* the Unite signature hairline */
  content: ""; position: absolute; inset: 0 0 auto 0; height: 4px;
  background: var(--art-gradient);
}
/* ⚠️ THE LOGO SITS ON A LIGHT PLATE, AND THAT IS NOT DECORATION.
   Art.AI.Fact has NO reversed (light-on-dark) brand asset: all four candidates
   — artifact-logo-light/-dark.png (md5 e2e34bf5…) and artifact-light/-dark.png
   (7d2f3fd6…) — are byte-identical PAIRS of the same POSITIVE artwork, with a
   near-black "ART.AI.FACT" wordmark and black outlines. Dropped straight onto
   the purple panel the wordmark is barely legible (measured on the rendered
   page, then zoomed to confirm). A CSS invert would flip the brand purple to
   green. So the logo gets the light ground it was drawn for. `--art-accent-
   contrast` is #ffffff in BOTH themes, so the plate is constant like the panel.
   ⭐ Replace this with a real reversed asset the day one exists, and delete the
   plate — do not treat it as the brand. */
.login-brand-plate {
  background-color: var(--art-accent-contrast);
  border-radius: var(--art-radius-md);
  padding: 18px 26px;
  display: inline-flex;
  box-shadow: var(--art-shadow-md);
}
.login-brand-logo { width: 190px; max-width: 100%; display: block; }
.login-brand h1 {
  color: var(--art-accent-contrast);
  font-size: 26px;
  font-weight: var(--art-fw-bold);
  margin: 34px 0 0;
  text-align: center;
}
.login-brand p {
  color: var(--art-accent-contrast);
  opacity: .85;
  font-size: 15px;
  line-height: 1.6;
  margin: 14px 0 0;
  text-align: center;
  max-width: 22em;
}
/* ⚠️ `flex: 1 1 auto` + `min-height` let this grow past the panel and the
   figure was CLIPPED at the bottom — seen on the rendered page, not in review.
   Bounded height and a real bottom gutter instead. */
.login-art {
  flex: 0 1 auto;
  width: 100%;
  margin: 32px 0 40px;
  background-repeat: no-repeat;
  background-position: center bottom;
  background-size: contain;
  height: 300px;
  max-height: 34vh;
  background-image: url("/assets/media/illustrations/sigma-1/17.png");
}
:root[data-bs-theme="dark"] .login-art {
  background-image: url("/assets/media/illustrations/sigma-1/17-dark.png");
}

/* ---------- the form side (follows the theme) ---------------------------- */
.login-side {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--art-page);
  padding: 40px 24px;
}
.login-form-wrap { width: 100%; max-width: 380px; }
.login-form-wrap h1 {
  color: var(--art-text);
  font-size: 22px;
  font-weight: var(--art-fw-bold);
  text-align: center;
  margin: 0 0 28px;
}
.login-form-wrap .form-label {
  color: var(--art-text);
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 6px;
}
.login-form-wrap .form-control { min-height: var(--art-control-h); }
.login-forgot {
  font-size: 12.5px;
  color: var(--art-accent);
  text-decoration: none;
}
.login-forgot:hover { text-decoration: underline; color: var(--art-accent); }

/* ---------- the OR divider ----------------------------------------------- */
.login-or {
  display: flex; align-items: center; gap: 12px;
  margin: 22px 0;
  color: var(--art-text-muted);
  font-size: 11px; letter-spacing: .08em;
}
.login-or::before, .login-or::after {
  content: ""; flex: 1 1 auto; height: 1px; background-color: var(--art-border);
}

/* ---------- the SSO buttons ---------------------------------------------- */
.btn-sso {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%;
  min-height: var(--art-control-h);
  margin-bottom: 10px;
  border: 1px solid var(--art-border);
  border-radius: var(--art-radius-sm);
  background-color: var(--art-surface-2);
  color: var(--art-text);
  font-size: 13px;
  font-weight: 600;
  transition: background-color var(--art-transition), border-color var(--art-transition);
}
.btn-sso:hover { background-color: var(--art-accent-soft); border-color: var(--art-accent); color: var(--art-text); }
.btn-sso img { width: 18px; height: 18px; }

/* ⚠️ THE BUTTONS ARE PRESENT AND NOT YET WIRED (Jeremy's call: *"buttons
   unwired for now. We can wire them immediately after."*). They are NOT
   `disabled`, because the point of shipping them now is to review how they
   will actually look — a greyed-out control is a different design. Clicking
   raises an honest toast instead of silently doing nothing. */
.btn-sso[data-sso-pending="1"] { cursor: not-allowed; }

/* ---------- responsive: the brand panel is decoration, drop it first ------ */
@media (max-width: 991.98px) {
  .login-brand { display: none; }
  .login-split { min-height: 100vh; }
}
