/* ================================================================
   GLOBAL RESET & BASE
   ================================================================ */
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{font-family:'Inter',system-ui,-apple-system,sans-serif;font-feature-settings:'cv01','cv03','cv04','cv11';
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
  text-rendering:optimizeLegibility;line-height:1.5;scroll-behavior:smooth;
  overflow:clip;overscroll-behavior:none}
body{background:var(--sys-bg);color:var(--sys-text);font-size:var(--ref-font-base);
  font-weight:var(--sys-font-wt);overflow:clip;overscroll-behavior:none;
  min-height:100vh;min-height:100dvh;
  -webkit-tap-highlight-color:transparent;user-select:none;
  touch-action:pan-y pinch-zoom;
  /* v2.20.0 r5: Changed from 100vw to 100%. On mobile browsers, 100vw
     includes the scrollbar gutter width, making the body wider than the
     visible viewport. This causes the entire layout to shift right — the
     left edge gets proper padding but the right column clips past the
     screen edge. 100% constrains to the actual visible width. */
  max-width:100%;
  position:relative}

/* v2.20.0 r5: Hard constraint on html to prevent ANY horizontal overflow.
   The body's overflow:clip should handle this, but iOS Safari PWA sometimes
   ignores it when the html element itself is wider. Belt and suspenders. */
html {
  overflow-x:hidden;
  width:100%;
  max-width:100%;
}
/* v2.15.0: visibility:hidden moved to inline skeleton CSS in header.php.
   Body is visible from the start; the skeleton overlay covers content
   until ts-ready fires. The old 3s CSS fallback is replaced by the
   skeleton's own failsafe animation. */
body.ts-ready{}
button{border:none;background:none;font:inherit;color:inherit;cursor:pointer;
  outline:none;-webkit-tap-highlight-color:transparent;touch-action:manipulation}
button:focus-visible{box-shadow:var(--sys-focus);border-radius:var(--ref-radius-sm)}
input,textarea,select{font:inherit;color:inherit;border:none;outline:none;
  background:none;-webkit-tap-highlight-color:transparent}
input:focus-visible,textarea:focus-visible,select:focus-visible{box-shadow:var(--sys-focus)}
a{color:var(--sys-brand);text-decoration:none;touch-action:manipulation}
h1,h2,h3,h4,h5,h6{font-weight:var(--sys-font-wt-sb);line-height:1.25}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}
/* v2.14.3: Utility classes consumed by app.js settings templates */
.mt-4{margin-top:var(--ref-space-4)!important}
.w-full{width:100%!important}
@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation-duration:.01ms!important;
  animation-iteration-count:1!important;transition-duration:.01ms!important;scroll-behavior:auto!important}}

/* ---- CSS fallback if JS fails ---- */
/* v2.15.0: If JS never boots (no ts-ready), the skeleton auto-hides after 4s
   and content becomes visible via the inline <style> failsafe in header.php.
   The old visibility:hidden + 3s unhide animation is removed — the skeleton
   approach gives users immediate visual feedback instead of a blank screen. */

/* ================================================================
   LAYOUT — Views
   ================================================================ */
.view{display:none;width:100%;min-height:100vh;min-height:100dvh}
.view.active{display:flex;flex-direction:column;
  /* v2.10.0 fix: Explicit height creates a "definite main size" so flex:1
     children (sub-views) resolve their height correctly. Without this,
     iOS Safari treats the available space as infinite (WebKit Bug #137730)
     and overflow-y:auto on sub-views never triggers a scrollbar.
     min-height retained as fallback for edge cases. */
  height:100vh;height:100dvh}
#view-login.active{display:flex;align-items:center;justify-content:center}
#view-main{padding-bottom:calc(49px + env(safe-area-inset-bottom,0px));
  overscroll-behavior:contain;
  transition:transform .35s cubic-bezier(.32,.72,0,1),opacity .35s ease}
.sub-view{display:none;flex:1;overflow-y:auto;overflow-x:clip;
  /* v2.21.0: contain overscroll so iOS rubber-banding does not reveal the
     darker page void behind the surface (the "ugly black section" on scroll). */
  overscroll-behavior:contain;
  padding:var(--ref-space-4);padding-top:calc(env(safe-area-inset-top,0px) + var(--ref-space-3));
  max-width:100%;width:100%}
.sub-view.active{display:block}
/* v2.14.0: Enforce the visibility contract with !important so that plugin CSS
   using ID selectors (e.g. #sv-team { display:flex } in nav-inject.css) cannot
   accidentally override the base .sub-view { display:none }. Without this,
   ID-specificity (1,0,0) beats the class rule (0,1,0) and the non-active sub-view
   renders its content below the active view — visually leaking skeleton/loading
   states at the bottom of the page. When .active IS present, plugins' own display
   value (flex/grid/etc.) still wins normally since this :not(.active) guard won't
   match. */
.sub-view:not(.active){display:none !important}

/* v2.14.3.1: Pull-to-refresh indicator — inserted at the top of the active
   sub-view by initPullToRefresh() in app.js. Consistent with the TSA/TSIM
   plugin-level PTR pattern but at the theme shell level. */
.ts-pull-indicator {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 0;
  overflow: hidden;
  font-size: var(--ref-font-sm, 15px);
  color: var(--sys-text-ter, #94A3B8);
  background: var(--sys-bg-alt, var(--sys-surface-raised, #F1F5F9));
  transition: opacity 0.15s ease;
  border-radius: var(--ref-radius-md, 8px);
  margin-bottom: var(--ref-space-1, 4px);
}

/* ================================================================
   LOGIN
   ================================================================ */
#view-login{background:linear-gradient(145deg,var(--ref-brand-600) 0%,var(--ref-brand-900) 50%,var(--ref-brand-950) 100%);
  padding:var(--ref-space-4)}
.login-card{background:var(--sys-surface);border-radius:var(--ref-radius-2xl);
  padding:var(--ref-space-8) var(--ref-space-6);width:100%;max-width:380px;
  box-shadow:var(--sys-shadow-xl)}
.login-logo{text-align:center;margin-bottom:var(--ref-space-6)}
.login-logo .logo-mark{width:56px;height:56px;background:var(--ref-brand-500);
  border-radius:var(--ref-radius-lg);display:inline-flex;align-items:center;
  justify-content:center;color:#fff;margin-bottom:var(--ref-space-3)}
.login-logo h1{font-size:var(--ref-font-xl);font-weight:var(--sys-font-wt-b);letter-spacing:-.02em}
.login-logo p{font-size:var(--ref-font-sm);color:var(--sys-text-sec);margin-top:var(--ref-space-1)}
.login-version{font-size:var(--ref-font-xs);color:var(--sys-text-ter);text-align:center;margin-top:var(--ref-space-4)}
.fg{margin-bottom:var(--ref-space-4)}
.fg label{display:block;font-size:var(--ref-font-sm);font-weight:var(--sys-font-wt-md);
  color:var(--sys-text-sec);margin-bottom:var(--ref-space-1)}
.fg input,.fg select{width:100%;padding:var(--ref-space-3) var(--ref-space-4);
  border:1.5px solid var(--sys-border);border-radius:var(--ref-radius-md);
  background:var(--sys-bg);font-size:var(--ref-font-base);min-height:52px;
  transition:border-color var(--ref-dur-fast) var(--ref-ease)}
.fg input:focus,.fg select:focus{border-color:var(--sys-brand)}
.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--ref-space-2);
  padding:var(--ref-space-3) var(--ref-space-5);border-radius:var(--ref-radius-md);
  font-size:var(--ref-font-base);font-weight:var(--sys-font-wt-md);min-height:52px;
  transition:all var(--ref-dur-fast) var(--ref-ease);cursor:pointer}
.btn-brand{background:var(--sys-brand);color:var(--sys-text-brand);width:100%}
.btn-brand:hover{background:var(--sys-brand-hover)}
.btn-brand:active{background:var(--sys-brand-dark);transform:scale(.98)}
.btn-outline{border:1.5px solid var(--sys-border);color:var(--sys-text);background:var(--sys-surface)}
.btn-outline:hover{border-color:var(--sys-brand);color:var(--sys-brand)}
.btn-sm{min-height:36px;padding:var(--ref-space-2) var(--ref-space-3);font-size:var(--ref-font-sm);
  border-radius:var(--ref-radius-sm)}
.btn-icon{width:48px;height:48px;min-height:48px;border-radius:var(--ref-radius-md);
  display:inline-flex;align-items:center;justify-content:center}
.btn-icon:hover{background:var(--sys-brand-light)}

/* ================================================================
   v2.21.0: NO-SUBMIT-SHIFT HOOKS
   Shared affordances for the "stay on the button, show inline status,
   never reflow" submit pattern. Plugins opt in (Surveys, Leads,
   Commissions, Estimates) so every widget behaves identically.
   See NO-SUBMIT-SHIFT-CONTRACT-v1.md. CLS-correct by construction:
   the busy button keeps its box dimensions, the status slot reserves
   its space when empty, and everything animates with opacity only.
   ================================================================ */
/* Busy button: keep EXACT dimensions while working — never let the label
   swap change width/height (that is the layout shift). Spinner replaces the
   label in place; pointer events disabled; announced to AT via aria-busy. */
.ts-btn-busy{position:relative;pointer-events:none;color:transparent !important}
.ts-btn-busy > *{visibility:hidden}
.ts-btn-busy::after{
  content:'';position:absolute;top:50%;left:50%;
  width:1.1em;height:1.1em;margin:-.55em 0 0 -.55em;
  border:2px solid currentColor;border-top-color:transparent;border-radius:50%;
  /* currentColor is transparent above, so paint the spinner from the busy
     button's intended ink: brand-text for filled buttons, text otherwise. */
  color:var(--sys-text-brand);
  animation:appSpin .7s linear infinite;visibility:visible}
.btn-outline.ts-btn-busy::after,.ts-btn-busy.ts-btn-busy-ink-text::after{color:var(--sys-text)}
/* v2.20.3-patched-9: defer to plugin-supplied busy contents.
   The hook above is for a PLAIN button — the theme hides the label and paints
   its own ::after spinner. But some plugins (e.g. Surveys) ALSO inject their
   own spinner + "Running…/Syncing…" label INTO the button and then add
   .ts-btn-busy as the contract instructs. Without this guard the theme's
   `color:transparent` + `> * {visibility:hidden}` + ::after would erase the
   plugin's label and double the spinner. When the busy button contains a
   plugin-injected spinner/label child, step back entirely: don't blank the
   text, don't hide children, and suppress the theme ::after — let the plugin's
   own busy UI show. Plugins that inject nothing (e.g. Leads) are unaffected and
   still get the theme spinner. (:has() is already used elsewhere in this file;
   Safari 17+/target devices support it.) */
.ts-btn-busy:has(> [class*="spinner"]),
.ts-btn-busy:has(> [class*="busy-label"]){
  color:inherit !important;
}
.ts-btn-busy:has(> [class*="spinner"]) > *,
.ts-btn-busy:has(> [class*="busy-label"]) > *{
  visibility:visible;
}
.ts-btn-busy:has(> [class*="spinner"])::after,
.ts-btn-busy:has(> [class*="busy-label"])::after{
  content:none;
}

/* Inline status slot: ALWAYS occupies space (reserved min-height) so showing
   a message never pushes content. Fades in via opacity. Place directly above
   or below the action button. Opaque surface so it reads over anything. */
.ts-inline-status{
  min-height:var(--ref-space-6);
  display:flex;align-items:center;gap:var(--ref-space-2);
  padding:var(--ref-space-2) var(--ref-space-3);
  font-size:var(--ref-font-sm);color:var(--sys-text-sec);
  background:var(--sys-surface);border-radius:var(--ref-radius-md);
  opacity:0;transition:opacity var(--ref-dur-norm) var(--ref-ease)}
.ts-inline-status.is-visible{opacity:1}
.ts-inline-status .ts-inline-spinner{
  width:1em;height:1em;flex-shrink:0;
  border:2px solid var(--sys-border);border-top-color:var(--sys-brand);
  border-radius:50%;animation:appSpin .7s linear infinite}
.ts-inline-status.is-success{color:var(--sys-success)}
.ts-inline-status.is-error{color:var(--sys-error)}
[data-theme="sunlight"] .ts-inline-status{border:2px solid #000}

/* ================================================================
   GREETING ROW
   ================================================================ */
.greeting-row{padding:var(--ref-space-2) 0 var(--ref-space-1);
  display:flex;align-items:center;justify-content:space-between;
  /* v2.21.0: reserve the refresh button's full height so the row cannot
     vertically collapse into the search trigger below it. */
  min-height:40px}
.greeting-row h2{font-size:var(--ref-font-xl);font-weight:var(--sys-font-wt-b);
  letter-spacing:-.02em;color:var(--sys-text);margin:0;line-height:1.2}
/* v2.14.4 A1: Visible refresh button */
.ts-refresh-btn{width:40px;height:40px;min-height:40px;border-radius:var(--ref-radius-md,12px);
  display:flex;align-items:center;justify-content:center;flex-shrink:0;
  color:var(--sys-text-sec);background:var(--sys-surface);
  border:1px solid var(--sys-border);
  /* v2.21.0: keep the refresh button's tap region self-contained so it can
     never share/overlap a hit-rect with the search trigger on large phones.
     position:relative + z-index keeps it above the adjacent search button;
     touch-action prevents gesture bleed during the compact-bar transition. */
  position:relative;z-index:2;touch-action:manipulation;
  transition:all var(--ref-dur-fast) var(--ref-ease);cursor:pointer}
.ts-refresh-btn:hover{color:var(--sys-brand);border-color:var(--sys-brand);
  background:var(--sys-brand-light)}
.ts-refresh-btn:active{transform:scale(.9)}
[data-theme="sunlight"] .ts-refresh-btn{border:2px solid #000;color:#000}
/* v2.16.0 T2: Inner wrapper for greeting + refresh alignment */
.greeting-row-inner{display:flex;align-items:center;justify-content:space-between;width:100%}

/* ================================================================
   DASHBOARD SEARCH TRIGGER
   ================================================================ */
.dash-search-trigger{display:flex;align-items:center;gap:var(--ref-space-2);
  width:100%;padding:var(--ref-space-3) var(--ref-space-4);
  background:var(--sys-surface);border:1.5px solid var(--sys-border);
  /* v2.21.0: rounded rectangle (was --ref-radius-full pill — the only oval in
     the UI). Matches the dock tiles / cards so the search bar is visually
     consistent in both its full and compact states. */
  border-radius:var(--ref-radius-lg);min-height:48px;
  color:var(--sys-text-ter);font-size:var(--ref-font-base);
  /* v2.21.0: explicit top gap guarantees vertical separation from the
     greeting/refresh row above; touch-action keeps the tap region clean. */
  margin-top:var(--ref-space-2);margin-bottom:var(--ref-space-3);
  touch-action:manipulation;
  transition:border-color var(--ref-dur-fast) var(--ref-ease),
  box-shadow var(--ref-dur-fast) var(--ref-ease)}
.dash-search-trigger:hover{border-color:var(--sys-brand);box-shadow:var(--sys-shadow-sm)}
.dash-search-trigger:active{transform:scale(.99)}
.dash-search-trigger svg{flex-shrink:0}
.dash-search-trigger span{flex:1;text-align:left}

/* ================================================================
   v2.14.4.3 A4: APP DOCK — 2-column big-touch grid
   Full-column rounded squares, large icons + labels, portrait-first.
   ================================================================ */
/* v2.14.5: bigger tiles — more padding, min-height, and label text for
   mobile readability. Icons unchanged per design spec. */
.app-dock{
  /* v2.20.1: minmax(0, 1fr) instead of bare 1fr — Safari WebKit can
     resolve 1fr to fractional px that exceeds the container when combined
     with gap on high-DPI devices. minmax(0, 1fr) constrains the minimum
     and prevents sub-pixel overflow on the right column. */
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:var(--ref-space-3);
  padding:var(--ref-space-3) 0 var(--ref-space-4);
  max-width:100%;box-sizing:border-box}
.dock-app{
  position:relative;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:var(--ref-space-3);
  padding:var(--ref-space-5) var(--ref-space-4);
  background:var(--sys-surface);
  border:1px solid var(--sys-border);
  border-radius:var(--ref-radius-xl);
  box-shadow:var(--sys-shadow-sm);
  cursor:pointer;
  min-height:130px;
  transition:all var(--ref-dur-fast) var(--ref-ease);
  -webkit-tap-highlight-color:transparent;
  -webkit-touch-callout:none;-webkit-user-select:none;user-select:none;
  /* Stagger entrance animation */
  opacity:0;animation:fadeSlideUp var(--ref-dur-norm) var(--ref-ease-out) forwards}
.dock-app:nth-child(1){animation-delay:30ms}
.dock-app:nth-child(2){animation-delay:70ms}
.dock-app:nth-child(3){animation-delay:110ms}
.dock-app:nth-child(4){animation-delay:150ms}
.dock-app:nth-child(5){animation-delay:190ms}
.dock-app:nth-child(6){animation-delay:230ms}
.dock-app:nth-child(7){animation-delay:270ms}
.dock-app:nth-child(8){animation-delay:310ms}
/* Desktop hover — subtle lift + glow */
@media(hover:hover){
  .dock-app:hover{box-shadow:var(--sys-shadow-md);
    border-color:var(--sys-brand);transform:translateY(-2px)}
}
/* Press / tap — darken + scale down, visible feedback */
.dock-app:active{
  transform:scale(.93);
  box-shadow:none;
  background:var(--sys-surface-subtle);
  border-color:var(--sys-brand);
  transition-duration:50ms}
.dock-icon{
  width:72px;height:72px;border-radius:var(--ref-radius-lg);
  display:flex;align-items:center;justify-content:center;color:#fff;flex-shrink:0;
  box-shadow:0 0 8px rgba(0,0,0,.18);margin:0 auto}
.dock-icon svg{width:34px;height:34px;stroke-width:1.75}
.dock-label{
  font-size:var(--ref-font-lg);font-weight:var(--sys-font-wt-sb);
  color:var(--sys-text);text-align:center;line-height:1.25;
  max-width:100%;word-wrap:break-word;
  /* v2.21.0: reserve a fixed two-line band so a 1- vs 2-line label (e.g.
     "Commissions" wrapping) can never change tile height — eliminates the
     per-tile cumulative layout shift. Labels longer than two lines clamp
     with an ellipsis rather than growing the tile.
     v2.24.3: the band is now sized off a FIXED unit (--dock-label-lh, derived
     from the full-size font) instead of `2em` of the element's own font-size,
     so a smaller .is-long label reserves the SAME pixel band. Content is
     TOP-anchored (-webkit-box-pack:start) so a 1-line label aligns with line 1
     of a 2-line label rather than floating to the band's center — this is what
     keeps every label in a row on a shared baseline. */
  --dock-label-lh:calc(var(--ref-font-lg) * 1.25);
  min-height:calc(var(--dock-label-lh) * 2);
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  -webkit-box-pack:start;
  overflow:hidden}
/* Description hidden by default — too cluttered on phone */
.dock-desc{display:none}
/* v2.21.0: long-label fit. Carleton asked that only the long dock names
   ("Commissions", "Knowledge") be slightly smaller so they sit on ONE line —
   not a global type change. app.js tags labels over a length threshold with
   .is-long; these rules shrink ONLY those.
   v2.24.3 alignment fix: shrink the FONT ONLY. The previous rule also forced
   the label to one line AND collapsed its reserved height to `1.25em`, so a
   long label's text block was half as tall as its neighbors' and, inside the
   center-justified tile, dropped to a lower vertical position — the "labels
   don't line up" report (Knowledge/Analytics/Commission/Messages rendered
   smaller AND lower than Sketch/Game/Receipt). With the band height no longer
   overridden, the long label keeps the same top-anchored two-line band as
   every other tile, so the whole row shares one baseline. The smaller font
   still comfortably fits on one line within that band. */
/* v2.24.5: UNIFORM LABEL SIZE. Carleton: "we need a uniform pt size for the
   text under the apps." The .is-long shrink is removed so EVERY app label
   renders at the same size. Long names (Estimates, Commission, Knowledge,
   Analytics, Messages) now wrap to two lines inside the reserved two-line band
   (already top-anchored, so the row stays baseline-aligned) instead of being
   shrunk to fit one line. Result: one consistent type size across the whole
   dock/grid, still aligned, no truncation. The .is-long class may still be
   emitted by app.js — these no-op overrides keep the size uniform regardless. */
.dock-label.is-long{
  font-size:var(--ref-font-lg);   /* same as base — no shrink */
}
.app-ic .nm.is-long{
  font-size:var(--ref-font-sm);   /* same as base — no shrink */
}
/* Tablet+ (600px): bigger icons, more padding */
@media(min-width:600px){
  .dock-app{min-height:150px;padding:var(--ref-space-6) var(--ref-space-5)}
  .dock-icon{width:84px;height:84px;border-radius:var(--ref-radius-xl)}
  .dock-icon svg{width:40px;height:40px}
  .dock-label{font-size:var(--ref-font-xl)}
  /* Show description only on tablet where there's room */
  .dock-desc{display:-webkit-box;font-size:var(--ref-font-xs);color:var(--sys-text-sec);
    text-align:center;line-height:1.3;max-width:100%;
    -webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}
}
/* Wide tablet / desktop: 3-col if many apps */
@media(min-width:900px){
  .app-dock.dock-many{grid-template-columns:repeat(3,minmax(0,1fr))}
}
[data-theme="sunlight"] .dock-app{border:2.5px solid #000;box-shadow:none}
[data-theme="sunlight"] .dock-icon{border:2px solid #000}
/* v2.16.0 T11: Light mode dock-app surface contrast */
:root[data-theme="light"] .dock-app{background:var(--ref-gray-0);border-color:var(--ref-gray-200)}

/* ================================================================
   v2.14.4 C2: NOTIFICATION BADGES (on nav items)
   Non-red, subtle brand-accent badge. Plugins inject badge elements;
   this provides the styling contract.
   ================================================================ */
.ni-badge{position:absolute;top:2px;right:50%;transform:translateX(calc(50% + 14px));
  min-width:18px;height:18px;border-radius:var(--ref-radius-full);
  background:var(--sys-brand);color:var(--sys-text-brand);
  font-size:11px;font-weight:var(--sys-font-wt-sb);
  display:flex;align-items:center;justify-content:center;
  padding:0 5px;line-height:1;pointer-events:none;
  box-shadow:0 1px 3px rgba(0,0,0,.2)}
.ni-badge:empty{width:10px;height:10px;min-width:10px;padding:0}
[data-theme="sunlight"] .ni-badge{background:#000;color:#fff;border:1.5px solid #fff}

/* ================================================================
   v2.14.4 D1: ADD-TO-HOMESCREEN INSTALL BANNER
   ================================================================ */
.ts-install-banner{position:fixed;bottom:calc(49px + env(safe-area-inset-bottom,0px) + 8px);
  left:16px;right:16px;z-index:140;
  background:var(--sys-surface);border:1px solid var(--sys-border);
  border-radius:var(--ref-radius-lg);box-shadow:var(--sys-shadow-lg);
  padding:var(--ref-space-3) var(--ref-space-4);
  display:flex;align-items:center;gap:var(--ref-space-3);
  animation:toastIn var(--ref-dur-norm) var(--ref-ease) forwards}
.ts-install-text{flex:1;font-size:var(--ref-font-sm);font-weight:var(--sys-font-wt-md);
  color:var(--sys-text);line-height:1.3}
.ts-install-how-btn{padding:var(--ref-space-2) var(--ref-space-4);
  background:var(--sys-brand);color:var(--sys-text-brand);
  border-radius:var(--ref-radius-full);font-size:var(--ref-font-sm);
  font-weight:var(--sys-font-wt-sb);white-space:nowrap;min-height:36px;
  transition:background var(--ref-dur-fast) var(--ref-ease)}
.ts-install-how-btn:hover{background:var(--sys-brand-hover)}
.ts-install-dismiss-btn{width:32px;height:32px;display:flex;align-items:center;
  justify-content:center;color:var(--sys-text-ter);border-radius:var(--ref-radius-sm);
  flex-shrink:0}
.ts-install-dismiss-btn:hover{background:var(--sys-bg-alt)}
@media(min-width:820px){
  .ts-install-banner{left:96px;max-width:480px}
}
[data-theme="sunlight"] .ts-install-banner{border:2px solid #000}
[data-theme="sunlight"] .ts-install-how-btn{background:#000}

/* v2.17.2: iOS Add-to-Home-Screen visual guide overlay */
.ts-ios-guide{
  display:none;position:fixed;inset:0;z-index:200;
  align-items:flex-start;justify-content:center;
  padding:calc(env(safe-area-inset-top,0px) + 16px) 16px 0;
}
.ts-ios-guide-backdrop{position:absolute;inset:0;background:rgba(0,0,0,.55);}
.ts-ios-guide-card{
  position:relative;z-index:1;
  background:var(--sys-surface,#1e293b);border:1px solid var(--sys-border,#334155);
  border-radius:20px;padding:24px 20px 20px;
  width:100%;max-width:380px;
  box-shadow:0 8px 32px rgba(0,0,0,.4);
  animation:toastIn 250ms ease forwards;
}
.ts-ios-guide-close{
  position:absolute;top:12px;right:12px;width:32px;height:32px;
  display:flex;align-items:center;justify-content:center;
  border-radius:50%;background:var(--sys-bg-alt,#374151);color:var(--sys-text-sec,#9ca3af);
  font-size:16px;cursor:pointer;border:none;
}
.ts-ios-guide-title{
  font-size:18px;font-weight:700;color:var(--sys-text,#fff);margin:0 0 4px;
}
.ts-ios-guide-sub{
  font-size:13px;color:var(--sys-text-sec,#9ca3af);margin:0 0 16px;
}
.ts-ios-guide-steps{display:flex;flex-direction:column;gap:14px;}
.ts-ios-guide-step{display:flex;align-items:flex-start;gap:12px;}
.ts-ios-guide-num{
  flex-shrink:0;width:28px;height:28px;
  display:flex;align-items:center;justify-content:center;
  background:var(--sys-brand,#2C5F8A);color:#fff;
  border-radius:50%;font-size:14px;font-weight:700;
}
.ts-ios-guide-content{
  display:flex;align-items:center;gap:10px;
  font-size:14px;color:var(--sys-text,#e2e8f0);line-height:1.4;
  padding-top:3px;
}
.ts-ios-guide-content strong{color:var(--sys-brand,#60a5fa);font-weight:600;}
.ts-ios-guide-icon{
  flex-shrink:0;width:36px;height:36px;
  display:flex;align-items:center;justify-content:center;
  background:var(--sys-bg-alt,#374151);border-radius:8px;
  color:var(--sys-brand,#60a5fa);
}
.ts-ios-guide-arrow{
  display:flex;justify-content:flex-end;margin-top:16px;padding-right:8px;
  color:var(--sys-text-sec,#9ca3af);
}
.ts-ios-guide-arrow.ts-arrow-bounce{
  animation:ts-bounce-arrow 1.2s ease-in-out infinite;
}
@keyframes ts-bounce-arrow{
  0%,100%{transform:translateY(0);opacity:.6}
  50%{transform:translateY(8px);opacity:1}
}

/* ================================================================
   QUICK STATS ROW — v2.14.4.3: larger touch-friendly stat cards
   ================================================================ */
.quick-stats{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--ref-space-3);padding:var(--ref-space-3) 0}
.stat-pill{display:flex;align-items:center;gap:var(--ref-space-3);
  padding:var(--ref-space-4);background:var(--sys-surface);
  border:1px solid var(--sys-border);border-radius:var(--ref-radius-lg);
  box-shadow:var(--sys-shadow-sm);min-height:64px;min-width:0;
  cursor:pointer;transition:all var(--ref-dur-fast) var(--ref-ease)}
@media(hover:hover){
  .stat-pill:hover{box-shadow:var(--sys-shadow-md);border-color:var(--sys-brand)}
}
.stat-pill:active{transform:scale(.95);box-shadow:none;
  background:var(--sys-surface-subtle);border-color:var(--sys-brand);
  transition-duration:50ms}
.stat-pill .stat-icon{width:36px;height:36px;border-radius:var(--ref-radius-md);display:flex;
  align-items:center;justify-content:center;flex-shrink:0}
.stat-pill .stat-icon svg{width:20px;height:20px}
.stat-pill .stat-val{font-size:var(--ref-font-md);font-weight:var(--sys-font-wt-sb)}
.stat-pill .stat-label{font-size:var(--ref-font-sm);color:var(--sys-text-sec)}
/* When 3 pills, let the third span full width */
.quick-stats .stat-pill:nth-child(3):last-child{grid-column:1 / -1}

/* ================================================================
   SEARCH BAR (inline — hidden by default, kept for compatibility)
   ================================================================ */
.search-wrap{position:relative;margin-bottom:var(--ref-space-3)}
.search-bar{display:flex;align-items:center;gap:var(--ref-space-2);
  background:var(--sys-surface);border:1.5px solid var(--sys-border);
  /* v2.21.0: match .dash-search-trigger — rounded rectangle, not pill. */
  border-radius:var(--ref-radius-lg);padding:0 var(--ref-space-4);
  min-height:52px;transition:border-color var(--ref-dur-fast) var(--ref-ease),
  box-shadow var(--ref-dur-fast) var(--ref-ease)}
.search-bar:focus-within{border-color:var(--sys-brand);box-shadow:var(--sys-shadow-md)}
.search-bar svg{color:var(--sys-text-ter);flex-shrink:0;width:18px;height:18px}
.search-bar input{flex:1;background:none;font-size:var(--ref-font-base);
  color:var(--sys-text);min-height:50px;user-select:text}
.search-bar input::placeholder{color:var(--sys-text-ter)}
.search-results{position:absolute;top:calc(100% + 4px);left:0;right:0;
  background:var(--sys-surface);border:1px solid var(--sys-border);
  border-radius:var(--ref-radius-lg);box-shadow:var(--sys-shadow-lg);
  max-height:320px;overflow-y:auto;z-index:90;display:none}
.search-results.show{display:block}
.search-item{display:flex;align-items:center;gap:var(--ref-space-3);
  padding:var(--ref-space-3) var(--ref-space-4);cursor:pointer;
  transition:background var(--ref-dur-fast) var(--ref-ease)}
.search-item:hover{background:var(--sys-bg-alt)}
.search-item .si-icon{width:36px;height:36px;border-radius:var(--ref-radius-sm);
  display:flex;align-items:center;justify-content:center;color:#fff;flex-shrink:0}
.search-item .si-text{flex:1;min-width:0}
.search-item .si-name{font-size:var(--ref-font-sm);font-weight:var(--sys-font-wt-md)}
.search-item .si-hint{font-size:var(--ref-font-xs);color:var(--sys-text-ter);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.search-item .si-slash{font-size:var(--ref-font-xs);color:var(--sys-text-ter);
  font-family:monospace;background:var(--sys-bg-alt);padding:2px 6px;
  border-radius:var(--ref-radius-xs);flex-shrink:0}

/* ================================================================
   RECENTLY USED
   ================================================================ */
.section-label{font-size:var(--ref-font-sm);font-weight:var(--sys-font-wt-sb);
  color:var(--sys-text-ter);text-transform:uppercase;letter-spacing:.06em;
  margin-bottom:var(--ref-space-2);padding-left:var(--ref-space-1)}
.recent-row{display:flex;flex-wrap:wrap;gap:var(--ref-space-3);padding-bottom:var(--ref-space-3)}
.recent-chip{flex:0 0 auto;display:flex;align-items:center;gap:var(--ref-space-2);
  padding:var(--ref-space-2) var(--ref-space-4) var(--ref-space-2) var(--ref-space-2);
  background:var(--sys-surface);border:1px solid var(--sys-border);
  border-radius:var(--ref-radius-full);cursor:pointer;min-height:48px;
  transition:all var(--ref-dur-fast) var(--ref-ease)}
.recent-chip:hover{border-color:var(--sys-brand);background:var(--sys-brand-light)}
.recent-chip:active{transform:scale(.97)}
.recent-chip .rc-icon{width:32px;height:32px;border-radius:50%;display:flex;
  align-items:center;justify-content:center;color:#fff;flex-shrink:0}
.recent-chip .rc-icon svg{width:16px;height:16px}
.recent-chip .rc-name{font-size:var(--ref-font-sm);font-weight:var(--sys-font-wt-md);
  white-space:nowrap}

/* ================================================================
   APP GRID
   ================================================================ */
.cat-section{margin-bottom:var(--ref-space-5)}
.cat-label{font-size:var(--ref-font-sm);font-weight:var(--sys-font-wt-sb);
  color:var(--sys-text-ter);text-transform:uppercase;letter-spacing:.06em;
  margin-bottom:var(--ref-space-3);padding-left:var(--ref-space-1);
  display:flex;align-items:center;gap:var(--ref-space-2)}
.cat-label .cat-dot{width:8px;height:8px;border-radius:50%}
.app-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--ref-space-4)}
@media(min-width:400px){.app-grid{grid-template-columns:repeat(4,1fr)}}
@media(min-width:600px){.app-grid{grid-template-columns:repeat(5,1fr)}}
@media(min-width:900px){.app-grid{grid-template-columns:repeat(6,1fr)}}
.app-ic{display:flex;flex-direction:column;align-items:center;gap:var(--ref-space-2);
  padding:var(--ref-space-2);cursor:pointer;border-radius:var(--ref-radius-md);
  transition:all var(--ref-dur-fast) var(--ref-ease)}
@media(hover:hover){
  .app-ic:hover{transform:translateY(-2px)}
  .app-ic:hover .ic{box-shadow:var(--sys-shadow-md)}
}
.app-ic:active{transform:scale(.90);transition-duration:50ms}
.app-ic:active .ic{box-shadow:none;filter:brightness(.85)}
.app-ic .ic{width:80px;height:80px;border-radius:var(--ref-radius-lg);
  display:flex;align-items:center;justify-content:center;color:#fff;
  box-shadow:var(--sys-shadow-sm);transition:box-shadow var(--ref-dur-fast) var(--ref-ease)}
.app-ic:hover .ic{box-shadow:var(--sys-shadow-md)}
.app-ic .ic svg{width:36px;height:36px;stroke-width:var(--sys-icon-wt)}
.app-ic .nm{font-size:var(--ref-font-sm);font-weight:var(--sys-font-wt-md);
  text-align:center;color:var(--sys-text-sec);line-height:1.2;
  max-width:96px;
  /* v2.24.5: uniform size, no truncation. Was white-space:nowrap + ellipsis +
     a single-line band, which (with the .is-long shrink removed) would clip a
     long name like "Commission" at full size. Now the label wraps to a fixed
     TWO-line band (sized off the full font), top-anchored, clamped at 2 lines —
     so every grid label is the same size, never truncated, and still shares a
     top baseline with its neighbours whether it's 1 or 2 lines. */
  --nm-lh:calc(var(--ref-font-sm) * 1.2);
  min-height:calc(var(--nm-lh) * 2);
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  -webkit-box-pack:start;overflow:hidden}

/* ---- Staggered App Grid Animations ---- */
@keyframes fadeSlideUp{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}
.app-ic{opacity:0;animation:fadeSlideUp var(--ref-dur-norm) var(--ref-ease-out) forwards}
.app-ic:nth-child(1){animation-delay:30ms}.app-ic:nth-child(2){animation-delay:60ms}
.app-ic:nth-child(3){animation-delay:90ms}.app-ic:nth-child(4){animation-delay:120ms}
.app-ic:nth-child(5){animation-delay:150ms}.app-ic:nth-child(6){animation-delay:180ms}
.app-ic:nth-child(7){animation-delay:210ms}.app-ic:nth-child(8){animation-delay:240ms}

/* ---- Large Action Cards (role-adaptive ≤4 apps) ---- */
.app-grid-hero{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--ref-space-4);
  margin-bottom:var(--ref-space-5)}
.action-card{display:flex;align-items:center;gap:var(--ref-space-4);
  padding:var(--ref-space-5);background:var(--sys-surface);
  border:1px solid var(--sys-border);border-radius:var(--ref-radius-xl);
  cursor:pointer;min-height:88px;
  box-shadow:var(--sys-shadow-sm);
  transition:all var(--ref-dur-fast) var(--ref-ease);
  opacity:0;animation:fadeSlideUp var(--ref-dur-norm) var(--ref-ease-out) forwards}
@media(hover:hover){
  .action-card:hover{box-shadow:var(--sys-shadow-md);transform:translateY(-2px);
    border-color:var(--sys-brand)}
}
.action-card:active{transform:scale(.95);box-shadow:none;
  background:var(--sys-surface-subtle);border-color:var(--sys-brand);
  transition-duration:50ms}
.action-card:nth-child(1){animation-delay:30ms}.action-card:nth-child(2){animation-delay:90ms}
.action-card:nth-child(3){animation-delay:150ms}.action-card:nth-child(4){animation-delay:210ms}
.action-card .ac-icon{width:60px;height:60px;border-radius:var(--ref-radius-lg);
  display:flex;align-items:center;justify-content:center;color:#fff;flex-shrink:0}
.action-card .ac-icon svg{width:30px;height:30px;stroke-width:var(--sys-icon-wt)}
.action-card .ac-text{flex:1;min-width:0}
.action-card .ac-name{font-size:var(--ref-font-md);font-weight:var(--sys-font-wt-sb)}
.action-card .ac-desc{font-size:var(--ref-font-sm);color:var(--sys-text-sec);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:2px}

/* ---- View Fade Animation ---- */
@keyframes viewFadeIn{from{opacity:0}to{opacity:1}}
.sub-view.active{animation:viewFadeIn var(--ref-dur-norm) var(--ref-ease)}

/* ================================================================
   KPI METRIC GRID (v2.8.0 — Owner / Admin / Mfg views)
   ================================================================ */
.kpi-grid{
  /* v2.21.0: minmax(0,1fr) (was bare 1fr) — same Safari/high-DPI sub-pixel
     fix the app dock uses. Bare 1fr + gap can resolve to fractional px that
     exceeds the container, clipping the right column. */
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:var(--ref-space-3);padding:var(--ref-space-2) 0;margin-bottom:var(--ref-space-3)}
.kpi-card{
  background:var(--sys-surface);border:1px solid var(--sys-border);
  border-radius:var(--ref-radius-lg);padding:var(--ref-space-3) var(--ref-space-4);
  display:flex;flex-direction:column;gap:2px;min-height:72px;
  box-shadow:var(--sys-shadow-sm);transition:all var(--ref-dur-fast) var(--ref-ease)}
@media(hover:hover){.kpi-card:hover{box-shadow:var(--sys-shadow-md)}}
.kpi-card.kpi-primary{
  padding:var(--ref-space-4) var(--ref-space-5);min-height:96px}
.kpi-card.kpi-action{cursor:pointer}
@media(hover:hover){
  .kpi-card.kpi-action:hover{border-color:var(--sys-brand);background:var(--sys-brand-light)}
}
.kpi-card.kpi-action:active{transform:scale(.95);box-shadow:none;
  background:var(--sys-surface-subtle);border-color:var(--sys-brand);
  transition-duration:50ms}
.kpi-label{
  font-size:var(--ref-font-xs);font-weight:var(--sys-font-wt-md);
  color:var(--sys-text-sec);text-transform:uppercase;letter-spacing:.04em}
.kpi-value{
  font-size:var(--ref-font-xl);font-weight:var(--sys-font-wt-b);
  color:var(--sys-text);line-height:1.1}
.kpi-card.kpi-primary .kpi-value{font-size:var(--ref-font-2xl)}
.kpi-sub{font-size:var(--ref-font-xs);color:var(--sys-text-ter);margin-top:2px}
.kpi-icon{display:flex;align-items:center;margin-bottom:2px}
.kpi-icon svg{width:20px;height:20px;stroke-width:var(--sys-icon-wt)}
/* KPI loading shimmer & loaded flash */
.kpi-loading{
  background:linear-gradient(90deg,var(--sys-bg-alt) 25%,var(--sys-border) 50%,var(--sys-bg-alt) 75%);
  background-size:200% 100%;animation:shimmer 1.5s infinite;
  border-radius:var(--ref-radius-xs);color:transparent!important;
  min-width:40px;min-height:1.1em}
@keyframes kpiFadeIn{from{opacity:.3}to{opacity:1}}
.kpi-loaded{animation:kpiFadeIn .3s ease forwards}
/* Responsive: 4-col on wider screens, primary cards span 2 */
@media(min-width:600px){
  .kpi-grid{grid-template-columns:repeat(4,1fr)}
  .kpi-card.kpi-primary{grid-column:span 2}}
@media(min-width:900px){
  .kpi-grid:not(.kpi-grid-compact){grid-template-columns:repeat(4,1fr)}}

/* ---- Sunlight overrides ---- */
[data-theme="sunlight"] .action-card{border:2.5px solid #000;box-shadow:none}
[data-theme="sunlight"] .app-ic .ic{border:2.5px solid #000;box-shadow:none}
[data-theme="sunlight"] .stat-pill{border:2px solid #000;box-shadow:none}
[data-theme="sunlight"] .kpi-card{border:2.5px solid #000;box-shadow:none}
[data-theme="sunlight"] .kpi-card.kpi-primary{border-width:3px}
[data-theme="sunlight"] .recent-chip{border:2px solid #000}
[data-theme="sunlight"] .search-bar{border:2px solid #000}
[data-theme="sunlight"] .dash-search-trigger{border:2px solid #000}
[data-theme="sunlight"] .bnav{border-top:2px solid #000}

/* v2.14.4 A6: Bigger bottom nav on tablet (before sidebar breakpoint) */
@media(min-width:600px) and (max-width:819px){
  .bnav{height:56px}
  .ni{min-height:52px}
  .ni svg{width:26px;height:26px}
  .ni .ni-label{font-size:15px}
}

/* ================================================================
   BOTTOM SHEET (App Interaction Panel — legacy, kept for plugins)
   ================================================================ */
.sheet-overlay{position:fixed;inset:0;background:var(--sys-overlay);z-index:200;
  opacity:0;pointer-events:none;transition:opacity var(--ref-dur-norm) var(--ref-ease)}
.sheet-overlay.show{opacity:1;pointer-events:auto}
.bottom-sheet{position:fixed;bottom:0;left:0;right:0;z-index:210;
  background:var(--sys-surface);border-radius:var(--ref-radius-2xl) var(--ref-radius-2xl) 0 0;
  box-shadow:var(--sys-shadow-xl);transform:translateY(100%);
  transition:transform var(--ref-dur-slow) var(--ref-ease-spring);
  max-height:92dvh;display:flex;flex-direction:column;
  padding-bottom:env(safe-area-inset-bottom,0)}
.bottom-sheet.show{transform:translateY(0)}
.sheet-handle{width:36px;height:4px;background:var(--sys-border-strong);
  border-radius:var(--ref-radius-full);margin:var(--ref-space-3) auto var(--ref-space-2);flex-shrink:0}
.sheet-header{display:flex;align-items:center;gap:var(--ref-space-3);
  padding:0 var(--ref-space-5) var(--ref-space-4);border-bottom:1px solid var(--sys-border)}
.sheet-header .sh-icon{width:44px;height:44px;border-radius:var(--ref-radius-md);
  display:flex;align-items:center;justify-content:center;color:#fff;flex-shrink:0}
.sheet-header .sh-icon svg{width:22px;height:22px;stroke-width:var(--sys-icon-wt)}
.sheet-header .sh-info{flex:1;min-width:0}
.sheet-header .sh-title{font-size:var(--ref-font-lg);font-weight:var(--sys-font-wt-sb)}
.sheet-header .sh-desc{font-size:var(--ref-font-sm);color:var(--sys-text-sec)}
.sheet-header .sh-close{color:var(--sys-text-ter)}
.sheet-body{flex:1;overflow-y:auto;padding:var(--ref-space-4) var(--ref-space-5);
  -webkit-overflow-scrolling:touch}
.sheet-body .fg{margin-bottom:var(--ref-space-4)}
.sheet-body .fg label{display:block;font-size:var(--ref-font-sm);font-weight:var(--sys-font-wt-md);
  color:var(--sys-text-sec);margin-bottom:var(--ref-space-1)}
.sheet-body .fg input,.sheet-body .fg select,.sheet-body .fg textarea{width:100%;
  padding:var(--ref-space-3) var(--ref-space-4);border:1.5px solid var(--sys-border);
  border-radius:var(--ref-radius-md);background:var(--sys-bg);font-size:var(--ref-font-base);
  min-height:52px;transition:border-color var(--ref-dur-fast) var(--ref-ease)}
.sheet-body .fg input:focus,.sheet-body .fg select:focus,.sheet-body .fg textarea:focus{
  border-color:var(--sys-brand)}
.sheet-body .fg textarea{min-height:80px;resize:vertical}
.sheet-footer{padding:var(--ref-space-4) var(--ref-space-5);border-top:1px solid var(--sys-border);
  display:flex;gap:var(--ref-space-3)}
.sheet-footer .btn{flex:1}

/* ---- Output area ---- */
.output-area{margin-top:var(--ref-space-4);padding:var(--ref-space-4);
  background:var(--sys-bg-alt);border-radius:var(--ref-radius-md);
  border-left:3px solid var(--sys-success);font-size:var(--ref-font-sm);
  font-family:'Inter',monospace;line-height:1.65;white-space:pre-wrap;
  word-break:break-word;display:none;color:var(--sys-text)}
.output-area.visible{display:block}
.output-area.loading{border-left-color:var(--sys-brand)}

/* Shimmer */
.shimmer{background:linear-gradient(90deg,var(--sys-bg-alt) 25%,var(--sys-border) 50%,var(--sys-bg-alt) 75%);
  background-size:200% 100%;animation:shimmer 1.5s infinite;border-radius:var(--ref-radius-sm);
  height:14px;margin:var(--ref-space-2) 0}
@keyframes shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}

/* ================================================================
   BOTTOM NAVIGATION (3-item: Dashboard | Logo | Chat)
   ================================================================ */
.bnav{position:fixed;bottom:0;left:0;right:0;z-index:150;
  background-color:var(--sys-surface);
  background:var(--sys-surface-blur);backdrop-filter:blur(16px);
  -webkit-backdrop-filter:blur(16px);
  border-top:1px solid var(--sys-border);
  padding-bottom:env(safe-area-inset-bottom,0);
  display:flex;align-items:center;justify-content:center;min-height:49px;
  transition:transform .35s cubic-bezier(.32,.72,0,1)}
/* v2.16.0 T9: Dark mode solid fallback for nav bar */
[data-theme="dark"] .bnav{background-color:var(--ref-gray-800)}
@media(prefers-color-scheme:dark){
  [data-theme="system"] .bnav{background-color:var(--ref-gray-800)}
}

/* Left + Right nav items (Apps, Chat) */
.ni{display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:2px;flex:1;min-height:44px;color:var(--sys-text-ter);
  transition:color var(--ref-dur-fast) var(--ref-ease);position:relative;padding:4px 0}
.ni svg{width:22px;height:22px;stroke-width:var(--sys-icon-wt);
  transition:all var(--ref-dur-fast) var(--ref-ease)}
.ni .ni-label{font-size:var(--ref-font-xs);font-weight:var(--sys-font-wt-md)}
.ni.active{color:var(--sys-brand)}
.ni.active svg{stroke-width:calc(var(--sys-icon-wt) + .5)}
.ni.active::before{content:'';position:absolute;top:4px;width:36px;height:28px;
  background:var(--sys-brand-light);border-radius:var(--ref-radius-full);z-index:-1}

/* Center logo button */
.bnav-logo{flex:0 0 auto;display:flex;align-items:center;justify-content:center;
  padding:4px var(--ref-space-4);height:100%;cursor:pointer;
  transition:opacity var(--ref-dur-fast) var(--ref-ease)}
.bnav-logo:hover{opacity:.8}
.bnav-logo:active{transform:scale(.95)}
.bnav-logo img{max-width:100px;max-height:36px;width:auto;height:auto;object-fit:contain}
.bnav-logo-text{font-size:var(--ref-font-lg);font-weight:var(--sys-font-wt-b);
  color:var(--sys-brand);letter-spacing:-.02em}
/* v2.21.0: logo contrast safety net.
   The correct fix is uploading a dark-inked light-mode logo to ts_logo_light
   (see DATA-THEME-CONTRACT). But if that asset is missing/misconfigured, the
   dark-mode (light-inked) wordmark renders on a near-white bar and becomes
   invisible. As a defensive backstop in the light surfaces ONLY, paint a
   subtle neutral chip behind the logo so a light-inked wordmark still reads.
   Scoped to light/sunlight; dark mode (where the light-inked logo is correct)
   is untouched. Kept subtle so it's invisible when the right asset IS used. */
:root[data-theme="light"] .bnav-logo,
:root[data-theme="sunlight"] .bnav-logo{
  background:var(--sys-surface-subtle);border-radius:var(--ref-radius-md)}

/* Settings-active state: highlight logo when settings view is open */
.bnav-logo.settings-active{opacity:1;position:relative}
.bnav-logo.settings-active::after{content:'';position:absolute;bottom:2px;
  width:24px;height:3px;background:var(--sys-brand);border-radius:var(--ref-radius-full)}

/* FAB (Floating Action Button) — REMOVED v2.6.3 */

/* ================================================================
   SETTINGS
   ================================================================ */
.profile-card{background:var(--sys-surface);border:1px solid var(--sys-border);
  border-radius:var(--ref-radius-lg);padding:var(--ref-space-5);
  display:flex;align-items:center;gap:var(--ref-space-4);margin-bottom:var(--ref-space-5)}
.profile-avatar{width:54px;height:54px;border-radius:50%;background:var(--sys-brand);
  display:flex;align-items:center;justify-content:center;color:var(--sys-text-brand);
  font-size:var(--ref-font-xl);font-weight:var(--sys-font-wt-b);flex-shrink:0}
.profile-info h3{font-size:var(--ref-font-lg);font-weight:var(--sys-font-wt-sb)}
.profile-info p{font-size:var(--ref-font-sm);color:var(--sys-text-sec)}
.settings-section{margin-bottom:var(--ref-space-5)}
.settings-section h4{font-size:var(--ref-font-sm);font-weight:var(--sys-font-wt-sb);
  color:var(--sys-text-ter);text-transform:uppercase;letter-spacing:.06em;
  margin-bottom:var(--ref-space-3)}
.theme-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--ref-space-3)}
.theme-btn{padding:var(--ref-space-3);border:2px solid var(--sys-border);
  border-radius:var(--ref-radius-md);text-align:center;min-height:56px;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;
  transition:all var(--ref-dur-fast) var(--ref-ease);font-size:var(--ref-font-xs);
  font-weight:var(--sys-font-wt-md)}
.theme-btn:hover{border-color:var(--sys-brand)}
.theme-btn.active{border-color:var(--sys-brand);background:var(--sys-brand-light);
  color:var(--sys-brand)}
.theme-btn .tb-preview{width:28px;height:28px;border-radius:50%;border:2px solid var(--sys-border)}
.settings-row{display:flex;align-items:center;justify-content:space-between;
  padding:var(--ref-space-4);background:var(--sys-surface);
  border:1px solid var(--sys-border);border-radius:var(--ref-radius-md);
  margin-bottom:var(--ref-space-2);min-height:56px}
.settings-row .sr-left{display:flex;align-items:center;gap:var(--ref-space-3)}
.settings-row .sr-left svg{color:var(--sys-text-sec);width:20px;height:20px}
.settings-row .sr-label{font-size:var(--ref-font-sm);font-weight:var(--sys-font-wt-md)}
.settings-row .sr-right{font-size:var(--ref-font-sm);color:var(--sys-text-sec)}
.status-badge{display:inline-flex;align-items:center;gap:var(--ref-space-1);
  padding:var(--ref-space-1) var(--ref-space-3);border-radius:var(--ref-radius-full);
  font-size:var(--ref-font-xs);font-weight:var(--sys-font-wt-md)}
.status-badge.online{background:rgba(16,185,129,.12);color:var(--ref-green-600)}
.status-badge.offline{background:rgba(239,68,68,.12);color:var(--ref-red-600)}
.status-dot{width:6px;height:6px;border-radius:50%;background:currentColor}

/* ================================================================
   TOAST
   ================================================================ */
.toast-container{position:fixed;top:calc(env(safe-area-inset-top,0) + 16px);
  left:50%;transform:translateX(-50%);z-index:300;display:flex;flex-direction:column;
  gap:var(--ref-space-2);pointer-events:none;width:calc(100% - 32px);max-width:400px}
.toast{background:var(--sys-surface-raised);color:var(--sys-text);
  padding:var(--ref-space-3) var(--ref-space-4);border-radius:var(--ref-radius-md);
  box-shadow:var(--sys-shadow-lg);border:1px solid var(--sys-border);
  font-size:var(--ref-font-sm);font-weight:var(--sys-font-wt-md);
  display:flex;align-items:center;gap:var(--ref-space-2);
  transform:translateY(-20px);opacity:0;pointer-events:auto;
  animation:toastIn var(--ref-dur-norm) var(--ref-ease) forwards}
@keyframes toastIn{to{transform:translateY(0);opacity:1}}
.toast.out{animation:toastOut var(--ref-dur-norm) var(--ref-ease) forwards}
@keyframes toastOut{to{transform:translateY(-20px);opacity:0}}

/* ================================================================
   COMMAND PALETTE (Spotlight)
   ================================================================ */
.cmd-overlay{position:fixed;inset:0;background:var(--sys-overlay);z-index:250;
  display:none;align-items:flex-start;justify-content:center;padding-top:80px}
.cmd-overlay.show{display:flex}
.cmd-box{background:var(--sys-surface);border-radius:var(--ref-radius-xl);
  box-shadow:var(--sys-shadow-xl);width:calc(100% - 32px);max-width:480px;
  overflow:hidden}
.cmd-input-wrap{display:flex;align-items:center;gap:var(--ref-space-3);
  padding:var(--ref-space-4);border-bottom:1px solid var(--sys-border)}
.cmd-input-wrap svg{color:var(--sys-text-ter);flex-shrink:0}
.cmd-input-wrap input{flex:1;font-size:var(--ref-font-lg);background:none;user-select:text}
.cmd-results{max-height:360px;overflow-y:auto}
.cmd-item{display:flex;align-items:center;gap:var(--ref-space-3);
  padding:var(--ref-space-3) var(--ref-space-4);cursor:pointer;
  transition:background var(--ref-dur-fast) var(--ref-ease)}
.cmd-item:hover,.cmd-item.focused{background:var(--sys-brand-light)}
.cmd-item .ci-icon{width:36px;height:36px;border-radius:var(--ref-radius-sm);
  display:flex;align-items:center;justify-content:center;color:#fff;flex-shrink:0}
.cmd-item .ci-icon svg{width:18px;height:18px}
.cmd-item .ci-text{flex:1}
.cmd-item .ci-name{font-size:var(--ref-font-sm);font-weight:var(--sys-font-wt-md)}
.cmd-item .ci-desc{font-size:var(--ref-font-xs);color:var(--sys-text-ter)}
.cmd-item .ci-shortcut{font-size:var(--ref-font-xs);color:var(--sys-text-ter);
  font-family:monospace;background:var(--sys-bg-alt);padding:2px 8px;
  border-radius:var(--ref-radius-xs);flex-shrink:0}
.cmd-item-analytics{border-top:1px solid var(--sys-border)}
.cmd-item-analytics .ci-desc{font-style:italic}
.cmd-empty{padding:var(--ref-space-8);text-align:center;color:var(--sys-text-ter);
  font-size:var(--ref-font-sm)}

/* ================================================================
   BUG REPORT — Moved to bug-reporter.css (v2.7.0)
   ================================================================ */

/* ================================================================
   SUNLIGHT MODE SPECIFIC
   ================================================================ */
[data-theme="sunlight"] .bottom-sheet{border:2px solid #000;border-bottom:none}
[data-theme="sunlight"] .profile-card{border:2px solid #000}
[data-theme="sunlight"] .settings-row{border:2px solid #000}
[data-theme="sunlight"] .theme-btn{border:2px solid #000}
[data-theme="sunlight"] .toast{border:2px solid #000}

/* ================================================================
   DARK MODE CONTRAST (v2.14.4 A7)
   Increased border visibility and card elevation for dark/system.
   ================================================================ */
[data-theme="dark"] .kpi-card,
[data-theme="dark"] .dash-widget-container,
[data-theme="dark"] .stat-pill{
  border-color:var(--ref-gray-600);
  box-shadow:0 2px 8px rgba(0,0,0,.35)}
[data-theme="dark"] .dash-widget-header{
  background:rgba(255,255,255,.04);border-bottom-color:var(--ref-gray-600)}
[data-theme="dark"] .kpi-label{color:var(--ref-gray-300)}
@media(prefers-color-scheme:dark){
  [data-theme="system"] .kpi-card,
  [data-theme="system"] .dash-widget-container,
  [data-theme="system"] .stat-pill{
    border-color:var(--ref-gray-600);
    box-shadow:0 2px 8px rgba(0,0,0,.35)}
  [data-theme="system"] .dash-widget-header{
    background:rgba(255,255,255,.04);border-bottom-color:var(--ref-gray-600)}
  [data-theme="system"] .kpi-label{color:var(--ref-gray-300)}
}


/* ================================================================
   DASHBOARD WIDGETS (v2.0)
   ================================================================ */
.dash-widget-zone{
  display:flex;flex-direction:column;gap:var(--ref-space-4);
  margin-bottom:var(--ref-space-5);
}
.dash-widget-container{
  background:var(--sys-surface);border:1px solid var(--sys-border);
  border-radius:var(--ref-radius-xl);overflow:hidden;
  box-shadow:var(--sys-shadow-sm);
}
.dash-widget-header{
  display:flex;align-items:center;gap:var(--ref-space-3);
  padding:var(--ref-space-3) var(--ref-space-4);
  border-bottom:1px solid var(--sys-border);background:var(--sys-bg-alt);
}
.dash-widget-header .dw-icon{
  width:32px;height:32px;border-radius:var(--ref-radius-sm);
  display:flex;align-items:center;justify-content:center;color:#fff;flex-shrink:0;
}
.dash-widget-header .dw-icon svg{width:18px;height:18px}
.dash-widget-header .dw-title{
  font-size:var(--ref-font-md);font-weight:var(--sys-font-wt-sb);margin:0;
}
/* v2.14.3.1: Widget reorder controls — up/down arrows in the header */
.dw-reorder{
  margin-left:auto;display:flex;gap:2px;flex-shrink:0;
}
.dw-reorder-btn{
  width:32px;height:28px;border:1px solid var(--sys-border, #e2e8f0);border-radius:var(--ref-radius-sm, 6px);
  background:var(--sys-surface, #fff);color:var(--sys-text, #1e293b);cursor:pointer;
  display:flex;align-items:center;justify-content:center;transition:all 0.15s ease;
  padding:0;-webkit-tap-highlight-color:transparent;touch-action:manipulation;
}
.dw-reorder-btn:active:not(:disabled){background:var(--sys-brand, #2C5F8A);color:#fff;transform:scale(0.92);}
.dw-reorder-btn:disabled{opacity:0.25;cursor:not-allowed;}
.dw-reorder-btn svg{pointer-events:none;}
/* v2.20.3-patched-8: dark-mode reorder chevron contrast. The currentColor SVG
   arrows were nearly invisible on the dark widget surface when inking from
   --sys-text-sec. Pin the glyph to full --sys-text and give the tap target a
   faint surface lift + stronger border so it reads on the shop floor. Light
   and sunlight are unchanged. */
[data-theme="dark"] .dw-reorder-btn{
  color:var(--sys-text, #f1f5f9);
  background:rgba(255,255,255,.06);
  border-color:var(--ref-gray-600);
}
@media(prefers-color-scheme:dark){
  [data-theme="system"] .dw-reorder-btn{
    color:var(--sys-text, #f1f5f9);
    background:rgba(255,255,255,.06);
    border-color:var(--ref-gray-600);
  }
}
.dash-widget-body{padding:var(--ref-space-4)}

/* ================================================================
   v2.21.0: GAME CONTAINER BUDGET (wide screens / landscape)
   The game shown during AI waits is "tiny in the middle" on desktop/
   wide-tablet. The THEME owns the container height; the ts-game PLUGIN
   scales its canvas (letterbox, no resolution change) to fill it.
   Contract: a game surface may grow to --ts-game-max-h. On phones the
   budget is unchanged; on >=900px and in landscape it is larger so the
   plugin has vertical room to scale into. Target the game widget body
   or any element the plugin tags with [data-ts-game-surface].
   ================================================================ */
:root{ --ts-game-max-h: 480px; }
@media (min-width: 900px){ :root{ --ts-game-max-h: 720px; } }
@media (orientation: landscape) and (min-width: 740px){ :root{ --ts-game-max-h: 78vh; } }

/* ────────────────────────────────────────────────────────────────
   v2.24.8 — THE ACTUAL BLACK-SLIVER ROOT CAUSE (finally).
   v2.24.7 was structurally right (min-height on the container, body
   flex:1 1 auto + max-height) but STILL produced the sliver, because
   the budget variable resolved to 0px at the point of use.

   Why: the ts-game PLUGIN's own stylesheet (assets/css/game.css,
   v1.5.2) declares  :root{ --ts-game-max-h: 0px }  as its "no extra
   budget" fallback — and game.css is enqueued AFTER the theme's
   app.css. At equal :root specificity, the LATER sheet wins, so the
   theme's :root{480px} above is overridden back to 0px on the live
   page. Every `var(--ts-game-max-h)` below then evaluated to 0:
       container min-height:0  →  collapses
       body     max-height:0   →  clips the 324px game wrap to ~44px
   = the black sliver. (Verified live: computed --ts-game-max-h on
   :root read 0px; the v2.24.7 rule was present in the CSSOM but futile.)

   FIX: stop depending on the bare-:root variable the plugin owns and
   resets. Re-declare the budget on the game CONTAINER selector, whose
   specificity (0,1,1 — class + attribute) beats :root (0,0,1), so it
   wins regardless of sheet load order. Proven live: with the budget
   declared on this selector the container computed the real value
   (480 / 720 / 78vh) instead of 0. The :root defaults above are kept
   only as a harmless fallback for any element outside the container. */
.dash-widget-container[data-app-id="game"]{ --ts-game-max-h: 480px; }
@media (min-width: 900px){
  .dash-widget-container[data-app-id="game"]{ --ts-game-max-h: 720px; }
}
@media (orientation: landscape) and (min-width: 740px){
  .dash-widget-container[data-app-id="game"]{ --ts-game-max-h: 78vh; }
}

/* v2.24.6 / v2.24.7 / v2.24.8: the registered Game app id is "game" (NOT
   "ts-game"). The container gets a min-height so it grows (max-height alone
   only caps, never stretches); the body is flex:1 1 auto with a matching
   min-height so it FILLS the grown container instead of collapsing to the
   game's intrinsic 44px. With v2.24.8's container-scoped budget above, the
   var() references here now resolve to the real budget instead of the
   plugin's 0px — so the container grows, the body fills, and the canvas
   paints at full height. No more black sliver. */
.dash-widget-container[data-app-id="game"]{
  /* max() floor: even in the pathological case where some later sheet resets
     the budget var to 0 again, the container still keeps a usable height
     instead of collapsing. Proven live: with the container-scoped var above
     resolving correctly this grows to the budget; the floor is belt-and-braces. */
  min-height:max(var(--ts-game-max-h), 480px);
}
.dash-widget-container[data-app-id="game"] .dash-widget-body,
.dash-widget-container[data-app-id="game"] > div:last-child,
.dash-widget-container[data-app-id="game"] [data-ts-game-surface]{
  flex:1 1 auto;
  /* The deployed bug was specifically here: max-height:var(--ts-game-max-h)
     evaluated to max-height:0 (plugin's :root won), which CLIPPED the 324px
     game wrap to the ~44px sliver. A max() floor on BOTH min/max-height means
     the body can never be clipped to nothing again; the container's own
     max-height (max(560px,88vh)) remains the real ceiling, and the inner
     body keeps its overflow scroll for anything taller. (Verified live: body
     grew 44px → 493px once max-height was lifted off 0.) */
  min-height:max(var(--ts-game-max-h), 436px);
  max-height:max(var(--ts-game-max-h), 436px);
}

/* Sunlight overrides for widgets */
[data-theme="sunlight"] .dash-widget-container{
  border:2px solid #000;box-shadow:none;
}
[data-theme="sunlight"] .dash-widget-header{
  border-bottom:2px solid #000;background:#fff;
}

/* ================================================================
   FULL-SCREEN APP VIEWPORT
   ================================================================ */
/* v2.16.0 T5: Instant jump on mobile (no slide animation) */
.app-viewport{position:fixed;inset:0;z-index:200;background:var(--sys-bg);
  display:flex;flex-direction:column;
  transform:translateY(100%);
  transition:none;
  will-change:transform;
  pointer-events:none}
.app-viewport.active{transform:translateY(0);pointer-events:auto}

/* ---- App Header (compact) ---- */
/* v2.14.5: position:relative + z-index creates a stacking context so scrolled
   content in .app-body never composites above the header. box-shadow masks
   any sub-pixel edge bleed at the header/body boundary. */
.app-header{display:flex;align-items:center;gap:var(--ref-space-2);
  padding:env(safe-area-inset-top,0) var(--ref-space-3) 0;
  min-height:56px;background:var(--sys-surface);
  border-bottom:1px solid var(--sys-border);flex-shrink:0;
  position:relative;z-index:10;
  box-shadow:0 2px 6px -2px rgba(0,0,0,.1)}
.app-back{width:44px;height:44px;display:flex;align-items:center;justify-content:center;
  border-radius:var(--ref-radius-md);color:var(--sys-brand)}
.app-back:hover{background:var(--sys-brand-light)}
.app-header-info{display:flex;align-items:center;gap:var(--ref-space-2);flex:1;min-width:0}
.app-header-icon{width:28px;height:28px;border-radius:var(--ref-radius-sm);
  display:flex;align-items:center;justify-content:center;color:#fff;flex-shrink:0}
.app-header-icon svg{width:16px;height:16px}
.app-header-title{font-size:var(--ref-font-md);font-weight:var(--sys-font-wt-sb);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.app-header-actions{display:flex;align-items:center;gap:var(--ref-space-1)}

/* ---- App Body ---- */
/* v2.21.0: surface background (not the darker --sys-bg page void) so short
   ajax_html content doesn't flash a black band, and contain overscroll so
   rubber-banding past the content reveals the surface, not the void. */
.app-body{flex:1;overflow-y:auto;-webkit-overflow-scrolling:touch;
  background:var(--sys-surface);overscroll-behavior:contain}
.app-body.has-iframe{overflow:hidden}
.app-body iframe{width:100%;height:100%;border:none;display:block}

/* ---- Loading & Error states ---- */
.app-loading{display:flex;align-items:center;justify-content:center;height:200px;
  color:var(--sys-text-sec)}
.app-loading-spinner{width:28px;height:28px;border:3px solid var(--sys-border);
  border-top-color:var(--sys-brand);border-radius:50%;animation:appSpin .7s linear infinite}
@keyframes appSpin{to{transform:rotate(360deg)}}
.app-error{padding:var(--ref-space-6);text-align:center;color:var(--sys-text-sec);
  font-size:var(--ref-font-sm)}

/* ---- Chrome transitions when app is active ---- */
body.app-active .bnav{transform:translateY(100%);pointer-events:none}
body.app-active #view-main{pointer-events:none}

/* ---- Sunlight overrides for viewport ---- */
[data-theme="sunlight"] .app-header{border-bottom:2px solid #000}
/* v2.14.5: stronger shadow in dark themes for header edge masking */
[data-theme="dark"] .app-header{box-shadow:0 2px 8px -2px rgba(0,0,0,.35)}
@media(prefers-color-scheme:dark){
  [data-theme="system"] .app-header{box-shadow:0 2px 8px -2px rgba(0,0,0,.35)}
}

/* ================================================================
   DESKTOP RESPONSIVE BREAKPOINTS
   ================================================================ */
@media(min-width: 820px) {
  /* Bottom nav becomes left sidebar */
  .bnav {
    top: 0;
    bottom: 0;
    left: 0;
    right: auto;            /* ← UNSET mobile right:0 */
    width: 88px;            /* v2.14.4 A6: wider sidebar for bigger touch targets */
    height: 100%;           /* ← Explicit full height (not auto) */
    height: 100dvh;         /* ← Modern override for dynamic viewport */
    flex-direction: column;
    justify-content: flex-start;
    padding-top: var(--ref-space-4);
    padding-bottom: var(--ref-space-2);
    border-top: none;
    border-right: 1px solid var(--sys-border);
    transition: transform .35s cubic-bezier(.32,.72,0,1);
    overflow-y: auto;       /* ← Scrollable if content overflows */
    overflow-x: hidden;
  }
  .ni {
    flex: none;
    width: 100%;
    min-height: 76px;       /* v2.14.4 A6: taller touch targets */
  }
  .ni svg {
    width: 32px;            /* v2.16.0 T14: larger sidebar icons */
    height: 32px;
  }
  .ni .ni-label {
    font-size: 16px;        /* v2.16.0 T14: larger sidebar label text */
  }
  /* Logo in sidebar: sits between Dashboard and Chat */
  .bnav-logo {
    width: 100%;
    padding: var(--ref-space-4) var(--ref-space-2);
    justify-content: center;
  }
  .bnav-logo img {
    max-width: 64px;
    max-height: 32px;
  }
  /* v2.14.4 A5: Vertical logo gets more height in the sidebar */
  .bnav-logo img.logo-vertical {
    max-width: 56px;
    max-height: 64px;
    object-fit: contain;
  }
  .bnav-logo.settings-active::after {
    bottom: auto;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 3px;
    height: 24px;
  }

  #view-main {
    padding-bottom: 0;
    padding-left: 88px;     /* v2.14.4 A6: match wider sidebar */
  }

  /* v2.16.0 T5: Fast 0.1s transition on desktop */
  .app-viewport {
    transform: scale(0.96);
    opacity: 0;
    transition: transform .1s cubic-bezier(.32,.72,0,1), opacity .1s ease;
  }
  .app-viewport.active {
    transform: scale(1);
    opacity: 1;
  }

  /* Hide sidebar when app is active (slide left on desktop) */
  body.app-active .bnav {
    transform: translateX(-100%);
  }
}

@media(min-width: 1024px) {
  .app-grid {
    grid-template-columns: repeat(8, 1fr);
  }
  .cmd-overlay {
    padding-top: 120px;
  }
}

/* v2.13.0: App Authorizations + Nutshell modal (company-wide) */
.ts-app-auth-card { margin-bottom: var(--ref-space-4, 16px); }
.ts-app-auth-card .ts-auth-hint {
  font-size: var(--ref-font-xs, 13px);
  color: var(--sys-text-sec);
  margin: var(--ref-space-1, 4px) 0 var(--ref-space-3, 12px);
  line-height: 1.45;
}
.ts-app-auth-card .btn.is-loading { opacity: .7; cursor: wait; }
.ts-app-auth-card .ts-auth-divider {
  height: 1px;
  background: var(--sys-border);
  margin: var(--ref-space-4, 16px) 0;
}
.ts-app-auth-card .ts-auth-scope {
  font-size: var(--ref-font-xs, 12px);
  font-weight: 400;
  color: var(--sys-text-sec);
  margin-left: var(--ref-space-1, 4px);
}

/* v2.20.2: Field Preferences card */
.ts-field-prefs-card { margin-bottom: var(--ref-space-4, 16px); }
.ts-fp-header {
  display: flex; align-items: center; justify-content: space-between;
  cursor: pointer; padding: var(--ref-space-3, 12px) 0;
  -webkit-tap-highlight-color: transparent;
}
.ts-fp-header-left {
  display: flex; align-items: center; gap: var(--ref-space-2, 8px);
}
.ts-fp-header-left svg {
  width: 18px; height: 18px; color: var(--sys-text-sec);
}
.ts-fp-header-left h4 {
  margin: 0; font-size: var(--ref-font-sm, 14px);
  font-weight: var(--sys-font-wt-sb, 600);
  color: var(--sys-text-ter); text-transform: uppercase; letter-spacing: .06em;
}
.ts-fp-chevron {
  width: 18px; height: 18px; color: var(--sys-text-sec);
  transition: transform var(--ref-dur-fast, 150ms) var(--ref-ease);
  flex-shrink: 0;
}
.ts-fp-body { padding-bottom: var(--ref-space-3, 12px); }
.ts-fp-hint {
  font-size: var(--ref-font-xs, 13px); color: var(--sys-text-sec);
  line-height: 1.45; margin: 0 0 var(--ref-space-4, 16px);
}
.ts-fp-field { margin-bottom: var(--ref-space-4, 16px); }
.ts-fp-label {
  display: block; font-size: var(--ref-font-sm, 14px);
  font-weight: var(--sys-font-wt-md, 500); color: var(--sys-text);
  margin-bottom: var(--ref-space-1, 4px);
}
.ts-fp-field-hint {
  font-size: var(--ref-font-xs, 12px); color: var(--sys-text-ter);
  line-height: 1.4; margin: 0 0 var(--ref-space-2, 8px);
}
.ts-fp-textarea {
  width: 100%; box-sizing: border-box;
  padding: var(--ref-space-3, 12px);
  font-family: inherit; font-size: var(--ref-font-sm, 14px);
  line-height: 1.5; color: var(--sys-text);
  background: var(--sys-surface); border: 1px solid var(--sys-border);
  border-radius: var(--ref-radius-md, 8px);
  resize: vertical; min-height: 56px;
  transition: border-color var(--ref-dur-fast, 150ms) var(--ref-ease);
}
.ts-fp-textarea:focus {
  outline: none; border-color: var(--sys-brand);
  box-shadow: 0 0 0 3px var(--sys-brand-light, rgba(44,95,138,.15));
}
.ts-fp-textarea::placeholder { color: var(--sys-text-ter); }
.ts-fp-actions {
  padding-top: var(--ref-space-3, 12px);
}
.ts-fp-actions .btn { display: flex; align-items: center; justify-content: center; gap: var(--ref-space-2, 8px); }
.ts-fp-actions .btn svg { width: 16px; height: 16px; }
.ts-fp-loading, .ts-fp-error {
  font-size: var(--ref-font-sm, 14px); color: var(--sys-text-sec);
  padding: var(--ref-space-4, 16px) 0; text-align: center;
}
.ts-fp-error { color: var(--ref-red-600, #DC2626); }
.ts-fp-migration-banner {
  display: flex; align-items: flex-start; gap: var(--ref-space-2, 8px);
  background: var(--sys-brand-light, rgba(44,95,138,.08));
  border: 1px solid var(--sys-brand, #2C5F8A);
  border-radius: var(--ref-radius-md, 8px);
  padding: var(--ref-space-3, 12px);
  font-size: var(--ref-font-xs, 13px); color: var(--sys-text);
  line-height: 1.45; margin-bottom: var(--ref-space-4, 16px);
}
.ts-fp-migration-banner svg { width: 16px; height: 16px; flex-shrink: 0; margin-top: 1px; color: var(--sys-brand); }

/* Nutshell modal */
.ts-ns-overlay {
  position: fixed; inset: 0;
  background: var(--sys-overlay, rgba(0,0,0,.45));
  display: flex; align-items: center; justify-content: center;
  padding: var(--ref-space-4, 16px);
  z-index: 9998;
  opacity: 0;
  transition: opacity var(--ref-dur-norm, 250ms) var(--ref-ease, cubic-bezier(.4,0,.2,1));
}
.ts-ns-overlay.show { opacity: 1; }
.ts-ns-modal {
  background: var(--sys-surface);
  color: var(--sys-text);
  border: 1px solid var(--sys-border);
  border-radius: var(--ref-radius-lg, 16px);
  box-shadow: var(--sys-shadow-xl);
  width: 100%;
  max-width: 440px;
  max-height: 90vh;
  overflow-y: auto;
}
.ts-ns-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--ref-space-4, 16px);
  border-bottom: 1px solid var(--sys-border);
}
.ts-ns-header h3 {
  margin: 0;
  font-size: var(--ref-font-md, 19px);
  font-weight: var(--sys-font-wt-sb, 600);
  display: flex; align-items: center; gap: var(--ref-space-2, 8px);
}
.ts-ns-body {
  padding: var(--ref-space-4, 16px);
  display: flex; flex-direction: column; gap: var(--ref-space-3, 12px);
}
.ts-ns-label {
  display: flex; flex-direction: column; gap: var(--ref-space-1, 4px);
  font-size: var(--ref-font-sm, 15px);
  font-weight: var(--sys-font-wt-md, 500);
}
.ts-ns-label input {
  width: 100%;
  padding: var(--ref-space-2, 8px) var(--ref-space-3, 12px);
  border: 1px solid var(--sys-border-strong);
  border-radius: var(--ref-radius-sm, 8px);
  background: var(--sys-surface);
  color: var(--sys-text);
  font-size: var(--ref-font-base, 17px);
  box-sizing: border-box;
}
.ts-ns-label input:focus {
  outline: none;
  box-shadow: var(--sys-focus);
  border-color: var(--sys-brand);
}
.ts-ns-hint {
  font-size: var(--ref-font-xs, 13px);
  color: var(--sys-text-sec);
  margin: 0;
  line-height: 1.45;
}
.ts-ns-error {
  background: rgba(239,68,68,.1);
  color: var(--sys-error);
  border: 1px solid var(--sys-error);
  border-radius: var(--ref-radius-sm, 8px);
  padding: var(--ref-space-2, 8px) var(--ref-space-3, 12px);
  font-size: var(--ref-font-xs, 13px);
}
.ts-ns-footer {
  padding: var(--ref-space-4, 16px);
  border-top: 1px solid var(--sys-border);
  display: flex; justify-content: flex-end; gap: var(--ref-space-2, 8px);
}
.ts-ns-footer .btn.is-loading { opacity: .7; cursor: wait; }

/* === LAYOUT v2.12.5 — sidebar preserved on desktop, sticky headers === */

/* 1. SINGLE-COLUMN DASHBOARD — widget zone and grid container stack vertically.
      The .app-grid itself keeps its responsive grid columns (repeat(3-6,1fr))
      defined above. Only the outer containers stack. */
.dash-widget-zone,
#app-grid-container {
  display: flex !important;
  flex-direction: column !important;
  gap: var(--ref-space-4, 16px);
  width: 100%;
  max-width: 100%;
}
.dash-widget-container,
.dash-widget-zone > *,
.app-card {
  width: 100% !important;
  max-width: 100% !important;
  min-width: 0 !important;
  margin-inline: 0 !important;
}

/* Wrap rows — no horizontal scrolling anywhere (v2.14.1) */
.recent-row,
.app-chips-row,
.greeting-row {
  display: flex !important;
  flex-direction: row !important;
  flex-wrap: wrap !important;
  overflow-x: visible !important;
  gap: var(--ref-space-2, 8px);
}
/* Quick stats: grid layout, prevent horizontal overflow */
.quick-stats {
  overflow-x: hidden !important;
  max-width: 100%;
}

/* 2. SCROLL OWNERSHIP — .sub-view owns vertical scroll */
.view.active {
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: clip;
  max-width: 100%; /* v2.20.0 r5: was 100vw which includes scrollbar gutter */
}
.sub-view {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: clip;
  overscroll-behavior-x: none;
  overscroll-behavior-y: none;
  max-width: 100%;
}
/* v2.14.4.3: Hard clip — prevent ANY horizontal shift */
#view-main, #sv-dash, .app-viewport {
  max-width: 100%;
  overflow-x: hidden; /* r5: changed from clip to hidden — iOS WebKit converts
                         clip to auto when paired with overflow-y:auto */
  overscroll-behavior-x: none;
}
/* v2.20.0 r5: The right-side layout shift is caused by the sub-view's
   scroll container being wider than the viewport. Force explicit width
   and symmetric padding. */
#sv-dash.sub-view {
  width: 100%;
  box-sizing: border-box;
  padding-left: var(--ref-space-4);
  padding-right: var(--ref-space-4);
}
/* Force all direct children of the dash sub-view to stay within bounds */
#sv-dash > * {
  max-width: 100%;
  box-sizing: border-box;
}
/* v2.21.0: when a widget is launched from the dock, bridge.js scrolls it to the
   top of #sv-dash via scrollIntoView({block:'start'}). scroll-margin-top reserves
   space above the target equal to the sticky dash chrome (top bar + sticky icon
   strip) plus a small gap, so the launched widget's header lands just below the
   chrome instead of under it. Declarative offset — no JS geometry. */
.dash-widget-container {
  scroll-margin-top: calc(var(--dash-top-h, 0px) + var(--dash-sticky-h, 0px) + 8px);
}
.app-body { min-height: 0; }

/* 3. STICKY WIDGET HEADERS — handled in the consolidated v2.14.1 block below.
      Container setup (overflow, isolation) remains here. */
@media (max-width: 1199.98px) {
  .dash-widget-container {
    position: relative;
    isolation: isolate;
    overflow: clip;
    overflow-clip-margin: 0;
  }
}

/* 4. BOTTOM NAV — ONLY phones + iPad mini (<820px).
      At ≥820px the theme's original left-sidebar rules take over untouched. */
@media (max-width: 819.98px) {
  .bnav {
    position: fixed !important;
    left: 0 !important;
    right: 0 !important;
    bottom: 0 !important;
    top: auto !important;
    width: 100% !important;
    height: auto !important;
    min-height: 49px;
    flex-direction: row !important;
    z-index: 200;
    padding-bottom: env(safe-area-inset-bottom, 0);
    transform: translateZ(0);
    backface-visibility: hidden;
    -webkit-backdrop-filter: saturate(180%) blur(12px);
    backdrop-filter:         saturate(180%) blur(12px);
    background: color-mix(in srgb, var(--sys-surface) 92%, transparent);
    border-top: 1px solid var(--sys-border, rgba(0,0,0,.08));
    border-right: none !important;
    /* v2.14.4.3: Ensure bottom nav never clips off-screen */
    overflow: visible;
  }
  /* v2.14.4.3: Padding ONLY on .sub-view (not #view-main or .view.active)
     to avoid stacking padding that pushes content past viewport edge */
  #view-main,
  .view.active {
    margin-left: 0 !important;
    padding-left: 0;
    padding-right: 0;
  }
  .sub-view {
    margin-left: 0 !important;
    padding-left: var(--ref-space-4, 16px);
    padding-right: var(--ref-space-4, 16px);
  }
  /* Clear space below so content doesn't hide behind bnav */
  #view-main {
    padding-bottom: calc(
      56px +
      env(safe-area-inset-bottom, 0px) +
      12px
    );
  }
}

/* 5. TABLET-PORTRAIT-AND-UP SCROLL FIX (≥600px, doesn't touch nav) */
@media (min-width: 600px) {
  .view.active { overflow: clip; }
}

/* 6. Desktop reading-width cap (sidebar preserved by theme default) */
@media (min-width: 1280px) {
  .sub-view {
    max-width: 1200px;
    margin: 0 auto;
    padding-inline: var(--ref-space-6);
  }
}
@media (min-width: 1680px) {
  .sub-view { max-width: 1400px; }
}

/* 6b. Full-width opt-out (v2.14.0) — chat-style sub-views need to fill the
   viewport on desktop. iMessage/Slack on macOS are full-width by design;
   the messaging plugin's Team tab and any future chat-shaped plugin should
   match that expectation. Plugins opt in by adding `.ts-fullwidth` to
   their .sub-view element. The TS Internal Messaging plugin's #sv-team is
   wired in here directly so it works without any plugin-side change.
   Cap-overrides repeat at every cap breakpoint so a more-specific media
   rule above can't sneak the cap back in. The !important on the base rule
   matches what the messaging plugin previously shipped in nav-inject.css —
   migrating the responsibility to the theme keeps that contract.

   Note: this co-exists fine with the messaging plugin's own #sv-team rule
   in nav-inject.css (v1.0.13). When that plugin drops its workaround in a
   future release, this is the one source of truth. */
.sub-view.ts-fullwidth,
#sv-team {
  max-width: none !important;
  padding-inline: 0 !important;
}
@media (min-width: 1280px) {
  .sub-view.ts-fullwidth,
  #sv-team {
    max-width: none;
    padding-inline: 0;
  }
}
@media (min-width: 1680px) {
  .sub-view.ts-fullwidth,
  #sv-team { max-width: none; }
}

/* 7. App viewport cleanup */
.app-viewport:not(.active) { visibility: hidden; pointer-events: none; }
/* === END LAYOUT v2.12.5 === */
/* === STICKY HEADER AESTHETIC FIX v2.14.1 === */
/* Sticky widget headers need:
   1. Explicit opaque background (not inherit — that resolves to transparent
      when the parent chain has no solid bg, causing content peek-through)
   2. Rounded top corners matching the card container
   3. Soft bottom shadow instead of hard edge
   4. top: var(--dash-top-h) so they stick BELOW the dash-top, not behind it
   overflow:clip on containers clips at the border-radius without creating a
   scrolling context, so sticky children still work. */

/* v2.17.1: --dash-top-h is set by JS after measuring .dash-top height.
   Widget headers use it to stick right below the greeting/search bar,
   eliminating the 20-80px transparent gap where content was peeking through. */
:root { --dash-top-h: 0px; --dash-sticky-h: 0px; }

@media (max-width: 1199.98px) {
  .widget-card {
    overflow: clip;
    overflow-clip-margin: 0;
  }

  .dash-widget-container > *:first-child,
  .dash-widget-container > div > *:first-child:not(div),
  .dash-widget-container .widget-header,
  .dash-widget-container .card-header,
  .dash-widget-container > header,
  .dash-widget-container > h2:first-child,
  .dash-widget-container > h3:first-child,
  .widget-card > .widget-header,
  .widget-card > header,
  .widget-card > h3:first-child,
  /* v2.14.5: cover plugin headers rendered inside app-body scroll context */
  .app-body .widget-header,
  .app-body .card-header,
  .app-body > div > header:first-child,
  .app-body [data-sticky-header] {
    position: sticky !important;
    /* v2.20.0 r4: Widget headers stick below BOTH the greeting bar AND the
       sticky icon bar. Previously they stuck at --dash-top-h only, which
       is the same position as the icon bar — so headers slid behind it.
       Adding --dash-sticky-h pushes widget headers below the icon bar. */
    top: calc(var(--dash-top-h, 0px) + var(--dash-sticky-h, 0px)) !important;
    z-index: 20 !important;

    /* Explicit opaque surface — NEVER inherit or transparent. This is the
       fix for the "content peeking under header" bug. */
    background: var(--sys-surface, #fff) !important;
    background-clip: padding-box !important;

    border-top-left-radius:  var(--ref-radius-lg, 16px) !important;
    border-top-right-radius: var(--ref-radius-lg, 16px) !important;
    border-bottom-left-radius: 0 !important;
    border-bottom-right-radius: 0 !important;

    box-shadow: 0 6px 8px -6px rgba(0, 0, 0, 0.12) !important;

    padding-block: var(--ref-space-3, 12px);
    padding-inline: var(--ref-space-4, 16px);
  }
}
/* === END STICKY HEADER AESTHETIC FIX === */

/* ================================================================
   v2.16.0 T15: DASH-TOP — sticky greeting + search
   ================================================================ */
.dash-top{
  position:sticky;top:0;z-index:30;
  background:var(--sys-bg);
  /* v2.17.1: Extend into safe area so content never peeks above the greeting */
  padding-top:calc(env(safe-area-inset-top, 0px) + var(--ref-space-3));
  padding-bottom:var(--ref-space-2);
  /* v2.21.0: shadow removed at rest — over a near-white light-mode bg the
     hairline read as a horizontal "cut off" seam (dark/sun never showed it
     because they force a matched background below). A subtle separation is
     reapplied only in the compact/scrolled state (.dash-compact), when the
     bar is genuinely floating over scrolled content. */
}
/* v2.17.1: The dashboard sub-view must have NO padding-top — the sticky
   dash-top covers the full top area (including safe-area on notched phones).
   Without this, the sub-view's padding creates a gap above the stuck header
   where content scrolls through and is visible as a "bar". */
#sv-dash.sub-view{ padding-top:0 }
/* v2.17.1: Greeting — shrink text to fit instead of truncating with "..." */
.greeting-row h2,.greeting-row-inner h2{
  white-space:nowrap;overflow:hidden;
  max-width:calc(100% - 56px);
  /* Scale from 18px (cramped) to full xl size, fitting the viewport */
  font-size:clamp(18px, 5.5vw, var(--ref-font-xl));
}
/* v2.17.1: pseudo-element covers any sub-pixel gap at the bottom edge
   so scrolling content never peeks through below the sticky bar */
.dash-top::after{
  content:'';display:block;position:absolute;
  left:0;right:0;bottom:-2px;height:2px;
  background:var(--sys-bg);z-index:30;
}
[data-theme="dark"] .dash-top{background:var(--ref-gray-950)}
[data-theme="dark"] .dash-top::after{background:var(--ref-gray-950)}
@media(prefers-color-scheme:dark){
  [data-theme="system"] .dash-top{background:var(--ref-gray-950)}
  [data-theme="system"] .dash-top::after{background:var(--ref-gray-950)}
}
[data-theme="sunlight"] .dash-top{background:#FFFFFF;box-shadow:0 4px 6px -4px rgba(0,0,0,.15)}
[data-theme="sunlight"] .dash-top::after{background:#FFFFFF}
/* v2.21.0: explicit light-mode parity. Light previously fell through to
   --sys-bg with no matched cover, which (with the old hairline shadow) showed
   a seam under the greeting/search zone. Match .dash-top and its ::after
   cover to the page background so the sticky header blends seamlessly, the
   same way dark/system/sunlight already do. */
:root[data-theme="light"] .dash-top{background:var(--sys-bg)}
:root[data-theme="light"] .dash-top::after{background:var(--sys-bg)}
/* v2.21.0: reintroduce a subtle separation ONLY when scrolled/compact, so the
   floating bar reads as elevated over content without showing a seam at rest. */
.dash-top.dash-compact{box-shadow:0 4px 6px -4px rgba(0,0,0,.12)}

/* ================================================================
   v2.16.0 T4: DASH-STICKY — compact app bar (visible on scroll)
   v2.17.1: top uses --dash-top-h so it sits below dash-top, not behind it
   ================================================================ */
.dash-sticky{
  position:fixed;left:0;right:0;
  /* v2.20.1: Removed env(safe-area-inset-top) — it was double-counted.
     --dash-top-h is measured from getBoundingClientRect().height on .dash-top,
     which ALREADY includes the safe-area padding. Adding safe-area again
     pushed the bar below the search bar by ~47px on notched iPhones in PWA. */
  top:var(--dash-top-h,0px);
  z-index:35;
  background:var(--sys-bg);
  border-bottom:1px solid var(--sys-border);
  padding:var(--ref-space-1) 0;
  /* Hidden by default — slides in when sentinel scrolls out of view */
  transform:translateY(-110%);
  opacity:0;pointer-events:none;
  transition:transform 200ms ease-out, opacity 150ms ease-out;
  will-change:transform,opacity;
  -webkit-touch-callout:none;-webkit-user-select:none;user-select:none;
}
.dash-sticky.visible{
  transform:translateY(0);
  opacity:1;pointer-events:auto;
}
.sticky-app-strip{
  display:flex;gap:var(--ref-space-2);
  overflow-x:auto;
  padding:0 var(--ref-space-3);
  scrollbar-width:none;
  mask-image:linear-gradient(to right, #000 82%, transparent 100%);
  -webkit-mask-image:linear-gradient(to right, #000 82%, transparent 100%);
}
.sticky-app-strip::-webkit-scrollbar{display:none}
/* v2.17.2: Desktop — center the strip when there's extra space */
@media(min-width:900px){
  .sticky-app-strip{justify-content:center;padding:0 var(--ref-space-5);}
}
.sticky-app-btn{
  position:relative;
  flex:0 0 auto;display:flex;flex-direction:column;align-items:center;gap:2px;
  padding:var(--ref-space-1);border-radius:var(--ref-radius-lg);
  transition:transform var(--ref-dur-fast) var(--ref-ease);
  -webkit-touch-callout:none;-webkit-user-select:none;user-select:none;
}
.sticky-app-btn:active{transform:scale(.88)}
/* v2.17.2: Sticky bar icons — large enough to be tappable */
.sticky-app-icon{
  width:52px;height:52px;border-radius:var(--ref-radius-lg);
  display:flex;align-items:center;justify-content:center;color:#fff;
  box-shadow:0 1px 4px rgba(0,0,0,.12);
}
.sticky-app-icon svg{width:24px;height:24px;stroke-width:1.75}
/* v2.17.2: Compact labels */
.sticky-app-label{
  font-size:10px;font-weight:600;color:var(--sys-text-sec);
  text-align:center;line-height:1.1;max-width:62px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
[data-theme="dark"] .dash-sticky{background:var(--ref-gray-950);border-bottom-color:var(--ref-gray-700)}
@media(prefers-color-scheme:dark){
  [data-theme="system"] .dash-sticky{background:var(--ref-gray-950);border-bottom-color:var(--ref-gray-700)}
}
[data-theme="sunlight"] .dash-sticky{background:#FFFFFF;border-bottom:2px solid #000}
[data-theme="sunlight"] .sticky-app-icon{border:2px solid #000;box-shadow:none}

/* ================================================================
   v2.16.0 T10: SUNLIGHT MODE — additional overrides
   ================================================================ */
[data-theme="sunlight"] .dock-icon{box-shadow:none}
[data-theme="sunlight"] .kpi-value{color:#000}
[data-theme="sunlight"] .kpi-label{color:#000}
[data-theme="sunlight"] .btn-brand{background:#000;color:#fff}
[data-theme="sunlight"] .btn-brand:hover{background:#222}
[data-theme="sunlight"] .btn-brand:active{background:#000}

/* ================================================================
   v2.16.0 T5: WIDGET HIGHLIGHT — pulse animation for deep-link
   ================================================================ */
@keyframes widgetHighlight{
  0%{box-shadow:0 0 0 0 rgba(44,95,138,.4)}
  50%{box-shadow:0 0 0 8px rgba(44,95,138,.15)}
  100%{box-shadow:0 0 0 0 rgba(44,95,138,0)}
}
.widget-highlight{animation:widgetHighlight .8s ease 2}

/* ── v2.17.3: Dock & Sticky Bar Edit Mode (unified reorder) ── */
@keyframes dock-shake {
  0%,100% { transform:rotate(0deg); }
  25% { transform:rotate(-1.5deg); }
  75% { transform:rotate(1.5deg); }
}
.dock-edit-mode [data-app-id] {
  animation:dock-shake 0.3s ease-in-out infinite;
  cursor:default;
}
.dock-edit-mode [data-app-id]:nth-child(odd) { animation-delay:0.05s; }
.dock-edit-mode [data-app-id]:nth-child(even) { animation-delay:0.15s; }
@keyframes dock-swap { 0%{opacity:.5;transform:scale(.9)} 100%{opacity:1;transform:scale(1)} }
.dock-swapping { animation:dock-swap 200ms ease-out !important; }

/* Chevron arrows — hidden until edit mode */
.dock-reorder-arrows, .sticky-reorder-arrows {
  display:none !important;position:absolute;z-index:5;overflow:hidden;
  width:0;height:0;opacity:0;pointer-events:none;
}
/* Show only in edit mode */
.dock-edit-mode .dock-reorder-arrows {
  display:flex !important;position:static;width:auto;height:auto;opacity:1;pointer-events:auto;
  flex-direction:row;gap:8px;justify-content:center;margin-top:4px;
}
.dock-edit-mode .sticky-reorder-arrows {
  display:flex !important;position:static;width:auto;height:auto;opacity:1;pointer-events:auto;
  flex-direction:row;gap:4px;justify-content:center;margin-top:2px;
}

/* Arrow buttons — shared */
.dock-arr, .sticky-arr {
  display:flex;align-items:center;justify-content:center;
  width:36px;height:36px;border-radius:50%;
  background:var(--sys-surface,#fff);border:1.5px solid var(--sys-border,#E2E8F0);
  color:var(--sys-text,#0F172A);cursor:pointer;
  box-shadow:0 1px 4px rgba(0,0,0,.12);
  transition:all 100ms ease;
  -webkit-tap-highlight-color:transparent;
  touch-action:manipulation;padding:0;
}
.dock-arr:active, .sticky-arr:active {
  transform:scale(.85);background:var(--sys-brand,#2C5F8A);color:#fff;border-color:var(--sys-brand,#2C5F8A);
}
/* Disable arrows at edges using data-pos attribute */
.dock-edit-mode [data-pos="first"] .dock-arr-left,
.dock-edit-mode [data-pos="first"] .sticky-arr-left { opacity:0.2;pointer-events:none; }
.dock-edit-mode [data-pos="last"] .dock-arr-right,
.dock-edit-mode [data-pos="last"] .sticky-arr-right { opacity:0.2;pointer-events:none; }

/* Sticky bar arrows are smaller */
.sticky-arr { width:28px;height:28px; }
.sticky-arr svg { width:12px;height:12px; }

/* Done button */
.dock-edit-done {
  display:block;width:auto;margin:12px auto 0;padding:12px 36px;
  font-size:16px;font-weight:600;color:#FFF;
  background:var(--sys-brand,#2C5F8A);border:none;border-radius:12px;
  cursor:pointer;touch-action:manipulation;
  transition:filter 150ms;
  box-shadow:0 2px 8px rgba(44,95,138,0.3);
}
.dock-edit-done:hover { filter:brightness(1.1); }
.dock-edit-done:active { transform:scale(.95); }
/* Compact Done button inside sticky bar */
.dock-edit-done-sticky {
  display:block;width:auto;margin:6px auto 4px;padding:8px 24px;
  font-size:13px;border-radius:8px;
}

/* Pin sticky bar visible during edit mode */
.dash-sticky.edit-pinned { transform:translateY(0);opacity:1;pointer-events:auto; }

/* Dark mode adjustments */
[data-theme="dark"] .dock-arr, [data-theme="dark"] .sticky-arr {
  background:var(--ref-gray-800,#1e293b);border-color:var(--ref-gray-600,#475569);color:#e2e8f0;
}
/* Sunlight mode adjustments */
[data-theme="sunlight"] .dock-arr, [data-theme="sunlight"] .sticky-arr {
  background:#fff;border:2px solid #000;color:#000;
}
[data-theme="sunlight"] .dock-edit-done { background:#000; }

/* ================================================================
   v2.20.0 — THEME IMPROVEMENT BATCH
   ================================================================ */

/* ── Light mode contrast improvements ─────────────────────────── */
/* CRITICAL: These rules are scoped to [data-theme="light"] ONLY.
   Using :root or unscopied selectors breaks dark mode by forcing
   white backgrounds on every card.
   
   Strategy: subtle box-shadow instead of borders. Shadows respect
   the existing background color (no forced white), and they're
   softer than the 1px border approach which created "big white box"
   artifacts the user didn't like. */

[data-theme="light"] .kpi-card,
[data-theme="light"] .stat-pill {
  box-shadow:0 1px 3px rgba(0,0,0,.06), 0 0 0 1px rgba(0,0,0,.04);
}
[data-theme="light"] .app-tile {
  box-shadow:0 1px 3px rgba(0,0,0,.05), 0 0 0 1px rgba(0,0,0,.03);
}
[data-theme="light"] #profile-card-area > div {
  box-shadow:0 1px 4px rgba(0,0,0,.06), 0 0 0 1px rgba(0,0,0,.04);
}
/* Personal Records container */
[data-theme="light"] .dash-widget-zone .personal-records-card,
[data-theme="light"] [class*="personal-record"] {
  box-shadow:0 1px 3px rgba(0,0,0,.05);
}

/* ── Safari overflow fixes ────────────────────────────────────── */
body {
  overflow-x:hidden;
  -webkit-text-size-adjust:100%;
}
#view-main, .sub-view {
  max-width:100%; /* v2.20.0 r5: was 100vw */
  overflow-x:clip;
}
#app-grid-container {
  overflow:hidden;
}
.app-tile {
  min-width:0;
}
.view.active {
  min-height:100dvh;
}

/* ── Sticky app bar: smoother animation ───────────────────────── */
.dash-sticky {
  transition: transform 320ms cubic-bezier(0.22, 1, 0.36, 1),
              opacity 250ms ease-out;
}

/* ── KPI error state ──────────────────────────────────────────── */
.kpi-value.kpi-error {
  color:var(--ref-amber-500);
  font-size:var(--ref-font-md);
}
.kpi-card.kpi-error-card {
  border-left:3px solid var(--ref-amber-500);
}
.kpi-error-hint {
  font-size:11px;
  color:var(--ref-amber-600);
  margin-top:2px;
}

/* ── Personal records: empty state ────────────────────────────── */
.personal-records-empty {
  text-align:center;
  padding:var(--ref-space-6) var(--ref-space-4);
  color:var(--sys-text-sec);
  font-size:var(--ref-font-xs);
  line-height:1.5;
}
.personal-records-empty-icon {
  font-size:32px;
  margin-bottom:var(--ref-space-2);
  opacity:0.5;
}

/* ── Widget overflow & layout containment ─────────────────────── */
/* v2.20.1: Removed max-height:65vh and overflow-y:auto that caused
   "double scrolling" — widgets now expand to natural height and the
   page scrolls as one continuous document. No nested scroll traps.
   
   CRITICAL: Must use overflow:clip (not hidden). overflow:hidden
   creates a scroll container per CSS spec, which traps position:sticky
   inside the widget body. overflow:clip clips visual overflow without
   creating a scroll container, so plugin sticky headers (Surveys
   batch history, Sketch save bar) bind to the page-level scroll
   context (#sv-dash.sub-view) and stick correctly.
   
   Do NOT change to overflow:hidden or overflow:auto — plugins depend
   on this NOT being a scroll container. */
.dash-widget-body {
  overflow:clip;
  max-width:100%;
}
/* Prevent any child inside a widget from creating horizontal overflow */
.dash-widget-body * {
  max-width:100%;
  box-sizing:border-box;
}
/* Widget stat grids (Leads "2 BATCHES / 11 LEADS" row) — ensure
   the grid wraps instead of overflowing on narrow screens */
.dash-widget-body .tsl-stats,
.dash-widget-body [class*="stats-row"],
.dash-widget-body [class*="stat-grid"] {
  flex-wrap:wrap;
  overflow:hidden;
}
/* Tables inside widgets should scroll horizontally within the body */
.dash-widget-body table {
  display:block;
  overflow-x:auto;
  max-width:100%;
}

/* ── Layout shift prevention ──────────────────────────────────── */
/* The sticky app bar was shifting left/right on appear/disappear.
   Force containment so it doesn't affect document flow. */
.dash-sticky {
  contain:layout style;
}
/* The app dock should reserve its space to prevent CLS when it
   scrolls out of view and the sticky bar replaces it.
   v2.20.1: Removed contain:layout — it created an independent
   formatting context that could miscalculate grid column widths
   on high-DPI iOS devices with sub-pixel rounding. */
#app-dock {
  min-height:68px; /* prevent collapse before icons load */
}

/* ── v2.20.1: Dash-sticky desktop sidebar offset ────────────── */
/* On ≥820px the left sidebar is 88px wide. The dash-sticky is
   position:fixed so it must offset left to avoid covering the nav. */
@media (min-width: 820px) {
  .dash-sticky {
    left: 88px;
  }
}

/* ── v2.20.1: Mobile top spacing reduction ──────────────────── */
/* Tighten the gap between the search bar and mini-app icons on
   portrait phones. Every pixel counts for usable widget space. */
@media (max-width: 819.98px) {
  .dash-top {
    padding-bottom: var(--ref-space-1);  /* was ref-space-2 (8px→4px) */
  }
  .greeting-row {
    padding: var(--ref-space-1) 0 0;     /* was ref-space-2 0 ref-space-1 */
  }
  .app-dock {
    padding-top: var(--ref-space-1);     /* was ref-space-3 (12px→4px) */
    padding-bottom: var(--ref-space-2);  /* was ref-space-4 (16px→8px) */
  }
}

/* ── v2.20.1: Search bar compact-on-scroll ──────────────────── */
/* When user scrolls down, the dash-top shrinks dramatically to
   reclaim screen real estate. JS adds .dash-compact class at ~40%
   of the sticky threshold, and .dash-sticky-on when the icon strip
   appears. GPU-accelerated via translateZ(0) for consistent
   smoothness across iPhone, iPad, and desktop. */
.dash-top {
  transition: padding 150ms ease, box-shadow 100ms ease;
  transform: translateZ(0); /* GPU composite layer — consistent across devices */
  -webkit-transform: translateZ(0);
}
.dash-search-trigger {
  transition: padding 150ms ease, min-height 150ms ease, font-size 150ms ease;
}
.dash-top.dash-compact {
  padding-top: calc(env(safe-area-inset-top, 0px) + 2px);
  padding-bottom: 0;
}
/* v2.20.1: Solid box-shadow extends below dash-top, covering scrolling
   content in the gap between search bar and sticky icon strip. ONLY
   active when both compact AND sticky bar are visible (.dash-sticky-on
   added by JS when the icon strip appears). Without this gate, the
   shadow covers dock tiles that are still visible during the compact-
   only window (compact triggers before sticky). */
.dash-top.dash-compact.dash-sticky-on {
  box-shadow: 0 80px 0 0 var(--sys-bg);
}
[data-theme="dark"] .dash-top.dash-compact.dash-sticky-on {
  box-shadow: 0 80px 0 0 var(--ref-gray-950);
}
@media(prefers-color-scheme:dark){
  [data-theme="system"] .dash-top.dash-compact.dash-sticky-on {
    box-shadow: 0 80px 0 0 var(--ref-gray-950);
  }
}
[data-theme="sunlight"] .dash-top.dash-compact.dash-sticky-on {
  box-shadow: 0 80px 0 0 #FFFFFF;
}
.dash-top.dash-compact .dash-search-trigger {
  padding: 8px var(--ref-space-3);
  min-height: 36px;
  font-size: 14px;
  border-radius: var(--ref-radius-sm);
}
.dash-top.dash-compact .greeting-row {
  display: none;
}

/* ── Kiosk / Demo Mode (v2.21.0) ─────────────────────────────────────────
   A persistent, unobtrusive top banner shown only while the device is in
   demo mode (running as the shared General account). It signals the safe
   state and offers the PIN-gated exit. Pinned to the top respecting the
   safe-area inset; pushes nothing else since it overlays a thin strip. */
#ts-kiosk-banner{
  position:fixed;top:0;left:0;right:0;z-index:99998;
  display:flex;align-items:center;justify-content:space-between;gap:var(--ref-space-3);
  padding:calc(env(safe-area-inset-top,0px) + 6px) var(--ref-space-4) 6px;
  background:linear-gradient(135deg,#0f766e 0%,#0d9488 100%);
  color:#fff;font-size:var(--ref-font-xs);font-weight:var(--sys-font-wt-sb);
  box-shadow:0 2px 10px rgba(13,148,136,.35);
}
#ts-kiosk-banner .ts-kiosk-banner-label{display:flex;align-items:center;gap:6px;line-height:1}
#ts-kiosk-banner .ts-kiosk-banner-label i,
#ts-kiosk-banner .ts-kiosk-banner-label svg{width:15px;height:15px}
#ts-kiosk-banner .ts-kiosk-banner-exit{
  background:rgba(255,255,255,.18);color:#fff;border:1px solid rgba(255,255,255,.35);
  border-radius:var(--ref-radius-md);padding:3px 12px;font-size:var(--ref-font-xs);
  font-weight:var(--sys-font-wt-sb);cursor:pointer;white-space:nowrap;
}
#ts-kiosk-banner .ts-kiosk-banner-exit:active{background:rgba(255,255,255,.3)}
/* When the banner is present, nudge the app content down so the greeting
   is not hidden under the strip. */
body:has(#ts-kiosk-banner) #view-main{padding-top:calc(env(safe-area-inset-top,0px) + 44px)}
/* The settings "Exit Demo Mode" button gets the same teal accent so it reads
   as the deliberate way out rather than a generic outline button. */
.ts-kiosk-exit-btn{border-color:#0d9488 !important;color:#0d9488 !important}

/* ================================================================
 *  v2.21.3: LEADS ACTION TILE
 *  "You have N leads to contact" — surfaced at the top of the
 *  dashboard for a salesperson from the unified action-items feed.
 *  Visual language matches .dash-search-trigger / dock tiles: a
 *  raised surface card, rounded-lg, 48px+ tap target, subtle hover
 *  shadow, scale-on-press. The left accent rail is driven by the
 *  --leads-tile-accent custom property set inline from the server's
 *  item color, with urgency modifiers for emphasis.
 * ================================================================ */
.leads-tile{margin-top:var(--ref-space-2);margin-bottom:var(--ref-space-3)}
.leads-tile-btn{
  display:flex;align-items:center;gap:var(--ref-space-3);
  width:100%;padding:var(--ref-space-3) var(--ref-space-4);
  background:var(--sys-surface-raised,var(--sys-surface));
  border:1.5px solid var(--sys-border);
  border-left:4px solid var(--leads-tile-accent,#22C55E);
  border-radius:var(--ref-radius-lg);min-height:56px;
  color:var(--sys-text);text-align:left;cursor:pointer;
  touch-action:manipulation;
  transition:border-color var(--ref-dur-fast) var(--ref-ease),
    box-shadow var(--ref-dur-fast) var(--ref-ease),
    transform var(--ref-dur-fast) var(--ref-ease)}
.leads-tile-btn:hover{border-color:var(--leads-tile-accent,#22C55E);box-shadow:var(--sys-shadow-sm)}
.leads-tile-btn:active{transform:scale(.99)}
.leads-tile-btn:focus-visible{box-shadow:var(--sys-focus)}
.leads-tile-icon{
  flex-shrink:0;display:flex;align-items:center;justify-content:center;
  width:40px;height:40px;border-radius:var(--ref-radius-md);
  color:var(--leads-tile-accent,#22C55E);
  background:color-mix(in srgb,var(--leads-tile-accent,#22C55E) 14%,transparent)}
.leads-tile-icon svg{width:22px;height:22px}
.leads-tile-body{flex:1;min-width:0;display:flex;align-items:baseline;gap:var(--ref-space-2)}
.leads-tile-count{
  font-size:var(--ref-font-xl);font-weight:var(--sys-font-wt-b,700);
  color:var(--leads-tile-accent,#22C55E);line-height:1;flex-shrink:0}
.leads-tile-label{
  font-size:var(--ref-font-sm);font-weight:var(--sys-font-wt-md,500);
  color:var(--sys-text-sec);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.leads-tile-cta{
  flex-shrink:0;font-size:var(--ref-font-sm);font-weight:var(--sys-font-wt-sb,600);
  color:var(--leads-tile-accent,#22C55E);white-space:nowrap}
/* Urgency modifiers: high gets a soft tinted field + heavier weight. */
.leads-tile--high .leads-tile-btn,.leads-tile-btn.leads-tile--high{
  background:color-mix(in srgb,var(--leads-tile-accent,#22C55E) 8%,var(--sys-surface-raised,var(--sys-surface)))}
@media (prefers-reduced-motion:reduce){
  .leads-tile-btn{transition:none}
  .leads-tile-btn:active{transform:none}}

/* v2.21.5: Inline orchestrator answer card (dashboard "ask" field) */
#ts-inline-answer{margin:var(--ref-space-2,8px) 0 var(--ref-space-1,4px)}
.ts-inline-card{background:var(--sys-surface,#fff);border:1px solid var(--sys-border,#E2E8F0);
  border-left:3px solid var(--sys-brand,#6366F1);border-radius:var(--ref-radius-md,10px);
  padding:var(--ref-space-3,12px) var(--ref-space-4,16px);box-shadow:var(--sys-shadow-sm,0 1px 2px rgba(0,0,0,.06));
  font-size:var(--ref-font-sm,15px);color:var(--sys-text,#0F172A)}
.ts-inline-loading{color:var(--sys-text-ter,#94A3B8);font-style:italic}
.ts-inline-name{font-weight:var(--sys-font-wt-sb,600);font-size:var(--ref-font-base,16px);margin-bottom:var(--ref-space-2,8px)}
.ts-inline-co{font-weight:400;color:var(--sys-text-sec,#64748B)}
.ts-inline-row{display:flex;align-items:center;gap:8px;min-height:36px;padding:4px 0;
  color:var(--sys-text,#0F172A);text-decoration:none;line-height:1.35;word-break:break-word}
a.ts-inline-row{color:var(--sys-brand,#6366F1)}
.ts-inline-ic{flex:0 0 auto;width:20px;text-align:center}
.ts-inline-msg{line-height:1.45}
.ts-inline-sub{margin-top:6px;color:var(--sys-text-sec,#64748B)}
.ts-inline-note{margin-top:6px;font-size:var(--ref-font-xs,13px);color:var(--sys-text-ter,#94A3B8)}
.ts-inline-chat{margin-top:var(--ref-space-3,12px);min-height:36px;padding:6px 14px;
  font-size:var(--ref-font-sm,14px);font-weight:600;color:var(--sys-brand,#6366F1);
  background:transparent;border:1px solid var(--sys-brand,#6366F1);border-radius:var(--ref-radius-sm,8px);
  cursor:pointer;-webkit-tap-highlight-color:transparent;touch-action:manipulation}
.ts-inline-chat:hover{color:#fff;background:var(--sys-brand,#6366F1)}

/* v2.28.0: Inline COMMISSION card — headline figure + collapsible drill-down. */
.ts-cc-head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;flex-wrap:wrap}
.ts-cc-amount{font-size:var(--ref-font-2xl,26px);font-weight:var(--sys-font-wt-sb,600);color:var(--sys-text,#0F172A)}
.ts-cc-who{font-size:var(--ref-font-sm,14px);color:var(--sys-text-sec,#64748B)}
.ts-cc-basis{font-size:var(--ref-font-xs,12px);color:var(--sys-text-ter,#64748B);margin:2px 0 6px}
.ts-cc-sec{border-top:1px solid var(--sys-border,#E2E8F0)}
.ts-cc-sec-head{width:100%;display:flex;align-items:center;justify-content:space-between;gap:8px;
  padding:10px 0;background:transparent;border:none;cursor:pointer;font-size:var(--ref-font-sm,14px);
  font-weight:600;color:var(--sys-text,#0F172A);-webkit-tap-highlight-color:transparent;touch-action:manipulation;text-align:left}
.ts-cc-sec-title{display:inline-flex;align-items:center;gap:6px}
.ts-cc-sec-count{font-weight:400;color:var(--sys-text-ter,#94A3B8);font-size:var(--ref-font-xs,12px)}
.ts-cc-caret{color:var(--sys-text-ter,#94A3B8);font-size:11px;transition:transform 150ms}
.ts-cc-sec.is-open .ts-cc-caret{transform:rotate(90deg)}
.ts-cc-sec-body{display:none;padding:0 0 8px}
.ts-cc-sec.is-open .ts-cc-sec-body{display:block}
.ts-cc-row{display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:5px 0;font-size:var(--ref-font-sm,13px);color:var(--sys-text-sec,#475569);
  border-top:1px dashed var(--sys-border,#EEF2F7)}
.ts-cc-row:first-child{border-top:none}
.ts-cc-row-l{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ts-cc-row-r{flex:none;font-weight:600;color:var(--sys-text,#0F172A);font-variant-numeric:tabular-nums}
.ts-cc-actions{display:flex;gap:8px;margin-top:12px;flex-wrap:wrap}
.ts-inline-chip{min-height:34px;padding:6px 12px;font-size:var(--ref-font-sm,13px);font-weight:600;
  color:var(--sys-text-sec,#475569);background:transparent;border:1px solid var(--sys-border,#CBD5E1);
  border-radius:999px;cursor:pointer;-webkit-tap-highlight-color:transparent;touch-action:manipulation}
.ts-inline-chip:hover{color:var(--sys-text,#0F172A);border-color:var(--sys-text-ter,#94A3B8)}
[data-theme="dark"] .ts-cc-amount,[data-theme="dark"] .ts-cc-row-r,[data-theme="dark"] .ts-cc-sec-head{color:var(--sys-text,#E2E8F0)}
[data-theme="dark"] .ts-inline-chip{color:var(--sys-text-sec,#94A3B8);border-color:var(--sys-border,#334155)}

/* v2.28.8: INSTANT SKELETON + freshness. The card frame paints immediately with
   a shimmering placeholder for the figure; a cache hit fills it in ~50ms. */
.ts-shimmer,.ts-shimmer-line{position:relative;overflow:hidden;background:var(--sys-border,#E2E8F0);
  border-radius:6px;color:transparent!important}
.ts-cc-amount.ts-shimmer{display:inline-block;min-width:96px;height:24px}
.ts-shimmer-line{display:block;height:12px;margin:6px 0;width:70%}
.ts-contact-card .ts-shimmer-line:nth-of-type(2){width:55%}
.ts-skeleton .ts-shimmer::after,.ts-skeleton .ts-shimmer-line::after{content:'';position:absolute;inset:0;
  transform:translateX(-100%);background:linear-gradient(90deg,transparent,rgba(255,255,255,.45),transparent);
  animation:tsShimmer 1.1s ease-in-out infinite}
@keyframes tsShimmer{100%{transform:translateX(100%)}}
@media (prefers-reduced-motion: reduce){.ts-skeleton .ts-shimmer::after,.ts-skeleton .ts-shimmer-line::after{animation:none}}
[data-theme="dark"] .ts-shimmer,[data-theme="dark"] .ts-shimmer-line{background:var(--sys-border,#334155)}
[data-theme="dark"] .ts-skeleton .ts-shimmer::after,[data-theme="dark"] .ts-skeleton .ts-shimmer-line::after{
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.10),transparent)}
.ts-cc-fresh{font-size:var(--ref-font-xs,11px);color:var(--sys-text-ter,#94A3B8);margin-top:6px}
.ts-cc-fresh .ts-cc-dot{color:#10B981}


/* ============================================================
   v2.23.0: INLINE ASK FIELD + RESULTS DROPDOWN
   Replaces the pop-up command-palette overlay. The field sits inline in
   #dash-top; #cmd-results drops down beneath it (absolute, so it overlays the
   dashboard tiles instead of pushing them down). Reuses .cmd-item/.ci-* styles.
   ============================================================ */
.dash-ask {
  position:relative;
  /* v2.24.0: explicit separation from the greeting/refresh row above. Carleton
     reported the refresh button visually "touching" the search bar on mobile —
     the greeting row's small bottom padding (--ref-space-1) wasn't enough of a
     gap. A dedicated top margin guarantees clear air between the two without
     needing more height elsewhere. */
  margin-top:var(--ref-space-3);
}
.dash-ask-field {
  display:flex; align-items:center; gap:var(--ref-space-2);
  width:100%; box-sizing:border-box;
  padding:12px 16px; min-height:48px;
  background:var(--sys-bg-alt, var(--sys-surface)); color:var(--sys-text);
  border:1px solid var(--sys-border); border-radius:var(--ref-radius-md);
  transition:border-color 150ms, box-shadow 150ms;
}
.dash-ask-field:focus-within { border-color:var(--sys-brand); box-shadow:var(--sys-shadow-sm); }
.dash-ask-field svg { color:var(--sys-text-ter); flex-shrink:0; }
.dash-ask-field input {
  flex:1; min-width:0; border:none; outline:none; background:none;
  font-family:inherit; font-size:var(--ref-font-base); color:var(--sys-text);
  user-select:text;
}
.dash-ask-field input::placeholder { color:var(--sys-text-ter); }
.dash-ask-field input:focus-visible { box-shadow:none; } /* the wrap shows focus */

/* Results dropdown — hidden until JS adds .show */
.dash-ask .cmd-results {
  display:none;
  position:absolute; top:calc(100% + 6px); left:0; right:0; z-index:200;
  max-height:min(60vh, 460px); overflow-y:auto;
  background:var(--sys-surface); border:1px solid var(--sys-border);
  border-radius:var(--ref-radius-md); box-shadow:var(--sys-shadow-xl);
  padding:var(--ref-space-1);
  -webkit-overflow-scrolling:touch;
}
.dash-ask .cmd-results.show { display:block; }

/* ════════════════════════════════════════════════════════════════
   v2.24.0 — MOBILE DASHBOARD DENSITY + UNIFICATION PASS
   Field-feedback wave (Carleton, mobile, June 2026). Goals, in order:
     1. Reclaim vertical space — the greeting + app dock ate ~25% of the
        screen; target ~15% while keeping every tap target >= 44px.
     2. Unify dashboard widget tiles so different apps (surveys vs.
        estimates) share ONE card shell — same footer, same max size.
     3. Bump light-mode contrast a touch so white cards read against the bg.
     4. Theme-aware text + overflow guards for embedded app widgets
        (commission calculator dark text / off-screen controls; knowledge
        base horizontal width). These are belt-and-suspenders theme-side
        overrides; the deeper fixes live in the plugins (see handoff docs).
   Everything here is additive and scoped — no existing rule is removed,
   so older behavior is preserved and this block is easy to audit/revert.
   ════════════════════════════════════════════════════════════════ */

/* ---- 1. GREETING: tighter, single compact line ---------------------
   Was: padding 8px/4px + min-height 40px, xl greeting text. The greeting
   is a glance, not a headline — trim its vertical footprint. Refresh
   button stays 40px (its own tap target), so the row can't collapse. */
.greeting-row{
  padding:var(--ref-space-1) 0 0 !important;
  min-height:36px !important;
}
.greeting-row h2,.greeting-row-inner h2{
  /* Slightly smaller ceiling than --ref-font-xl so the greeting reads as a
     subtitle, not a banner. clamp keeps it from cramping on small phones. */
  font-size:clamp(17px, 4.6vw, 24px) !important;
  line-height:1.15 !important;
}

/* ---- 2. APP DOCK ----------------------------------------------------
   v2.24.4 SIZE RESTORE (Carleton, June 2026): the v2.24.0 density pass
   shrank the dock icons (72→56 / 84→64) and tiles (130/150→96/118) with
   UNSCOPED !important rules, so the shrink applied at EVERY width —
   including tablet/desktop, where it made the icons and tiles look too
   small ("the overall app icon size got smaller which I don't want").
   These rules now RESTORE the original larger footprint at all widths.
   They are kept (rather than deleted) as explicit, auditable overrides
   that re-assert the base values over any other !important still around.
   The greeting trim (section 1) stays — only the dock size is restored. */
.app-dock{
  gap:var(--ref-space-3) !important;
  padding:var(--ref-space-3) 0 var(--ref-space-4) !important;
}
.dock-app{
  min-height:130px !important;
  padding:var(--ref-space-5) var(--ref-space-4) !important;
  gap:var(--ref-space-3) !important;
}
.dock-icon{
  /* v2.24.5: larger icon plate (Carleton: "the app icon size should be larger
     too"). 72→80 on phone, svg 34→38. The ≥600 block below takes it to 96. */
  width:80px !important;height:80px !important;
}
.dock-icon svg{width:38px !important;height:38px !important}
.dock-label{
  /* Keep the fixed 2-line band (prevents per-tile layout shift). Font size
     is governed by the base .dock-label / .is-long rules (v2.24.3, uniform as
     of v2.24.5), not forced here, so all labels stay the same size + aligned. */
  line-height:1.25 !important;
}
/* Tablet+ — larger icon plate + tile height. v2.24.5: icon 84→96, svg 40→44,
   tile 150→158 so the bigger plate keeps comfortable breathing room. */
@media(min-width:600px){
  .dock-app{min-height:158px !important;padding:var(--ref-space-6) var(--ref-space-5) !important}
  .dock-icon{width:96px !important;height:96px !important}
  .dock-icon svg{width:44px !important;height:44px !important}
}

/* ---- 3. UNIFIED DASHBOARD WIDGET SHELL ----------------------------
   Carleton: "surveys and estimates are totally different in the way
   they're displaying… we want unification, so they all have the same
   bottom part where they sit." Each plugin renders its own body, but the
   OUTER shell (.dash-widget-container) is the theme's. Pin a consistent
   max-height and a uniform footer rhythm so every app's dashboard widget
   sits in an identically-sized card. The estimate widget was the tallest;
   this caps it a touch smaller and lets shorter widgets fill the same
   frame, so the grid reads as one family. Bodies scroll inside the cap
   rather than each card free-sizing. */
.dash-widget-zone{ gap:var(--ref-space-3) !important; }
.dash-widget-container{
  /* v2.24.6 — let content-heavy widgets grow toward the full viewport.
     Carleton: "the widgets aren't occupying the full viewport, which is what
     they should when possible (e.g. Game)." The earlier flat cap (380px on a
     typical window) clamped EVERY widget to a short slab — the Game's content
     is ~678px but was squeezed into 380px. The cap is now content-driven:
       • max-height is a GENEROUS viewport-relative ceiling (88vh, never below
         560px) — so a tall widget (Game, Camera) expands to nearly the whole
         screen, while a short widget (a one-line status) stays short because
         max-height only caps, it doesn't stretch.
       • the inner body keeps its own overflow scroll (below) as the safety
         valve for anything taller than the ceiling.
     The unified-shell look (header / scrolling body / footer rhythm) is
     unchanged; widgets simply size to their content again instead of all
     being forced to one short height. */
  max-height:max(560px, 88vh);
  display:flex;flex-direction:column;
}
.dash-widget-container > .dash-widget-header{ flex:0 0 auto; }
.dash-widget-container > .dash-widget-body,
.dash-widget-container > .dw-body,
.dash-widget-container > div:last-child{
  flex:1 1 auto;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;
}
/* Uniform footer seat: whatever a plugin puts last in its widget gets the
   same bottom padding + top hairline, so all cards share one "bottom part". */
.dash-widget-container .dw-footer,
.dash-widget-container .widget-footer,
.dash-widget-container [data-widget-footer]{
  border-top:1px solid var(--sys-border);
  padding:var(--ref-space-3) var(--ref-space-4) !important;
  margin-top:auto;
}

/* ---- 3b. SURVEY BATCH TEXT: bigger, no truncated names -------------
   Carleton: "the surveys retractables text is just crazy small… make
   that text bigger and don't truncate the names — too much info crammed
   into the batch info." Floor the font size and allow wrapping inside
   the Surveys widget so batch labels/customer names show in full. Scoped
   to the surveys widget so nothing else changes. Selectors are broad on
   purpose (the plugin's exact classes vary by version); they only raise a
   floor and unclamp wrapping, never restyle. */
.dash-widget-container[data-app-id*="survey"] *,
.dash-widget-container[data-app-id*="satisfaction"] *{
  font-size:max(1em, 15px);
}
.dash-widget-container[data-app-id*="survey"] .batch-name,
.dash-widget-container[data-app-id*="survey"] .batch-title,
.dash-widget-container[data-app-id*="survey"] .name,
.dash-widget-container[data-app-id*="satisfaction"] .batch-name,
.dash-widget-container[data-app-id*="satisfaction"] .name{
  white-space:normal !important;overflow:visible !important;
  text-overflow:clip !important;word-break:break-word;
}

/* ---- 4. LIGHT-MODE CONTRAST BUMP ----------------------------------
   Carleton: "the light mode could use a tiny bit more contrast between
   the background — a slightly brighter/cooler page bg so white cards
   pop." Light bg was --ref-gray-100 (#F1F5F9), surfaces are pure white.
   Nudge the page bg one cooler step so the white surfaces separate more
   clearly, and firm up the card border. Only the LIGHT theme is touched;
   dark/sunlight/system are untouched. */
:root[data-theme="light"]{
  --sys-bg:#E9EEF5;          /* was gray-100 #F1F5F9 — a touch deeper/cooler */
  --sys-bg-alt:#DCE3EC;
  --sys-border:#D2DBE6;      /* slightly stronger card edge for definition */
}
:root[data-theme="light"] .dash-widget-container,
:root[data-theme="light"] .kpi-card,
:root[data-theme="light"] .stat-pill{
  box-shadow:0 1px 3px rgba(15,31,53,.10),0 1px 2px rgba(15,31,53,.06);
}

/* ---- 4b. EMBEDDED-WIDGET CONTRAST + OVERFLOW GUARDS ----------------
   Belt-and-suspenders for plugin-rendered widgets (commission calculator,
   knowledge base, media) that paint their own surfaces. Carleton reported,
   in dark mode, "very dark text on dark areas" in Commissions and "elements
   running off the screen." The real fix is in each plugin (handoff docs),
   but these theme overrides prevent the worst cases regardless:
     • force readable text color from the theme token on un-themed children
     • never let a widget's inner content exceed the card width. */
.app-body, .dash-widget-body{ max-width:100%; }
.app-body *, .dash-widget-body *{ max-width:100%; box-sizing:border-box; }
.app-body img, .app-body table, .app-body pre,
.dash-widget-body img, .dash-widget-body table, .dash-widget-body pre{
  max-width:100% !important;height:auto;overflow-x:auto;
}
/* Commission calculator (and any widget tagged with its id): in dark/system,
   pin any element that inherited a hard-coded dark text color back to the
   readable theme token. We can't restyle the plugin's markup, but we can
   guarantee text/background CONTRAST follows the active theme. */
[data-theme="dark"] .dash-widget-container[data-app-id*="commission"] :where(p,span,td,th,li,label,div),
[data-theme="dark"] #app-body[data-app-id*="commission"] :where(p,span,td,th,li,label,div){
  color:var(--sys-text);
}
@media(prefers-color-scheme:dark){
  [data-theme="system"] .dash-widget-container[data-app-id*="commission"] :where(p,span,td,th,li,label,div),
  [data-theme="system"] #app-body[data-app-id*="commission"] :where(p,span,td,th,li,label,div){
    color:var(--sys-text);
  }
}

/* ---- 5. KNOWLEDGE BASE: use the horizontal width ------------------
   Carleton: "knowledge base is not using its horizontal width very well."
   When the Knowledge app renders in the full-screen viewport, let its body
   and any inner iframe/content span the full available width (the SPA's
   global max-width opt-out). Padding stays modest so reading lines aren't
   edge-to-edge uncomfortable, but the wasted side gutters are removed. */
#app-viewport[data-app-id*="knowledge"] .app-body,
#app-viewport[data-app-id*="vault"] .app-body{
  padding-left:0;padding-right:0;
}
#app-viewport[data-app-id*="knowledge"] .app-body > *,
#app-viewport[data-app-id*="vault"] .app-body > *{
  width:100% !important;max-width:100% !important;
}
#app-viewport[data-app-id*="knowledge"] .app-body iframe,
#app-viewport[data-app-id*="vault"] .app-body iframe{
  width:100% !important;
}

/* ────────────────────────────────────────────────────────────────
   v2.24.1 — KPI CARD UNIFORMITY + extra top-zone trim (screenshot pass)
   Carleton's phone screenshots showed the dashboard METRIC cards
   (.kpi-card inside .kpi-grid: "WEBSITE REVIEWS", "FreshBooks $15.7K",
   "153 CONTACTED", "200 NEW LEADS", "6 LEADS→JOBS") rendering at
   different heights / as different-sized squares, not lined up. Those
   are .kpi-card — a DIFFERENT element from the .dash-widget-container
   widgets normalized in v2.24.0 — so they needed their own pass. Also,
   the top zone (greeting + search + horizontal app dock) was still
   visibly >15%, so trim it a bit more.
   ──────────────────────────────────────────────────────────────── */

/* ---- KPI metric cards: ONE uniform size across the whole grid ----
   Force every row to the same height (grid-auto-rows:1fr) and give each
   card a single fixed min-height, so a 2-line label (e.g. "WEBSITE
   REVIEWS") or a "FreshBooks · updated just now" sub-line can no longer
   make one card taller than its neighbours. Cards stretch to fill their
   cell, content top-aligns, and the grid reads as uniform squares. */
.kpi-grid{
  grid-auto-rows:1fr !important;       /* equal-height rows */
  align-items:stretch !important;
  gap:var(--ref-space-2) !important;   /* tighter, matches the dock */
}
.kpi-card{
  min-height:104px !important;         /* one height for ALL cards */
  height:100% !important;              /* fill the equal-height row cell */
  justify-content:flex-start !important;
}
/* The "primary" revenue cards (YTD/MTD) were intentionally bigger; on the
   phone that's what made the top row tower over the rest. Pin them to the
   SAME footprint as every other card so the grid is truly uniform. */
.kpi-card.kpi-primary{
  min-height:104px !important;
  padding:var(--ref-space-3) var(--ref-space-4) !important;
  grid-column:auto !important;         /* no 2-col span on phone */
}
.kpi-card.kpi-primary .kpi-value{
  font-size:var(--ref-font-xl) !important;  /* match non-primary value size */
}
/* Keep the sub-line from adding height: clamp it to one line. */
.kpi-sub{
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;
}
/* Labels may wrap to 2 lines without changing the card height (the fixed
   min-height + stretch absorbs it); just keep them tidy. */
.kpi-label{ line-height:1.15; }
/* On tablet+ the primary cards may resume spanning 2 columns if desired,
   but still share the uniform height. (Comment-only note — the base
   600px rule already restores the 4-col grid; we deliberately do NOT
   re-enable the 2-col span on phones.) */
@media(min-width:600px){
  .kpi-card.kpi-primary{ grid-column:span 2 !important; }
}

/* ---- Also normalize the simpler .stat-pill strip (tech/sales roles) ----
   Same idea for the other KPI surface so it's uniform too. */
.quick-stats{ grid-auto-rows:1fr !important; align-items:stretch !important; }
.quick-stats .stat-pill{ min-height:72px !important; height:100% !important; }

/* ---- Extra top-zone trim: get the header zone under ~15% ----
   Tighten the sticky dash-top padding and the horizontal app-dock row
   (the scrolling icon strip: .app-dock) a little more. Tap targets in the
   strip stay comfortable (icon plate + label still ~ 78px tall). */
.dash-top{
  padding-top:calc(env(safe-area-inset-top, 0px) + var(--ref-space-2)) !important;
  padding-bottom:var(--ref-space-1) !important;
}
.dash-ask-field{ min-height:44px !important; padding:10px 14px !important; }
/* v2.24.4: the v2.24.1 top-zone trim re-trimmed .app-dock padding here, but
   that selector also matches the main vertical springboard dock — so it was
   re-shrinking the dock we just restored in section 2. Padding is now left to
   the section-2 restore (space-3/space-4) so the dock keeps its larger,
   breathing footprint at all widths. (The scrolled compact strip below uses
   .dash-sticky / .sticky-app-icon, which are still trimmed on purpose.) */
/* Greeting: one more small step down. */
.greeting-row{ min-height:32px !important; }
.greeting-row h2,.greeting-row-inner h2{ font-size:clamp(16px,4.3vw,22px) !important; }

/* ---- Scrolled state: the compact sticky app strip (.dash-sticky) ----
   When the user scrolls, the greeting+search (.dash-top) stays pinned AND
   the .dash-sticky app strip slides in below it — stacked, that doubled
   header is what pushed past 15% in the screenshots. Trim the sticky
   strip's icon plate + padding so the scrolled header is shorter. Icons
   stay tappable (44px). */
.dash-sticky{ padding:2px 0 !important; }
.sticky-app-icon{ width:44px !important; height:44px !important; }
.sticky-app-icon svg{ width:22px !important; height:22px !important; }
.sticky-app-label{ font-size:9px !important; }

/* ---- v2.24.2: subtle dashboard build stamp ----
   Tiny, muted, centered. Just enough to read which build is loaded; never
   draws attention. Sits at the very bottom of the dashboard scroll. */
.ts-build-stamp{
  text-align:center;
  font-size:11px;
  color:var(--sys-text-ter);
  opacity:.55;
  padding:var(--ref-space-2) 0 var(--ref-space-4);
  letter-spacing:.02em;
  -webkit-user-select:none;user-select:none;
}
/* === END v2.24.1 KPI UNIFORMITY + TOP-ZONE TRIM === */
/* === END v2.24.0 MOBILE DASHBOARD DENSITY + UNIFICATION PASS === */

/* ════════════════════════════════════════════════════════════════
   v2.25.0 — DESKTOP DASHBOARD CORRECTNESS PASS (Carleton, field review)
   Authoritative, source-LAST overrides. Everything here is intentionally
   the final word in the cascade so it wins over earlier theme rules AND
   over any later-loading plugin stylesheet at equal specificity.

   Goals (verbatim from the request):
     1. Normalize the icon-name type size — ONE size for every app label,
        sized so the LONGEST name (Commission / Knowledge) fits on a single
        line; every shorter name renders at that same size.
     2. Tile tap must land on the right widget with content visible — no
        dead taps, no black screen.
     3. Widgets must NOT scroll inside themselves — they grow to natural
        height and the dashboard page scrolls as one document. Long content
        (e.g. Leads "All Leads") opens via expansion sections, growing the
        widget downward instead of trapping a scrollbar.
     4. Surveys "Batch History" sticky banner must not float over unrelated
        content.
     5. Commission calculator (and the scheduler) must render readable text
        in dark / system mode — no dark-on-dark.
   ════════════════════════════════════════════════════════════════ */

/* ---- 1. UNIFORM APP-LABEL TYPE SIZE ------------------------------
   The deployed dashboard showed "Camera" and "Commission" at different
   sizes. The fix: a SINGLE shared size token, applied to BOTH the
   springboard grid label (.app-ic .nm) and the vertical dock label
   (.dock-label), with the .is-long divergence fully neutralised. The
   size is chosen so the longest real app name ("Commission", 10 chars)
   fits on ONE line within the tile's label box at every breakpoint, and
   every shorter name renders at that exact same size — so a row reads as
   one consistent type size with a shared baseline.

   --ts-applabel-size is defined per-breakpoint (phones smallest tiles →
   smaller; desktop/tablet wider tiles → larger) and capped so the long
   word never overflows. Labels are single-line (no wrap, no 2-line band)
   so they can never reflow to a different height or position. */
:root{
  /* v2.25.1 (Carleton): ONE flat size at EVERY width — a single standard,
     no per-breakpoint graduation. 18px is the chosen standard. On the very
     narrowest phones (3-col grid, ~360px) "Commission" can be a hair wider
     than the tile at 18px; the label's text-overflow:ellipsis handles that
     by trimming to "Commiss…" rather than shrinking the font or wrapping —
     so the size stays uniform across the whole row on every device. */
  --ts-applabel-size:18px;
  --ts-applabel-lh:1.2;
}

/* Springboard grid label — single line, uniform, sized to fit the long
   name. Overrides the earlier 2-line band + the .is-long no-op. */
.app-grid .app-ic .nm,
.app-grid .app-ic .nm.is-long{
  font-size:var(--ts-applabel-size) !important;
  line-height:var(--ts-applabel-lh) !important;
  /* Single line — the size already guarantees the longest name fits, so a
     reserved 2-line band is no longer needed and would only add dead space
     and re-introduce baseline drift. */
  display:block !important;
  -webkit-line-clamp:none !important;
  white-space:nowrap !important;
  overflow:hidden !important;
  text-overflow:ellipsis !important;
  min-height:calc(var(--ts-applabel-size) * var(--ts-applabel-lh)) !important;
  max-width:100% !important;
}
/* Vertical dock label (left springboard / "Apps" list tiles) — same token,
   same single-line treatment, so dock and grid labels match each other too. */
.app-dock .dock-app .dock-label,
.app-dock .dock-app .dock-label.is-long{
  font-size:var(--ts-applabel-size) !important;
  line-height:var(--ts-applabel-lh) !important;
  display:block !important;
  -webkit-line-clamp:none !important;
  white-space:nowrap !important;
  overflow:hidden !important;
  text-overflow:ellipsis !important;
  min-height:calc(var(--ts-applabel-size) * var(--ts-applabel-lh)) !important;
}

/* ---- 3. NO INTERNAL WIDGET SCROLL — WIDGETS GROW, PAGE SCROLLS ----
   Carleton: "the widget should be expandable as needed by the user… it
   should NOT scroll within its own widget… widgets don't internally
   scroll within themselves." The v2.24.6 unified shell capped every
   container at max-height:max(560px,88vh) and made the body
   overflow-y:auto — i.e. it TRAPPED a scrollbar inside each widget. That
   is exactly what we're removing.

   New behavior:
     • .dash-widget-container has NO max-height — it grows to its content.
     • the body is overflow:clip (NOT auto) — this both removes the
       internal scrollbar AND satisfies WIDGET-OVERFLOW-CONTRACT-v1
       (clip = no scroll container, so plugin position:sticky still binds
       to the page scroll). overflow:auto VIOLATED that contract; clip
       restores it.
     • the single scroll owner remains #sv-dash.sub-view (the page), so
       the whole dashboard scrolls as one document.

   This block is source-LAST and uses the SAME selectors as the v2.24.6
   block, so it overrides it without needing to delete the older rules. */
.dash-widget-container{
  max-height:none !important;   /* was max(560px,88vh) — no cap, grow freely */
}
.dash-widget-container > .dash-widget-body,
.dash-widget-container > .dw-body,
.dash-widget-container > div:last-child,
.dash-widget-body{
  overflow:clip !important;     /* was overflow-y:auto — kills internal scroll,
                                   keeps sticky binding to page (contract) */
  max-height:none !important;
}
/* Game is the one widget that legitimately wants a fixed budget so its
   canvas fills a known box. Keep its container/body floor (it never
   scrolled internally anyway — the canvas letterboxes), but drop the hard
   max-height clamp so it too can grow to its budget without a scrollbar. */
.dash-widget-container[data-app-id="game"] .dash-widget-body,
.dash-widget-container[data-app-id="game"] > div:last-child,
.dash-widget-container[data-app-id="game"] [data-ts-game-surface]{
  overflow:clip !important;
}

/* ---- 4. SURVEYS "BATCH HISTORY" BANNER — STOP IT HOVERING ----------
   Carleton: the Batch History banner "clips through and hovers over
   anything… if I scroll, it just hovers." Root cause: the surveys plugin
   pinned .ts-sw-section-header with position:sticky. That was correct when
   the widget body scrolled internally — the header stayed put while you
   scrolled inside the widget. But section 3 above removed internal widget
   scroll, so the ONLY scrolling ancestor left is the page (#sv-dash); the
   sticky therefore bound to the page and, once you scrolled past the
   Surveys widget, floated at the top of the viewport over whatever widget
   was beneath it — the "it just hovers" report.

   CSS fact: position:sticky needs a SCROLLABLE ancestor; it cannot be
   "contained" to a non-scrolling box (overflow:clip is non-scrollable and
   so can't serve as the sticky's scrollport). Since the widget no longer
   scrolls internally, the header has nothing useful to stick to — so the
   correct fix is to make it a normal in-flow header. It then scrolls with
   its own content and can never hover over other widgets. We enforce that
   here too (defensively, in case an older/cached plugin widget.css still
   says sticky), and keep an opaque background. `isolation:isolate` on the
   container is harmless insurance for z-index scoping. */
.dash-widget-container[data-app-id*="survey"],
.dash-widget-container[data-app-id*="satisfaction"]{
  isolation:isolate;            /* own stacking context (z-index hygiene) */
}
.dash-widget-container[data-app-id*="survey"] .ts-sw-section-header,
.dash-widget-container[data-app-id*="satisfaction"] .ts-sw-section-header{
  position:static !important;   /* never sticky → never hovers over neighbours */
  z-index:auto !important;
  background:var(--sys-surface) !important;  /* always opaque */
  background-clip:padding-box !important;
}

/* ---- 5. COMMISSION + SCHEDULER DARK-THEME TEXT --------------------
   Carleton: "the commissions app is still not showing the dark color
   theme text correctly… very dark text on dark areas," and "same for the
   scheduler." The earlier rule (v2.24.0 §4b) only pinned a handful of
   tags and only matched [data-app-id*="commission"]. Broaden it: in
   dark/system mode, force EVERY text-bearing element inside the
   commission OR scheduler widget to the readable theme token, and pin
   their input/control surfaces to the raised surface token so the form
   fields (the FROM/TO dates, dropdowns) aren't dark-on-dark either.
   Light & sunlight are untouched. */
[data-theme="dark"] .dash-widget-container[data-app-id*="commission"] :where(p,span,td,th,li,label,div,a,small,strong,em,h1,h2,h3,h4,h5,h6,option),
[data-theme="dark"] .dash-widget-container[data-app-id*="schedul"] :where(p,span,td,th,li,label,div,a,small,strong,em,h1,h2,h3,h4,h5,h6,option),
[data-theme="dark"] #app-body[data-app-id*="commission"] :where(p,span,td,th,li,label,div,a,small,strong,em,h1,h2,h3,h4,h5,h6,option),
[data-theme="dark"] #app-body[data-app-id*="schedul"] :where(p,span,td,th,li,label,div,a,small,strong,em,h1,h2,h3,h4,h5,h6,option){
  color:var(--sys-text) !important;
}
/* Inputs / selects / the date + dropdown controls — readable fill + text */
[data-theme="dark"] .dash-widget-container[data-app-id*="commission"] :where(input,select,textarea),
[data-theme="dark"] .dash-widget-container[data-app-id*="schedul"] :where(input,select,textarea),
[data-theme="dark"] #app-body[data-app-id*="commission"] :where(input,select,textarea),
[data-theme="dark"] #app-body[data-app-id*="schedul"] :where(input,select,textarea){
  color:var(--sys-text) !important;
  background:var(--sys-surface-raised) !important;
  border-color:var(--sys-border) !important;
}
/* Same for system mode following an OS dark preference. */
@media(prefers-color-scheme:dark){
  [data-theme="system"] .dash-widget-container[data-app-id*="commission"] :where(p,span,td,th,li,label,div,a,small,strong,em,h1,h2,h3,h4,h5,h6,option),
  [data-theme="system"] .dash-widget-container[data-app-id*="schedul"] :where(p,span,td,th,li,label,div,a,small,strong,em,h1,h2,h3,h4,h5,h6,option),
  [data-theme="system"] #app-body[data-app-id*="commission"] :where(p,span,td,th,li,label,div,a,small,strong,em,h1,h2,h3,h4,h5,h6,option),
  [data-theme="system"] #app-body[data-app-id*="schedul"] :where(p,span,td,th,li,label,div,a,small,strong,em,h1,h2,h3,h4,h5,h6,option){
    color:var(--sys-text) !important;
  }
  [data-theme="system"] .dash-widget-container[data-app-id*="commission"] :where(input,select,textarea),
  [data-theme="system"] .dash-widget-container[data-app-id*="schedul"] :where(input,select,textarea),
  [data-theme="system"] #app-body[data-app-id*="commission"] :where(input,select,textarea),
  [data-theme="system"] #app-body[data-app-id*="schedul"] :where(input,select,textarea){
    color:var(--sys-text) !important;
    background:var(--sys-surface-raised) !important;
    border-color:var(--sys-border) !important;
  }
}
/* === END v2.25.0 DESKTOP DASHBOARD CORRECTNESS PASS === */
