/* ============================================================================
   tokens.css — the single source of truth for TotaLand's colour, type and
   spacing values. Phase 0 of the shell redesign.

   LOAD ORDER CONTRACT:
       bootstrap5.css  →  THIS FILE  →  legacy-theme.css  →  default.css
   Custom properties inherit from :root and are resolved where they are USED,
   so position only matters for redefinition. This file sits ahead of the theme
   layer so both legacy-theme.css and default.css can read from it.

   THIS FILE IS DELIBERATELY INERT ON ARRIVAL. It only declares custom
   properties; nothing consumes them yet. A declared-but-unreferenced custom
   property has no rendering effect, so adding this file cannot change a single
   page. Call sites migrate to the tokens in later commits, one area at a time.

   WHAT IT DOES *NOT* DEFINE, on purpose:
     --tl-form-max, --tl-radius, --tl-side2, --tl-side2-cap, --tl-side2-max
   Those are layout tokens already declared in bs5-custom.css and in active use.
   Redefining them here would change page geometry, which is not this commit's
   job. Leave them where they are.

   ---------------------------------------------------------------------------
   THREE COLOUR AXES, KEPT APART
   ---------------------------------------------------------------------------
   1. BRAND   — identity and interactive state (--tl-brand-*, --tl-accent-*)
   2. STATUS  — success / warning / danger / info (--tl-status-*)
   3. REALM   — the seven entity types (--tl-realm-*)
   (and, beside them, the four subscription plans' identity, --tl-plan-*: §3b)

   These must never borrow from each other. The moment a realm colour is also
   the warning colour, a Units badge starts reading as an alert.

   ---------------------------------------------------------------------------
   THE -ink SUFFIX, AND WHY IT EXISTS
   ---------------------------------------------------------------------------
   Every raw brand/accent value in this codebase was chosen as a FILL, to sit
   behind dark text. Most of them fail WCAG AA as foreground text on white.
   Measured against #ffffff:

       #346689 brand   6.16:1  PASSES as text
       #44555F slate   7.75:1  PASSES as text
       #74AEC6 cyan    2.44:1  FAILS  -> use --tl-accent-ink   (#3e7d97, 4.6:1)
       #8CBA4A green   2.27:1  FAILS  -> use --tl-status-success-ink (#5f7f30)
       #E2BD16 gold    1.82:1  FAILS  -> use --tl-status-warning-ink (#8a740d)

   RULE: `-fill` values are backgrounds and take dark text. `-ink` values are
   for text, icon strokes and borders on a light ground. Never use a bare fill
   as a text colour.

   ---------------------------------------------------------------------------
   THE COLOUR GUIDE IS GENERATED FROM THIS FILE
   ---------------------------------------------------------------------------
   docs/brand/ (brand-guide.md, brand-guide.html and the Office, Adobe and GIMP
   palettes used for videos, decks and documents) is built from these values by
       python -X utf8 TotaLand/docs/tools/build_brand_guide.py
   Re-run it after changing or adding a colour token and commit the results
   with the change; BrandGuideDriftTests fails until you do.

   ---------------------------------------------------------------------------
   DARK THEME
   ---------------------------------------------------------------------------
   Not built, and not currently planned — it would mean validating a second
   palette across ~1,310 views for an audience that has not asked. No dark
   tokens are declared here rather than shipping speculative ones nothing reads.
   The derived dark realm shades are recorded in the redesign plan if that
   changes; they do not belong in the stylesheet until something consumes them.
   ========================================================================= */

:root {

  /* ==========================================================================
     1. BRAND
     ====================================================================== */

  /* TotaLand blue ("Brand Blue" in the colour guide). The site's link colour
     (default.css `a:link`), and on the redesigned pages the primary action,
     the selection and every chart data mark. */
  --tl-brand:              #346689;
  --tl-brand-ink:          #346689;  /* 6.16:1 on white — safe as text as-is  */
  --tl-brand-hover:        #4b8cba;  /* default.css `a:hover`, kept verbatim  */
  --tl-brand-wash:         #e4edf3;  /* tint for selected rows / active nav   */
  --tl-on-brand:           #ffffff;  /* 6.16:1 — white on brand is AA         */

  /* Secondary accent: the legacy What's New tab accent bar. The redesigned
     pages do not use the fill; the ink and wash mark the search palette's
     realm-filter chip. (The legacy .btn1-cyan button is its own #00a1e0 ->
     #0079a8 gradient, not this value.) */
  --tl-accent:             #74aec6;
  --tl-accent-ink:         #3e7d97;  /* raw is 2.44:1 — unusable as text      */
  --tl-accent-wash:        #e2eff4;
  --tl-on-accent:          #14212a;  /* DARK text on cyan, not white          */

  /* Neutral base. The site's structural dark: the current top-nav tab, input
     hover borders, neutral icons -- and the logo's own grey. (The legacy
     footer band, still drawn under every page, is a navy -- #0f3a71 in
     default.css -- not this value.) */
  --tl-slate:              #44555f;
  --tl-slate-ink:          #44555f;  /* 7.75:1 on white                       */
  --tl-on-slate:           #ffffff;

  /* ==========================================================================
     2. STATUS  — semantic only. Never reuse a realm colour here.
     ====================================================================== */

  --tl-status-success:     #8cba4a;
  --tl-status-success-ink: #5f7f30;
  --tl-status-success-wash:#eaf2df;

  --tl-status-warning:     #e2bd16;
  --tl-status-warning-ink: #8a740d;
  --tl-status-warning-wash:#f8f0d2;

  --tl-status-danger:      #b03a2e;
  --tl-status-danger-ink:  #9e3327;
  --tl-status-danger-wash: #f8e4e1;

  --tl-status-info:        #0397d5;  /* default.css span.entityCount default  */
  --tl-status-info-ink:    #0f6a94;
  --tl-status-info-wash:   #e2eff4;

  /* ==========================================================================
     3. REALM  — seven entity types, two values each.
     --------------------------------------------------------------------------
     TINT  = the badge/chip FILL. These are the established identity colours,
             carried over verbatim from default.css:898-926 (`span.entityCount.*`)
             so the app still looks like itself. They take BLACK text, except
             Wells which takes white.
     SHADE = derived from the same hue, dark enough for icon strokes, 1px
             borders, rail accents and active state. NEW in this commit.

     WHY THE SHADES SIT AT DIFFERENT CONTRAST TARGETS (7.64:1 down to 3.40:1)
     rather than all at the AA minimum: equal contrast means equal luminance,
     which means the seven become IDENTICAL IN GREYSCALE — exactly what colour
     vision deficiency leaves a user with. Spreading them across a luminance
     ladder (Unit 26% -> Instrument 9%) preserves separation on the one axis
     CVD cannot destroy. Do not "tidy" these to a uniform ratio.

     ⚠ COLOUR IS NEVER THE SOLE CARRIER. Seven categorical hues cannot be made
     colour-blind-safe — simulation still collides Contract/Tract and
     Tract/Party (tints), and Contract/Unit and Party/Wells (shades). Every
     realm marker MUST pair its colour with the realm ICON and its NAME. The
     colour makes a known realm faster to spot; it is never decoded alone.

     ⚠ THESE VALUES ARE ALSO BAKED INTO GENERATED SVG FILES. The realm markers
     at /site_images/icons/realm/*.svg are composed from these tokens plus the
     monochrome glyphs in that folder's glyph/ subdirectory. They are flat files
     rather than CSS masks because 7 rules put the icon on an <input> (replaced
     elements take no pseudo-element) and 128 call sites use it as a direct
     <img src>; neither can be masked by the host document.

     SO: EDITING A TINT OR SHADE HERE IS HALF THE JOB. Re-run
         python TotaLand/tools/build-realm-icons.py
     and commit the regenerated SVGs, or the markers will silently disagree
     with the tokens. This file remains the single source of truth; the script
     reads FROM here.
     ====================================================================== */

  /* Contract — amber */
  --tl-realm-contract-tint:    #e6b159;
  --tl-realm-contract-shade:   #9e6b18;  /* 4.60:1 on white */

  /* Tract — green */
  --tl-realm-tract-tint:       #8dce8d;
  --tl-realm-tract-shade:      #379137;  /* 3.99:1 */

  /* Party — dusty pink */
  --tl-realm-party-tint:       #daafbe;
  --tl-realm-party-shade:      #b5456c;  /* 5.21:1 */

  /* Instrument — blue */
  --tl-realm-instrument-tint:  #d3e9ff;
  --tl-realm-instrument-shade: #0052a5;  /* 7.64:1 */

  /* Unit — yellow. NOTE: 3.40:1 clears the 3:1 NON-TEXT threshold for icons
     and borders but NOT the 4.5:1 text threshold. Pure yellow cannot reach
     4.5:1 without turning olive and losing the identity, so this shade is
     icon-and-border only. Never set text in it. */
  --tl-realm-unit-tint:        #ffff4f;
  --tl-realm-unit-shade:       #998d0a;  /* 3.40:1 — non-text use only */

  /* Wells — neutral grey. The only achromatic realm, and the only tint that
     takes WHITE text (7.00:1) rather than black. */
  --tl-realm-wells-tint:       #595959;
  --tl-realm-wells-shade:      #5f5f5f;  /* 6.39:1 */

  /* Abstract — violet. NEW: this realm was #ffffff, i.e. literally the page
     colour at 1.00:1, so its badge was invisible. Violet was chosen by
     sweeping hue for maximum worst-case separation from Party (the nearest
     hue): it clears Party by >=73 in normal, protanopia AND deuteranopia, and
     it resolves Instrument/Abstract, which were confusable at NORMAL vision.

     ⚠ DO NOT lighten this tint to match its siblings' weight. The paler
     family-matched variant (#c0b5e3, 1.92:1) was tested and collapses into
     Party at d=37 — two pale desaturated adjacent hues converge. Its slightly
     heavier weight is the price of being distinguishable, and the family is
     not uniform anyway (Wells sits at 7.00:1). */
  --tl-realm-abstract-tint:    #a289ea;
  --tl-realm-abstract-shade:   #7c49ba;  /* 5.97:1 */

  /* ==========================================================================
     PROJECT — an ENTITY MARKER, deliberately NOT a realm.
     --------------------------------------------------------------------------
     A project is the CONTAINER the seven realms live in, not an eighth peer, so
     it is named --tl-entity-* rather than --tl-realm-* and the realm set stays
     closed at seven. It is drawn from the BRAND axis for the same reason: an
     eighth distinct hue would read as another realm, whereas brand blue reads
     as the app's own structure.

     Note this is a deliberate, documented borrow across the brand/realm line —
     the one place the three axes touch. It is safe because a project marker
     never indicates status and never appears in a realm legend.

     THE CHIP IS DARK AND TAKES A WHITE GLYPH, like Wells and unlike the five
     pale realms. That is the point: Project should not look like a realm.

     MEASURED: 6.16:1 on white (white glyph on it is the same 6.16:1). Its
     worst-case separation from a realm chip is 49, against Wells, under
     deuteranopia — both being dark mid-tones that desaturate toward grey.
     That is inside the band already shipped and documented above
     (Tract/Party is 46), and the folder glyph is nothing like a derrick.
     Deepening the blue was tested and is WORSE before it is better: #2a5474 and
     #24485f both fall to 29 because they pass straight through Wells' own
     luminance, and it only beats 49 at #193448, which is near-black and throws
     the brand away. Left at brand. */
  --tl-entity-project-tint:    #346689;
  --tl-entity-project-shade:   #2a5474;

  /* ── Four more non-realm markers (2026-09-01) ───────────────────────────────
     The realm set stays CLOSED AT SEVEN. These are entity- prefixed for the same
     reason Project is: they are not realms, and giving them realm names would
     reopen a set the rest of the app treats as fixed.

     CONTRACT PARTY reuses the party tint with the CONTRACT shade as its border.
     That is not invention — it is what the existing contractParty.png already
     does: sampling it gives #daafbe (the party tint, 132px of it) with #e6b159
     (the contract tint) as its accent. So the icon already said "a party, in a
     contract", and the chip says the same thing with the border instead. It
     reads as a Party at a glance, which is correct, and its glyph (two people,
     not one) and its border separate it up close.

     CONTRACT PARTY PAYMENT goes gold rather than pink. The existing PNG is a
     washed pink-grey that reads as a dimmer ContractParty; money is the thing
     that distinguishes it, and gold is the one unused hue on the brand axis.

     REPORT takes the cyan accent and RECENT the slate: both are cross-cutting
     VIEWS rather than records, so they sit on the neutral/secondary end of the
     axis instead of claiming an entity hue. */
  --tl-entity-report-tint:     #c9dde6;
  --tl-entity-report-shade:    #3e7d97;   /* = --tl-accent-ink, 4.6:1 */

  --tl-entity-recent-tint:     #ccd2d6;
  --tl-entity-recent-shade:    #44555f;   /* = --tl-slate, 7.75:1 */

  --tl-entity-contractparty-tint:  #daafbe;   /* party tint, deliberately */
  --tl-entity-contractparty-shade: #9e6b18;   /* contract shade — "in a contract" */

  --tl-entity-contractpartypayment-tint:  #f0d98a;
  --tl-entity-contractpartypayment-shade: #8a6d0f;

  /* ── GIS MAP (2026-09-29, user request) ──────────────────────────────────────
     The project's GIS map links in the sub-menu (the "GIS Map" pill, the "GIS
     Maps" menu and its entries), which wore a 16px raster picture -- the only
     non-SVG marker left in the row. Not a realm: a TOOL, like Report and Recent
     are views, so entity- prefixed and the realm set stays closed at seven.

     SIENNA, AND DARK WITH A WHITE GLYPH like Wells and Project. The pale-tint
     space is full: twelve chips already, and every pale candidate measured
     (teal, sea, mint, coral, orange, sky, peach, lime) fell within 3-12 of an
     existing tint under protanopia or deuteranopia. Sweeping hue, lightness and
     saturation for the largest worst-case separation (CIELAB, normal /
     protanopia / deuteranopia against every realm and entity tint) put a
     saturated royal blue first -- rejected, it shouts against the muted family --
     and, among muted hues, sienna first: worst case 37.5 (Contract, under
     deuteranopia), better than most of the shipped pairs. Redder variants lost
     separation to Wells under protanopia and drifted toward --tl-status-danger
     (#b03a2e); this stays brown, and it reads as a map pin.
     The glyph is a MAP PIN, deliberately not a folded map: that is the Map
     Builder maps' icon (mapBuilderIcon.svg), and the two kinds of map must stay
     told apart. White glyph on the chip: 6.45:1. */
  --tl-entity-map-tint:        #87521d;
  --tl-entity-map-shade:       #6b4116;

  /* ── ON DEMAND PLATS (2026-09-29, user request) ──────────────────────────────
     The plat drawings a record's "On Demand Plats" link opens (and the plat
     generator's own heading), which wore map.png -- a second raster map picture.
     A tool, like the GIS map: entity- prefixed, realm set closed at seven.

     BLUEPRINT INDIGO, DARK WITH A WHITE GLYPH, measured the same way as the map
     chip (sienna now among the chips it must clear). Muted greens, teals, olives
     and plums all fell to 19-28 against Tract, Wells, Party, Contract or the map
     chip; deep indigo measured best: worst case 35.1 (Abstract, under
     protanopia), level with the map chip's 37.5, and apart from Project's steel
     blue and Abstract's violet at normal vision. Blueprint is also what a plat
     IS -- a survey drawing. The saturation is held below the sweep's peak
     (0.55, not 0.7) so it sits with the muted family instead of glowing.
     The glyph is a SHEET WITH A PARCEL DRAWN ON IT: the plat, not the tract
     (whose glyph is the bare parcel) and not the map pin. White on the chip:
     10.75:1. */
  --tl-entity-plat-tint:       #2c2f96;
  --tl-entity-plat-shade:      #20226e;

  /* ── THE SUPPORT AREA (2026-10-05, user request) ─────────────────────────────
     The page headings of the Support area -- Resources, the Help Desk and the
     Notification Log (support-area.css) -- wore three unrelated raster pictures
     (siteResources.png's tiles, support.png's monitor, email.png's envelope).
     They take the symbols of the area's tabs now, as chips: an open book
     (resources.svg), a lifebuoy (helpdesk.svg) and a bell (notification.svg) --
     site-wide, at the owner's request: the book and the lifebuoy stand wherever
     the tiles and the monitor did (default.css).
     ONE colour for the three, the area's, the way its tabs share one look; the
     symbol says which page. An area, not a realm: entity- prefixed.

     AUBERGINE, DARK WITH A WHITE GLYPH -- the owner's choice among six measured
     options (2026-10-05), replacing a first-pass safety green they did not like.
     The space is full (fourteen chips): the sweep (CIELAB worst case, normal /
     protanopia / deuteranopia, against every tint) found nothing above ~22, and
     each alternative traded distinctness for fit. Brand blue IS the Project
     chip (0.0) -- and the page's "Resources" link stands beside it on project
     pages; accent blue fell to 9.0 and petrol to 10.8 against Project, slate to
     13.4 against Wells, ink navy (21.6) reads as Plat's indigo at a glance.
     Aubergine measured 14.5 (Project, under deuteranopia), borrows no other
     chip's colour family -- Abstract's violet is a pale tint, this a deep one --
     and stays quiet beside the muted family. White on the chip: 10.41:1. */
  --tl-entity-support-tint:    #5b2f5f;
  --tl-entity-support-shade:   #432246;

  /* ProjectAssignment and PipelineSegments are the other two RealmAccess
     members (Domain/Entities/_Enums.cs). Neither gets a token:
       ProjectAssignment — an access realm, not a navigable entity; never
                           renders a badge. Absence here is deliberate.
       PipelineSegments  — pipeline management will not be active in the port.
     The realm set is closed at seven. */

  /* ==========================================================================
     3b. PLAN  — the four subscription plans' identity colours (2026-10-04).
     --------------------------------------------------------------------------
     The public pricing page's plan columns (Views/Public/PricingUpdated.cshtml,
     main.css .plan-lite / .plan-billing / .plan-basic / .plan-advanced),
     carried over verbatim so the Subscription hub's plan cards match the page a
     customer bought from (owner addendum, SPEC-B §4.4a). Read Only with Billing
     has no public column and wears Billing's grey.

     A plan colour is IDENTITY, never status and never the only signal: it is
     the card's top bar and outline, and the plan's NAME is always printed
     beside it. Basic's green is not Success; Lite's amber is not Warning.

     The fills are borders and bars. Lite (2.27:1), Billing (3.49:1) and Basic
     (3.52:1) FAIL as text on white, so any text or icon in a plan colour uses
     its -ink partner, which passes 4.5:1 on White, Sunken and Brand Wash (a
     selected card). Advanced's navy passes as it is (8.73:1). */
  --tl-plan-lite:              #e39f24;
  --tl-plan-lite-ink:          #8a5f12;  /* 5.64:1 on white */
  --tl-plan-billing:           #8a8989;
  --tl-plan-billing-ink:       #686767;  /* 5.64:1 */
  --tl-plan-basic:             #54966d;
  --tl-plan-basic-ink:         #3f7152;  /* 5.68:1 */
  --tl-plan-advanced:          #104d82;
  --tl-plan-advanced-ink:      #104d82;  /* 8.73:1 */

  /* ==========================================================================
     4. SURFACE & TEXT
     ====================================================================== */

  --tl-surface:            #ffffff;
  --tl-surface-sunken:     #f3f6f7;  /* bands, table heads, inset boxes; the page itself stays white */
  --tl-surface-raised:     #e8eef1;  /* table headers, filter bars          */
  --tl-surface-hover:      #f0f4f6;

  --tl-text:               #14212a;
  --tl-text-muted:         #4e626c;  /* 6.39:1 — AA for body text           */
  --tl-text-subtle:        #6b7f89;  /* 4.18:1 — under AA for small text; large text/non-text only */
  --tl-text-inverse:       #ffffff;

  --tl-border:             #d2dde2;
  --tl-border-strong:      #b3c3ca;
  --tl-focus-ring:         #346689;  /* keyboard focus — 2px, never removed */

  /* ==========================================================================
     5. TYPE SCALE
     --------------------------------------------------------------------------
     IBM Plex Sans for interface and body, IBM Plex Mono for identifiers, counts
     and currency. One family for both, so the numeric columns come from the same
     design as the interface rather than a borrowed mono.

     REPLACED FIRA SANS, which shipped first and was too narrow -- measured at
     -9.7% against the Helvetica Neue stack the app used before, where Plex is
     -2.5%. The full comparison and the reasoning are in css/fonts.css. Do not
     "improve" this to Source Sans 3: it looks like the obvious humanist UI pick
     and measures -14.0%, far narrower than the face being complained about.

     SELF-HOSTED, see css/fonts.css. Not the Google CDN: this is an internal app
     that must work on restricted corporate networks, and a CDN font that fails
     to load silently downgrades every page to the fallback.

     THE FALLBACK STACK IS LOAD-BEARING, not decoration. font-display:swap means
     every page paints in the fallback first, so it has to be metrically close
     enough that the swap is not a lurch. Helvetica Neue/Arial stay in the stack
     for exactly that, and they are also what the app rendered in before.

     SIZES ARE ABSOLUTE PX, NOT em/rem, and deliberately so: default.css sets
     `body { font-size: 73% }`, so any relative unit here would compound
     against a percentage base that varies by nesting depth. Density is not a
     stated pain, so nothing here is intended to change current sizing — the
     scale exists so NEW chrome stops inventing one-off sizes.
     ====================================================================== */

  --tl-font:               "IBM Plex Sans", "Helvetica Neue", Calibri, Geneva, Arial, Helvetica, sans-serif;
  --tl-font-mono:          "IBM Plex Mono", ui-monospace, Consolas, "Courier New", monospace;

  --tl-text-2xs:           10px;
  --tl-text-xs:            11px;   /* current chrome baseline               */
  --tl-text-sm:            12px;
  --tl-text-base:          13px;
  --tl-text-md:            14px;
  --tl-text-lg:            16px;   /* form inputs — avoids mobile auto-zoom */
  --tl-text-xl:            20px;
  --tl-text-2xl:           24px;
  --tl-text-3xl:           32px;

  --tl-weight-normal:      400;
  --tl-weight-medium:      500;
  --tl-weight-semibold:    600;
  --tl-weight-bold:        700;

  --tl-leading-tight:      1.25;
  --tl-leading-normal:     1.5;
  --tl-leading-loose:      1.7;

  --tl-tracking-label:     .08em;  /* uppercase micro-labels only */

  /* ==========================================================================
     6. SPACING  — 4px base, dense end of the scale to suit data-heavy pages.
     ====================================================================== */

  --tl-space-1:            2px;
  --tl-space-2:            4px;
  --tl-space-3:            8px;
  --tl-space-4:            12px;
  --tl-space-5:            16px;
  --tl-space-6:            24px;
  --tl-space-7:            32px;
  --tl-space-8:            48px;

  /* ==========================================================================
     7. ELEVATION & LAYERING
     --------------------------------------------------------------------------
     The z-index values match what the app already uses so nothing re-stacks:
     jQuery UI dialogs sit at 100000+ and the tooltip override in bs5-custom.css
     is 100010, so the site chrome has to stay well below both.
     ====================================================================== */

  --tl-shadow-sm:          0 1px 2px rgba(20, 33, 42, .06);
  --tl-shadow-md:          0 1px 2px rgba(20, 33, 42, .06), 0 4px 14px rgba(20, 33, 42, .05);
  --tl-shadow-lg:          0 8px 28px rgba(20, 33, 42, .12);

  --tl-z-base:             1;
  --tl-z-sticky:           100;    /* sticky table headers / totals rows     */
  --tl-z-rail:             200;    /* the unified left rail                  */
  --tl-z-topbar:           300;
  --tl-z-overlay:          400;    /* rail drawer scrim on tablet            */

  /* Sticky offset. WCAG 2.2 AA `focus-not-obscured` (2.4.11): a sticky header
     must not cover the keyboard-focused element. Any scroll container with a
     sticky header sets `scroll-padding-top` from this. Without it, tabbing
     through a billing grid parks the focused row underneath the header. */
  --tl-sticky-offset:      44px;
}
