/* ============================================================
   TRUE FLOW POOL SOLUTIONS — brand/tokens.css
   CANONICAL design tokens (colors, type, scale) for EVERY
   surface: website + ops (PoolBooks) + inspector (PoolInspector)
   + crew (PoolCrew) + dock (PoolDock) + any future app.

   SINGLE SOURCE OF TRUTH. The /deploy skill copies the whole
   brand/ folder into each app on every deploy, so a brand color
   or token can only ever be changed HERE. Apps consume these via
   a thin per-app alias block (legacy variable names -> these
   tokens) so existing component CSS keeps working unchanged.

   The core palette block is lifted verbatim from the live
   website's styles.css :root (the production source of truth),
   including the WCAG-AA accessibility fixes shipped 2026-06-24
   (--text-muted darkened to #456f83).

   South Beach Boutique — Miami pool water at noon. Vibrant aqua
   + cyan ocean blues, warm sand neutrals, sun-kissed gold.
   ============================================================ */
:root {

  /* ===== CORE PALETTE (verbatim from website styles.css :root) ===== */

  /* Core brand */
  --tf-cerulean:     #0b4f6c;   /* deep ocean — hero/dark sections */
  --tf-navy:         #062840;   /* midnight — footer */
  --tf-aqua-soft:    #c8eef8;   /* shallow water shimmer */
  --tf-sand:         #f5ead8;   /* warm beach sand */
  --tf-saffron:      #c9a96e;   /* sun-kissed gold */
  --tf-white:        #ffffff;

  /* Blues — vivid, coastal, saturated */
  --tf-cerulean-700: #083d56;
  --tf-cerulean-600: #0d6080;
  --tf-cerulean-500: #1a7fa0;
  --tf-aqua:         #7dd4ea;   /* bright sky/lagoon blue */
  --tf-aqua-600:     #2db5d5;   /* vivid mid aqua */
  --tf-ocean:        #1a9fc4;   /* signature pool-water cyan */
  --tf-ocean-700:    #0f7a9a;   /* deeper ocean - AA on white/cream ONLY, see below */
  --tf-ocean-800:    #0e6f8f;   /* READABLE ocean text. Use this for ocean-coloured
                                   WORDS on any pale ocean tint. --tf-ocean-700 is
                                   4.92:1 on white but 4.31-4.49:1 on every tint in
                                   this file (--tf-aqua-050, --app-bg-blue,
                                   --status-info-soft, --ocean-50), i.e. it fails AA
                                   on exactly the backgrounds it keeps being paired
                                   with. Eight such sites were live across admin and
                                   crew on 2026-08-15. This member is 4.99-5.69:1 on
                                   all of them. Same lesson as the --status-*-text
                                   members: the gap was that nothing named "ocean
                                   text you may actually read", so every consumer
                                   reached for -700 and quietly shipped sub-AA. */
  --tf-aqua-050:     #e4f7fc;   /* airy aqua wash */

  /* Boutique accent — INTERNAL APPS ONLY (PoolBooks, PoolCrew, PoolDock).
     Declared by "True Flow Brand Style Guide - Final.html"; scoped off the
     customer website by Meesha 2026-08-25. The website cannot inherit these
     by accident (it only pulls the --status- family from this file, see
     True Flow Website/sync-brand-tokens.py), and that script also FAILS THE
     BUILD if any of these three appear in a website file, in either hex or
     rgba() notation.

     The guide's rule is "1-2 moments per page max", and the cap IS the
     meaning: coral says "this is the most important thing on this screen".
     Two coral things on one screen means neither is. It is NOT a status
     colour and never replaces --status-danger/-warning/-success — red means
     something is WRONG, coral means START HERE, and the thing coral points
     at is usually perfectly healthy. Spending red on "look here" is how red
     stops working when a charge actually fails.

     --tf-coral-text exists because --tf-coral does not pass AA as words:
     3.45:1 on white, and 3.45:1 under white as a button fill, so it fails in
     BOTH directions. Same lesson as --tf-ocean-800 above — ship a colour
     without naming its readable member and every consumer reaches for the
     base and quietly ships sub-AA. Use -text for any words and for any solid
     fill carrying white text (5.91:1 on white, 5.82:1 on --bg-page, 5.06:1
     on --tf-coral-bg, 4.97:1 on --tf-sand); use the base for rules, borders,
     icons and tints only. */
  --tf-coral:        #d9694e;   /* Art Deco coral — fills, rules, borders, icons */
  --tf-coral-bg:     #faeae6;   /* coral wash — the tint coral text sits on */
  --tf-coral-text:   #a8452c;   /* READABLE coral. Any words, any solid fill under white. */

  /* Neutrals — ocean-tinted (website ramp), warm not cool grey */
  --tf-ink-900:      #062840;
  --tf-ink-700:      #1a4a62;
  --tf-ink-500:      #4d7d93;
  --tf-ink-400:      #7fa3b5;
  --tf-ink-300:      #a8c8d8;
  --tf-ink-200:      #cce3ec;
  --tf-ink-100:      #e3f2f7;
  --tf-ink-050:      #f2fafc;

  /* Semantic roles (website) */
  --bg-page:         #fffdf9;   /* warm cream white, not clinical */
  --surface-trust:   #c8eef8;   /* aqua wash band */
  --surface-deep:    #0b4f6c;   /* deep ocean sections */
  --surface-deepest: #062840;   /* footer */
  --text-strong:     #062840;   /* headlines */
  --text-body:       #1a4a62;   /* body — ocean-tinted, not grey */
  --text-muted:      #456f83;   /* secondary — darkened from #4d7d93 for WCAG AA (4.5:1 on white/sand/aqua) */
  --text-on-dark:    #d8f2fb;   /* on deep/midnight sections */
  --text-link:       #1a9fc4;   /* cyan links */
  --cta-bg:          #c9a96e;   /* warm gold */
  --cta-bg-hover:    #a8844e;   /* gold hover */
  --cta-text:        #ffffff;   /* white on gold */

  /* ===== CROSS-SURFACE TOKENS (apps + design-system.md) =====
     The apps (ops/inspector/crew/dock) use a cooler neutral ramp
     and a set of status colors the marketing site doesn't. These
     are canonical brand values too (per brand/design-system.md)
     and live here so apps can source them from one file. The cool
     ramp (--app-*) and the ocean ramp (--tf-ink-*) intentionally
     COEXIST — do not collapse them without a previewed restyle.
     ----------------------------------------------------------- */

  /* Cool neutral ramp — apps (design-system.md "warm sand, not grey") */
  --app-text-primary:   #1a2332;   /* primary body text (dock --text) */
  --app-text-secondary: #6b7c8d;   /* muted labels (dock --muted) */
  --app-border:         #e8e2d8;   /* warm border/divider (dock --line) */
  --app-border-soft:    #f0ece2;
  --app-bg-blue:        #e6f3f8;   /* pale ocean secondary fill */
  --app-neutral-bg:     #f2f0ec;   /* TRUE neutral chip fill — no ocean cast */
  --app-neutral-border: #e0dcd4;   /* its 1px rule */
  --app-text-neutral:   #4a5761;   /* READABLE true grey. 6.3:1 on --app-neutral-bg.

     Added 2026-09-01. The ramp above had no genuinely neutral member: every
     "grey" here is either ocean-tinted (--tf-ink-*, --text-muted #456f83) or
     warm sand (--app-border #e8e2d8). That is correct for the brand and wrong
     for the one job that actually needs grey — saying "this is not a status,
     it does not apply". A "Not present" condition pill built from --tf-ink-050
     plus --text-muted read as pale BLUE and sat in the same hue family as the
     info/typical-wear pill beside it, so the one chip meaning "no verdict here"
     looked like a verdict. Same lesson as --tf-ocean-800 and the --status-*-text
     members: nothing named the value people needed, so every consumer reached
     for the nearest ocean token and shipped the wrong meaning. */
  --app-surface:        #ffffff;   /* card surface */

  /* Status colors — shared across apps.

     WHICH MEMBER FOR WHICH USE:
       --status-<s>         FILL / ICON only. Never words.
       --status-<s>-soft    background tint behind a status chip or banner.
       --status-<s>-border  1px rules and chip borders only. Never words.
       --status-<s>-text    ANY text — a label, a message, an inline status word.
       --status-<s>-hover   interactive hover state of the base fill.

     The base values do NOT pass WCAG AA as text on their own -soft background
     (#16a34a on #e6f5ee is 2.93:1, #d97706 on #fef3e0 is 2.90:1, #dc2626 on
     #fee2e2 is 3.95:1). The -text member is the same hue darkened to clear
     4.5:1, and is the only member that may carry words.

     Added 2026-07-30 after the visit report page was found defining its own
     private copy of this palette, having drifted on exactly these two values.
     --status-danger-text and -border added 2026-08-15: the family was missing
     both, so consumers who needed readable red words or a red rule had nothing
     to reach for and hand-picked their own — Booking App/admin/index.html alone
     had accumulated #b91c1c, #b52b2b, #b3261e, #a11d1d and #c0392b, and
     Booking App/dock/ was rendering #c0392b through an undefined `var(--red)`.
     A missing member generates its own drift; every status family must carry
     the full set. The -border values are derived, not picked: each sits at the
     same contrast against its own -soft (success 1.42:1, warning 1.36:1,
     danger 1.39:1, info 1.39:1), which is what makes a 1px rule read with
     equal weight across the four families.

     --status-info-border and -text added 2026-08-15, closing the last
     incomplete family. The base MOVED to --tf-ocean the same day, and that is
     the more interesting half.

     It had been #2563eb, a generic blue at hue 221 sitting against a hue-194
     soft -- a 28-degree gap where the other three families sit within 10. The
     tempting fix was to re-tint the soft toward the blue. The codebase had
     already voted the other way and nobody had noticed: the base's ONLY
     consumer was `--info: var(--status-info, var(--ocean))`, whose fallback was
     already ocean, and every real use of -soft painted its text with
     --ocean-700. The generic blue was vestigial -- declared, mirrored, and
     consumed by nothing. Read the code that USES a token to learn what the
     token means.

     Moving to ocean did three things at once: it made the family hue-coherent
     (193 vs 194), it put info on an actual brand colour instead of a Tailwind
     default, and it FIXED a live AA failure -- six surfaces were painting
     --ocean-700 (#0f7a9a) on -soft at 4.31:1, below the 4.5 minimum, because
     they had reached for the ocean the token set did not offer them. It also
     removed a carve-out: the old base was 4.53:1 on its own -soft, i.e. it
     passed AA by 0.03 and had to be excluded from the "fills must not pass"
     test assertion. Ocean is 2.71:1, so info rejoins that rule and the check
     covers all four families with no exception.

     WATCH: the base is now the same hue as links and other interactive
     affordances. That is acceptable because only ONE status moved onto ocean --
     success/warning/danger stay green/amber/red, so this is not a repeat of the
     2026-07-08 bug where several distinct statuses collapsed into one blue.
     Do not move a second family onto a brand hue without revisiting that. */
  /* ╔════════════════════════════════════════════════════════════════════╗
     ║  COLOUR LAW — WHEN to use these, not just what they contrast at.   ║
     ╚════════════════════════════════════════════════════════════════════╝
     Added 2026-09-16. Everything above this point documents contrast ratios
     and hue coherence in detail and says NOTHING about when to reach for
     which family, which is exactly why the same mistake kept recurring: a
     prototype painted "ready to collect" red and "comped" amber, neither of
     which is anything to be alarmed about. A token set gaining a member
     people need is only half the fix; the other half is saying what it is FOR.

     FIVE STATES. ONE COLOUR EACH. THE SAME ON EVERY SCREEN.
     A thing does not change colour because you opened a different page. If it
     is red on the Dashboard it is red on Financial. Colour describes the
     STATE. What differs between pages is whether an ACTION is attached, and
     that is carried by the presence of a button, not by the colour.

       success  green      done, the money is in       collected, billing normally
       info     blue       an action is needed NOW,    ready to collect
                           and nothing is wrong
       neutral  warm grey  a holding pattern,          not due yet, comped
                           nothing to do
       warning  amber      ageing, heading toward      owed 20 days, estimate
                           a problem                   about to expire
       danger   red        broken, or money at risk,   stuck charge, failed
                           a human must act            renewal

     NEUTRAL IS THE DEFAULT. A status earns colour by being good news or by
     needing a person. "We are waiting and that is correct" is a real state,
     not a leftover, and it is what --app-neutral-* is for.

     BLUE IS A LEGITIMATE STATUS, with one guardrail. The WATCH note above
     warns against moving a SECOND family onto a brand hue, because on
     2026-07-08 several distinct statuses collapsed into one blue and it
     stopped meaning anything. That is a warning about ambiguity, not a ban:
     --status-info-text exists at 4.99:1 precisely so info can be read as a
     status. GUARDRAIL: only ONE status may ever be blue. "Needs an action"
     owns it. A second action-ish state does not also get blue; either it is
     the same state, or it needs a different colour.

     HERO NUMBERS ARE FACTS, NOT STATUSES. Leave a big headline figure in
     default ink and let the pills underneath carry the colour. A total is
     usually a MIX of states (September's $243.50 owed is $74.50 ready to
     collect plus $169 not yet due), so no single status colour is honest
     about it.

     Consumers: .tf-status-{ok|act|wait|warn|alert} in PoolBooks' app.css.
     Full reasoning: True Flow Pools/Booking App/INFORMATION-ARCHITECTURE-SPEC.md
     ──────────────────────────────────────────────────────────────────────── */
  --status-danger:         #dc2626;
  --status-danger-hover:   #b91c1c;
  --status-danger-soft:    #fee2e2;
  --status-danger-border:  #eebaba;   /* 1.39:1 on -soft */
  --status-danger-text:    #b91c1c;   /* 5.30:1 on -soft */
  --status-warning:        #d97706;
  --status-warning-soft:   #fef3e0;
  --status-warning-border: #f0d08a;
  --status-warning-text:   #9a5b00;   /* 4.94:1 on -soft */
  --status-success:        #16a34a;
  --status-success-soft:   #e6f5ee;
  --status-success-border: #a3d9b8;
  --status-success-text:   #157a3a;   /* 4.81:1 on -soft */
  --status-info:           #1a9fc4;   /* = --tf-ocean; fill/icon only, 2.71:1 on -soft */
  --status-info-soft:      #e2f3f8;
  --status-info-border:    #b2d3dc;   /* 1.39:1 on -soft */
  --status-info-text:      #0e6f8f;   /* 4.99:1 on -soft; = --tf-ocean-800 (asserted in brand-colors_test) */

  /* Effects */
  --shadow-sm:       0 1px 3px rgba(6,40,64,0.08), 0 1px 2px rgba(6,40,64,0.05);
  --shadow-md:       0 6px 16px -6px rgba(11,79,108,0.18), 0 2px 6px -2px rgba(11,79,108,0.10);
  --shadow-lg:       0 18px 40px -12px rgba(11,79,108,0.24), 0 6px 14px -8px rgba(11,79,108,0.12);
  --radius-sm:       6px;
  --radius-md:       10px;
  --radius-lg:       16px;
  --radius-pill:     999px;
  --ease-standard:   cubic-bezier(0.4,0,0.2,1);
  --duration-base:   200ms;

  /* Spacing — 4px base grid (apps) */
  --space-1:  4px;   --space-2:  8px;   --space-3: 12px;  --space-4: 16px;
  --space-5: 20px;   --space-6: 24px;   --space-8: 32px;  --space-10: 40px;
  --space-12: 48px;

  /* Typography */
  --font-display:    "Libre Baskerville", Georgia, serif;
  --font-sans:       "Inter", system-ui, -apple-system, sans-serif;

  /* ── Type scale ───────────────────────────────────────────────────────────
     Added 2026-08-09. This file's own header has always claimed to be canonical
     for "colors, TYPE, scale" and carried no type scale at all, so every surface
     picked its own sizes. The website drifted UP and stayed readable; PoolBooks
     drifted DOWN and became hard to read: ~90 hand-picked sizes between .62rem
     and .82rem (10px-13px), where the WEBSITE'S SMALLEST body text is .88rem and
     its body is 1.0625rem. The admin's ceiling was roughly the website's floor.

     Values are derived from the website's live, approved usage rather than
     invented, so adopting them moves an app toward the look we already ship.

     --fs-eyebrow is the ONLY member below --fs-xs, and it is not a small-text
     licence: it is for UPPERCASE, letter-spaced section labels ONLY, where the
     caps make up the lost x-height. Never use it for a sentence.
     --fs-xs is the FLOOR for anything a person has to read or act on. If a
     layout only fits by going under it, the layout is wrong, not the type. */
  --fs-eyebrow:  0.72rem;   /* 11.5px — uppercase tracked labels only */
  --fs-xs:       0.8125rem; /* 13px — metadata, badges, timestamps. Hard floor. */
  --fs-sm:       0.875rem;  /* 14px — secondary text, dense table cells */
  --fs-base:     0.9375rem; /* 15px — default UI body inside apps */
  --fs-md:       1.0625rem; /* 17px — website body copy, long-form reading */
  --fs-lg:       1.25rem;   /* 20px — card titles, panel headings */
  --fs-xl:       1.5rem;    /* 24px — section headings */
  --fs-2xl:      2rem;      /* 32px — page titles */
  --fs-3xl:      2.5rem;    /* 40px — hero / big stat numbers */

  --lh-tight:    1.25;      /* headings */
  --lh-snug:     1.45;      /* dense UI rows */
  --lh-base:     1.6;       /* body */
  --lh-loose:    1.75;      /* long-form marketing copy */

  /* ── Icon sizes ───────────────────────────────────────────────────────────
     Icons were being drawn at 12-14px next to 10-11px text, which is why the
     carets and chevrons read as decoration rather than as controls. --icon-md is
     the default; anything a finger or cursor is meant to hit uses --icon-md or
     larger and gets --tap-min of clickable area regardless of the glyph size. */
  --icon-sm:     16px;      /* inline, decorative, inside a line of text */
  --icon-md:     20px;      /* DEFAULT — buttons, list affordances, carets */
  --icon-lg:     24px;      /* primary actions, empty states */
  --icon-xl:     32px;      /* feature/marketing */
  --tap-min:     44px;      /* minimum hit target, per WCAG 2.5.5 */


  /* ── KPI PACE PALETTE — INTERNAL APPS ONLY, NEVER THE WEBSITE ──────────
     Five states for the PoolBooks KPI board (and any internal dashboard
     that scores a metric against a target). Colour here means PACE and
     nothing else: how a number is tracking, not how a person should feel
     about it. That is a different job from --status-*, which says whether
     a THING is fine, and the two must not be substituted for each other.
     A late metric is "behind", not "danger"; a metric with no target is
     stone, not grey-because-disabled.

     This family is deliberately NOT synced to the marketing site. The
     website sync only pulls --status- and --fs- (see FAMILIES in
     True Flow Website/sync-brand-tokens.py), so a pace colour cannot
     reach a customer-facing page by accident -- the same guard that keeps
     coral off the site. If that FAMILIES tuple is ever widened, --kpi-
     must stay out of it.

     Each state is a three-slot family, same shape as --status-:
       base  the bar/accent fill, strong enough to read at 3px
       -text the ONLY member that may carry words. Derived to clear
             4.5:1 on BOTH its own -soft tint and white, so a number can
             move between a tinted card and a plain table without being
             re-checked. Figures in admin/prototypes/kpi-color-system.html.
       -soft the card tint. Pale enough that four of them side by side
             read as one board rather than as a traffic light.

     --kpi-onpace deliberately repeats --tf-ocean as both base and text:
     "on pace" is the unremarkable case and should look like the brand,
     not like an alert. Picked over a fifth hue for exactly that reason. */
  --kpi-ahead:   #5ea167;   /* beating the target */
  --kpi-ahead-text:  #47794e;
  --kpi-ahead-soft:  #edf5ee;
  --kpi-onpace:  #0b4f6c;   /* tracking to hit it */
  --kpi-onpace-text: #0b4f6c;
  --kpi-onpace-soft: #e6f3f8;
  --kpi-watch:   #bf9540;   /* slipping, still recoverable */
  --kpi-watch-text:  #896b2e;
  --kpi-watch-soft:  #f9f4e9;
  --kpi-behind:  #b5524a;   /* will miss without a change */
  --kpi-behind-text: #ad4e47;
  --kpi-behind-soft: #f7ebe9;
  --kpi-stone:   #8b857c;   /* NO TARGET SET — not a verdict, an absence */
  --kpi-stone-text:  #6b665e;
  --kpi-stone-soft:  #f4f3f1;
  --kpi-track:   #e4e2dd;   /* the unfilled portion of a progress bar */

  /* Layout & legacy compatibility */
  --max-w:           1120px;
  --border:          rgba(6,40,64,0.10);
  --border-blue:     rgba(26,159,196,0.22);
}
