/* ════════ Caprix UI refresh layer — consistent tokens + polish ════════ */
:root{
  --radius-sm:8px; --radius-md:12px; --radius-lg:18px; --radius-xl:24px;
  --shadow-sm:0 1px 2px rgba(0,0,0,.4);
  --shadow-md:0 10px 30px -12px rgba(0,0,0,.55), 0 2px 8px rgba(0,0,0,.34);
  --shadow-lg:0 30px 70px -24px rgba(0,0,0,.7), 0 8px 24px -12px rgba(0,0,0,.5);
  --ring:0 0 0 3px var(--primary-glow, rgba(124,58,237,.35));
  --transition:180ms cubic-bezier(.4,0,.2,1);
  --border:rgba(255,255,255,.08);
}
[data-theme="light"]{
  --shadow-sm:0 1px 2px rgba(15,23,42,.08);
  --shadow-md:0 10px 30px -12px rgba(15,23,42,.16), 0 2px 8px rgba(15,23,42,.07);
  --shadow-lg:0 30px 70px -24px rgba(15,23,42,.22), 0 8px 24px -12px rgba(15,23,42,.12);
  --border:rgba(15,23,42,.12);
}
a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,
textarea:focus-visible,summary:focus-visible,[tabindex]:focus-visible,
[role="button"]:focus-visible,[role="tab"]:focus-visible{
  outline:2px solid var(--primary,#7C3AED); outline-offset:2px;
}
*{ scrollbar-width:thin; scrollbar-color:rgba(127,127,140,.42) transparent; }
*::-webkit-scrollbar{ width:11px; height:11px; }
*::-webkit-scrollbar-track{ background:transparent; }
*::-webkit-scrollbar-thumb{ background:rgba(127,127,140,.38); border-radius:99px; border:3px solid transparent; background-clip:content-box; }
*::-webkit-scrollbar-thumb:hover{ background:rgba(127,127,140,.6); }
::selection{ background:rgba(124,58,237,.32); }
body{ -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale; text-rendering:optimizeLegibility; }
*{ -webkit-tap-highlight-color:transparent; }
button:not(:disabled),[role="button"]:not(:disabled),summary{ cursor:pointer; }
button:disabled,input:disabled,select:disabled,textarea:disabled{ cursor:not-allowed; }
input,textarea,select{ transition:border-color var(--transition), box-shadow var(--transition), background-color var(--transition); }
@media (prefers-reduced-motion: reduce){
  *,::before,::after{ animation-duration:.01ms!important; animation-iteration-count:1!important; transition-duration:.01ms!important; scroll-behavior:auto!important; }
}

/* ── iOS: never auto-zoom on focus ────────────────────────────────
   Mobile Safari zooms the viewport whenever a focused text field's font-size
   is under 16px, then leaves the page zoomed. Every input in the app is
   smaller than that by design (boq's grid cells are .78rem), so on an iPhone
   the BOQ grid became a constant zoom-in/pinch-out fight — one zoom per cell
   you tap. 16px on touch widths is the only reliable way to suppress it;
   `maximum-scale=1` would work too but disables pinch-zoom entirely, which
   breaks accessibility.
   Desktop keeps the compact type — this applies only below the tablet break.

   !important is deliberate and load-bearing: the page-level rules that set the
   compact sizes (.modal input, .cell-in, .rl-edit .cell-in …) are all more
   specific than a bare element selector, so without it this rule loses and the
   zoom still happens. The override is scoped to the media query, so desktop
   styling is untouched. */
@media (max-width: 768px){
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
  textarea,
  select{
    font-size:16px !important;
  }
}

/* ── Loading skeletons ────────────────────────────────────────────
   Shared shimmer used while a panel's data is in flight. Before this,
   every async surface in the app rendered a literal "—" or "…" and then
   popped — indistinguishable from "the value really is zero" or from a
   request that failed silently.

   Usage: give the placeholder `.sk` (plus `.sk-line` for a text row, or
   `.sk-pill` for an inline badge-sized block), then replace the node's
   innerHTML once the data lands. `.sk-w40`/`.sk-w60`/`.sk-w80` vary the
   width so a stack of lines doesn't read as a solid rectangle.

   currentColor-based so it inherits whatever surface it sits on and needs
   no light/dark variant. The animation is already neutralised by the
   prefers-reduced-motion block above; the block stays visible, it just
   stops shimmering. */
.sk{
  position:relative;
  display:block;
  overflow:hidden;
  border-radius:6px;
  background:currentColor;
  opacity:.09;
  animation:caprix-sk-pulse 1.4s ease-in-out infinite;
}
.sk-line{ height:.72rem; margin:6px 0; }
.sk-pill{ display:inline-block; height:1em; min-width:3.5em; vertical-align:middle; border-radius:99px; }
.sk-w40{ width:40%; } .sk-w60{ width:60%; } .sk-w80{ width:80%; }
@keyframes caprix-sk-pulse{ 0%,100%{ opacity:.09; } 50%{ opacity:.19; } }

/* A skeleton row shaped like a sidebar/list entry: icon dot + two lines. */
.sk-row{
  display:flex; align-items:center; gap:8px;
  padding:8px 10px; margin-bottom:2px;
}
.sk-row .sk-dot{ width:16px; height:16px; border-radius:50%; flex-shrink:0; }
.sk-row .sk-body{ flex:1; min-width:0; }
.sk-row .sk-body .sk-line{ margin:3px 0; }

/* Inline "this panel failed to load" line — the counterpart to the
   skeleton, so an error is never rendered as an empty state. */
.load-error{
  font-size:.78rem;
  line-height:1.5;
  color:#FCA5A5;
  padding:8px 6px;
}
.load-error button{
  margin-left:6px;
  padding:2px 8px;
  border:1px solid currentColor;
  border-radius:6px;
  background:transparent;
  color:inherit;
  font:inherit;
  font-size:.72rem;
}
[data-theme="light"] .load-error{ color:#B91C1C; }

/* ── "Cost Climb" mini-game modal ─────────────────────────────────
   The break behind the plan badge on chat.html and boq.html. Markup and
   logic are injected by caprix-common.js; these are the only styles.
   Both pages carried their own ~95-line copy of this until the two had
   drifted — boq's was missing the light-theme .gbtn colour below, so the
   Higher/Lower buttons rendered white-on-white in light mode. */
.game-backdrop {
  display: none; position: fixed; inset: 0; z-index: 300;
  background: rgba(4,4,12,.72); backdrop-filter: blur(6px);
  align-items: center; justify-content: center; padding: 20px;
}
.game-backdrop.open { display: flex; animation: gameFade .2s ease; }
@keyframes gameFade { from { opacity: 0; } to { opacity: 1; } }
.game-modal {
  width: 100%; max-width: 440px;
  background: linear-gradient(180deg,#10101F,#0A0A14);
  border: 1px solid rgba(139,92,246,.28);
  border-radius: 22px; overflow: hidden;
  box-shadow: 0 30px 80px rgba(0,0,0,.6), 0 0 0 1px rgba(255,255,255,.02) inset;
  animation: gamePop .28s cubic-bezier(.2,.9,.3,1.2);
}
@keyframes gamePop { from { transform: scale(.9) translateY(14px); opacity: 0; } to { transform: scale(1) translateY(0); opacity: 1; } }
.game-head {
  position: relative; padding: 20px 22px 18px;
  background: radial-gradient(120% 140% at 0% 0%, rgba(139,92,246,.35), transparent 60%),
              radial-gradient(120% 140% at 100% 0%, rgba(124,58,237,.32), transparent 60%);
  border-bottom: 1px solid rgba(255,255,255,.06);
}
.game-eyebrow { font-size: .66rem; font-weight: 800; letter-spacing: .14em; text-transform: uppercase; color: #C4B5FD; }
.game-title { font-size: 1.32rem; font-weight: 800; letter-spacing: -.4px; margin-top: 4px; color: #fff; }
.game-thanks { font-size: .82rem; color: var(--text-muted); margin-top: 6px; line-height: 1.5; }
.game-close {
  position: absolute; top: 16px; right: 16px;
  width: 30px; height: 30px; border-radius: 9px; cursor: pointer;
  background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.1);
  color: #fff; display: flex; align-items: center; justify-content: center;
}
.game-close:hover { background: rgba(255,255,255,.16); }
/* Icon must not swallow the click — keep the whole 30px box the target so the
   close button never "misses" (the lucide <i> → <svg> swap could shift the hit area). */
.game-close i, .game-close svg { pointer-events: none; }
.game-body { padding: 18px 22px 24px; }
.game-stats { display: flex; justify-content: center; gap: 10px; margin-bottom: 16px; }
.game-chip {
  flex: 1; max-width: 130px; text-align: center;
  background: rgba(255,255,255,.04); border: 1px solid var(--border);
  border-radius: 12px; padding: 8px 6px;
}
.game-chip .lbl { font-size: .58rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; color: var(--text-muted); }
.game-chip .val { font-size: 1.45rem; font-weight: 800; letter-spacing: -.5px; color: #fff; line-height: 1.1; }
.game-chip .val.amber { color: #FCD34D; }

.game-prompt { text-align: center; font-size: .82rem; color: var(--text-muted); margin-bottom: 12px; }
.game-prompt strong { color: var(--text); }
.game-cards { display: grid; grid-template-columns: 1fr auto 1fr; align-items: stretch; gap: 8px; margin-bottom: 16px; }
.gcard {
  background: linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.02));
  border: 1px solid var(--border); border-radius: 16px;
  padding: 16px 12px; text-align: center;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  min-height: 152px;
}
.gcard .emoji { font-size: 2.1rem; line-height: 1; filter: drop-shadow(0 4px 8px rgba(0,0,0,.4)); }
.gcard .name { font-size: .82rem; font-weight: 700; color: var(--text); line-height: 1.25; }
.gcard .unit { font-size: .66rem; color: var(--text-muted); }
.gcard .price { font-size: 1.1rem; font-weight: 800; color: #6EE7B7; letter-spacing: -.3px; margin-top: 2px; min-height: 1.1em; }
.gcard .price.hidden-price { color: var(--text-muted); }
.gcard.flash-ok  { animation: flashOk .5s ease; }
.gcard.flash-bad { animation: flashBad .5s ease; }
@keyframes flashOk  { 0%,100%{ box-shadow:none; } 40%{ box-shadow:0 0 0 2px #10B981, 0 0 22px rgba(16,185,129,.5);} }
@keyframes flashBad { 0%,100%{ box-shadow:none; } 40%{ box-shadow:0 0 0 2px #EF4444, 0 0 22px rgba(239,68,68,.5);} }
.game-vs { display: flex; align-items: center; justify-content: center; font-size: .72rem; font-weight: 800; color: var(--text-subtle); }
.game-btns { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.gbtn {
  padding: 13px; border-radius: 13px; cursor: pointer;
  font-family: inherit; font-size: .9rem; font-weight: 700;
  border: 1px solid var(--border); color: #fff;
  display: flex; align-items: center; justify-content: center; gap: 7px;
  transition: transform .1s, opacity .2s, border-color .2s;
}
.gbtn:disabled { opacity: .5; cursor: default; }
.gbtn:not(:disabled):active { transform: scale(.96); }
.gbtn.higher { background: linear-gradient(135deg, rgba(16,185,129,.22), rgba(16,185,129,.1)); border-color: rgba(16,185,129,.4); }
.gbtn.lower  { background: linear-gradient(135deg, rgba(239,68,68,.2), rgba(239,68,68,.1)); border-color: rgba(239,68,68,.4); }
.gbtn.higher:not(:disabled):hover { border-color: #10B981; }
.gbtn.lower:not(:disabled):hover  { border-color: #EF4444; }
.game-over { text-align: center; padding: 4px 0; }
.game-over .go-title { font-size: 1.15rem; font-weight: 800; color: #fff; margin-bottom: 4px; }
.game-over .go-sub { font-size: .82rem; color: var(--text-muted); margin-bottom: 16px; line-height: 1.5; }
.game-replay {
  width: 100%; padding: 13px; border: none; border-radius: 13px; cursor: pointer;
  font-family: inherit; font-size: .9rem; font-weight: 700; color: #fff;
  background: linear-gradient(135deg,#8B5CF6,#6366F1);
  box-shadow: 0 6px 20px rgba(139,92,246,.35); transition: opacity .2s, transform .1s;
}
.game-replay:hover { opacity: .92; }
.game-replay:active { transform: scale(.98); }
[data-theme="light"] .game-modal { background: linear-gradient(180deg,#FFFFFF,#F4F6FB); }
[data-theme="light"] .game-title, [data-theme="light"] .game-chip .val,
[data-theme="light"] .gcard .name, [data-theme="light"] .go-title { color:#0F172A; }
[data-theme="light"] .gcard .price { color:#047857; }
[data-theme="light"] .gbtn { color:#0F172A; }

/* ── Free-plan locked options ──────────────────────────────────────
   The Ask form and the BOQ / Rate Library rate picker keep every category,
   state and city in their dropdowns and disable the ones outside the free
   slice (caprix-free-tier-scope.sql). Greyed rather than hidden, so the rest
   of the dataset stays visible as something to unlock.

   The plain `option:disabled` rule below is the fallback for any select that
   hasn't been enhanced by caprixLockSelect() (caprix-common.js). Where it HAS
   been, the popup below replaces the browser's drop-down list, because a
   native <option> can't carry a badge — every engine paints a disabled option
   flat grey and strips any markup, so a locked category read as broken data
   rather than as a paid feature. */
select option:disabled { opacity: .55; }
.scope-note {
  flex: 1 0 100%; margin: 6px 2px 0; font-size: .78rem; line-height: 1.5;
  color: var(--text-muted);
}
.scope-note a { color: var(--accent, #8B5CF6); text-decoration: none; font-weight: 600; }
.scope-note a:hover { text-decoration: underline; }

/* ── caprixLockSelect: styled listbox over a native <select> ───────
   The <select> stays in the DOM (visually hidden) and remains the value —
   every existing .value read, .value write and change handler is untouched.
   Only the drop-down list is ours. The trigger button deliberately carries no
   colours or padding of its own: each page adds `.cx-sel-btn` to its own
   `select` rule so the closed field is pixel-identical to its neighbours. */
.cx-sel { position: relative; display: block; width: 100%; }
/* The <select> keeps its box — hidden, but still laid out — and the button is
   stretched over it. That way the enhanced field is exactly as tall as a plain
   select on the same page without this file needing to know either page's
   padding or font size. */
.cx-sel > select.cx-sel-native {
  display: block; width: 100%; visibility: hidden; pointer-events: none;
}
.cx-sel-btn {
  position: absolute; inset: 0;
  display: flex; align-items: center; gap: 8px;
  text-align: left; cursor: pointer; font-family: inherit;
}
.cx-sel-btn .cx-sel-val {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cx-sel-btn .cx-sel-caret {
  flex: 0 0 auto; width: 14px; height: 14px; opacity: .55;
  transition: transform .18s ease;
}
.cx-sel[data-open="true"] .cx-sel-caret { transform: rotate(180deg); }
.cx-sel[data-open="true"] > .cx-sel-btn { border-color: var(--primary, #7C3AED); }

/* The popup is parented to <body> and positioned fixed: the Ask form sits in a
   bottom-anchored bar and the rate picker lives inside a scrollable modal, and
   an in-flow popup gets clipped by both. */
.cx-sel-pop {
  position: fixed; z-index: 9999; display: none;
  min-width: 180px; max-width: min(400px, 94vw);
  max-height: min(340px, 58vh); overflow-y: auto; overscroll-behavior: contain;
  padding: 5px; border-radius: 12px;
  background: #12121F;
  border: 1px solid var(--border, rgba(255,255,255,.1));
  box-shadow: 0 20px 46px rgba(0,0,0,.55), 0 2px 8px rgba(0,0,0,.4);
}
.cx-sel-pop.open { display: block; animation: cx-sel-in .13s ease-out; }
@keyframes cx-sel-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .cx-sel-pop.open { animation: none; }
  .cx-sel-btn .cx-sel-caret { transition: none; }
}
.cx-sel-opt {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 9px; border-radius: 8px; cursor: pointer;
  font-size: .8rem; line-height: 1.35; color: var(--text, #E5E7EB);
}
.cx-sel-opt .cx-sel-lbl { flex: 1 1 auto; min-width: 0; }
.cx-sel-opt:hover, .cx-sel-opt.active { background: rgba(124,58,237,.16); }
.cx-sel-opt.sel { color: #C4B5FD; font-weight: 650; }
.cx-sel-opt.sel::after {
  content: ''; flex: 0 0 auto; width: 6px; height: 6px;
  border-radius: 50%; background: #8B5CF6;
}
/* The locked row: dimmed label + a Pro pill that matches the locked mode pills
   in chat.html, so "you need a paid plan" reads the same everywhere. */
.cx-sel-opt.locked { cursor: default; color: var(--text-muted, #94A3B8); }
.cx-sel-opt.locked .cx-sel-lbl { opacity: .8; }
.cx-sel-opt.locked:hover, .cx-sel-opt.locked.active { background: rgba(139,92,246,.10); }
.cx-sel-pro {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 3px;
  padding: 2px 7px 2px 5px; border-radius: 999px; color: #fff;
  font-size: .6rem; font-weight: 800; letter-spacing: .05em; text-transform: uppercase;
  background: linear-gradient(135deg, #8B5CF6, #6366F1);
  box-shadow: 0 1px 4px rgba(139,92,246,.4);
}
.cx-sel-pro svg { width: 9px; height: 9px; flex: 0 0 9px; }
/* Sticky so the way out of the lock is on screen however far the list scrolls. */
.cx-sel-cta {
  position: sticky; bottom: -5px; z-index: 1;
  display: flex; align-items: center; gap: 6px;
  margin: 5px 0 0; padding: 8px 9px; border-radius: 8px;
  text-decoration: none; font-size: .72rem; font-weight: 700; color: #C4B5FD;
  background: linear-gradient(135deg, rgba(139,92,246,.22), rgba(99,102,241,.16)), #12121F;
  border: 1px solid rgba(139,92,246,.32);
}
.cx-sel-cta svg { width: 12px; height: 12px; flex: 0 0 12px; }
.cx-sel-cta:hover { border-color: rgba(139,92,246,.7); color: #DDD6FE; }
.cx-sel-cta.flash { animation: cx-sel-flash .5s ease; }
@keyframes cx-sel-flash {
  0%, 100% { transform: none; border-color: rgba(139,92,246,.32); }
  40%      { transform: scale(1.03); border-color: rgba(196,181,253,.95); }
}
@media (prefers-reduced-motion: reduce) { .cx-sel-cta.flash { animation: none; } }
[data-theme="light"] .cx-sel-pop {
  background: #FFFFFF; border-color: rgba(15,23,42,.12);
  box-shadow: 0 20px 46px rgba(15,23,42,.16), 0 2px 8px rgba(15,23,42,.08);
}
[data-theme="light"] .cx-sel-opt { color: #0F172A; }
[data-theme="light"] .cx-sel-opt.sel { color: #6D28D9; }
[data-theme="light"] .cx-sel-opt.locked { color: #64748B; }
[data-theme="light"] .cx-sel-cta {
  color: #6D28D9;
  background: linear-gradient(135deg, rgba(139,92,246,.14), rgba(99,102,241,.10)), #FFFFFF;
}
