/* ============================================================================
   Southport Signal — TOKENS & MOTION (wave 5, W5-00)
   ----------------------------------------------------------------------------
   DESIGN.md "2026 interface direction" asks for two things this file provides and
   nothing else in the app may re-invent:

     1. ONE SEMANTIC PALETTE IN THREE MODES — default, dark and road — so a lane
        writes var(--surface) once and gets the right paint in all three.
     2. ONE MOTION SYSTEM — a single easing, three durations, a small set of
        shared keyframes. "No ad-hoc transitions" is a rule for every wave-5 lane.

   HOW IT WORKS WITHOUT EDITING THE APPROVED CSS
   ---------------------------------------------
   app.css Part 1 is the client-approved mockup system and is never edited by
   anyone (WAVE5.md, Lane 0 "Must not touch"). Happily it is already entirely
   variable-driven: --page, --card, --line, --ink, --blue and friends are declared
   in its `:root` block and used by every rule beneath.

   So this file does not fight it — it re-declares those same variables inside
   `html[data-theme="…"]`, whose specificity (0,1,1) beats `:root` (0,1,0) NO
   MATTER WHICH STYLESHEET LOADS FIRST. Every existing view therefore recolours in
   dark and road for free, and nothing in Part 1 changed. If the attribute is ever
   missing (JS failed, or a screenshot tool loaded the page raw) app.css's own
   `:root` values apply and the app looks exactly as it shipped in wave 4.

   The NEW semantic names (--surface, --ink-muted, --accent, --positive …) are
   what wave-5 lanes should write. They are aliases onto the same palette, so the
   two vocabularies can never disagree.

   THE RAIL STAYS NAVY IN EVERY MODE. That is the client's identity and the one
   thing a mode switch must not take away — dark mode deepens it, road mode
   darkens it further for glare, neither replaces it.
   ========================================================================== */

/* ---------------------------------------------------------------- semantics --
   Declared on :root so a lane can use them even before the theme attribute is
   stamped. Values here are the DEFAULT (light) palette; the theme blocks below
   re-point them. `--ink` and `--line` deliberately are not re-declared here:
   app.css already defines both with exactly these meanings and values, and
   letting one win at :root is a load-order coin toss nobody should have to think
   about. Both are set in every theme block, where specificity settles it. */
:root{
  --surface:#fff;            /* panels, cards, the top bar                     */
  --surface-2:#FAFBFC;       /* panel headers, table stripes, inset wells      */
  --surface-3:#F4F6F8;       /* the page behind the panels                     */
  --ink-muted:#6C7B86;       /* secondary sentences, meta lines                */
  --ink-faint:#9AA7B1;       /* timestamps, footnotes — never load-bearing     */
  --accent:#00619F;          /* Southport blue: links, the active rail item    */
  --accent-2:#22B8F0;        /* Southport sky: charts, highlights, focus glow  */
  --positive:#1A7A4C;
  --warning:#A9711A;
  --danger:#B93A2F;

  /* --------------------------------------------------------------- motion ---
     ONE easing and THREE durations, exactly as DESIGN.md specifies. A lane that
     needs a transition uses these; a lane that invents `ease-in-out .25s` is a
     bug, because twenty of those is what makes an app feel homemade. */
  --ease:cubic-bezier(.2,.8,.2,1);
  --dur-1:120ms;             /* a state change you feel: hover, tick, press    */
  --dur-2:200ms;             /* a thing appearing or leaving: toast, sheet     */
  --dur-3:320ms;             /* a list rearranging itself, a view swap         */
  --ease-spring:cubic-bezier(.34,1.4,.5,1); /* list moves only — one spring    */

  /* Floating layers only (DESIGN §7 "Depth without cheapness"): the ban on
     shadows still holds for content panels. Command palette, popovers, sheets
     and toasts may use this one soft ambient shadow. Glass, not plastic. */
  --shadow-float:0 1px 2px rgba(16,28,38,.05),0 18px 44px -16px rgba(16,28,38,.28);
  --blur-float:saturate(140%) blur(10px);

  /* --------------------------------------------------- wave 6: density ---
     WAVE6.md §3. The work surface's four measurements, in one place so a page
     lane writes var(--row-h) and gets the right density in all three modes.

       --row-h        a list row on a desktop pointer (WAVE6 §7: 44px)
       --row-h-touch  the same row where a finger has to hit it
       --list-w       the list column of the split; the pane takes the rest
       --detail-top   where the sticky pane parks: the 54px top bar + the
                      wrap's 18px padding, less a couple of px of overlap

     Nothing above this comment changed. */
  --row-h:44px;
  --row-h-touch:56px;
  --list-w:42%;
  --detail-top:64px;
}

/* ============================================================== DEFAULT ==== */
/* The client-approved paint, restated. Values match app.css Part 1 exactly, so
   stamping data-theme="default" is a visual no-op — which is the point: it is a
   safe place to stand, not a fourth look.

   ONE DELIBERATE EXCEPTION: --faint/--ink-faint. app.css Part 1 still has the
   original #9AA7B1 in its own :root (never edited, per this file's own header
   comment above), but that :root value is only ever the FALLBACK for a page
   with no data-theme attribute at all — this block's (0,1,1) specificity wins
   whenever the attribute is stamped, which the boot script in index.html
   always does. #9AA7B1 measured 2.37:1 on --surface-2 (#FAFBFC) — nowhere
   near the 4.5:1 AA floor for the body-size text most --faint usages actually
   are (timestamps, table headers, footnotes down to 9.5px). #63727F clears
   4.5:1 against BOTH --surface and --surface-2 with a real margin (4.78:1 /
   4.95:1) rather than sitting right on the line. It reads only a shade darker
   than --ink-muted (#6C7B86, itself 4.2–4.4:1 — its own separate, unfixed
   issue) rather than staying lighter, because --ink-muted does not leave
   enough headroom above 4.5 for --faint to sit visibly lighter AND compliant
   at the same time; what still keeps a --faint use quiet next to a
   --ink-muted one is size (--faint is used at 9.5–11.5px almost everywhere)
   and role (footnote, not sentence), not raw luminance distance. Dark and
   road already cleared 4.5:1 on both surfaces before this change (4.66:1 and
   5.53:1 minimum, measured) and are unchanged. */
html[data-theme="default"]{
  color-scheme:light;
  --navy:#002B47;--navyink:#9FBACE;--blue:#00619F;--bluedk:#004E80;--bluetint:#F1F6FA;--sky:#22B8F0;
  --green:#1A7A4C;--amber:#A9711A;--hot:#C1611A;--red:#B93A2F;--violet:#5F4B93;--teal:#0E6E7A;
  --page:#F4F6F8;--card:#fff;--surf2:#FAFBFC;--sand:#FBFAF7;--line:#E2E7EC;--line2:#EDF1F4;
  --ink:#101C26;--ink2:#3B4A55;--mute:#6C7B86;--faint:#63727F;--rule:#E6EBEF;--rule2:#D5DDE3;

  --surface:#fff;--surface-2:#FAFBFC;--surface-3:#F4F6F8;
  --ink-muted:#6C7B86;--ink-faint:#63727F;
  --accent:#00619F;--accent-2:#22B8F0;
  --positive:#1A7A4C;--warning:#A9711A;--danger:#B93A2F;
  --shadow-float:0 1px 2px rgba(16,28,38,.05),0 18px 44px -16px rgba(16,28,38,.28);
  --row-h:44px;--row-h-touch:56px;--list-w:42%;--detail-top:64px;
}

/* ================================================================= DARK ==== */
/* "True navy-black, luminous accents, for early mornings and the demo screen."
   The page is near-black with a blue cast rather than neutral grey — the whole
   product is navy, and a neutral dark mode would read as somebody else's app.
   Accents brighten because #00619F on #0A1219 is unreadable; the hue does not
   move, only the luminance. Every ink/surface pair below clears AA (4.5:1). */
html[data-theme="dark"]{
  color-scheme:dark;
  --navy:#051622;            /* the rail — deepest surface, still navy         */
  --navyink:#9FBACE;
  --blue:#3BA3E8;            /* luminous, same hue family as #00619F           */
  --bluedk:#2A82BE;
  --bluetint:#12283A;
  --sky:#4FC9F5;
  --green:#35B87C;--amber:#D9A441;--hot:#E8873F;--red:#E4685C;--violet:#9B87D6;--teal:#34A9B8;
  --page:#0A1219;--card:#101C26;--surf2:#16242F;--sand:#1A1B17;
  --line:#22333F;--line2:#1C2B36;--rule:#22333F;--rule2:#2E4353;
  --ink:#E6EEF4;--ink2:#B4C6D3;--mute:#8497A5;--faint:#7B8E9C;

  --surface:#101C26;--surface-2:#16242F;--surface-3:#0A1219;
  --ink-muted:#8497A5;--ink-faint:#7B8E9C;
  --accent:#3BA3E8;--accent-2:#22B8F0;
  --positive:#35B87C;--warning:#D9A441;--danger:#E4685C;
  /* A dark surface cannot show a grey shadow; depth comes from a darker halo. */
  --shadow-float:0 1px 2px rgba(0,0,0,.5),0 18px 44px -16px rgba(0,0,0,.7);
  --row-h:44px;--row-h-touch:56px;--list-w:42%;--detail-top:64px;
}
/* The rail's own hardcoded greys (Part 1, and correct there) go slightly cooler
   so the navy rail still separates from a near-black page. */
html[data-theme="dark"] .side{border-right:1px solid var(--line)}
html[data-theme="dark"] .side nav a.on{color:#04121C}          /* dark ink on a luminous accent */
html[data-theme="dark"] .side nav a.on .c{color:rgba(4,18,28,.75)}
/* Southport's logo is white-on-transparent: correct on navy in every mode. */

/* ================================================================= ROAD ==== */
/* "High-contrast, larger type and targets, for a phone in sunlight." Isaac walks
   ten to twelve doors a morning and reads this on a phone in Florida daylight,
   where #6C7B86 on #FAFBFC is simply gone. Road mode is not a dark mode and not
   a fourth brand: it is the same app with every contrast ratio pushed up, the
   hairlines made visible, the body type one step larger and touch targets at
   44px. Type only goes up one step on purpose — a bigger jump breaks the page
   budgets QA asserts, and a list that no longer ends is worse than small text. */
html[data-theme="road"]{
  color-scheme:light;
  --navy:#001C2E;--navyink:#C7DCEA;--blue:#004E80;--bluedk:#003A60;--bluetint:#E6F0F7;--sky:#1194C6;
  --green:#0F5C38;--amber:#7A4E08;--hot:#8F430E;--red:#94271E;--violet:#46356F;--teal:#06525C;
  --page:#FFFFFF;--card:#FFFFFF;--surf2:#F0F4F7;--sand:#F7F5F0;
  --line:#9AA9B4;--line2:#B4C0C9;--rule:#9AA9B4;--rule2:#7E8F9C;
  --ink:#000C14;--ink2:#16242E;--mute:#3D4C57;--faint:#55646E;

  --surface:#FFFFFF;--surface-2:#F0F4F7;--surface-3:#FFFFFF;
  --ink-muted:#3D4C57;--ink-faint:#55646E;
  --accent:#004E80;--accent-2:#0B6E96;
  --positive:#0F5C38;--warning:#7A4E08;--danger:#94271E;
  --shadow-float:0 1px 2px rgba(0,12,20,.12),0 18px 44px -16px rgba(0,12,20,.45);
  /* Road mode is a phone in Florida daylight in a gloved hand: WAVE6 §3 allows
     this mode — and only this mode — the taller row. --list-w and --detail-top
     stay put; a wider list would only make the type wrap. */
  --row-h:52px;--row-h-touch:60px;--list-w:42%;--detail-top:64px;
}
/* One step of type, and hands-in-gloves touch targets. Selectors are scoped by
   the theme attribute, so none of this exists in the other two modes. */
html[data-theme="road"] body{font-size:15px}
html[data-theme="road"] .btn,
html[data-theme="road"] button.btn,
html[data-theme="road"] .k4out button,
html[data-theme="road"] .side nav a{min-height:40px}
html[data-theme="road"] .top h1{font-size:18.5px}
html[data-theme="road"] .lbl{font-size:11px;letter-spacing:.07em}
html[data-theme="road"] a{text-decoration-thickness:from-font}
/* Panels must not float away on white-on-white; the hairline carries them. */
html[data-theme="road"] .pan,
html[data-theme="road"] .card{border-color:var(--rule)}

/* ============================================================== KEYFRAMES == */
/* The shared vocabulary. A lane animates with these names or it does not
   animate. Each is written so it is safe to apply to a live element mid-list. */

@keyframes sig-fade-in{
  from{opacity:0;transform:translateY(4px)}
  to{opacity:1;transform:none}
}
@keyframes sig-fade-out{
  from{opacity:1;transform:none}
  to{opacity:0;transform:translateY(-4px)}
}
/* A card leaving because the rep tapped Called: it slides toward the rail and
   collapses its own height, so the cards below glide up rather than jumping. */
@keyframes sig-slide-out{
  from{opacity:1;transform:none;max-height:400px}
  60%{opacity:0;transform:translateX(-26px)}
  to{opacity:0;transform:translateX(-26px);max-height:0;margin-bottom:0;padding-top:0;padding-bottom:0;border-width:0}
}
/* A fresh reason announcing itself exactly once. Never a loop — a dot that
   pulses forever is an alarm, and DESIGN's whole thesis is that this app is not
   an alarm. */
@keyframes sig-pulse-once{
  0%{box-shadow:0 0 0 0 currentColor;opacity:1}
  70%{box-shadow:0 0 0 7px transparent;opacity:1}
  100%{box-shadow:0 0 0 0 transparent;opacity:1}
}
@keyframes sig-shimmer{
  from{background-position:-180% 0}
  to{background-position:180% 0}
}
/* Numbers roll to their new value rather than snapping (DESIGN §1). */
@keyframes sig-roll-up{
  from{transform:translateY(60%);opacity:0}
  to{transform:none;opacity:1}
}
@keyframes sig-draw{                      /* charts drawing in on first paint */
  from{stroke-dashoffset:var(--draw-len,1000)}
  to{stroke-dashoffset:0}
}

/* ============================================================== UTILITIES == */

.anim-in{animation:sig-fade-in var(--dur-2) var(--ease) both}
.anim-out{animation:sig-fade-out var(--dur-2) var(--ease) both}
.anim-leave{animation:sig-slide-out var(--dur-3) var(--ease) forwards;overflow:hidden;pointer-events:none}
.pulse-once{animation:sig-pulse-once var(--dur-3) var(--ease) 1}

/* Number roll. Wrap the digits: <span class="numroll"><span>12</span></span>.
   kit.js re-triggers it by replacing the inner span, so the value animates only
   when it actually changed — a number that re-animates on every repaint reads
   as a glitch, not as polish. */
.numroll{display:inline-block;overflow:hidden;vertical-align:bottom;line-height:1.15}
.numroll>*{display:inline-block;animation:sig-roll-up var(--dur-2) var(--ease) both}

/* --------------------------------------------------------------- skeleton --
   The shimmer that stands in for a row while it loads. Sized by the caller;
   this only paints. Deliberately low-contrast in every mode: a skeleton that
   competes with real content trains people to read the wrong thing. */
.skeleton{
  position:relative;overflow:hidden;border-radius:5px;
  background:var(--surface-2);
  background-image:linear-gradient(90deg,transparent 0%,rgba(127,155,180,.18) 45%,rgba(127,155,180,.28) 50%,rgba(127,155,180,.18) 55%,transparent 100%);
  background-size:180% 100%;background-repeat:no-repeat;
  animation:sig-shimmer 1.25s linear infinite;
  color:transparent;user-select:none;
}
.skeleton *{visibility:hidden}
.skeleton-line{height:11px;margin:6px 0}
.skeleton-line.w40{width:40%}.skeleton-line.w60{width:60%}.skeleton-line.w80{width:80%}
/* A skeleton card matching the Today card's silhouette, so the swap to real
   content does not move anything on the page. */
.skeleton-card{border:1px solid var(--line);border-radius:6px;background:var(--surface);padding:14px 16px;margin-bottom:10px}
.skeleton-card .skeleton{display:block}

html[data-theme="dark"] .skeleton{
  background-image:linear-gradient(90deg,transparent 0%,rgba(160,195,220,.10) 45%,rgba(160,195,220,.17) 50%,rgba(160,195,220,.10) 55%,transparent 100%);
}

/* -------------------------------------------------------- floating layers --
   The one place a shadow is allowed. Opt in by class; panels never get it. */
.floating{box-shadow:var(--shadow-float);backdrop-filter:var(--blur-float)}

/* ------------------------------------------------------------ view swaps --
   route() calls this on the view host so pages cross-fade instead of blinking. */
.viewswap{animation:sig-fade-in var(--dur-1) var(--ease) both}

/* --------------------------------------------------------- theme switcher --
   Lives in the rail's footer and in the mobile More sheet. Three segments, the
   current one filled. Painted against the navy rail in every mode, so its
   colours are rail colours rather than surface tokens. */
.themesw{display:flex;gap:2px;margin-top:12px;padding:3px;border-radius:8px;background:rgba(0,0,0,.22)}
.themesw button{
  flex:1;border:0;background:transparent;color:#A9C4D6;cursor:pointer;
  font-family:var(--raj);font-weight:600;font-size:11.5px;letter-spacing:.02em;
  padding:6px 4px;border-radius:6px;min-height:30px;
  transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease);
}
.themesw button:hover{background:rgba(255,255,255,.10);color:#fff}
.themesw button.on{background:rgba(255,255,255,.16);color:#fff}
.themesw button:focus-visible{outline:2px solid var(--accent-2);outline-offset:1px}
.themelbl{font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:#5B7B92;padding:0 4px;font-weight:600;margin-top:14px}
/* In the More sheet the switcher sits on a light surface, not on the rail. */
.msheet .themesw{background:var(--surface-2);border:1px solid var(--line)}
.msheet .themesw button{color:var(--ink-muted)}
.msheet .themesw button:hover{background:var(--surface-3);color:var(--ink)}
.msheet .themesw button.on{background:var(--accent);color:#fff}
.msheet .themelbl{color:var(--ink-faint)}

/* ================================================== REDUCED MOTION ========
   DESIGN.md: accessibility is baseline hygiene, and "reduced-motion honored" is
   on the list. Everything above collapses to an instant state change — the app
   still responds to every interaction, it just does not travel to get there.
   Durations are set to 1ms rather than 0 so `animationend`/`transitionend`
   listeners (kit.js waits on them before removing a card) still fire. */
@media (prefers-reduced-motion:reduce){
  :root{--dur-1:1ms;--dur-2:1ms;--dur-3:1ms;--ease:linear;--ease-spring:linear}
  .anim-in,.anim-out,.anim-leave,.pulse-once,.viewswap,.numroll>*{animation-duration:1ms!important}
  .skeleton{animation:none;background-image:none}
  *,*::before,*::after{
    animation-duration:1ms!important;animation-iteration-count:1!important;
    transition-duration:1ms!important;scroll-behavior:auto!important;
  }
}

/* ============================================== DARK-MODE CORRECTIONS =====
   Re-pointing the palette gets ninety per cent of the way. These are the places
   where a colour is doing a job the token name does not describe, and where
   swapping the value alone produces something wrong. Found by looking at the
   rendered pages in each mode, not by reading the stylesheet.

   1. --navy DOES TWO JOBS. It is the rail, the brief header and the toast — a
      dark BACKGROUND that must stay dark in every mode — and it is also the
      strongest INK on a light surface: the phone number on a Today card, the
      section rules, the active spine tab. Making it near-black for dark mode is
      right for the first job and catastrophic for the second: on the first dark
      build the phone number, which CONVENTIONS §9 requires to be the largest
      element on a call card, rendered #051622 on #101C26 and was invisible.

      Everywhere navy means "the strongest ink", dark mode points it at --ink.
      Road mode needs none of this: navy on white is already the highest
      contrast pairing in the app.

   2. THE PANEL-HEADER TINTS are hardcoded pale washes (§9's colour semantics:
      navy=structural, blue=book, green=service, amber=pricing, red=gone-quiet,
      violet=AI). The MEANING survives into dark mode; the wash cannot. Each one
      gets a deep tint of the same hue and its accent text brightened.
   ======================================================================== */

/* ---- 1. navy-as-ink ---------------------------------------------------- */
html[data-theme="dark"] .k4today .k4tel,          /* the Today card's phone   */
html[data-theme="dark"] .pers .tel .n,            /* the record's phone       */
html[data-theme="dark"] .pl .pn b,
html[data-theme="dark"] .pl a.tel.pn b,           /* the mobile call bar      */
html[data-theme="dark"] .srule h2,                /* section spine labels     */
html[data-theme="dark"] .spine a.on,
html[data-theme="dark"] .tbl .th .sortbtn.on{color:var(--ink)}
html[data-theme="dark"] .k4today .k4tel:hover,
html[data-theme="dark"] .pl a.tel.pn:hover b{color:var(--accent)}

/* Filled navy controls (an active chip, a filter button) keep a dark fill, so
   their white label stays legible — but the fill has to separate from a dark
   page, which #051622 does not. */
html[data-theme="dark"] .k4chip.on,
html[data-theme="dark"] .filters button.on,
html[data-theme="dark"] .fgrp button.on,
html[data-theme="dark"] .stagerow button.on{background:var(--accent);border-color:var(--accent);color:#04121C}

/* ---- 2. panel-header tone semantics ------------------------------------ */
html[data-theme="dark"] .ph.bl{background:#12283A;border-bottom-color:#1D3D55}
html[data-theme="dark"] .ph.gr{background:#0F2A20;border-bottom-color:#1B4232}
html[data-theme="dark"] .ph.am{background:#2C2312;border-bottom-color:#463920}
html[data-theme="dark"] .ph.rd{background:#2E1715;border-bottom-color:#4A2724}
html[data-theme="dark"] .ph.vi{background:#211C33;border-bottom-color:#352C4D}
html[data-theme="dark"] .k4brief .ph.vi{background:#211C33;border-bottom-color:#352C4D}
html[data-theme="dark"] .ph.nv svg{color:var(--sky)}

/* ---- 3. the remaining hardcoded washes in the wave-4 sheets ------------- */
html[data-theme="dark"] :is(.k4card, .k4today) .k4b:active{background:#1B3040}
html[data-theme="dark"] .w4ob.done{background:#12291C;border-color:#22462F;color:var(--positive)}
html[data-theme="dark"] .w4ev.fresh{background:#12283A}
html[data-theme="dark"] .toast{background:var(--surf2);border:1px solid var(--line)}

/* ---- 4. road mode: the tints are legible but weak in daylight ----------- */
html[data-theme="road"] .ph.bl{background:#DCEAF5;border-bottom-color:#A8C6DE}
html[data-theme="road"] .ph.gr{background:#DCEFE3;border-bottom-color:#A5CDB5}
html[data-theme="road"] .ph.am{background:#F8EBD3;border-bottom-color:#DCC08A}
html[data-theme="road"] .ph.rd{background:#F7DFDD;border-bottom-color:#DCB0AB}
html[data-theme="road"] .ph.vi{background:#E7E1F2;border-bottom-color:#BFB2D9}
