/*
Theme Name: TotalScreen
Theme URI: https://totalscreen.app
Author: ZORDERZ
Author URI: https://zorderz.com
Description: Mobile-first Field OS theme for the Screen Enclosure & Solar Panel industry. Features plugin auto-discovery with inline dashboard widgets (v2.0), 4-theme system (Light/Dark/System/Sunlight WCAG AAA), SPA architecture, role-based dashboard layouts with live KPI metrics from FreshBooks/Nutshell (v2.9.0), interactive KPI deep-links to Sales Analytics (v2.10.1), admin-editable review counts, user-facing ladybug bug reporter (v2.7.0+), fixed WP Admin User Management (v2.10.0), backend infrastructure for user goals & personal records (v2.13.0), View-As role switcher relocated to WP Admin Bar (v2.14.0), full-width opt-out for chat-style sub-views (v2.14.0), unified nav breakpoints & design-token compliance (v2.14.1), sticky header fix & WP Engine performance (v2.14.2), FOUC prevention & design-token sweep (v2.14.3), big-touch 2-col app dock, horizontal scroll lock, sales KPI data wiring (v2.14.4.3), mobile text size boost, header bleed fix, larger app tiles, deferred asset loading (v2.14.5), TS Game deferred/lazy asset integration and gameAvailable SPA flag (v2.14.6), self-hosted fonts & Lucide, app shell skeleton screen, service worker offline caching, boot error recovery (v2.15.0), PWA magic login bridge for iOS standalone auth (v2.18.0), cold-start login code fallback for PWA magic login (v2.19.0), OTP email code login for PWA (eliminates Safari dependency), KPI error states with API health diagnostics, light mode contrast improvements, Safari overflow fixes, smoother sticky app bar animation, command palette Brain Bot routing, iPad install instructions, and AI-powered workflow tools (v2.20.0). Salesperson leads action tile on the dashboard — renderLeadsTile() reads the unified /ts/v1/dashboard-items feed and surfaces "you have N leads to contact today," deep-linking into the Leads app in rep mode; fails silently and never blocks the dashboard, hidden for the shared-kiosk role (v2.21.3). Cross-app orchestrator: the dashboard "ask" field gains tsOrchestratorRoute() — a deterministic intent router that opens the Camera with a sticky pre-label in-shell, or routes contact-lookup / email-compose / document-lookup commands to the Brain Bot with a structured orchestrator_hint; adds the TS_Contact_Bridge shared contact-lookup capability ([TS_CONTACT]) with kiosk name-only disclosure and relationship/shared-job scope (v2.21.4). Inline orchestrator answers: contact-lookup commands now render a compact card directly under the dashboard "ask" field (tappable phone/email + "Open in chat →" handoff) via a new /ts/v1/contact-lookup REST endpoint that proxies TS_Contact_Bridge server-side — no chat switch, no Poe round-trip; disclosure/scope still enforced in the bridge (v2.21.5). Orchestrator camera-intent hardening: the dashboard "ask" router now tolerates common typos ("tak a photo", "snp a pic") and more phrasings ("snap a pic labeled before", "a before photo", "grab some photos tagged after") while still never firing on analytics or "take a look at…"; version bump also busts the cached app.js ?ver= so the router actually loads on devices (v2.21.6). Stronger cache-busting: theme JS/CSS (app.js, app.css, bridge.js, style.css, bug-reporter, exif-panel) are now versioned by file modification time (filemtime) appended to the theme version, so the ?ver= changes the instant a file's content changes — defeating NitroPack / CDN / PWA service-worker staleness without a manual version bump on every edit (v2.21.7). Contact-intent router widened: the inline contact card now triggers on natural phrasings the detector previously missed — bare "info"/"details" ("what's Craig Rifel's info?", "info for Craig", "Craig's details"), plus "who is X" and a lone "look up X" — while a tightened analytics guard keeps revenue/aggregate/"top N" questions out of the card (v2.21.8). Contact card data fix: the inline card was reporting "no phone or email on file" for customers who clearly had them — TS_Contact_Bridge now extracts FreshBooks fields the proven way (phone key is `mob_phone` not `mobile_phone`; email falls back through the contacts sub-array / username / pref_email; address tries shipping s_* fields), requests include[]=contacts on the client lookup, and salvages a phone-looking tail appended to the street string (FreshBooks stores some numbers that way, e.g. "23762 Moonglow Court 248-635-7783") — so Craig Rifel's phone + email now render in the card (v2.21.9). Contact-intent detector made punctuation/typo-tolerant: a normalize-then-match fallback strips stray apostrophes/semicolons/slashes and fires the inline card when an info-word ("info/details/contact/number/phone/email/reach") and a plausible name both appear in any order — so messy real-world typing like "what';s craig rifles info/" or "craig rifel info" still resolves, while analytics/"top N"/"list all" stay excluded (v2.21.10). Server-authoritative orchestrator: the dashboard "ask" field now resolves read verbs through a new Poe-free GET /ts/v1/orchestrate endpoint (TS_Orchestrator) that classifies intent server-side and returns contact/document-lookup data from the capability bridges in one call — so phrasing robustness no longer depends on the JS regex (which remains as an instant row-label hint + offline fallback). Email-compose is correctly disambiguated from contact lookup (checked first) and opens the full chat; contact + document lookups render inline with an "Open in chat →" handoff (v2.22.0). Contact card data fix (round 2): TS_Contact_Bridge now resolves FreshBooks clients with the SAME query the chat path uses — search[user_like] (broad fuzzy across all name fields, returns FULL client objects with phone/email/contacts) + include[]=contacts + archived-pool retry — instead of the leaner search[lname], which was why the inline card showed "no phone or email on file" and missed typo'd surnames. Added safe typo-tolerant disambiguation (prefix / Levenshtein ≤2) so "craig rifles"/"craig rifle" resolve to Craig Rifel without guessing between different people (v2.22.1). Inline ask field (no pop-up): the dashboard "Ask a question…" bar is now a real text input you type into directly, with results appearing in a dropdown beneath it — the separate command-palette overlay/dialog is removed. Empty field shows just the bar; typing opens the dropdown (apps, the inline contact/lookup card); Escape/outside-click/clear closes it; Ctrl/⌘-K focuses it. autocorrect/autocapitalize/spellcheck stay off so names aren't altered (v2.23.0). Self-healing app shell: the service worker is now actually served in production — WP Engine's nginx 404s the old virtual /sw.js route (any .js path with no physical file), so the SW had silently never registered on any device since v2.18.0; it is now served and registered at the extension-less /ts-sw. The SW (2.22.0) fetches same-origin GET navigations network-first with an HTTP-cache bypass (offline falls back to cache), and logged-in front-end page loads send explicit no-cache headers — so installed PWAs can never again pin a days-old dashboard shell with stale nonces and stale ?ver= script URLs (the root cause of the June 2026 camera "nonce check FAILED" saga). Also ships the previously-undeployed sw.js camera-drain hardening: single-uploader lease (2.21.1), ArrayBuffer-record support (2.21.2), X-WP-Nonce auth (2.21.3), and empty/size-mismatch byte refusal (2.21.4) (v2.23.1).
Version: 2.23.1
Requires at least: 6.0
Tested up to: 6.9
Requires PHP: 8.0
License: GNU General Public License v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: totalscreen
Tags: mobile-first, one-column, custom-colors, custom-logo, full-width-template, theme-options
*/

/* ================================================================
   TOTALSCREEN TOOLS V6 — DESIGN SYSTEM
   3-Tier Token Architecture: Primitive → Semantic → Component
   ================================================================ */

/* ---- TIER 1: Primitive Tokens ---- */
:root {
  --ref-brand-50:#EBF5FF;--ref-brand-100:#D6EBFF;--ref-brand-200:#ADCFFF;
  --ref-brand-300:#7AB3FF;--ref-brand-400:#4796F7;--ref-brand-500:#2C5F8A;
  --ref-brand-600:#1E3A5F;--ref-brand-700:#162D4A;--ref-brand-800:#0F1F35;
  --ref-brand-900:#091526;--ref-brand-950:#050D18;
  --ref-gray-0:#FFFFFF;--ref-gray-50:#F8FAFC;--ref-gray-100:#F1F5F9;
  --ref-gray-200:#E2E8F0;--ref-gray-300:#CBD5E1;--ref-gray-400:#94A3B8;
  --ref-gray-500:#64748B;--ref-gray-600:#475569;--ref-gray-700:#334155;
  --ref-gray-800:#1E293B;--ref-gray-900:#0F172A;--ref-gray-950:#020617;
  --ref-green-500:#10B981;--ref-green-600:#059669;
  --ref-amber-500:#F59E0B;--ref-amber-600:#D97706;
  --ref-red-500:#EF4444;--ref-red-600:#DC2626;
  --ref-space-1:4px;--ref-space-2:8px;--ref-space-3:12px;--ref-space-4:16px;
  --ref-space-5:20px;--ref-space-6:24px;--ref-space-8:32px;--ref-space-10:40px;
  --ref-space-12:48px;--ref-space-16:64px;--ref-space-20:80px;
  --ref-radius-xs:4px;--ref-radius-sm:8px;--ref-radius-md:12px;
  --ref-radius-lg:16px;--ref-radius-xl:20px;--ref-radius-2xl:28px;--ref-radius-full:9999px;
  --ref-font-xs:16px;--ref-font-sm:18px;--ref-font-base:20px;--ref-font-md:22px;
  --ref-font-lg:25px;--ref-font-xl:30px;--ref-font-2xl:34px;--ref-font-3xl:42px;
  --ref-dur-fast:150ms;--ref-dur-norm:250ms;--ref-dur-slow:350ms;
  --ref-ease:cubic-bezier(.4,0,.2,1);--ref-ease-out:cubic-bezier(0,0,.2,1);
  --ref-ease-spring:cubic-bezier(.32,.72,0,1);
  /* Category Colors (v2.14.1) */
  --cat-sales:#7C3AED;--cat-finance:#059669;--cat-service:#2563EB;
  --cat-field:#EA580C;--cat-admin:#DC2626;
  --cat-ops:#0891B2;--cat-team:#DB2777;
}

/* ---- TIER 2: Semantic Tokens — LIGHT (default) ---- */
:root, :root[data-theme="light"] {
  --sys-bg:var(--ref-gray-100);--sys-bg-alt:var(--ref-gray-200);
  --sys-surface:var(--ref-gray-0);--sys-surface-raised:#F8FAFC;
  /* v2.14.0: surface-raised is now slate-50 (one step from base white surface)
     so plugins relying on a real one-step elevation — alternating rows,
     skeleton shimmer gradients, card-on-card patterns — render the same in
     light mode as they already do in dark/system. Pre-2.14 this resolved
     to var(--ref-gray-0) (=#FFFFFF), giving zero contrast against surface. */
  --sys-surface-blur:rgba(255,255,255,.82);
  --sys-surface-subtle:rgba(0,0,0,.03);
  --sys-text:var(--ref-gray-900);--sys-text-sec:var(--ref-gray-600);
  /* v2.21.0 contrast retune: ter was gray-500, which passes on white surface
     (4.76:1) but FAILS on the --sys-bg gray-100 header zone (4.34:1). gray-600
     clears AA on both surface (7.58:1) and bg-100 (6.92:1). */
  --sys-text-ter:var(--ref-gray-600);--sys-text-inv:var(--ref-gray-0);
  --sys-text-brand:var(--ref-gray-0);
  --sys-border:var(--ref-gray-200);--sys-border-strong:var(--ref-gray-300);
  --sys-brand:var(--ref-brand-500);--sys-brand-light:var(--ref-brand-50);
  --sys-brand-hover:var(--ref-brand-600);--sys-brand-dark:var(--ref-brand-700);
  --sys-success:var(--ref-green-500);--sys-warning:var(--ref-amber-500);--sys-error:var(--ref-red-500);
  --sys-overlay:rgba(0,0,0,.45);
  --sys-shadow-sm:0 1px 3px rgba(0,0,0,.08);
  --sys-shadow-md:0 4px 12px rgba(0,0,0,.1);
  --sys-shadow-lg:0 12px 32px rgba(0,0,0,.12);
  --sys-shadow-xl:0 24px 48px rgba(0,0,0,.16);
  --sys-icon-wt:1.75;--sys-font-wt:400;--sys-font-wt-md:500;
  --sys-font-wt-sb:600;--sys-font-wt-b:700;
  --sys-focus:0 0 0 3px var(--ref-brand-200);
  --sys-topbar-bg:var(--ref-brand-700);--sys-topbar-text:var(--ref-gray-0);
}

/* ---- DARK ---- */
:root[data-theme="dark"] {
  --sys-bg:var(--ref-gray-950);--sys-bg-alt:var(--ref-gray-900);
  --sys-surface:var(--ref-gray-800);--sys-surface-raised:var(--ref-gray-700);
  --sys-surface-blur:rgba(30,41,59,.82);
  --sys-surface-subtle:rgba(255,255,255,.05);
  /* v2.21.0 contrast retune: secondary/tertiary text must clear WCAG AA on the
     WIDGET SURFACE (--sys-surface = gray-800), not just the page bg. Old values
     (sec=gray-400 @5.71:1 borderline, ter=gray-500 @3.07:1 FAIL on surface)
     produced the "gray text on dark-gray" complaint. New: sec=gray-300 (9.85:1,
     AAA) and ter=gray-400 (5.71:1, AA) on gray-800. */
  --sys-text:var(--ref-gray-100);--sys-text-sec:var(--ref-gray-300);
  --sys-text-ter:var(--ref-gray-400);--sys-text-inv:var(--ref-gray-900);
  --sys-text-brand:var(--ref-gray-0);
  --sys-border:var(--ref-gray-700);--sys-border-strong:var(--ref-gray-600);
  --sys-brand:var(--ref-brand-400);--sys-brand-light:var(--ref-brand-900);
  --sys-brand-hover:var(--ref-brand-300);--sys-brand-dark:var(--ref-brand-200);
  --sys-success:var(--ref-green-500);--sys-warning:var(--ref-amber-500);--sys-error:var(--ref-red-500);
  --sys-overlay:rgba(0,0,0,.65);
  --sys-shadow-sm:0 1px 3px rgba(0,0,0,.25);
  --sys-shadow-md:0 4px 12px rgba(0,0,0,.3);
  --sys-shadow-lg:0 12px 32px rgba(0,0,0,.4);
  --sys-shadow-xl:0 24px 48px rgba(0,0,0,.5);
  --sys-icon-wt:1.75;--sys-font-wt:400;--sys-font-wt-md:500;
  --sys-font-wt-sb:600;--sys-font-wt-b:700;
  --sys-focus:0 0 0 3px var(--ref-brand-800);
  --sys-topbar-bg:var(--ref-gray-900);--sys-topbar-text:var(--ref-gray-100);
}

/* ---- SUNLIGHT (WCAG AAA — 21:1) ---- */
:root[data-theme="sunlight"] {
  --sys-bg:#FFFFFF;--sys-bg-alt:#FFFFFF;
  --sys-surface:#FFFFFF;--sys-surface-raised:#F1F5F9;
  /* v2.14.0: surface-raised lifted to slate-100 (a touch more contrast than
     light theme to suit Sunlight's high-vis baseline) so alternating rows
     and elevation gradients are still perceptible without compromising
     21:1 text contrast. */
  --sys-surface-blur:rgba(255,255,255,.95);
  --sys-surface-subtle:rgba(0,0,0,.04);
  --sys-text:#000000;--sys-text-sec:#000000;
  --sys-text-ter:#1a1a1a;--sys-text-inv:#FFFFFF;
  --sys-text-brand:#FFFFFF;
  --sys-border:#000000;--sys-border-strong:#000000;
  --sys-brand:#000000;--sys-brand-light:#E0E0E0;
  --sys-brand-hover:#222222;--sys-brand-dark:#000000;
  --sys-success:#000000;--sys-warning:#000000;--sys-error:#000000;
  --sys-overlay:rgba(0,0,0,.75);
  --sys-shadow-sm:none;--sys-shadow-md:none;--sys-shadow-lg:none;--sys-shadow-xl:none;
  --sys-icon-wt:3;--sys-font-wt:600;--sys-font-wt-md:700;
  --sys-font-wt-sb:800;--sys-font-wt-b:900;
  --sys-focus:0 0 0 4px #000;
  --sys-topbar-bg:#000000;--sys-topbar-text:#FFFFFF;
}

/* ---- SYSTEM (auto) ---- */
@media(prefers-color-scheme:dark){
:root[data-theme="system"]{
  --sys-bg:var(--ref-gray-950);--sys-bg-alt:var(--ref-gray-900);
  --sys-surface:var(--ref-gray-800);--sys-surface-raised:var(--ref-gray-700);
  --sys-surface-blur:rgba(30,41,59,.82);
  --sys-surface-subtle:rgba(255,255,255,.05);
  /* v2.21.0 contrast retune — mirror of explicit dark mode (see above):
     sec=gray-300 (9.85:1 AAA), ter=gray-400 (5.71:1 AA) on --sys-surface. */
  --sys-text:var(--ref-gray-100);--sys-text-sec:var(--ref-gray-300);
  --sys-text-ter:var(--ref-gray-400);--sys-text-inv:var(--ref-gray-900);
  --sys-text-brand:var(--ref-gray-0);
  --sys-border:var(--ref-gray-700);--sys-border-strong:var(--ref-gray-600);
  --sys-brand:var(--ref-brand-400);--sys-brand-light:var(--ref-brand-900);
  --sys-brand-hover:var(--ref-brand-300);--sys-brand-dark:var(--ref-brand-200);
  --sys-success:var(--ref-green-500);--sys-warning:var(--ref-amber-500);--sys-error:var(--ref-red-500);
  --sys-overlay:rgba(0,0,0,.65);
  --sys-shadow-sm:0 1px 3px rgba(0,0,0,.25);--sys-shadow-md:0 4px 12px rgba(0,0,0,.3);
  --sys-shadow-lg:0 12px 32px rgba(0,0,0,.4);--sys-shadow-xl:0 24px 48px rgba(0,0,0,.5);
  --sys-icon-wt:1.75;--sys-font-wt:400;--sys-font-wt-md:500;
  --sys-font-wt-sb:600;--sys-font-wt-b:700;
  --sys-focus:0 0 0 3px var(--ref-brand-800);
  --sys-topbar-bg:var(--ref-gray-900);--sys-topbar-text:var(--ref-gray-100);
}}

/* === RESPONSIVE TYPOGRAPHY v2.12.5 === */
/* TABLET (≥600px) — iPad mini portrait, small tablets */
@media (min-width: 600px) {
  :root {
    --ref-font-xs:   16px;
    --ref-font-sm:   18px;
    --ref-font-base: 21px;
    --ref-font-md:   24px;
    --ref-font-lg:   29px;
    --ref-font-xl:   34px;
    --ref-font-2xl:  40px;
    --ref-font-3xl:  48px;
    --ref-space-3: 14px;
    --ref-space-4: 18px;
    --ref-space-5: 24px;
    --ref-space-6: 28px;
  }
}
/* v2.16.0 T13: Desktop sidebar breakpoint — +4pt readability boost */
@media (min-width: 820px) {
  :root {
    --ref-font-xs:   19px;
    --ref-font-sm:   21px;
    --ref-font-base: 23px;
    --ref-font-md:   26px;
    --ref-font-lg:   29px;
    --ref-font-xl:   34px;
    --ref-font-2xl:  40px;
    --ref-font-3xl:  48px;
  }
}
/* iPad Pro 11"/13" portrait + landscape */
@media (min-width: 900px) {
  :root {
    --ref-font-xs:   16px;
    --ref-font-sm:   19px;
    --ref-font-base: 22px;
    --ref-font-md:   26px;
    --ref-font-lg:   31px;
    --ref-font-xl:   37px;
    --ref-font-2xl:  44px;
    --ref-font-3xl:  54px;
  }
}
@media (min-width: 1280px) {
  :root {
    --ref-font-xs:   16px;
    --ref-font-sm:   19px;
    --ref-font-base: 22px;
    --ref-font-md:   26px;
    --ref-font-lg:   32px;
    --ref-font-xl:   38px;
    --ref-font-2xl:  46px;
    --ref-font-3xl:  56px;
    --ref-space-3: 16px;
    --ref-space-4: 22px;
    --ref-space-5: 30px;
    --ref-space-6: 38px;
  }
}
@media (min-width: 1680px) {
  :root {
    --ref-font-xs:   17px;
    --ref-font-sm:   20px;
    --ref-font-base: 24px;
    --ref-font-md:   28px;
    --ref-font-lg:   34px;
    --ref-font-xl:   42px;
    --ref-font-2xl:  52px;
    --ref-font-3xl:  64px;
  }
}
@media (min-width: 2400px) {
  :root {
    --ref-font-xs:   20px;
    --ref-font-sm:   24px;
    --ref-font-base: 28px;
    --ref-font-md:   34px;
    --ref-font-lg:   42px;
    --ref-font-xl:   52px;
    --ref-font-2xl:  62px;
    --ref-font-3xl:  78px;
  }
}
/* === END RESPONSIVE TYPOGRAPHY === */
