/* ============================================================================
   ps-ui.css — PS Web UI shared components (L1)
   PS_UI_VERSION: 0.2.0 (2026-09-25)

   One canonical version of each component the estate previously drew by hand
   per-template. Requires ps-ui-core.css loaded first; styles resolve ONLY
   through core tokens so theme + skin swaps need no component changes.
   ========================================================================== */

/* ---- Buttons ---- */
.btn{border:0;border-radius:var(--r-btn);padding:.5rem 1.05rem;font:700 .86rem/1.3 var(--sans);
  cursor:pointer;background:var(--accent);color:#fff;display:inline-flex;align-items:center;gap:.4rem}
.btn:hover{background:var(--accent-deep);color:#fff}
.btn.ghost{background:transparent;color:var(--tx);border:1px solid var(--line2)}
.btn.ghost:hover{background:var(--surface2);color:var(--tx)}
.btn.danger{background:var(--err);color:var(--on-err)}
.btn.danger:hover{background:var(--err);filter:brightness(.92)}
.btn.tiny{padding:.28rem .7rem;font-size:.76rem;min-height:24px}   /* secondary actions only: 24px is the floor */
.btn[disabled]{opacity:.55;cursor:not-allowed}   /* avoid; if a button must be unavailable, say why beside it */
/* dark-mode primary: accent is light, flip label to ink for contrast */
:root[data-theme="dark"] .btn{color:#10201B}
:root[data-theme="dark"] .btn:hover{color:#10201B}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .btn{color:#10201B}
  :root:not([data-theme="light"]) .btn:hover{color:#10201B}
}
:root[data-theme="dark"] .btn.ghost,:root[data-theme="dark"] .btn.danger{color:var(--tx)}
:root[data-theme="dark"] .btn.danger{color:var(--on-err)}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .btn.ghost{color:var(--tx)}
  :root:not([data-theme="light"]) .btn.danger{color:var(--on-err)}
}

/* ---- Pills (status) — one taxonomy ---- */
.pill{display:inline-block;font-size:.74rem;font-weight:700;border-radius:var(--r-chip);padding:.14rem .62rem;white-space:nowrap}
.pill.ok{color:var(--washtx);background:var(--wash-soft)}
.pill.warn{color:var(--amber);background:var(--amberbg)}
.pill.err{color:var(--err);background:var(--errbg)}
.pill.info{color:var(--info);background:var(--infobg)}
.pill.mut{color:var(--tx2);background:var(--surface2);border:1px solid var(--line)}

/* ---- Filter chips (interactive) ---- */
.fchip{display:inline-flex;align-items:center;gap:.4rem;font:600 .8rem/1.3 var(--sans);
  border:1px solid var(--line2);border-radius:var(--r-chip);padding:.28rem .8rem;
  background:var(--surface);color:var(--tx2);cursor:pointer}
.fchip:hover{border-color:var(--accent);color:var(--tx)}
.fchip.on{background:var(--accent);border-color:var(--accent);color:#fff}
.fchip.on::before{content:"✓" / "";font-weight:800}   /* selected is marked by shape too, never colour alone */
:root[data-theme="dark"] .fchip.on{color:#10201B}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]) .fchip.on{color:#10201B}}

/* ---- Cards ---- */
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-card);
  padding:1.1rem 1.25rem;box-shadow:var(--shadow1)}
.card.link{display:block;color:inherit;text-decoration:none;transition:transform .12s ease,box-shadow .12s ease}
.card.link:hover{transform:translateY(-2px);box-shadow:var(--shadow2);text-decoration:none}

/* ---- Tabs + chrome bar ---- */
.chrome{display:flex;align-items:center;gap:1rem;flex-wrap:wrap;padding:.6rem 0;border-bottom:1px solid var(--line)}
.tabs{display:flex;gap:.15rem;border-bottom:1px solid var(--line2);flex-wrap:wrap}
.tab{border:0;background:none;font:600 .88rem/1.3 var(--sans);color:var(--tx2);
  padding:.5rem .95rem;border-radius:9px 9px 0 0;cursor:pointer;
  border-bottom:2px solid transparent;margin-bottom:-1px;text-decoration:none;display:inline-block}
.tab:hover{background:var(--surface2);text-decoration:none;color:var(--tx)}
.tab.on{color:var(--accent-deep);border-bottom-color:var(--accent);background:var(--surface)}

/* ---- Tables ---- */
.tblwrap{overflow-x:auto;border:1px solid var(--line);border-radius:var(--r-card);background:var(--surface)}
table.tbl{border-collapse:collapse;width:100%;font-size:.88rem}
table.tbl th{font-size:.7rem;text-transform:uppercase;letter-spacing:.07em;color:var(--tx3);
  text-align:left;padding:.55rem .85rem;border-bottom:1px solid var(--line2);background:var(--surface2)}
table.tbl td{padding:.5rem .85rem;border-bottom:1px solid var(--line)}
table.tbl tr:last-child td{border-bottom:none}
table.tbl th.n,table.tbl td.n{text-align:right;font-variant-numeric:tabular-nums}
table.tbl tbody tr:hover{background:color-mix(in srgb,var(--accent) 7%,var(--surface))}
/* Sticky header: the scroll box must be the wrapper, so a sticky table sits in .tblwrap.sticky (which scrolls in
   both directions inside a capped height). An unwrapped sticky table sticks to the page; set --sticky-top to the
   height of any pinned page header. */
.tblwrap.sticky{max-height:min(70vh,720px);overflow:auto}
table.tbl.sticky thead th{position:sticky;top:var(--sticky-top,0);z-index:2}
/* Click-to-sort (standard Q1): <th aria-sort="ascending|descending|none"><button class="sort">Amount</button></th>.
   The script sets aria-sort on the sorted column and "none" on the rest; the arrow follows it. */
table.tbl th button.sort{font:inherit;color:inherit;letter-spacing:inherit;text-transform:inherit;background:none;border:0;
  padding:0;margin:0;cursor:pointer;display:inline-flex;align-items:center;gap:.3rem;min-height:24px}
table.tbl th.n button.sort{flex-direction:row-reverse}
table.tbl th button.sort::after{content:"↕" / "";opacity:.55}
table.tbl th[aria-sort="ascending"] button.sort::after{content:"↑" / "";opacity:1}
table.tbl th[aria-sort="descending"] button.sort::after{content:"↓" / "";opacity:1}
table.tbl tr.subtotal td{background:color-mix(in srgb,var(--wash) 22%,var(--surface));font-weight:700;border-top:1px solid var(--line2)}

/* ---- Banners ---- */
.banner{display:flex;gap:.6rem;align-items:flex-start;border-radius:11px;padding:.65rem .9rem;
  font-size:.86rem;border:1px solid}
.banner.ok{background:var(--okbg);border-color:color-mix(in srgb,var(--ok) 40%,transparent);color:var(--washtx)}
.banner.warn{background:var(--amberbg);border-color:color-mix(in srgb,var(--amber) 40%,transparent);color:var(--amber)}
.banner.err{background:var(--errbg);border-color:color-mix(in srgb,var(--err) 40%,transparent);color:var(--err)}
.banner.info{background:var(--infobg);border-color:color-mix(in srgb,var(--info) 40%,transparent);color:var(--info)}

/* ---- Toasts: success notices only (web UX standard M1, M2) ----
   The stack is in the page's first HTML, empty, so screen readers hear what lands in it:
     <div class="toasts" role="status" aria-live="polite"></div>
   A failure is never a toast: it goes in a .banner.err beside the thing that failed and stays until resolved.
   A toast with an action (Undo) stays until used or dismissed. Give each toast a dismiss button:
     <div class="toast"><span>Saved: 3 dispositions recorded.</span><button class="x" aria-label="Dismiss">✕</button></div>
   On phones the stack moves to the top, clear of the on-screen keyboard and the field being typed in. */
.toasts{position:fixed;right:1rem;bottom:1rem;display:flex;flex-direction:column;gap:.5rem;z-index:60;max-width:min(380px,90vw)}
.toast{background:var(--surface);border:1px solid var(--line2);border-left:4px solid var(--accent);
  border-radius:10px;padding:.6rem .9rem;font-size:.86rem;box-shadow:var(--shadow2)}
.toast.err{border-left-color:var(--err)}
.toast.warn{border-left-color:var(--amber)}
.toast:has(> .x){position:relative;padding-right:2.6rem}
.toast > .x{position:absolute;top:.3rem;right:.3rem}
@media (max-width:720px){
  .toasts{top:calc(.75rem + env(safe-area-inset-top,0px));bottom:auto;left:.75rem;right:.75rem;max-width:none}
}

/* ---- Freshness cluster ---- */
.fresh{display:inline-flex;align-items:center;gap:.45rem;font-size:.8rem;color:var(--tx2)}
.fdot{width:8px;height:8px;border-radius:50%;background:var(--tx3);flex:none}
.fdot.ok{background:var(--ok)}
.fdot.stale{background:var(--amber)}
.fdot.err{background:var(--err)}
@keyframes ps-blink{0%,100%{opacity:.25}50%{opacity:1}}
.fdots span{display:inline-block;width:5px;height:5px;border-radius:50%;background:var(--tx3);margin-right:3px;animation:ps-blink 1.2s infinite}
.fdots span:nth-child(2){animation-delay:.2s}
.fdots span:nth-child(3){animation-delay:.4s}

/* ---- Modal — the ONE modal system (web UX standard K3) ----
   Markup: a native dialog, named by its visible title, opened with showModal():
     <dialog class="modal" aria-labelledby="m1-title">
       <div class="mhead"><h2 class="mtitle" id="m1-title">Dismiss finding</h2>
         <button class="x" aria-label="Close">✕</button></div>
       …
       <button class="btn ghost">Cancel</button> <button class="btn danger">Dismiss finding</button>
     </dialog>
   The browser then takes focus into it, keeps Tab inside and closes it on Escape. The page's script closes it from
   Cancel and ✕ (dialog.close()) and puts focus back on the button that opened it. In a destructive confirmation,
   give Cancel the autofocus attribute.
   DEPRECATED alias, kept until every app has moved: <div class="ovl"><div class="modal" role="dialog">…</div></div>
   (it takes no focus, keeps none and ignores Escape). */
.ovl{position:fixed;inset:0;background:var(--scrim);display:flex;align-items:center;justify-content:center;
  padding:1rem;z-index:50}
.modal{background:var(--surface);color:var(--tx);border:1px solid var(--line);border-radius:16px;box-shadow:var(--shadow2);
  width:min(560px,94vw);max-height:88vh;overflow:auto;padding:1.1rem 1.3rem}
.modal.wide{width:min(75vw,1440px)}
.mhead{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;margin-bottom:.6rem}
.mtitle{font-family:var(--serif);font-weight:700;font-size:1.1rem;margin:0}
.x{border:0;background:none;font-size:1.1rem;line-height:1;color:var(--tx3);cursor:pointer;padding:.2rem .4rem;border-radius:6px;
  min-width:24px;min-height:24px}
.x:hover{background:var(--surface2);color:var(--tx)}
@keyframes ps-pop{from{opacity:0;transform:scale(.97)}to{opacity:1;transform:scale(1)}}
.modal{animation:ps-pop .14s ease}
dialog.modal{margin:auto}
dialog.modal::backdrop,dialog.panel::backdrop{background:var(--scrim)}

/* ---- Slide-in panel (right drill) ----
   Markup: the same native dialog as the modal, opened with showModal():
     <dialog class="panel" aria-labelledby="p1-title"><div class="mhead"><h2 class="mtitle" id="p1-title">…</h2>
       <button class="x" aria-label="Close">✕</button></div>…</dialog>
   A bare <div class="panel"> still draws, but takes no focus and ignores Escape (deprecated). */
.panel{position:fixed;top:0;right:0;bottom:0;width:min(620px,94vw);background:var(--surface);color:var(--tx);
  border-left:1px solid var(--line);box-shadow:var(--shadow2);z-index:50;overflow:auto;padding:1.1rem 1.3rem}
dialog.panel{left:auto;margin:0;height:100%;max-height:none;max-width:none;border:0;border-left:1px solid var(--line)}
@keyframes ps-slidein{from{transform:translateX(30px);opacity:0}to{transform:none;opacity:1}}
.panel{animation:ps-slidein .16s ease}

/* ---- Form controls ---- */
input[type="text"],input[type="email"],input[type="password"],input[type="date"],input[type="number"],
input[type="search"],select,textarea{
  font:400 .9rem/1.4 var(--sans);color:var(--tx);background:var(--bg);
  border:1px solid var(--line2);border-radius:var(--r-ctl);padding:.45rem .65rem}
input:focus,select:focus,textarea:focus{outline:2px solid var(--accent);outline-offset:1px;border-color:var(--accent)}
label{font-size:.8rem;font-weight:600;color:var(--tx2)}

/* ---- Empty state ---- */
.empty{border:1px dashed var(--line2);border-radius:var(--r-card);padding:1.6rem 1.2rem;
  text-align:center;color:var(--tx3);font-size:.9rem;background:var(--surface2)}

/* ---- Monogram / logo tile ----
   Fill defaults to wash; per-client accent overrides via inline --mono-bg. */
.mono{display:inline-grid;place-items:center;width:34px;height:34px;border-radius:9px;
  background:var(--mono-bg,var(--wash-soft));color:var(--mono-tx,var(--washtx));
  font-weight:800;font-size:.82rem;flex:none}

/* ---- User chip / identity ---- */
.userchip{display:inline-flex;align-items:center;gap:.5rem;font-size:.82rem;color:var(--tx2);
  border:1px solid var(--line);border-radius:var(--r-chip);padding:.25rem .75rem;background:var(--surface)}

/* ---- Phones (web UX standard P3): main and destructive actions 44px tall; close buttons 32px ---- */
@media (max-width:720px),(pointer:coarse){
  .btn:not(.tiny){min-height:44px}
  .x{min-width:32px;min-height:32px}
}

/* ---- Windows high-contrast / forced colours (standard C4): nothing important disappears ----
   Every button keeps an edge; the selected chip, tab and segment and the current stage show in the system's
   highlight colours; status dots keep their colours. */
@media (forced-colors:active){
  .btn,.btn.danger{border:1px solid ButtonText}
  .fchip.on,.fchip[aria-pressed="true"],.tab.on,.tab[aria-selected="true"]{
    forced-color-adjust:none;background:Highlight;color:HighlightText;border-color:Highlight}
  .tab{border-bottom-color:Canvas}
  .tab.on,.tab[aria-selected="true"]{border-bottom-color:Highlight}
  .fdot,.fdots span{forced-color-adjust:none}
}

/* ---- Print (standard Y4): controls hidden, meaningful fills kept ---- */
@media print{
  .btn,.x,.toasts,.ovl,dialog,.tabs,.fdots{display:none!important}
  :root:not([data-theme="light"]) .fchip.on,:root[data-theme="dark"] .fchip.on,.fchip.on{color:#fff}
  .pill,.fchip.on,.banner,.fdot,.mono,table.tbl tr.subtotal td{print-color-adjust:exact;-webkit-print-color-adjust:exact}
  .tblwrap,.tblwrap.sticky{max-height:none;overflow:visible}
}
