:root{
  --plum:#4a0e3f;
  --plum-deep:#3a0a31;
  --magenta:#c2185b;
  --crimson:#b5164f;
  --blue:#1a56c4;
  --blood-red:#ff1a1a;
  --ink:#241021;
  --muted:#6b5866;
  --paper:#fff;
  --wash:#faf6f9;
  --line:#ecdfe8;
  --ok-bg:#e8f6ee;
  --ok-fg:#1a6b3c;
  --err-bg:#fdeaee;
  --err-fg:#a3123a;
  --amber:#a86a08;
  --radius:12px;
  --radius-input:10px;
  --radius-pill:999px;
}
*{box-sizing:border-box;margin:0;padding:0}
/* Form controls don't inherit the page font by default — without this,
   a <button> renders at a different size/height than an <a class="btn">,
   and unstyled input types (number, date, etc.) fall back to the OS
   default font/height instead of matching text inputs. */
button,input,select,textarea{font:inherit}
body{
  font-family:"Segoe UI",system-ui,-apple-system,"Helvetica Neue",sans-serif;
  background:var(--wash);color:var(--ink);line-height:1.55;-webkit-font-smoothing:antialiased;
}
a{color:var(--magenta)}

/* Wireframe 1a: a visible 2px plum ring on every interactive element, not
   just inputs — the old input-only box-shadow (12% tint) was too faint to
   see, and .btn/.side a.nav/.pagination-link had no focus style at all. */
:focus-visible{outline:2px solid var(--plum);outline-offset:2px}

/* ---------- auth ---------- */
.auth-wrap{min-height:100vh;display:grid;place-items:center;padding:24px;
  background:linear-gradient(135deg,var(--plum-deep),var(--plum) 50%,var(--crimson));}
.auth-card{background:#fff;border-radius:18px;padding:40px;width:100%;max-width:420px;
  box-shadow:0 24px 60px rgba(0,0,0,.25)}
.auth-card img{height:52px;width:auto;display:block;margin:0 auto 26px}
.auth-card h1{font-size:1.35rem;color:var(--plum);margin-bottom:6px;text-align:center}
.auth-card .sub{color:var(--muted);font-size:.9rem;text-align:center;margin-bottom:26px}
.auth-foot{text-align:center;margin-top:18px;font-size:.88rem}

/* ---------- shell ---------- */
.shell{display:grid;grid-template-columns:250px 1fr;min-height:100vh}
.side{background:var(--plum-deep);padding:22px 0;position:sticky;top:0;height:100vh;overflow-y:auto;
  display:flex;flex-direction:column}
/* Pinned to the bottom of the sidebar itself (wireframe 1b), not the main
   content area — margin-top:auto pushes it down whether the nav is short
   or long enough to need the sidebar's own scroll. */
.side-version{margin-top:auto;padding:14px 22px 4px;border-top:1px solid rgba(255,255,255,.14);font-size:.7rem;color:rgba(255,255,255,.55)}
.side-version a{color:inherit;text-decoration:none}
.side-version a:hover{text-decoration:underline}
.side .brand{padding:0 22px 22px;border-bottom:1px solid rgba(255,255,255,.1);margin-bottom:16px}
.side .brand img{height:56px}
.side a.nav{display:flex;align-items:center;gap:11px;padding:13px 22px;min-height:44px;
  box-sizing:border-box;color:rgba(255,255,255,.85);
  text-decoration:none;font-size:.93rem;font-weight:500;border-left:3px solid transparent}
.side a.nav:hover{background:rgba(255,255,255,.06);color:#fff}
.side a.nav.on{background:rgba(194,24,91,.22);color:#fff;border-left-color:var(--magenta)}
.side .grp{padding:16px 22px 7px;font-size:.68rem;letter-spacing:.13em;text-transform:uppercase;
  color:rgba(255,255,255,.38);font-weight:700}
/* Every nav group (Business Panel/Finances/Reports/Admin Panel) is its own
   collapsible <details>/<summary> accordion section — closed by default so
   a long module list doesn't compete with daily-work items, opened
   server-side when the active route lives inside it, and mutually
   exclusive with the other groups (see the 'toggle' listener in
   layouts/portal.blade.php). The +/− indicator sits at the START of the
   label (not a trailing chevron), toggled via the [open] attribute. */
.side-grp{border:0}
.side-grp > summary{list-style:none;cursor:pointer;margin:0;display:flex;align-items:center}
.side-grp > summary::-webkit-details-marker{display:none}
.side-grp-toggle{position:relative;flex:none;width:9px;height:9px;margin-right:9px}
.side-grp-toggle::before,.side-grp-toggle::after{content:"";position:absolute;top:50%;left:50%;
  background:rgba(255,255,255,.5);transition:transform .15s}
.side-grp-toggle::before{width:9px;height:1.5px;transform:translate(-50%,-50%)}
.side-grp-toggle::after{width:1.5px;height:9px;transform:translate(-50%,-50%)}
.side-grp[open] .side-grp-toggle::after{transform:translate(-50%,-50%) scaleY(0)}
.side-grp > summary:hover .side-grp-toggle::before,
.side-grp > summary:hover .side-grp-toggle::after{background:rgba(255,255,255,.85)}
.side-grp > summary:hover{color:rgba(255,255,255,.6)}

.main{display:flex;flex-direction:column;min-width:0}
.top{background:#fff;border-bottom:1px solid var(--line);padding:0 30px;height:66px;
  display:flex;align-items:center;justify-content:space-between;gap:16px}
.head-title{display:flex;align-items:baseline;gap:14px;flex:1;min-width:0}
.top h1{font-size:1.15rem;color:var(--plum);font-weight:700;min-width:0;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
#header-clock{font-size:.82rem;color:var(--muted);white-space:nowrap;flex:none}
/* Hamburger — only shown on small screens (see media query below). */
.nav-toggle{display:none;background:none;border:0;color:var(--plum);font-size:1.5rem;line-height:1;
  padding:6px 8px;margin-left:-4px;cursor:pointer;border-radius:8px}
.nav-toggle:hover{background:var(--wash)}
.nav-backdrop{display:none}
.who{display:flex;align-items:center;gap:11px;flex:none}
/* overflow:hidden is the safety net if an <img> ever fails to load (e.g. a
   permission mismatch on the serving route) — without it the browser's alt
   text fallback overflows the circle instead of clipping to it. */
/* box-shadow, not border — a border would add to the element's box and
   throw off the width/height sizing callers pass via :size; the two-layer
   shadow (white gap, then plum) keeps the ring readable over any photo. */
.avatar{width:36px;height:36px;border-radius:50%;display:grid;place-items:center;color:#fff;
  font-weight:700;font-size:.8rem;background:linear-gradient(135deg,var(--plum),var(--magenta));overflow:hidden;
  border:none;box-shadow:0 0 0 2px #fff,0 0 0 3px var(--plum)}
.avatar img{border:none}

/* ---------- avatar picker (change/crop/remove) ---------- */
.avatar-picker-preview{position:relative;flex:none}
.avatar-crop-modal{width:min(360px,90vw);text-align:center}
.avatar-crop-stage{display:flex;justify-content:center;margin-bottom:14px}
.avatar-crop-canvas{width:220px;height:220px;border-radius:50%;background:var(--wash);
  box-shadow:0 0 0 2px #fff,0 0 0 3px var(--plum);cursor:grab;touch-action:none}
.avatar-crop-canvas:active{cursor:grabbing}
.avatar-crop-zoom{width:100%}
.who .nm{font-size:.85rem;font-weight:600;line-height:1.2;max-width:120px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.who .rl{font-size:.72rem;color:var(--muted)}
.signout-btn{display:flex;align-items:center;gap:6px}
.signout-icon{display:none;flex:none}

/* ---------- bell notifications (WI-343) ---------- */
.bell-wrap{position:relative;flex:none}
.bell-btn{position:relative;background:none;border:0;color:var(--plum);cursor:pointer;
  padding:6px;border-radius:8px;display:grid;place-items:center;flex:none}
.bell-btn:hover{background:var(--wash)}
.bell-badge{position:absolute;top:0;right:0;min-width:16px;height:16px;padding:0 4px;
  border-radius:999px;background:var(--magenta);color:#fff;font-size:.65rem;font-weight:700;
  line-height:16px;text-align:center;box-shadow:0 0 0 2px #fff}
/* Anchored dropdown, not a centered <dialog> — sits right under the bell,
   flush to the top-right of the viewport, so it reads as "belonging" to the
   icon that opened it rather than a page-blocking popup.
   display:none by default, toggled via .open (NOT the [hidden] attribute —
   author stylesheets always win over the UA stylesheet's [hidden]{display:
   none}, regardless of specificity, so a plain "display:flex" class rule
   here would silently defeat hidden and leave the panel stuck open). */
.bell-panel{display:none;position:absolute;top:calc(100% + 10px);right:0;z-index:200;
  width:min(380px,90vw);max-height:min(480px,70vh);flex-direction:column;
  background:#fff;border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:0 12px 32px rgba(31,10,26,.18);padding:16px}
.bell-panel.open{display:flex}
.bell-panel-head{display:flex;align-items:center;justify-content:space-between;gap:8px}
.bell-panel-head h2{margin:0;font-size:1rem;color:var(--plum)}
.bell-modal-close{background:var(--wash);border:1px solid var(--line);border-radius:50%;
  width:30px;height:30px;padding:0;flex:none;cursor:pointer;color:var(--plum);line-height:1;
  font-size:1.15rem;display:flex;align-items:center;justify-content:center}
.bell-modal-close span{transform:translateY(-1px)}
.bell-modal-close:hover{background:var(--magenta);color:#fff;border-color:transparent}
.bell-panel-actions{display:flex;align-items:center;gap:14px;margin-top:10px;
  padding-bottom:10px;border-bottom:1px solid var(--line)}
.bell-action{background:none;border:0;padding:0;color:var(--magenta);font-size:.78rem;
  font-weight:600;cursor:pointer}
.bell-action:hover{text-decoration:underline}
.bell-list{overflow-y:auto;margin-top:10px}
.bell-item{display:flex;align-items:flex-start;gap:10px;padding:12px 8px;border-bottom:1px solid var(--line)}
.bell-item.unread{background:rgba(194,24,91,.08);border-radius:8px}
.bell-item-body{flex:1;min-width:0}
.bell-item-heading{font-weight:600;color:var(--plum);font-size:.88rem}
.bell-item-text{font-size:.82rem;color:var(--ink);margin-top:2px;white-space:pre-wrap}
.bell-item-time{font-size:.72rem;color:var(--muted);margin-top:4px}
.bell-item-read,.bell-item-delete{background:none;border:1px solid var(--line);border-radius:50%;
  width:26px;height:26px;flex:none;cursor:pointer;color:var(--muted);line-height:1;font-size:.9rem}
.bell-item-read:hover{color:#fff;background:var(--ok-fg,#2f8f5b);border-color:transparent}
.bell-item-delete:hover{color:#fff;background:var(--err-fg,#c0304a);border-color:transparent}
.content{padding:30px;flex:1}

/* ---------- bits ---------- */
.card{background:#fff;border:1px solid var(--line);border-radius:var(--radius);padding:24px}
.card + .card{margin-top:20px}
/* .row grids stretch items to equal height by default; the rule above is
   meant for vertically stacked standalone cards, not grid siblings — inside
   a .row it just shrinks/offsets every card after the first. Needs 3 classes
   of specificity to beat the 2-class rule above regardless of source order. */
.row .card + .card{margin-top:0}
.stat-tile{min-height:88px;display:flex;flex-direction:column;justify-content:center}
.card h2{font-size:1rem;color:var(--plum);margin-bottom:4px}
.card .hint{color:var(--muted);font-size:.85rem;margin-bottom:18px}

.btn{display:inline-flex;align-items:center;justify-content:center;gap:7px;padding:10px 20px;
  min-height:44px;border-radius:var(--radius-pill);border:1px solid var(--magenta);
  background:var(--magenta);color:#fff;font-weight:600;
  font-size:.88rem;cursor:pointer;text-decoration:none;transition:background-color .15s,opacity .15s}
.btn:hover{background:var(--crimson);border-color:var(--crimson)}
.btn:active{background:var(--plum-deep);border-color:var(--plum-deep)}
.btn:disabled{opacity:.45;cursor:not-allowed}
.btn.wide{width:100%;justify-content:center;padding:12px}
.btn.ghost{background:#fff;color:var(--plum);border:1.5px solid var(--plum)}
.btn.ghost:hover{background:var(--wash)}
.btn.danger{background:#fff;color:var(--err-fg);border:1.5px solid #f3c6d2}
.btn.danger:hover{background:var(--err-bg)}
.btn.sm{padding:7px 14px;font-size:.8rem;min-height:36px}
.btn.is-loading{opacity:.75;cursor:wait;pointer-events:none}
.btn.is-loading::before{content:"";width:14px;height:14px;border-radius:50%;
  border:2px solid rgba(255,255,255,.5);border-top-color:#fff;
  animation:btn-spin .6s linear infinite}
.btn.ghost.is-loading::before{border-color:rgba(74,14,63,.3);border-top-color:var(--plum)}
@keyframes btn-spin{to{transform:rotate(360deg)}}

/* Component Standard §4: pagination is a row of circular bubbles — first «,
   prev ‹, numbered pages with an ellipsis, next ›, last ». The active page
   is filled plum; disabled ends stay visible but drop to #c9bcc6. 34px on
   desktop/tablet, 40px on a phone (see the list-footer phone rules). */
.pagination-nav{margin-top:16px}
.pagination-list{display:flex;flex-wrap:wrap;gap:6px;list-style:none;margin:0;padding:0}
.pagination-item{display:flex}
.pagination-link{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;
  padding:0;border-radius:50%;border:1.5px solid var(--line);
  background:#fff;color:var(--ink);
  font-size:.8rem;font-weight:600;text-decoration:none;transition:border-color .15s,background .15s}
.pagination-link.page-num{width:34px;height:34px;padding:0}
.pagination-item a.pagination-link:hover{border-color:var(--plum);background:var(--wash)}
.pagination-item.active .pagination-link{background:var(--plum);color:#fff;border-color:var(--plum);font-weight:700}
.pagination-item.disabled .pagination-link{color:#c9bcc6;border-color:var(--line);cursor:not-allowed}
.pagination-item.ellipsis .pagination-link{border:0;color:#a08a9a;font-weight:700;cursor:default}

label{display:block;font-size:.82rem;font-weight:600;color:var(--plum);margin-bottom:6px}
label .opt{font-weight:400;color:var(--muted)}
label.required::after{content:" *";color:#c2185b;font-weight:800}
input[type=text],input[type=email],input[type=password],input[type=tel],
input[type=number],input[type=date],input[type=datetime-local],input[type=month],
input[type=time],input[type=search],input[type=url],select{
  width:100%;min-height:44px;padding:11px 13px;border:1.5px solid var(--line);
  border-radius:var(--radius-input);font-size:.92rem;box-sizing:border-box;
  font-family:inherit;color:var(--ink);background:#fff}
input:focus,select:focus{outline:0;border-color:var(--plum);box-shadow:0 0 0 2px rgba(194,24,91,.35)}
/* A derived value shown in an input — a figure the server works out, never
   typed (List Price, the GST rupee amounts, Margin). It has to LOOK
   uneditable: a readonly box styled identically to an editable one reads as
   a field that simply refuses to accept typing. Tinted, muted and given a
   default cursor so the distinction is visible before anyone clicks it. */
input[readonly],textarea[readonly]{background:var(--wash,#faf6f8);color:var(--muted);
  border-style:dashed;cursor:default}
input[readonly]:focus{border-color:var(--line);box-shadow:none}
/* The date/time dropdown itself (calendar grid, hour/minute columns) is
   OS/browser-native chrome — no CSS reaches inside it. accent-color is the
   one hook Chromium/Safari expose, tinting its selected-day/AM-PM highlight
   toward the brand magenta instead of the browser default blue. */
input[type=date],input[type=datetime-local],input[type=time],input[type=month]{
  accent-color:var(--magenta);color-scheme:light}
/* date-inputs.js swaps each type=date's visible digits for a dd/mm/yyyy text
   field (native date inputs render mm/dd/yyyy on a US-locale browser and the
   page's lang attribute can't override that) — the native input survives
   underneath, shrunk to just its calendar-icon corner, purely to drive the
   OS picker. */
.date-input-wrap{position:relative;display:block}
.date-input-wrap input[type=text]{padding-right:34px}
.date-input-wrap::after{content:"\1F4C5";position:absolute;top:50%;right:11px;
  transform:translateY(-50%);font-size:.95rem;line-height:1;pointer-events:none;opacity:.75}
.date-input-wrap input.date-input-native{
  position:absolute;top:0;right:0;width:34px;height:100%;min-height:0;
  padding:0;border:0;background:transparent;opacity:0;cursor:pointer;z-index:1}
.field{margin-bottom:16px}
.row{display:grid;gap:16px}
.row.c2{grid-template-columns:1fr 1fr}
.row.c3{grid-template-columns:1fr 1fr 1fr}
.row.c4{grid-template-columns:1fr 1fr 1fr 1fr}
.row.c5{grid-template-columns:repeat(5,1fr)}
@media(max-width:1099px){.row.c5{grid-template-columns:1fr 1fr 1fr}}
.err{color:var(--err-fg);font-size:.79rem;margin-top:5px}
/* The invalid state has to read the same whichever control it lands on —
   before WI-275 only <input> turned red, so a missed <select> or <textarea>
   looked untouched next to a flagged text box. */
input.bad,select.bad,textarea.bad{border-color:#e4879f;background:var(--err-bg)}
.phone-field-group{display:flex;gap:8px;align-items:center}
.phone-cc-select{flex:0 0 auto;width:auto;min-width:82px}
.phone-number-input{flex:1 1 auto;min-width:0}
.phone-digit-counter{flex:0 0 auto;font-size:.79rem;color:var(--muted);white-space:nowrap}
@media(max-width:768px){
  .phone-field-group{flex-wrap:wrap}
  .phone-number-input{flex:1 1 100%}
}
.rolegrid.bad,[data-required-group].bad{border-radius:8px;box-shadow:0 0 0 2px #e4879f}

/* "Fields marked * are required" — rendered by public/js/form-validation.js on
   any form that has starred labels, so the star is explained everywhere
   without each view repeating the line. */
.req-legend{font-size:.78rem;color:var(--muted);margin:0 0 14px}
.req-legend .req-star{color:#c2185b;font-weight:800}

/* Summary shown at the top of a form when submit is blocked, so the count of
   what's outstanding is visible without hunting down the page for red text. */
.form-errors{background:var(--err-bg);border:1px solid #e4879f;border-left:4px solid var(--err-fg);
  color:var(--err-fg);font-size:.85rem;font-weight:600;padding:11px 14px;border-radius:8px;
  margin:0 0 16px}

input[type=file]{width:100%;padding:9px 12px;border:1.5px solid var(--line);border-radius:8px;
  font-size:.85rem;font-family:inherit;color:var(--muted);background:#fff;cursor:pointer}
input[type=file]:focus{outline:0;border-color:var(--plum);box-shadow:0 0 0 2px rgba(194,24,91,.35)}
input[type=file]::file-selector-button{display:inline-flex;align-items:center;gap:7px;
  padding:7px 14px;margin-right:12px;border:0;border-radius:7px;font-size:.82rem;font-weight:600;
  font-family:inherit;color:#fff;background:linear-gradient(135deg,var(--plum),var(--magenta));
  cursor:pointer}
input[type=file]::file-selector-button:hover{opacity:.9}
input[type=file].bad{border-color:#e4879f}

.pw-meter{margin-top:8px}
.pw-meter-bar{height:5px;border-radius:999px;background:var(--line);overflow:hidden}
.pw-meter-fill{height:100%;width:0;border-radius:999px;background:var(--line);transition:width .15s ease,background-color .15s ease}
.pw-meter-fill.weak{background:#c23b4a}
.pw-meter-fill.fair{background:#d68a1f}
.pw-meter-fill.good{background:#3d8fc4}
.pw-meter-fill.strong{background:var(--ok-fg)}
.pw-meter-label{font-size:.75rem;color:var(--muted);margin-top:3px;min-height:1.1em}
.pw-rules{list-style:none;margin:8px 0 0;padding:0;font-size:.79rem;color:var(--muted)}
.pw-rules li{position:relative;padding-left:16px;line-height:1.6}
.pw-rules li::before{content:"\2022";position:absolute;left:2px;color:var(--muted)}
.pw-rules li.met{color:var(--ok-fg)}
.pw-rules li.met::before{content:"\2713"}
.pw-rules li.pw-rule-static::before{content:"\2022"}

.flash{padding:12px 16px;border-radius:8px;font-size:.88rem;margin-bottom:20px}
.flash.ok{background:var(--ok-bg);color:var(--ok-fg);border:1px solid #b8e3c9}
.flash.no{background:var(--err-bg);color:var(--err-fg);border:1px solid #f3c6d2}

#toast-container{position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:9999;display:flex;flex-direction:column;align-items:center;gap:10px;
  max-width:380px}
.toast{display:flex;align-items:flex-start;gap:10px;padding:14px 16px;border-radius:10px;font-size:.88rem;
  font-weight:500;box-shadow:0 10px 30px rgba(0,0,0,.15);animation:toast-in .25s ease-out;line-height:1.4}
.toast span{flex:1}
.toast button{background:none;border:none;font-size:1.1rem;line-height:1;cursor:pointer;color:inherit;
  opacity:.6;padding:0 2px}
.toast button:hover{opacity:1}
.toast.ok{background:var(--ok-bg);color:var(--ok-fg);border:1px solid #b8e3c9;border-left:4px solid var(--ok-fg)}
.toast.no{background:var(--err-bg);color:var(--err-fg);border:1px solid #f3c6d2;border-left:4px solid var(--err-fg)}
.toast.out{animation:toast-out .2s ease-in forwards}
@keyframes toast-in{from{opacity:0;transform:translateY(-12px)}to{opacity:1;transform:translateY(0)}}
@keyframes toast-out{from{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-12px)}}
@media (max-width:480px){
  #toast-container{left:12px;right:12px;top:12px;max-width:none;transform:none}
}

dialog.modal{border:none;border-radius:var(--radius);padding:24px;width:min(420px,90vw);
  box-shadow:0 20px 60px rgba(0,0,0,.25);margin:auto;position:fixed;inset:0}
dialog.modal::backdrop{background:rgba(31,10,26,.45)}
dialog.modal h2{font-size:1rem;color:var(--plum);margin-bottom:4px}
dialog.modal .hint{color:var(--muted);font-size:.85rem;margin-bottom:18px}
dialog.modal .actions, #next-appt-form .actions{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}

#admin-delete-modal{width:min(480px,90vw)}
.admin-delete-group{margin-bottom:12px}
.admin-delete-group-title{font-size:.8rem;font-weight:600;color:var(--text);margin-bottom:4px}
.admin-delete-group ul{margin:0;padding-left:18px;font-size:.85rem;color:var(--muted)}
.admin-delete-group.will-unlink .admin-delete-group-title{color:var(--muted)}
.admin-delete-warning{color:var(--crimson,#b5164f)}

table{width:100%;border-collapse:collapse}
thead tr{background:var(--wash)}
th{text-align:left;font-size:.72rem;letter-spacing:.09em;text-transform:uppercase;color:var(--muted);
  padding:10px 12px;border-bottom:1.5px solid var(--line);font-weight:700;white-space:nowrap}
td{padding:12px;border-bottom:1px solid var(--line);font-size:.9rem;vertical-align:middle}
tr:last-child td{border-bottom:0}
/* List views (any table inside .tbl-wrap) read as scannable data grids, so
   every column centers by default — left/right alignment is reserved for
   .lines-style editable line-item tables, which keep their own rules. */
.tbl-wrap th{text-align:center}
.tbl-wrap td{text-align:center}
/* Wireframe 1a: alternating row background (odd white, even washed) so a
   wide row of numbers stays scannable without relying on hover alone. */
tbody tr:nth-child(even){background:var(--wash)}
tbody tr:hover{background:var(--wash)}
/* The finance period tables sit directly under a section toolbar, so their
   header cells need more breathing room than the default. */
.fin-period-table thead th{padding:14px 12px 16px}
.tbl-wrap{overflow-x:auto}

/* Below 640px a wide table (e.g. the 12-column Payments list, the
   8-column Encounters/Visits list) is unusable even inside .tbl-wrap's
   horizontal scroll — each row becomes a bordered card instead. Applies
   automatically to any table inside .tbl-wrap; add data-th="Column Name"
   to a <td> so its value gets a label in the stacked layout (falls back to
   an unlabelled line when omitted). Every column stays reachable at every
   width (WI-558) — a column visible on desktop must be visible on phone
   too, so nothing hides a column on mobile any more (the old
   class="hide-mobile" escape hatch was removed from every view that used
   it). The full table is unchanged at 640px and up. */
@media(max-width:640px){
  .tbl-wrap{overflow-x:visible}
  .tbl-wrap table,.tbl-wrap thead,.tbl-wrap tbody,.tbl-wrap tr,.tbl-wrap td,.tbl-wrap th{display:block;width:100%}
  .tbl-wrap thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}
  .tbl-wrap tbody tr{border:1px solid var(--line);border-radius:10px;margin-bottom:10px;padding:12px 14px;background:#fff}
  .tbl-wrap tbody tr:hover{background:#fff}
  .tbl-wrap td{border-bottom:0!important;padding:6px 0;display:flex;flex-wrap:wrap;justify-content:space-between;
    align-items:flex-start;gap:12px;text-align:left!important;white-space:normal!important}
  /* An actions cell (no data-th label, just a row of buttons/forms — e.g.
     Admin > Users' Edit/Resend Invite/Deactivate/Log in as/Delete) has no
     space-between pair to keep apart, so let its buttons wrap onto their
     own lines and fill the card width instead of overflowing past the
     screen edge on a single unwrapped row. */
  .tbl-wrap td:not([data-th]){justify-content:flex-start}
  .tbl-wrap td:not([data-th]) .inline{display:inline-flex}
  .tbl-wrap td:empty{display:none}
  .tbl-wrap td[data-th]::before{content:attr(data-th);font-weight:700;color:var(--muted);font-size:.68rem;
    text-transform:uppercase;letter-spacing:.05em;flex:0 0 auto;padding-top:2px}

  /* The status accent (tr.row-* td:first-child{box-shadow:inset 3px...})
     is an inset box-shadow on the first <td> only — on the desktop table
     every row is one flat strip so that reads as a full-height left bar.
     Once .tbl-wrap turns each row into a stacked card, "first <td>" is
     just the first field's line, so the bar shrinks to a short segment
     next to that one label. Move it to the <tr> (the whole card) instead,
     and cancel it back off the child cell so it isn't drawn twice. */
  .tbl-wrap tbody tr[class*="row-"] td:first-child{box-shadow:none}
  .tbl-wrap tbody tr.row-new{box-shadow:inset 3px 0 0 #e0a33a}
  .tbl-wrap tbody tr.row-submitted{box-shadow:inset 3px 0 0 #3b6fd4}
  .tbl-wrap tbody tr.row-pending{box-shadow:inset 3px 0 0 #d97a2b}
  .tbl-wrap tbody tr.row-paid{box-shadow:inset 3px 0 0 #2e9e5b}
  .tbl-wrap tbody tr.row-draft{box-shadow:inset 3px 0 0 #e0a33a}
  .tbl-wrap tbody tr.row-approved{box-shadow:inset 3px 0 0 #1a90b8}
  .tbl-wrap tbody tr.row-rejected{box-shadow:inset 3px 0 0 #c23a5c}
}

th.sortable{padding:0}
th.sortable a{display:flex;align-items:center;justify-content:center;gap:5px;padding:10px 12px;color:inherit;text-decoration:none}
th.sortable a:hover{color:var(--plum)}
th.sortable .sort-arrow{width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;
  border-bottom:5px solid var(--line);opacity:.6}
th.sortable.sort-asc .sort-arrow{border-bottom:5px solid var(--magenta);border-top:none;opacity:1}
th.sortable.sort-desc .sort-arrow{border-top:5px solid var(--magenta);border-bottom:none;opacity:1}
th.sortable.sort-asc,th.sortable.sort-desc{color:var(--plum)}

.pill{display:inline-block;padding:4px 10px;border-radius:999px;font-size:.72rem;font-weight:700;
  background:#f3e6ee;color:var(--plum);border:1px solid #dcc6d7;margin:2px 3px 2px 0;white-space:nowrap}
.pill.on{background:var(--ok-bg);color:var(--ok-fg);border-color:#bfe3ce}
.pill.off{background:#f1eef0;color:var(--muted);border-color:var(--line)}
.pill.wait{background:#fff4e0;color:#8a5a00;border-color:#f0d9a8}
.mono{font-family:ui-monospace,"SF Mono",Menlo,monospace;font-size:.84rem;letter-spacing:.04em}

/* Pill-shaped tabs — active tab filled plum with white text, inactive tabs
   tinted plum-10% (docs/TIVA Component Standard.html #5). Anchors, not
   buttons, so a tab click is a normal navigable GET (?tab=) — no JS. */
.tabs{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px}
.tabs a{display:inline-flex;align-items:center;min-height:36px;padding:8px 18px;border-radius:var(--radius-pill);
  font-weight:600;font-size:.86rem;text-decoration:none;background:rgba(74,14,63,.1);color:var(--plum)}
.tabs a:hover{background:rgba(74,14,63,.18)}
.tabs a.on{background:var(--plum);color:#fff}

.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:16px;margin-bottom:22px}
.stat{background:#fff;border:1px solid var(--line);border-radius:var(--radius);padding:20px;text-decoration:none;display:block}
.stat .v{font-size:1.9rem;font-weight:800;color:var(--plum);line-height:1.1}
.stat .l{font-size:.82rem;color:var(--muted);margin-top:4px}
/* A stat tile that carries several counts instead of one headline number:
   the label sits on top and each row is a label/value pair, with the Total
   row ruled off below the rest. */
.stat-split{text-align:left}
.stat-split .l{margin:0 0 10px;font-weight:700;color:var(--plum);text-transform:none}
.stat-rows{margin:0}
.stat-row{display:flex;align-items:baseline;gap:6px;padding:3px 0}
.stat-row dt{font-size:.85rem;font-weight:700;color:var(--ink);margin:0}
.stat-row dd{margin:0;font-size:1.05rem;font-weight:700;color:var(--plum);font-variant-numeric:tabular-nums}
.stat-row.is-total{border-top:1px solid var(--line);margin-top:6px;padding-top:7px}
.stat-row.is-total dd{font-size:1.35rem;font-weight:800}

/* ---- Dashboard stat section: two tiers -------------------------------
   Tier 1 is KPI tiles, one headline number each. Tier 2 is breakdown cards,
   a row per component figure with the total ruled off at the card's foot.
   These are separate from the older .stat/.stat-split set above, which is
   still used by generic tiles elsewhere.

   Both grids are explicit 4-up rather than auto-fit: the tiers must line up
   column-for-column with each other, which auto-fit cannot guarantee once
   the two rows hold different numbers of cards. */
.kpi-grid,.bdown-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:14px}
/* Three-up variant, for a KPI row with three tiles rather than four. It is a
   class and not an inline style on purpose (WI-509): an inline
   grid-template-columns outranks every stylesheet rule including the
   max-width media queries below, so the mobile collapse silently never
   applied and the page scrolled sideways on a phone. Declared above those
   queries so they still win at narrow widths. */
.kpi-grid-3{grid-template-columns:repeat(3,1fr)}
.bdown-grid{margin-bottom:22px}

.kpi{background:#fff;border:1px solid var(--line);border-radius:12px;
  box-shadow:0 1px 3px rgba(36,16,33,.08);padding:16px 18px;text-decoration:none;display:block}
.kpi-label{font-size:10.5px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--muted)}
/* Value and its secondary note share one baseline, so the big number and the
   small note read as a single line rather than two stacked blocks. */
.kpi-figure{display:flex;align-items:baseline;gap:8px;margin-top:8px;flex-wrap:wrap}
.kpi-value{font-size:32px;font-weight:800;letter-spacing:-.02em;line-height:1;color:var(--plum);
  font-variant-numeric:tabular-nums;white-space:nowrap}
.kpi-value.is-err{color:var(--err-fg)}
.kpi-value.is-ok{color:var(--ok-fg)}
.kpi-secondary{font-size:12.5px;font-weight:500;color:var(--muted)}
.kpi-secondary.is-ok{color:#1a6b3c}

/* Every card is a column so its rows list can take the slack (flex:1) and
   push the total to the card's foot — that is what makes the totals share a
   baseline across the row no matter how many rows each card holds. */
.bdown{background:#fff;border:1px solid var(--line);border-radius:12px;
  box-shadow:0 1px 3px rgba(36,16,33,.08);padding:16px 18px;text-decoration:none;
  display:flex;flex-direction:column}
.bdown-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:10px}
.bdown-title{font-size:12.5px;font-weight:700;color:var(--ink)}
.bdown-badge{background:var(--err-bg);color:var(--err-fg);border-color:var(--err-bg)}
.bdown-rows{margin:0;flex:1}
/* Label and value are two elements in a space-between row, never one text
   run — a long label ("Appointment") then wraps on its own instead of
   dragging its figure onto the next line. */
.bdown-row,.bdown-total{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
.bdown-row{padding:3px 0}
.bdown-row dt{margin:0;font-size:12.5px;font-weight:500;color:var(--muted);min-width:0;overflow-wrap:anywhere}
.bdown-row dd{margin:0;font-size:14px;font-weight:700;color:var(--ink);font-variant-numeric:tabular-nums}
/* Zero reads as absence: muted, so real activity is what the eye finds. */
.bdown-row dd.is-zero{color:#a08a9a}
.bdown-total{border-top:1px solid var(--line);margin-top:6px;padding-top:8px}
.bdown-total dt{margin:0;font-size:12px;font-weight:700;color:var(--ink)}
.bdown-total dd{margin:0;font-size:15px;font-weight:800;color:var(--plum);font-variant-numeric:tabular-nums}
.bdown-total dd.is-err{color:var(--err-fg)}
/* Currency never wraps mid-pair and always uses tabular figures, so columns
   of money line up digit for digit. */
.bdown-row dd.is-money,.bdown-total dd.is-money{white-space:nowrap;font-variant-numeric:tabular-nums}

/* Both tiers step down together so the two grids stay column-aligned with
   each other at every width. The label-left/value-right row structure is
   kept throughout — stacking a label over its figure reads worse on a narrow
   screen, not better, and is what caused the wrap this section replaced. */
@media(max-width:1100px){
  .kpi-grid,.bdown-grid{grid-template-columns:repeat(2,1fr)}
}
@media(max-width:768px){
  .kpi-grid,.bdown-grid{grid-template-columns:1fr;gap:12px}
  .kpi-value{font-size:28px}
}

/* Heading row of a dashboard attention list: title and blurb on the left, the
   list's "View All" action top-right, per the primary-action convention. The
   card itself is padding:0 so the table can run edge to edge, so the padding
   lives here. Wraps on a phone rather than squeezing the button. */
.dash-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;
  flex-wrap:wrap;padding:18px 20px;border-bottom:1px solid var(--line)}
.dash-head h2{font-size:1rem;color:var(--plum)}

.toolbar{display:flex;gap:12px;align-items:center;justify-content:space-between;margin-bottom:20px;flex-wrap:wrap}
/* "Primary page actions live top-right." justify-content only places items on a
   line that has room for both — a wide filter form (Visit/Check-In, Expenses,
   Employees) pushes the sort+action group onto a second line, where it is the
   only item and space-between leaves it hard against the LEFT edge, ~600px
   short of the right. margin-left:auto pins it right on its own line too. */
.toolbar > [style*="display:flex;gap:10px;align-items:center"],
.toolbar > a.btn:last-child,
.toolbar > button.btn:last-child{margin-left:auto}
/* A long unbreakable value — an email address, a reCAPTCHA key, a wa_id —
   sets its flex/grid item's min-content width, and a flex or grid item cannot
   shrink below that by default. On a 375px phone that pushed the whole page
   sideways (System Setup's key fields, My Profile's name/email block). Letting
   these values break anywhere, and grid tracks shrink, keeps the page within
   the viewport without touching desktop layout. */
.row > *{min-width:0}
.card .mono,.card p.hint,.card h2{overflow-wrap:anywhere}

.filters{display:flex;gap:10px;flex-wrap:wrap}
.filters input,.filters select{width:auto;min-width:170px}
/* Filter/Clear are inline toolbar controls sitting next to the compact
   Sort By / Reset pair — sized down to match them instead of the full .btn
   (which is meant for primary page actions like "Add New"/"Save"). */
.filters .btn{padding:7px 14px;font-size:.8rem;min-height:36px}

/* Multi-select filter (<x-checkbox-filter>): a <details> that reads as one
   more control in the bar until opened. The panel is absolutely positioned so
   opening it overlays the list rather than pushing it down the page — the row
   of filters must not jump as one is opened. */
.chk-filter{position:relative;min-width:170px}
.chk-filter > summary{list-style:none;cursor:pointer;display:flex;align-items:center;
  justify-content:space-between;gap:8px;min-height:44px;padding:11px 13px;
  border:1.5px solid var(--line);border-radius:var(--radius-input);background:#fff;
  font-size:.92rem;color:var(--ink);white-space:nowrap}
.chk-filter > summary::-webkit-details-marker{display:none}
.chk-filter > summary::after{content:"";flex:none;width:7px;height:7px;
  border-right:1.5px solid var(--muted);border-bottom:1.5px solid var(--muted);
  transform:translateY(-2px) rotate(45deg)}
.chk-filter[open] > summary{border-color:var(--plum)}
.chk-filter[open] > summary::after{transform:translateY(2px) rotate(-135deg)}
.chk-filter-panel{position:absolute;z-index:20;top:calc(100% + 4px);left:0;min-width:100%;
  max-width:320px;max-height:280px;overflow-y:auto;background:#fff;border:1px solid var(--line);
  border-radius:var(--radius-input);box-shadow:0 8px 24px rgba(74,14,63,.14);padding:8px}
.chk-filter-panel label{display:flex;align-items:center;gap:8px;padding:6px 8px;
  font-weight:400;font-size:.88rem;border-radius:6px;cursor:pointer;white-space:normal}
.chk-filter-panel label:hover{background:var(--wash)}
/* The generic .filters rule forces a 170px min-width on every input, which
   would blow each tickbox up into a giant square. */
.chk-filter-panel input[type=checkbox]{flex:none;width:auto;min-width:0;margin:0}
@media(max-width:760px){
  .chk-filter{min-width:130px}
  .chk-filter-panel{max-width:none;right:0}
}

.matrix th:first-child{width:34%}
.matrix td:first-child{font-weight:600;color:var(--plum)}
.matrix td.chk{text-align:center}
.matrix .desc{display:block;font-weight:400;color:var(--muted);font-size:.78rem;margin-top:2px}
.matrix .grp-row{background:var(--bg,#f6f4f8);font-size:.68rem;letter-spacing:.1em;text-transform:uppercase;
  color:var(--muted);font-weight:700;padding:8px 16px}

.tabbar{display:flex;gap:2px;padding:6px;border-bottom:1px solid var(--line);overflow-x:auto}
.tabbtn{background:none;border:none;border-radius:8px;padding:9px 14px;white-space:nowrap;
  font-size:.87rem;font-weight:600;color:var(--muted);cursor:pointer}
.tabbtn:hover{color:var(--plum);background:var(--wash)}
.tabbtn.on{color:#fff;background:var(--plum)}
.tabbtn .tabcount{display:inline-block;color:var(--muted);font-size:.85em;font-weight:600;margin-left:3px}
.tabbtn.on .tabcount{color:rgba(255,255,255,.7)}
input[type=checkbox]{width:17px;height:17px;accent-color:var(--magenta);cursor:pointer}
input[type=radio]{width:17px;height:17px;accent-color:var(--magenta);cursor:pointer;flex:none}
.rolechk{display:flex;align-items:center;gap:9px;padding:9px 12px;border:1.5px solid var(--line);
  border-radius:8px;cursor:pointer;font-size:.88rem;font-weight:500}
.rolechk:has(input:checked){border-color:var(--magenta);background:#fdf3f7}
.rolegrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px}
.radio-group{display:flex;flex-direction:column;gap:8px;max-height:260px;overflow-y:auto}
.radio-option{display:flex;align-items:center;gap:9px;padding:9px 12px;border:1.5px solid var(--line);
  border-radius:8px;cursor:pointer;font-size:.88rem;font-weight:500}
.radio-option:has(input:checked){border-color:var(--magenta);background:#fdf3f7}

/* ---------- patient shell (layouts/patient.blade.php) ---------- */
/* Structurally the SAME shell as the staff portal — .patient-shell also
   carries the .shell class, so it gets the identical responsive sidebar
   (desktop) / hamburger-drawer (<900px) mechanics and .shell.impersonating
   rules for free (see .shell / .side / .nav-toggle above and the
   .dev-ribbon overrides below). This block only holds what's genuinely
   different: cards stack with a consistent gap. The content column fills
   the same width as the admin/employee .content column (WI-281 — a fixed
   760px cap here made the patient portal read narrower than the rest of
   the app and squeezed wide tables like Your Visits). */
.patient-content{display:flex;flex-direction:column;gap:14px}

/* ---------- non-production environment ribbon ---------- */
.dev-ribbon{
  position:fixed;top:0;left:0;right:0;z-index:210;height:26px;
  background:#b91c1c;color:#fff;display:flex;align-items:center;justify-content:center;
  font-size:.78rem;font-weight:400;letter-spacing:.12em;box-shadow:0 2px 10px rgba(0,0,0,.2)
}
/* Push everything below it down, including the impersonation bar and app shell. */
.dev-ribbon ~ .imp-bar{top:26px}
.dev-ribbon ~ .shell.impersonating{padding-top:72px}
.dev-ribbon ~ .shell.impersonating .side{height:calc(100vh - 72px);top:72px}
.dev-ribbon ~ .shell:not(.impersonating){padding-top:26px}
.dev-ribbon ~ .shell:not(.impersonating) .side{height:calc(100vh - 26px);top:26px}
@media(max-width:900px){
  .dev-ribbon ~ .shell.impersonating{padding-top:26px}
  .dev-ribbon ~ .shell:not(.impersonating){padding-top:26px}
}

/* ---------- impersonation ---------- */
.imp-bar{
  position:fixed;top:0;left:0;right:0;z-index:200;height:46px;
  background:#8a5a00;color:#fff;display:flex;align-items:center;justify-content:center;
  gap:18px;font-size:.88rem;padding:0 16px;box-shadow:0 2px 10px rgba(0,0,0,.2)
}
.imp-bar strong{font-weight:800}
.imp-bar button{
  background:#fff;color:#8a5a00;border:0;border-radius:6px;padding:6px 14px;
  font-weight:700;font-size:.82rem;cursor:pointer;font-family:inherit;white-space:nowrap
}
.imp-bar button:hover{opacity:.9}
/* Push the whole app down so the bar never covers the sidebar or header. */
.shell.impersonating{padding-top:46px}
.shell.impersonating .side{height:calc(100vh - 46px);top:46px}
@media(max-width:900px){
  .imp-bar{height:auto;padding:8px 12px;flex-direction:column;gap:8px;position:static}
  .shell.impersonating{padding-top:0}
}

/* ---------- invoicing ---------- */
td.num,th.num{font-variant-numeric:tabular-nums}
/* Line-item editor tables (table.lines) are the one place numeric columns
   keep the accounting right-align — everything under .tbl-wrap centers
   instead, per the rule above. */
table.lines td.num,table.lines th.num{text-align:right}
input.num{text-align:right}
/* Secondary line inside a table cell — used where two related figures were
   merged into one column to stop the table scrolling sideways (Inventory
   Cost Price). Inherits the cell's alignment on purpose. */
td .cell-sub{display:block;margin-top:2px;font-size:.72rem;color:var(--muted);
  font-variant-numeric:tabular-nums;white-space:nowrap}
/* Opt-in wrapping header. th is nowrap by default so short labels never break
   mid-word; a long two-word label (Quantity Available) instead forces the whole
   column wide. This lets one header wrap without changing every table. */
th.wrap{white-space:normal}
.stat .v{font-variant-numeric:tabular-nums}
textarea{width:100%;padding:10px 12px;border:1.5px solid var(--line);border-radius:8px;
  font-size:.92rem;font-family:inherit;color:var(--ink);background:#fff;resize:vertical}
textarea:focus{outline:0;border-color:var(--plum);box-shadow:0 0 0 2px rgba(194,24,91,.35)}

/* Marketing campaign Email WYSIWYG editor (WI-402) */
.wysiwyg-toolbar{display:flex;gap:6px;margin-bottom:8px;flex-wrap:wrap}
.wysiwyg-editor{width:100%;min-height:160px;padding:10px 12px;border:1.5px solid var(--line);
  border-radius:8px 8px 0 0;border-bottom:0;font-size:.92rem;color:var(--ink);background:#fff;overflow-y:auto}
.wysiwyg-editor:focus{outline:0;border-color:var(--plum);box-shadow:0 0 0 2px rgba(194,24,91,.35)}
.wysiwyg-editor img{max-width:100%;border-radius:4px}

/* Label paired with a small inline control (e.g. the Emoji trigger) — keeps
   the button on the label's baseline instead of pushing the field down. */
.lbl-row{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}
.lbl-row > label{margin-bottom:0}

/* Emoji picker (WI-434) — the panel is appended to <body> and positioned in
   page coordinates, so it must not be clipped by the form card it sits in. */
.emoji-panel{display:none;position:absolute;z-index:120;width:min(320px,calc(100vw - 16px));
  max-height:280px;overflow-y:auto;padding:10px;background:#fff;border:1.5px solid var(--line);
  border-radius:10px;box-shadow:0 10px 28px rgba(58,10,49,.22)}
.emoji-group-title{font-size:.7rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  color:var(--plum);margin:6px 2px 4px}
.emoji-grid{display:grid;grid-template-columns:repeat(10,1fr);gap:2px}
.emoji-btn{border:0;background:none;padding:4px 0;font-size:1.15rem;line-height:1.2;cursor:pointer;
  border-radius:6px}
.emoji-btn:hover{background:var(--wash)}
@media(max-width:520px){
  .emoji-grid{grid-template-columns:repeat(8,1fr)}
  .emoji-btn{font-size:1.3rem;padding:6px 0}
}

/* Campaign audience picker (WI-592): a §4 list view built by hand inside
   the campaign form (see _form.blade.php). Only what the shared list-view
   rules don't already cover lives here. */
#aud-list .list-chips > .filter-label{align-self:center}
#aud-list .list-chips > input[type=number]{max-width:120px}
#aud-list .aud-toggle{cursor:pointer;font-weight:500}
#aud-list .aud-toggle input{margin:0;accent-color:var(--magenta)}
#aud-list td:first-child input{margin:0;accent-color:var(--magenta);width:18px;height:18px}
#aud-list .list-more .row-menu-panel .btn{width:100%;justify-content:flex-start}
#aud-list #aud-empty{padding:40px 16px}
@media(max-width:640px){
  #aud-list .list-chips > .filter-label{width:100%}
  #aud-list .list-chips > input[type=number]{max-width:none}
  #aud-list tbody td:first-child{padding:0 8px 0 0}
}

/* Status is carried by colour AND text — colour alone would exclude anyone who
   cannot distinguish these hues. */
.pill.status-new{background:#fff4e0;color:#8a5a00;border-color:#f0d9a8}
.pill.status-submitted{background:#e7eefc;color:#1c3f8f;border-color:#c7d8f7}
.pill.status-pending{background:#fde3cf;color:#9a4b00;border-color:#f5c39a}
.pill.status-paid{background:var(--ok-bg);color:var(--ok-fg);border-color:#bfe3ce}

tr.row-new td:first-child{box-shadow:inset 3px 0 0 #e0a33a}
tr.row-submitted td:first-child{box-shadow:inset 3px 0 0 #3b6fd4}
tr.row-pending td:first-child{box-shadow:inset 3px 0 0 #d97a2b}
tr.row-paid td:first-child{box-shadow:inset 3px 0 0 #2e9e5b}

/* ---------- expenses ---------- */
.pill.status-draft{background:#f2e9f0;color:var(--plum);border-color:#dcc6d7}
.pill.status-approved{background:#e6f2f7;color:#0f5f7a;border-color:#bcdce8}
.pill.status-rejected{background:var(--err-bg);color:var(--err-fg);border-color:#f3c6d2}

/* ---------- care plans ---------- */
.pill.status-active{background:var(--ok-bg);color:var(--ok-fg);border-color:#bfe3ce}
.pill.status-expired{background:var(--err-bg);color:var(--err-fg);border-color:#f3c6d2}
.pill.status-consumed{background:#fff4e0;color:#8a5a00;border-color:#f0d9a8}

tr.row-draft td:first-child{box-shadow:inset 3px 0 0 #e0a33a}
tr.row-approved td:first-child{box-shadow:inset 3px 0 0 #1a90b8}

/* ---------- appointments ---------- */
.pill.status-scheduled{background:#fff4e0;color:#8a5a00;border-color:#f0d9a8}
.pill.status-completed{background:var(--ok-bg);color:var(--ok-fg);border-color:#bfe3ce}
.pill.status-cancelled{background:#f1eef0;color:var(--muted);border-color:var(--line)}
.pill.status-no_show{background:var(--err-bg);color:var(--err-fg);border-color:#f3c6d2}

#appointments-calendar .fc-bg-event.doctor-block, #next-appt-calendar .fc-bg-event.doctor-block{background:repeating-linear-gradient(45deg,#e5e5e5,#e5e5e5 6px,#f2f2f2 6px,#f2f2f2 12px);opacity:1}

/* Event box itself stays a plain frame — .fc-appt-card owns all the
   visual styling, so there's only one layer of border/background instead
   of FullCalendar's own event chrome fighting with ours. */
#appointments-calendar .fc-timegrid-event, #next-appt-calendar .fc-timegrid-event{background:transparent;border:0;padding:0;box-shadow:none;pointer-events:none;overflow:hidden}
#appointments-calendar .fc-event-time, #next-appt-calendar .fc-event-time{color:var(--muted);font-size:.72rem;font-weight:600;padding:2px 4px 0}
#appointments-calendar .fc-timegrid-event .fc-event-main, #next-appt-calendar .fc-timegrid-event .fc-event-main{padding:0;height:100%}

/* Card carries no text — pure color coding, full detail on click instead
   (setupCardInfoPopover in appointments-calendar.js): the left bar is the
   doctor's color, the fill is the status color. Card fills its harness
   fully (no click-through margin — there's no text overlap risk left to
   avoid, and a bare sliver of page background reading as a stray "white
   bar" was exactly the complaint the old 90%-width margin caused). */
#appointments-calendar .fc-appt-card, #next-appt-calendar .fc-appt-card{
  /* --appt-doctor-color defaults here so a doctor with no assigned color
     (or a color that fails to reach this event) doesn't invalidate the
     border declaration below — an invalid custom property resets it to
     its initial value, rendering as a blank white box with no visible
     border instead of a plainly-neutral card. */
  --appt-doctor-color:var(--plum);
  border-left:4px solid var(--appt-doctor-color);
  background:#fce4ae; /* scheduled fill (default) — deeper than the pill's
    #fff4e0 since that reads too close to white/the pink today-column tint
    at card size */
  border-radius:5px;box-sizing:border-box;padding:2px 6px;
  width:100%;height:100%;pointer-events:auto;cursor:pointer;
  display:flex;align-items:center;overflow:hidden;position:relative;
}
/* Bottom bar flags appointment Type (Phone/Home/Clinic) — absolutely
   positioned so it doesn't disturb the centered patient-name row above,
   same treatment as the doctor-color left bar but along the bottom edge. */
#appointments-calendar .fc-appt-type-bar, #next-appt-calendar .fc-appt-type-bar{
  position:absolute;left:0;right:0;bottom:0;height:3px;
  background:var(--appt-type-color);
}
/* Patient name — single line, free to clip with an ellipsis rather than
   wrap or grow the card, since a narrow overlapping-slot card is expected
   to cut it off (full detail is one click away via the info popover). */
#appointments-calendar .fc-appt-name, #next-appt-calendar .fc-appt-name{
  font-size:.74rem;font-weight:600;color:var(--ink);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
#appointments-calendar .fc-appt-card.fc-appt-status-completed, #next-appt-calendar .fc-appt-card.fc-appt-status-completed{background:#bfe9d2}
/* Left bar always stays the doctor's color, even for cancelled/no-show —
   only the fill changes to flag the status, so the bar still answers
   "whose appointment was this" regardless of what happened to it. */
/* Darker/more saturated than --err-bg (used elsewhere for form-error
   backgrounds) — that pale a red reads too close to the pink "today" column
   tint to tell a No-Show card apart from its own background at a glance. */
#appointments-calendar .fc-appt-card.fc-appt-status-no_show, #next-appt-calendar .fc-appt-card.fc-appt-status-no_show{background:#f6c3ce}
/* No opacity here (was .85) — opacity dims the whole box including the
   left bar, so the doctor's color visually reads as washed-out/different
   even though the underlying --appt-doctor-color value is unchanged. The
   muted look comes from the grey fill alone now. */
#appointments-calendar .fc-appt-card.fc-appt-status-cancelled, #next-appt-calendar .fc-appt-card.fc-appt-status-cancelled{background:#dcd3d9}
/* Marks which card the open info popover (setupCardInfoPopover) belongs to
   — with no text left on the card itself, there's otherwise no visual link
   between the popover and the card it describes once more than one card is
   on screen. */
#appointments-calendar .fc-appt-card.fc-appt-card--selected, #next-appt-calendar .fc-appt-card.fc-appt-card--selected{
  /* box-shadow (inset), not outline — the harness wrapper this card fills
     has overflow:hidden, which clips an outline drawn outside the box. */
  box-shadow:0 0 0 2px var(--plum) inset}

/* Taller rows (wireframe 2a's ~66px bands) than FullCalendar's cramped
   default, and a rounded, clipped outer frame instead of the grid's raw
   square edges. */
#appointments-calendar .fc-timegrid-slot{height:2.6em}
#appointments-calendar{border:1px solid var(--line);border-radius:8px;overflow:hidden}

/* Weekly closed days (Sunday by default) and any out-of-hours band. Rendered
   with the SAME hatch as a holiday-block closure rather than a flat tint —
   both mean "the clinic is shut", so they should read identically; the only
   difference is that a holiday also carries a label. FullCalendar's stock
   .fc-non-business (rgba(215,215,215,.3)) was invisible on this near-white
   grid, and a flat grey still read as merely inactive rather than closed. */
#appointments-calendar .fc-non-business, #next-appt-calendar .fc-non-business{
  background:repeating-linear-gradient(45deg,#f6e6ef,#f6e6ef 7px,#efd6e4 7px,#efd6e4 14px)}

/* FullCalendar's timegrid renders each slot row as a plain <tr> inside a
   <tbody>, so the global list-table zebra rule (tbody tr:nth-child(even),
   further up this file) was leaking alternating row shading into the
   calendar grid — wireframe 2a has no such striping. Same specificity
   shape (an #id-scoped equivalent) beats the unscoped rule and resets it. */
#appointments-calendar tbody tr:nth-child(even), #next-appt-calendar tbody tr:nth-child(even){background:transparent}

/* Same leak, on hover instead of stripe: FullCalendar's timegrid renders
   one <tr> per time SLOT spanning every day column, so the global
   `tbody tr:hover` list-table rule (further up this file) highlights the
   whole row — every day at that time — pink instead of just the cell under
   the pointer. Only the dashed "+" hint box (setupSlotHoverHint in
   appointments-calendar.js) should mark the hovered slot. */
#appointments-calendar tbody tr:hover, #next-appt-calendar tbody tr:hover{background:transparent}

/* Wireframe 2a's empty-slot hover hint — one shared box that JS
   (setupSlotHoverHint in appointments-calendar.js) moves to whichever empty
   spot the pointer is over, sized to the default appointment duration,
   since FullCalendar has no per-(day,slot) DOM node a plain :hover rule
   could target. pointer-events:none so the hint never blocks the click
   it's illustrating. */
.cal-slot-hint{position:absolute;left:2px;right:2px;box-sizing:border-box;
  border:2px dashed var(--crimson);background:transparent;border-radius:4px;
  display:flex;align-items:center;justify-content:center;color:var(--crimson);
  font-weight:700;font-size:1rem;overflow:hidden;white-space:nowrap;
  pointer-events:none;z-index:2}
/* Same specificity-tie bug as .cal-card-popover[hidden] above: this class's
   own display:flex ties with the browser's [hidden]{display:none} and, as
   the later-declared rule, silently wins — meaning hint.hidden=true (set
   whenever the pointer leaves the grid or hovers an occupied slot) was
   never actually hiding it. The hint just stayed visually planted at
   wherever it was last shown instead of disappearing, which is almost
   certainly what read as it "appearing in the wrong place." */
.cal-slot-hint[hidden]{display:none!important}

/* Click-to-open info popover for appointment/block cards
   (setupCardInfoPopover) — not hover, so it works identically on touch
   devices. The card itself carries no text now (pure color coding), so this
   stacked box is the only way to see Doctor/Time/Patient/Status; doctor-
   block cards keep a plain one-liner. position:absolute (document-relative,
   set in JS via scrollX/scrollY) so it scrolls with the page instead of
   staying pinned to the viewport once the card underneath moves. */
.cal-card-popover{position:absolute;z-index:50;
  --popover-doctor-color:var(--plum);
  border-left:4px solid var(--popover-doctor-color);
  background:#fff;color:var(--ink);
  padding:10px 30px 10px 12px;border-radius:8px;font-size:.82rem;
  white-space:nowrap;box-shadow:0 6px 18px rgba(0,0,0,.22)}
/* Status reads through text color instead of the fill — same colors as the
   calendar's own Status legend, not the card's pale status-fill treatment. */
.cal-card-popover-status{font-weight:600}
.cal-card-popover-status-scheduled .cal-card-popover-status{color:#8a5a00}
.cal-card-popover-status-completed .cal-card-popover-status{color:var(--ok-fg)}
.cal-card-popover-status-cancelled .cal-card-popover-status{color:var(--muted)}
.cal-card-popover-status-no_show .cal-card-popover-status{color:var(--err-fg)}
.cal-card-popover--stacked{display:flex;flex-direction:column;gap:5px;min-width:150px}
/* [hidden] and .cal-card-popover--stacked{display:flex} tie on specificity
   (one attribute selector vs one class selector), so the later-declared
   rule wins the cascade — meaning our own --stacked rule was silently
   overriding the browser's built-in [hidden]{display:none} and keeping the
   popover visually open no matter what pop.hidden got set to. This forces
   [hidden] to always win regardless of what other display rules apply. */
.cal-card-popover[hidden]{display:none!important}
/* No field labels — each value carries its own meaning through color
   instead: doctor's name in their legend color (set inline via
   --popover-doctor-color), patient's name in plum matching how names read
   elsewhere in the portal, time/status in the default ink color. */
.cal-card-popover-doctor{font-weight:600}
.cal-card-popover-patient{color:var(--plum);font-weight:600}
/* Visit type (Phone/Home/Clinic) — muted, since the card's own coloured
   bottom bar already carries it at a glance; this spells it out. */
.cal-card-popover-type{color:var(--muted)}
.cal-card-popover-close{position:absolute;top:4px;right:6px;width:22px;height:22px;
  border:none;background:transparent;color:var(--muted);font-size:1.1rem;
  line-height:1;cursor:pointer;border-radius:50%}
.cal-card-popover-close:hover{background:rgba(0,0,0,.08);color:var(--ink)}
.cal-card-popover-open{display:block;margin-top:2px;padding-top:6px;
  border-top:1px solid var(--line);color:var(--plum);font-weight:600;
  text-align:center;text-decoration:none}
.cal-card-popover-open:hover{text-decoration:underline}

/* Doctor "Block Out Time" slots (wireframe 11a) — a diagonal hatch instead
   of a flat tint, so a blocked stretch reads as unavailable at a glance
   rather than looking like just another (odd-colored) appointment. */
#appointments-calendar .fc-bg-event.doctor-block, #next-appt-calendar .fc-bg-event.doctor-block{
  background:repeating-linear-gradient(45deg,#f1eef0,#f1eef0 6px,#e4dde2 6px,#e4dde2 12px);
  opacity:1;border:1px solid var(--line)}
#appointments-calendar .fc-block-label, #next-appt-calendar .fc-block-label{
  font-weight:600;font-size:.68rem;color:var(--muted);padding:3px 6px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* Clinic-wide blocked days (holidays/closures, WI-423) — same hatch idea as
   a doctor block, but in the plum/magenta family and covering the whole day
   column, so a closure reads as "nobody works today", not "one doctor is
   out". */
#appointments-calendar .fc-bg-event.holiday-block, #next-appt-calendar .fc-bg-event.holiday-block{
  background:repeating-linear-gradient(45deg,#f6e6ef,#f6e6ef 7px,#efd6e4 7px,#efd6e4 14px);
  opacity:1;border:1px solid #e3c2d5}
#appointments-calendar .fc-bg-event.holiday-block .fc-block-label, #next-appt-calendar .fc-bg-event.holiday-block .fc-block-label{
  color:var(--plum);font-weight:700}

/* ---------- calendar toolbar (wireframe 2a/2c) ---------- */
/* A custom segmented rounded-rect toolbar (8px corners, not a full pill —
   the wireframe's toolbar buttons are a squarer segmented group than
   .btn/.pagination-link elsewhere) in place of FullCalendar's native
   buttons (headerToolbar:false in appointments-calendar.js). */
.cal-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;
  flex-wrap:wrap;margin-bottom:14px}
.cal-nav-group, .cal-view-toggle{display:flex;border-radius:8px;
  overflow:hidden;border:1px solid var(--plum)}
/* Segments default to a light/plum-text look so an "active" (or pressed)
   segment reads as clearly distinct once it switches to a solid plum fill —
   the previous all-plum default made every segment the same shade, so which
   view was selected (or which nav button was just pressed) wasn't visible
   at a glance. */
.cal-nav-btn, .cal-view-btn{background:#fff;color:var(--plum);border:none;
  border-right:1px solid var(--plum);padding:9px 15px;min-height:40px;
  font-size:.8125rem;font-weight:600;font-family:inherit;cursor:pointer;
  transition:background .15s,color .15s;box-sizing:border-box}
.cal-nav-btn:last-child, .cal-view-btn:last-child{border-right:none}
.cal-nav-btn:hover, .cal-view-btn:hover{background:var(--magenta);color:#fff}
/* Today/‹/› have no persistent "active" state (each click is a one-off
   navigation, not a mode) so they get a brief solid-fill press instead, for
   the same at-a-glance feedback the Week/Day toggle gets from .active. */
.cal-nav-btn:active{background:var(--plum-deep);color:#fff}
.cal-view-btn.active{background:var(--plum-deep);color:#fff}
.cal-toolbar-title{font-weight:700;font-size:1.15rem;color:var(--plum);text-align:center;flex:1 1 auto}
@media (max-width:640px){
  .cal-toolbar{justify-content:center}
  .cal-toolbar-title{order:1;flex-basis:100%;text-align:center}
}

/* ---------- calendar legend ---------- */
/* A single wrapping row (Doctor group, then Status group) rather than
   stacked lines, matching wireframe 2a's legend layout exactly. */
#appointments-calendar-legend{display:flex;flex-direction:column;gap:10px;margin-top:14px;padding-top:14px;border-top:1px solid var(--line);font-size:.75rem;color:var(--muted)}
.legend-group{display:flex;flex-wrap:wrap;align-items:center;gap:10px}
.legend-group-label{font-size:.72rem;font-weight:700;color:var(--plum);text-transform:uppercase;letter-spacing:.1em;margin-right:2px}
.legend-item{display:flex;align-items:center;gap:6px;font-size:.78rem;color:var(--ink)}
.legend-swatch{width:11px;height:11px;border-radius:3px;flex:0 0 auto}
.legend-swatch.doctor{background:var(--legend-color)}
/* Status legend uses colored text instead of a swatch — the card fill
   colors (.fc-appt-status-* above) are deliberately pale for readability
   against the name text, which reads as barely-there at legend size, so the
   legend uses the same bold/saturated colors as the status pill elsewhere. */
.legend-status-text-scheduled{color:#8a5a00;font-weight:600}
.legend-status-text-completed{color:var(--ok-fg);font-weight:600}
.legend-status-text-cancelled{color:var(--muted);font-weight:600}
.legend-status-text-no_show{color:var(--err-fg);font-weight:600}

#appointments-calendar, #next-appt-calendar{
  --fc-button-bg-color:var(--plum);
  --fc-button-border-color:var(--plum);
  --fc-button-hover-bg-color:var(--magenta);
  --fc-button-hover-border-color:var(--magenta);
  --fc-button-active-bg-color:var(--plum-deep);
  --fc-button-active-border-color:var(--plum-deep);
  --fc-button-text-color:#fff;
  --fc-border-color:var(--line);
  --fc-neutral-bg-color:var(--wash);
  --fc-today-bg-color:#f3e6ee;
  --fc-now-indicator-color:var(--crimson);
  --fc-page-bg-color:#fff;
  font-family:inherit;
}
/* FullCalendar's default now-indicator line is a hairline (1px) that reads
   as barely-there against the grid — bold it so the current-time marker is
   actually legible at a glance, matching how prominent it is in wireframe 2a. */
#appointments-calendar .fc-timegrid-now-indicator-line, #next-appt-calendar .fc-timegrid-now-indicator-line{border-width:2.5px 0 0}
/* The arrow is a CSS border-triangle (transparent on 3 sides, colored on
   the 4th to form a single right-pointing wedge) — setting all four
   border-width values equal (an earlier pass here) turns it into a
   symmetric diamond that reads as two arrowheads back to back. Scale the
   real asymmetric default (5px 0 5px 6px) up instead, keeping it a single
   wedge. */
#appointments-calendar .fc-timegrid-now-indicator-arrow, #next-appt-calendar .fc-timegrid-now-indicator-arrow{border-width:7px 0 7px 8px}
#appointments-calendar .fc-toolbar-title, #next-appt-calendar .fc-toolbar-title{color:var(--plum);font-weight:700;font-size:1.15rem}
#appointments-calendar .fc-button, #next-appt-calendar .fc-button{font-family:inherit;font-weight:600;text-transform:capitalize;box-shadow:none!important}
#appointments-calendar .fc-button-primary:disabled, #next-appt-calendar .fc-button-primary:disabled{opacity:.5}
#appointments-calendar .fc-button-primary:not(:disabled).fc-button-active,
#appointments-calendar .fc-button-primary:not(:disabled):active,
#next-appt-calendar .fc-button-primary:not(:disabled).fc-button-active,
#next-appt-calendar .fc-button-primary:not(:disabled):active{box-shadow:none!important}
#appointments-calendar .fc-col-header-cell-cushion,
#appointments-calendar .fc-daygrid-day-number,
#next-appt-calendar .fc-col-header-cell-cushion,
#next-appt-calendar .fc-daygrid-day-number{color:var(--ink)}
#appointments-calendar a, #next-appt-calendar a{color:inherit}

/* Day-of-week + a large date number stacked, matching wireframe 2a — see
   dayHeaderContent in appointments-calendar.js. Today's column additionally
   gets the wireframe's pink tint (FullCalendar adds .fc-day-today itself). */
#appointments-calendar .fc-day-header-stack, #next-appt-calendar .fc-day-header-stack{padding:4px 0}
#appointments-calendar .fc-day-header-dow, #next-appt-calendar .fc-day-header-dow{font-weight:700;font-size:.78rem;color:var(--muted)}
#appointments-calendar .fc-day-header-num, #next-appt-calendar .fc-day-header-num{font-weight:800;font-size:1rem;color:var(--ink)}
#appointments-calendar .fc-day-today .fc-day-header-dow, #next-appt-calendar .fc-day-today .fc-day-header-dow,
#appointments-calendar .fc-day-today .fc-day-header-num, #next-appt-calendar .fc-day-today .fc-day-header-num{color:var(--plum)}
#appointments-calendar th.fc-day-today, #next-appt-calendar th.fc-day-today,
#appointments-calendar td.fc-day-today, #next-appt-calendar td.fc-day-today{background:#f3e6ee!important}
tr.row-rejected td:first-child{box-shadow:inset 3px 0 0 #c23a5c}

.attachment-list{list-style:none;margin:10px 0 0;padding:0;display:flex;flex-direction:column;gap:8px}
.attachment-list li{display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:8px 12px;border:1.5px solid var(--line);border-radius:8px;font-size:.86rem}
.attachment-list .name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

table.lines td{padding:6px 8px;border-bottom:0}
table.lines th{padding:8px}
table.lines input{width:100%}
table.lines tfoot td{border-top:1.5px solid var(--line);padding-top:12px}
table.lines tfoot tr:not(:last-child) td{border-top:0;padding-top:4px;font-size:.85rem}
table.lines tfoot tr:first-child td{border-top:1.5px solid var(--line);padding-top:12px}

/* ---------- inventory type-ahead on the billable-lines table ---------- */
/* The suggestion list floats over the rows below it, so the cell has to be
   the positioning context and the row must not clip it. */
.service-lines .svc-desc-cell{position:relative}
.service-lines .line-total{white-space:nowrap;color:var(--plum);font-weight:600}
.svc-suggest{position:absolute;z-index:30;left:8px;right:8px;top:100%;
  background:var(--paper);border:1.5px solid var(--line);border-radius:10px;
  box-shadow:0 10px 24px rgba(36,16,33,.16);max-height:260px;overflow-y:auto}
.svc-suggest-option{display:block;width:100%;text-align:left;background:none;border:0;
  padding:8px 12px;cursor:pointer;font:inherit;color:var(--ink);border-bottom:1px solid var(--line)}
.svc-suggest-option:last-child{border-bottom:0}
.svc-suggest-option:hover{background:var(--wash)}
.svc-suggest-option.out-of-stock{cursor:not-allowed;opacity:.6}
.svc-suggest-option.out-of-stock:hover{background:none}
.svc-suggest-option.out-of-stock .svc-suggest-meta{color:var(--crimson);font-weight:600}
.svc-suggest-name{display:block;font-weight:600;font-size:.9rem}
.svc-suggest-meta{display:block;font-size:.76rem;color:var(--muted);margin-top:2px}

.lbl-sm{font-size:.7rem;letter-spacing:.1em;text-transform:uppercase;color:var(--magenta);
  font-weight:700;margin-bottom:4px}

/* ---------- finance dashboard ---------- */
/* The first group label sits right under the summary card's own toolbar,
   so it does not need the gap that separates the later groups. */
.fin-summary .fin-group-label:first-of-type{margin-top:16px}
.fin-group-label{margin:22px 0 10px;font-size:.72rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);border-bottom:1px solid var(--line);padding-bottom:6px}
.fin-tile-value{font-weight:800;font-size:1.05rem;white-space:nowrap;color:var(--ink)}
.fin-tile-sub{margin-top:4px;font-size:.76rem;color:var(--muted)}
.fin-tile-sub.accent{color:var(--amber);font-weight:600}
.fin-trend{font-size:.9em;margin-left:2px}
/* The derived KPIs carry a coloured left edge so profit, loss and runway
   read apart at a glance from the plain spend/inflow figures above them. */
.fin-tile.accent-green{border-left:4px solid var(--ok-fg)}
.fin-tile.accent-crimson{border-left:4px solid var(--crimson)}
.fin-tile.accent-purple{border-left:4px solid var(--plum)}
.fin-tile.muted{background:var(--wash)}
.fin-tile.muted .fin-tile-value{color:var(--muted)}
.meter{margin-top:8px;height:6px;border-radius:var(--radius-pill);background:var(--line);overflow:hidden}
.meter-fill{height:100%;border-radius:var(--radius-pill);background:var(--ok-fg)}

.fin-section-head{justify-content:space-between;align-items:flex-start;padding:16px 16px 0;flex-wrap:wrap;gap:10px;margin-bottom:0}
/* margin-left:auto rather than relying on space-between alone — once the
   header wraps, the filter would otherwise fall to the left of its own row. */
.fin-section-filters{margin:0 0 0 auto;flex-wrap:wrap;align-items:center;justify-content:flex-end}
.fin-range-filter{margin:0 0 0 auto;flex-wrap:wrap;align-items:center;justify-content:flex-end}

/* The formula behind a derived figure, on hover/focus rather than on the
   face of the card. */
.fin-info{position:relative;display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;
  margin-left:5px;border-radius:50%;background:var(--line);color:var(--muted);font-size:.62rem;font-weight:800;
  font-style:italic;cursor:help;vertical-align:1px;text-transform:none;letter-spacing:0}
.fin-info:hover,.fin-info:focus{background:var(--plum);color:#fff;outline:none}
/* Drawn rather than left to the native title attribute: that one needs a
   slow hover, never opens on click, and cannot be positioned. */
.fin-tip{position:absolute;top:calc(100% + 8px);left:50%;transform:translateX(-50%);
  width:190px;padding:8px 10px;border-radius:8px;background:var(--ink);color:#fff;
  font:400 .72rem/1.45 inherit;font-style:normal;letter-spacing:0;text-transform:none;text-align:left;
  white-space:normal;opacity:0;visibility:hidden;transition:opacity .1s;pointer-events:none;z-index:20}
.fin-info:hover .fin-tip,.fin-info:focus .fin-tip{opacity:1;visibility:visible}

/* Thirteen money columns is a wide table by design — it scrolls inside
   .tbl-wrap — so the cells themselves stay as tight as they can be. Only
   the figures shrink: headers keep the table's own size, so a money column
   heading reads the same as Period beside it. */
.fin-period-table th,.fin-period-table td{white-space:nowrap}
.fin-period-table td.num{font-size:.86rem}
/* The sort link is a flex block filling the cell, so a marker after it would
   wrap onto its own line. Pin it beside the label instead, with the link
   padded to leave room. */
.fin-period-table th.sortable{position:relative}
.fin-period-table th.sortable a{padding-right:24px}
.fin-period-table th .fin-info{position:absolute;top:50%;right:6px;transform:translateY(-50%);margin:0}

/* Per-row disclosure on the period lists. The chevron rotates rather than
   swapping glyphs so the control does not reflow the row as it opens. */
.fin-toggle-col{width:34px;text-align:center;padding-left:8px;padding-right:0}
.fin-row-toggle{border:0;background:none;cursor:pointer;font-size:1.1rem;line-height:1;color:var(--muted);padding:2px 6px;border-radius:var(--radius-input);transition:transform .12s,color .12s}
.fin-row-toggle:hover{color:var(--plum);background:var(--wash)}
.fin-row-toggle.open{transform:rotate(90deg);color:var(--plum)}
.fin-detail-row>td{background:var(--wash);padding:12px 16px}
.fin-detail{display:flex;flex-wrap:wrap;gap:18px;align-items:center;font-size:.85rem;color:var(--muted)}
.fin-detail strong{color:var(--ink)}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}

/* ---------- finance chart ---------- */
.chart-legend{display:flex;gap:16px;margin-bottom:10px;font-size:.8rem;color:var(--ink);font-weight:600}
.chart-legend-swatch{display:inline-block;width:10px;height:10px;border-radius:2px;margin-right:6px;vertical-align:-1px}
.finance-chart text{font-family:"Segoe UI",system-ui,-apple-system,"Helvetica Neue",sans-serif}
.chart-axis-label{font-size:11px;fill:var(--muted);font-weight:600}
.chart-tip-text{font-size:11px;font-weight:700;fill:var(--ink)}
.chart-dot-tip{opacity:0;transition:opacity .08s;pointer-events:none}
.chart-dot:hover .chart-dot-tip{opacity:1}
.chart-dot:hover .chart-dot-hit{fill:rgba(74,14,63,.06)}

/* ---------- loading skeletons ---------- */
/* For fetch-driven panels that show nothing (or a bare "Loading…") while
   waiting — the calendar, inventory type-ahead, admin-delete preview.
   .skel-line/.skel-block are plain placeholder shapes; .skel-row lays a few
   out like a table row. Toggle a wrapper's [hidden] attribute between the
   skeleton and the real content once the fetch resolves. */
.skel-line,.skel-block{background:#f1eef0;border-radius:999px;animation:skel-pulse 1.3s ease-in-out infinite}
.skel-block{border-radius:10px}
.skel-row{display:flex;gap:10px;align-items:center;margin-bottom:9px}
.skel-row .skel-line{height:12px}
@keyframes skel-pulse{0%,100%{opacity:1}50%{opacity:.45}}
.cal-wrap{position:relative}
.cal-loading-overlay{position:absolute;inset:0;z-index:5;background:#fff;padding:14px}
.cal-loading-overlay[hidden]{display:none}

.save-state{font-size:.8rem;color:var(--muted);margin-left:10px}
.save-state.saving{color:var(--muted)}
.save-state.ok{color:var(--ok-fg)}
.save-state.bad{color:var(--err-fg);font-weight:700}

.empty{text-align:center;padding:50px 20px;color:var(--muted)}
.empty h3{color:var(--plum);margin-bottom:6px;font-size:1rem}
/* .pager is now only ever a flex wrapper around {{ $x->links() }} — every
   call site is `<div class="pager">{{ $x->links() }}</div>`, never raw
   <a>/<span> children — so it carries no descendant styling of its own.
   It used to (a `.pager a,.pager span` rule with its own 6px-radius pill
   look), which silently beat `.pagination-link`'s pill styling on every
   page still using this wrapper: `.pager a` (class+element) out-specifies
   `.pagination-link` (class alone), so the pagination rendered as the old
   compact rectangles instead of the new pill shape wherever the two
   nested — a real, confirmed-live bug, not just a suspicion. */
.pager{display:flex;gap:6px;margin-top:18px;justify-content:center;flex-wrap:wrap}
.inline{display:inline}
/* Component Standard §4 footer: "Show N" chip + range readout left, page
   bubbles right, one hairline above. Same treatment on every list, always
   rendered whatever the record count. */
.list-footer{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;
  padding:16px 18px;border-top:1px solid var(--line)}
.list-footer .pager{margin-top:0}
.list-footer .pagination-nav{margin-top:0}
.list-footer-left{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.list-footer-sep{width:1px;height:18px;background:var(--line)}
.list-footer-count{color:var(--muted);font-size:.8rem;line-height:1;white-space:nowrap}
.list-footer-count strong{color:var(--ink);font-weight:700}
.per-page-select{margin:0}
.per-page-chip{margin:0;padding:0 13px;font-weight:600;color:var(--ink);cursor:pointer}
.per-page-chip select{border:0;background:transparent;padding:0 14px 0 0;min-height:0;height:auto;width:auto;
  font:inherit;font-weight:600;color:var(--ink);cursor:pointer;
  appearance:none;-webkit-appearance:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23a08a9a' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 2px center}
.per-page-chip select:focus{outline:none}
@media(max-width:640px){
  .list-footer{flex-direction:column;align-items:center;gap:12px;padding:16px 14px}
  .list-footer-left{flex-direction:column-reverse;gap:12px}
  .list-footer-sep{display:none}
  .list-footer .pagination-list{justify-content:center;gap:7px}
  .list-footer .pagination-link,.list-footer .pagination-link.page-num{width:40px;height:40px;font-size:.85rem}
  .per-page-chip{min-height:44px}
}

.card.collapsible{padding:0}
.card.collapsible > summary{list-style:none;cursor:pointer;position:relative}
.card.collapsible > summary::-webkit-details-marker{display:none}
.card.collapsible > summary::after{content:"";position:absolute;right:24px;top:26px;width:9px;height:9px;
  border-right:2px solid var(--muted);border-bottom:2px solid var(--muted);transform:rotate(-45deg);transition:transform .15s}
.card.collapsible[open] > summary::after{transform:rotate(45deg);top:30px}
.card.collapsible > summary:hover h2{text-decoration:underline}

/* ---------- visit form step rail (mobile only, see edit.blade.php JS) ---------- */
.step-rail{display:flex;gap:6px;overflow-x:auto;padding-bottom:2px;margin-bottom:16px}
.step-rail[hidden]{display:none}
.step-pill{flex:none;font-size:.78rem;font-weight:700;color:var(--muted);background:#fff;
  border:1.5px solid var(--line);border-radius:999px;padding:8px 14px;cursor:pointer;white-space:nowrap}
.step-pill.on{color:#fff;background:var(--plum);border-color:var(--plum)}
.step-nav{display:flex;gap:10px;margin-top:16px;position:sticky;bottom:0;background:var(--wash);
  padding-top:10px}
.step-nav[hidden]{display:none}
.step-nav .btn{flex:1}

@media(max-width:900px){
  /* Single column: the sidebar leaves the flow and becomes an off-canvas
     drawer toggled by the header hamburger, so content uses the full width. */
  .shell{grid-template-columns:1fr}
  .nav-toggle{display:block}
  .side{position:fixed;top:0;left:0;height:100vh;width:min(260px,84vw);z-index:120;
    transform:translateX(-100%);transition:transform .22s ease;box-shadow:0 0 40px rgba(0,0,0,.35)}
  .shell.nav-open .side{transform:translateX(0)}
  .shell.nav-open .nav-backdrop{display:block;position:fixed;inset:0;z-index:110;
    background:rgba(31,10,26,.45)}
  /* Keep the drawer clear of the impersonation bar when it's on screen. */
  .shell.impersonating .side{top:0;height:100vh}

  .row.c2,.row.c3,.row.c4,.row.c5{grid-template-columns:1fr}
  .content,.top{padding-left:16px;padding-right:16px}
  .top{padding-right:16px;gap:10px}
  .top h1{font-size:1.05rem}

  /* .toolbar padding-bottom:0 (visits/prescriptions/system-info index pages)
     is fine on desktop where heading/filters/sort-by sit on one row flush
     against the card's own top padding boundary. Below 900px .toolbar wraps
     onto several stacked rows, so that same 0 leaves the last row (Sort By)
     touching the table header right underneath it — give it room back. */
  .card > .toolbar[style*="padding:24px 24px 0"]{padding-bottom:16px!important}
}
@media(max-width:560px){
  .content{padding:18px 14px}
  /* On phones the role line and the header padding waste width — trim them. */
  #header-clock{display:none}
  .who{gap:8px}
  .who .rl{display:none}
  .who .nm{max-width:72px}
  /* Frees up header width for other components (e.g. the bell icon) — the
     text label is redundant next to an unmistakable logout glyph. */
  .signout-btn{padding:0;width:36px;height:36px;border-radius:50%;justify-content:center}
  .signout-label{display:none}
  .signout-icon{display:block}
  /* The panel is anchored to the bell button by default (right:0 relative to
     .bell-wrap) — on phones that button sits close to the left edge of a
     cramped header, so anchor the panel to the viewport edge instead of the
     button to keep it fully on-screen. */
  .bell-panel{position:fixed;top:70px;right:10px;left:10px;width:auto}
  .toolbar{align-items:stretch}
  .filters{width:100%}
  /* The toolbar's right-hand "sort + primary action" group (10 index pages,
     e.g. Admin > Visit/Check-In's Sort By select + "+ New Visit/Check-In")
     is an inline flex row with no wrap of its own — on phones the primary
     button got squeezed down to a sliver and wrapped its label onto 3
     lines next to the sort select. Let the group wrap and give the button
     its own full-width line instead. */
  .toolbar > [style*="display:flex;gap:10px;align-items:center"]{width:100%;flex-wrap:wrap}
  .toolbar > [style*="display:flex;gap:10px;align-items:center"] > .btn{flex:1 1 100%;text-align:center}
  /* flex:1;min-width:0 let every control (select, 2 date inputs, Filter
     button) shrink onto a single row instead of wrapping — squeezed a
     type select and both date inputs down to a few illegible characters
     on phones (My Wallet's type + from/to + Filter row). A real min-width
     forces flex-wrap to kick in and stack them onto multiple rows instead. */
  .filters input,.filters select,.filters .chk-filter{flex:1 1 130px;min-width:130px}
  .filters .chk-filter-panel input[type=checkbox]{flex:none}
  .filters .btn{flex:0 0 auto}
  .stats{grid-template-columns:1fr 1fr;gap:12px}
  .stat{padding:16px}
  .stat .v{font-size:1.5rem}
  /* A pill radius (999px) only reads as a pill on a single line of text.
     Narrow toolbars force multi-word labels ("Log in as", "+ New
     Visit/Check-In") onto 2 lines, and on that near-square box the same
     radius rounds all the way in — a bloated blob/circle, not a pill. Cap
     it to the standard corner radius below the width where wrapping
     starts, for every .btn use case (toolbar actions, Sign Out, Filter). */
  .btn{border-radius:var(--radius)}
  .btn.wide-sm{width:100%;justify-content:center}
}

/* Inline help marker next to a field label. The bubble is drawn here rather
   than left to the browser's own title tooltip, because a native title only
   ever appears on HOVER — on a touch screen, and on any click, nothing
   happens at all. Shown on hover AND on focus, and the marker is
   focusable (tabindex on the element), so tapping or clicking it opens the
   bubble exactly as hovering does. Text comes from data-tip; aria-label
   carries the same words for a screen reader. */
.tip{position:relative;display:inline-flex;align-items:center;justify-content:center;
  width:15px;height:15px;margin-left:5px;border-radius:50%;background:var(--plum);color:#fff;
  font-size:.65rem;font-weight:700;line-height:1;cursor:help;vertical-align:middle}
.tip:focus{outline:2px solid var(--magenta);outline-offset:1px}
.tip::after{content:attr(data-tip);position:absolute;bottom:calc(100% + 7px);left:50%;
  transform:translateX(-50%);z-index:60;min-width:120px;max-width:230px;width:max-content;
  padding:7px 10px;border-radius:8px;background:var(--plum);color:#fff;
  font-size:.72rem;font-weight:500;line-height:1.35;text-align:left;white-space:normal;
  box-shadow:0 6px 18px rgba(74,14,63,.28);opacity:0;visibility:hidden;
  transition:opacity .12s ease}
.tip::before{content:"";position:absolute;bottom:calc(100% + 2px);left:50%;
  transform:translateX(-50%);z-index:60;border:5px solid transparent;
  border-top-color:var(--plum);opacity:0;visibility:hidden;transition:opacity .12s ease}
.tip:hover::after,.tip:focus::after,.tip:hover::before,.tip:focus::before{opacity:1;visibility:visible}
/* Near the right edge of a narrow screen a centred bubble would run off the
   page; anchoring it to the marker's right keeps it on screen. */
@media (max-width:560px){
  .tip::after{left:auto;right:-6px;transform:none;max-width:min(230px,72vw)}
}

/* A block stacked one field per line, filling whichever column of the
   surrounding .row it sits in (see the inventory item's Tax and Pricing
   columns). */
.stack-fields{display:grid;gap:14px;align-content:start}

/* =====================================================================
   List view (Component Standard §4, WI-583) — <x-list-view>, the one card
   every index page is built from. Header row (search + primary action),
   filter row (chips · sort · reset), table, footer. Desktop / tablet /
   phone all render the same markup: on a phone the filter row folds into
   a bottom sheet behind "Filters" and "Sort" buttons and each row becomes
   a card, but nothing is dropped — every column, filter and action is
   reachable at every width.
   ===================================================================== */
.list-view{padding:0}
.list-view .list-heading{padding:20px 22px 0}
.list-view .list-heading h2{font-size:1rem;color:var(--plum);margin:0}
.list-view .list-heading .hint{margin:2px 0 0}
.list-view .list-heading + .list-toolbar{padding-top:14px}

/* ---- header + filter rows ---- */
.list-view .list-toolbar{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:12px;
  margin:0;padding:20px 22px 0}
.list-view .list-filters{display:contents}
.list-view .list-search{grid-column:1;display:flex;align-items:center;gap:9px;margin:0;
  max-width:360px;min-width:0;padding:0 16px;min-height:44px;
  background:#fff;border:1.5px solid var(--line);border-radius:var(--radius-pill);
  color:var(--muted);font-weight:400;cursor:text}
.list-view .list-search:focus-within{border-color:var(--plum);box-shadow:0 0 0 3px rgba(194,24,91,.12)}
.list-view .list-search input{flex:1;min-width:0;border:0;background:transparent;padding:0;min-height:0;
  font-size:.86rem;color:var(--ink);outline:none;box-shadow:none;width:auto}
.list-view .list-search input::placeholder{color:#a08a9a}
.list-view .list-search input::-webkit-search-cancel-button{-webkit-appearance:none}
.list-view .list-actions{grid-row:1;grid-column:2;display:flex;align-items:center;gap:10px;justify-content:flex-end;margin-left:auto}
/* grid-row:1 is load-bearing: the actions follow the filter row in the DOM
   (outside the form), and auto-placement would otherwise drop them below it. */
.list-view .list-toolbar:not(:has(.list-search)) .list-actions{grid-column:1/-1}
.list-view .list-search{grid-row:1}
/* Header actions (§1/§4): every button 44px tall and one width within the
   row — a 1fr auto-column grid sizes every track to the widest label, 132px
   minimum. Secondary (ghost) first, primary last. */
.list-view .list-actions-btns{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:10px;align-items:center}
.list-view .list-actions .btn{min-height:44px;min-width:132px;justify-content:center;white-space:nowrap}
/* The circular ⋯ More menu sits last in the row. It renders only when it
   holds something: the `more` slot, or (tablet/phone, list-view.js) the
   secondary action. Destructive items sit below a rule at the bottom. */
.list-view .list-more{display:none}
.list-view .list-more.has-items{display:inline-block}
.list-view .list-more > summary{width:44px;height:44px;border:1.5px solid var(--line);background:#fff;color:var(--plum);font-size:1.15rem;font-weight:700}
.list-view .list-more > summary:hover,.list-view .list-more[open] > summary{border-color:#d7bcce;background:#fff}
.list-view .list-more .row-menu-panel{min-width:210px}
.list-view .list-more .list-more-collapsed{display:contents}
.list-view .list-more .row-menu-panel .btn{min-height:44px}
.list-view .list-more .row-menu-panel :not(.danger) + .danger,.list-view .list-more .row-menu-panel :not(.danger) + form > .danger{
  border-top:1px solid var(--line);border-radius:0 0 8px 8px;margin-top:4px}
.list-view .list-filter-row{grid-column:1/-1;display:flex;align-items:center;gap:9px;flex-wrap:wrap;
  padding-bottom:14px;border-bottom:1px solid var(--line)}
.list-view .list-toolbar:not(:has(.list-filter-row)){padding-bottom:14px;border-bottom:1px solid var(--line)}
.list-view .list-mobile-bar,.list-view .list-sheet-head,.list-view .list-sheet-foot{display:none}
.list-view .list-chips{display:contents}
.list-view .list-sep{width:1px;height:22px;background:var(--line);margin:0 3px}
/* A bare date/text input in the filter row wears the same pill as a chip. */
.list-view .list-chips > input,.list-view .list-chips > select{
  width:auto;min-width:0;min-height:40px;padding:8px 14px;font-size:.8rem;font-weight:600;color:var(--ink);
  border:1.5px solid var(--line);border-radius:var(--radius-pill);background:#fff}
.list-view .list-chips > .filter-label{font-size:.8rem;color:var(--muted);font-weight:500}

/* A toolbar standing on its own above a report's tiles and charts. */
.list-view.list-toolbar-only{margin-bottom:20px}
.list-view.list-toolbar-only .list-toolbar{padding-bottom:16px}
.list-view.list-toolbar-only .list-filter-row{border-bottom:0;padding-bottom:0}
.list-view.list-toolbar-only .list-toolbar:not(:has(.list-filter-row)){border-bottom:0}

/* ---- chips (filter, sort, per-page, mobile buttons) ---- */
.list-chip{position:relative;display:inline-flex;align-items:center;gap:8px;min-height:40px;padding:8px 14px;
  font-size:.8rem;font-weight:600;color:var(--muted);background:#fff;border:1.5px solid var(--line);
  border-radius:var(--radius-pill);white-space:nowrap;cursor:pointer;user-select:none;list-style:none;margin:0}
details.list-chip{display:inline-block;padding:0}
details.list-chip > summary{display:flex;align-items:center;gap:8px;min-height:40px;padding:8px 14px;
  border-radius:var(--radius-pill);list-style:none;cursor:pointer}
details.list-chip > summary::-webkit-details-marker{display:none}
.list-chip .list-chip-caret{color:#a08a9a;font-size:.8rem}
.list-chip .list-chip-value{color:var(--ink)}
.list-chip.list-sort{color:var(--ink)}
.list-chip.list-sort .list-chip-label{color:var(--muted);font-weight:500}
.list-chip.is-active,details.list-chip.is-active > summary{color:var(--plum)}
details.list-chip.is-active,.list-chip-btn.is-active{background:#f3e6ee;border-color:#e0cddb}
details.list-chip.is-active > summary .list-chip-caret{color:#8d7688}
details.list-chip[open]{border-color:var(--plum)}
.list-chip:hover,details.list-chip:hover{border-color:#d7bcce}
.list-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 6px;
  font-size:.66rem;font-weight:700;color:#fff;background:var(--plum);border-radius:var(--radius-pill)}
.list-reset{display:inline-flex;align-items:center;min-height:40px;padding:8px 12px;
  font-size:.8rem;font-weight:600;color:#a3123a;text-decoration:none;border:1.5px solid transparent;border-radius:var(--radius-pill);white-space:nowrap}
.list-reset:hover{background:var(--err-bg)}
/* Refresh: always present; the icon spins and the rows dim while in flight. */
.list-refresh{display:inline-flex;align-items:center;gap:6px;min-height:40px;padding:8px 14px;
  font-size:.8rem;font-weight:600;color:var(--plum);background:#fff;border:1.5px solid var(--line);border-radius:var(--radius-pill);
  white-space:nowrap;cursor:pointer;font-family:inherit}
.list-refresh:hover{border-color:#d7bcce}
.list-refresh svg{flex:none}
.list-view.is-refreshing .list-refresh svg{animation:list-spin .8s linear infinite}
.list-view.is-refreshing [data-list-rows]{opacity:.45;pointer-events:none;transition:opacity .15s}
@keyframes list-spin{to{transform:rotate(360deg)}}
.list-updated{font-size:.72rem;font-weight:400;color:#a08a9a;white-space:nowrap}
.list-view .list-updated-line{display:none}
.list-view .list-mobile-bar .list-reset,.list-view .list-mobile-bar .list-refresh{display:none}

/* ---- dropdown panels ---- */
/* Chromium keeps a closed <details>' content in layout (content-visibility),
   so the panel must be taken out of flow explicitly or the chip grows to
   the panel's height while closed. */
details.list-chip:not([open]) > .list-panel,details.row-menu:not([open]) > .row-menu-panel{display:none}
.list-panel.is-flipped{left:auto;right:0}
.list-panel{position:absolute;z-index:30;top:calc(100% + 6px);left:0;width:250px;max-height:340px;overflow-y:auto;
  background:#fff;border:1.5px solid var(--line);border-radius:12px;box-shadow:0 12px 28px rgba(36,16,33,.16);
  padding:12px 6px 8px;text-align:left;white-space:normal;cursor:default;font-weight:400}
.list-panel-title{font-size:.66rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;padding:0 10px 3px}
.list-panel-hint{font-size:.7rem;font-weight:400;color:#a08a9a;padding:0 10px 8px}
.list-option{display:flex;align-items:center;gap:10px;padding:9px 10px;border-radius:8px;
  font-size:.86rem;font-weight:500;color:var(--ink);text-decoration:none;cursor:pointer;margin:0}
.list-option:hover{background:var(--wash)}
.list-option.is-selected{background:#f3e6ee;color:var(--plum);font-weight:600}
.list-option.is-zero{opacity:.45}
.list-option-tick{flex:none;width:15px;color:var(--plum);font-size:.78rem;font-weight:700}
.list-option-label{flex:1;min-width:0}
.list-option-count{font-size:.74rem;font-weight:500;color:#a08a9a}
.list-option input[type=checkbox]{flex:none;width:16px;height:16px;min-width:0;margin:0;accent-color:var(--magenta)}
.list-option:has(input:checked){background:var(--wash)}
.list-panel-foot{display:flex;justify-content:space-between;align-items:center;gap:10px;
  border-top:1px solid var(--line);margin-top:6px;padding:10px 10px 4px}
.list-panel-clear{border:0;background:transparent;padding:6px 0;font-size:.78rem;font-weight:600;color:var(--err-fg);cursor:pointer}
.list-panel-apply{border:0;background:var(--magenta);color:#fff;padding:7px 15px;border-radius:var(--radius-pill);
  font-size:.78rem;font-weight:700;cursor:pointer;min-height:32px}
.list-panel-apply:hover{background:var(--crimson)}
/* <x-checkbox-filter> is a chip now; the older `.chk-filter > summary` rules
   above still serve the campaign form's hand-rolled audience picker, so
   the chip resets what they would otherwise put on its summary. */
details.list-chip.chk-filter{min-width:0}
details.list-chip.chk-filter > summary{border:0;background:transparent;min-height:40px;padding:8px 14px;
  border-radius:var(--radius-pill);font-size:.8rem;color:inherit;justify-content:flex-start}
details.list-chip.chk-filter > summary::after{content:none}
details.list-chip.chk-filter[open] > summary{border-color:transparent}

/* ---- table ---- */
.list-view .list-intro{padding:14px 22px 0}
.list-view .list-intro .hint{margin:0}
.list-view .tbl-wrap{margin-top:0}
.list-view table{font-size:.84rem}
.list-view thead tr{background:transparent}
.list-view th{font-size:.7rem;letter-spacing:0;text-transform:none;color:var(--muted);font-weight:700;
  padding:11px 12px;border-bottom:1.5px solid var(--line);text-align:left;background:transparent}
.list-view th.sortable{padding:0}
.list-view th.sortable a{display:inline-flex;align-items:center;gap:5px;padding:11px 12px;color:inherit}
.list-view th.sortable a:hover{color:var(--plum)}
.list-view th.sortable.sort-asc,.list-view th.sortable.sort-desc{color:var(--plum)}
.list-view td{padding:12px;border-bottom:1px solid #f4edf2;font-size:.84rem;color:var(--ink);text-align:left}
.list-view tbody tr:last-child td{border-bottom:0}
.list-view tbody tr:nth-child(even){background:var(--wash)}
.list-view tbody tr:hover{background:#f6eef3}
.list-view td:first-child{font-weight:600}
/* Record links in the title column read as links — same colour and underline as every other
   in-cell link (WI-597); black-until-hover looked inert next to the customer-name link. */
.list-view td:first-child a{color:var(--magenta);text-decoration:underline}
.list-view td:first-child a:hover{color:var(--plum)}
.list-view td .cell-sub,.list-view td .hint{font-weight:400}
.list-view td.num,.list-view th.num,.list-view td.money{font-variant-numeric:tabular-nums;white-space:nowrap}
.list-view .pill{border:0;font-size:.68rem;padding:4px 10px;margin:0}
.list-view .empty{padding:50px 20px}

/* The TDS Tracker is one card of month groups, each its own table, so its
   alignment is set here rather than per list. */
.tds-groups th:nth-child(3),.tds-groups td:nth-child(3),.tds-groups th:nth-child(4),.tds-groups td:nth-child(4),
.tds-groups th:nth-child(5),.tds-groups td:nth-child(5){text-align:right}
.tds-groups th:nth-child(6),.tds-groups td:nth-child(6),.tds-groups th:nth-child(7),.tds-groups td:nth-child(7){text-align:center}
@media(max-width:640px){
  .tds-groups tbody td:nth-child(6),.tds-groups tbody td:nth-child(7){order:1}
  .tds-groups tbody td:nth-child(n+2):nth-child(-n+5){order:3}
}

/* ---- row action menu ---- */
.row-menu{position:relative;display:inline-block;list-style:none}
.row-menu > summary{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:50%;
  border:1.5px solid transparent;color:var(--plum);font-size:1rem;font-weight:700;line-height:1;cursor:pointer;list-style:none}
.row-menu > summary::-webkit-details-marker{display:none}
.row-menu > summary:hover,.row-menu[open] > summary{border-color:var(--line);background:#fff}
.row-menu-panel{position:absolute;z-index:30;top:calc(100% + 4px);right:0;min-width:170px;
  display:flex;flex-direction:column;align-items:stretch;gap:2px;padding:6px;
  background:#fff;border:1.5px solid var(--line);border-radius:12px;box-shadow:0 12px 28px rgba(36,16,33,.16);
  text-align:left;white-space:nowrap;font-weight:400}
.row-menu-panel .btn,.row-menu-panel > a,.row-menu-panel button{display:flex;align-items:center;justify-content:flex-start;width:100%;
  min-height:38px;padding:8px 12px;border:0;border-radius:8px;background:transparent;
  font-size:.84rem;font-weight:500;color:var(--ink);text-decoration:none;text-align:left;cursor:pointer;margin:0;box-shadow:none;opacity:1}
.row-menu-panel .btn:hover,.row-menu-panel > a:hover,.row-menu-panel button:hover{background:var(--wash);color:var(--plum)}
.row-menu-panel .btn.danger,.row-menu-panel .danger{color:var(--err-fg)}
.row-menu-panel .btn.danger:hover,.row-menu-panel .danger:hover{background:var(--err-bg);color:var(--err-fg)}
.row-menu-panel form,.row-menu-panel .inline{display:contents}
.row-menu-panel .row-menu-hint{padding:6px 12px;font-size:.74rem;color:var(--muted);white-space:normal}

/* ---- phone: cards + bottom sheets ---- */
@media(max-width:640px){
  .list-view .list-toolbar{display:flex;flex-direction:column;align-items:stretch;gap:10px;padding:16px 16px 0}
  .list-view .list-search{order:0;max-width:none;min-height:44px;font-size:.9rem}
  /* The primary goes full width directly under the search box, above the
     filter row, with the 44px ⋯ beside it holding every other action
     (list-view.js moves the secondary in and opens the panel as a sheet). */
  .list-view .list-actions{order:1;width:100%;margin:0;display:flex;align-items:stretch;gap:10px}
  .list-view .list-actions-btns{flex:1 1 auto;min-width:0;grid-auto-flow:row;grid-auto-columns:auto}
  .list-view .list-actions .btn{width:100%;min-width:0;text-align:center;min-height:44px}
  .list-view .list-more > summary{width:44px;height:44px}
  .list-view .list-more .row-menu-panel{position:fixed;top:auto;left:0;right:0;bottom:0;z-index:60;max-height:85vh;overflow-y:auto;
    border:0;border-radius:18px 18px 0 0;padding:14px 16px calc(16px + env(safe-area-inset-bottom));box-shadow:0 -12px 40px rgba(36,16,33,.28);gap:4px}
  .list-view .list-more .list-sheet-head{display:flex;align-items:center;justify-content:space-between;padding-bottom:10px;margin-bottom:6px;border-bottom:1px solid var(--line)}
  .list-view .list-more .row-menu-panel .btn,.list-view .list-more .row-menu-panel > a,.list-view .list-more .row-menu-panel button:not(.list-sheet-close){min-height:44px;font-size:.9rem}
  .list-view .list-toolbar:not(:has(.list-filter-row)){padding-bottom:12px}
  .list-view .list-mobile-bar{order:2;display:flex;gap:8px;padding-bottom:12px;border-bottom:1px solid var(--line)}
  .list-view .list-mobile-bar .list-chip-btn{flex:1;justify-content:center;min-height:44px;font-size:.86rem;color:var(--ink)}
  .list-view .list-mobile-bar .list-chip-btn.is-active{color:var(--plum)}
  /* Reset (text) and a 44px round Refresh sit beside Filters / Sort; the
     "Updated" stamp takes the line below. The row's own copies hide. */
  .list-view .list-mobile-bar .list-reset{display:inline-flex;flex:none;align-items:center;justify-content:center;min-height:44px;padding:0 4px;border:0}
  .list-view .list-mobile-bar .list-refresh{display:flex;flex:none;width:44px;height:44px;padding:0;align-items:center;justify-content:center;border-radius:50%}
  .list-view .list-mobile-bar:has(+ .list-updated-line){border-bottom:0;padding-bottom:6px}
  .list-view .list-updated-line{order:3;display:block;margin:-2px 0 0;padding-bottom:12px;border-bottom:1px solid var(--line)}
  .list-view .list-filter-row .list-reset,.list-view .list-filter-row .list-refresh,.list-view .list-filter-row .list-updated{display:none}
  /* The filter row lives in a bottom sheet until opened. */
  .list-view .list-filter-row{display:none;position:fixed;left:0;right:0;bottom:0;z-index:60;max-height:85vh;overflow-y:auto;
    flex-direction:column;align-items:stretch;gap:12px;padding:14px 16px calc(16px + env(safe-area-inset-bottom));
    background:#fff;border:0;border-radius:18px 18px 0 0;box-shadow:0 -12px 40px rgba(36,16,33,.28)}
  .list-view .list-filter-row.is-open{display:flex}
  .list-view .list-filter-row .list-sheet-head{display:flex;align-items:center;justify-content:space-between;
    padding-bottom:10px;border-bottom:1px solid var(--line)}
  .list-view .list-sheet-title{font-size:.95rem;font-weight:700;color:var(--plum)}
  .list-view .list-sheet-close{border:0;background:var(--wash);width:36px;height:36px;border-radius:50%;font-size:1.2rem;color:var(--muted);cursor:pointer}
  .list-view .list-filter-row .list-sheet-foot{display:flex}
  .list-view .list-filter-row .list-sheet-foot .btn{flex:1;min-height:44px}
  .list-view .list-filter-row[data-sheet="sort"] .list-chips,
  .list-view .list-filter-row[data-sheet="sort"] .list-sep,
  .list-view .list-filter-row[data-sheet="sort"] .list-sheet-foot,
  .list-view .list-filter-row[data-sheet="filters"] .list-sort,
  .list-view .list-filter-row[data-sheet="filters"] .list-sep{display:none}
  /* nowrap: with a max-height, `column` + `wrap` would spill the chips into
     side columns instead of scrolling. flex:0 0 auto beats the legacy
     `.filters .chk-filter{flex:1 1 130px}` rule, whose 130px basis becomes a
     130px HEIGHT in a column container. */
  .list-view .list-filter-row.is-open{flex-wrap:nowrap}
  .list-view .list-chips{display:flex;flex-direction:column;gap:10px}
  .list-view .filters .list-chips > *,.list-view .filters .list-chips > .chk-filter{flex:0 0 auto;min-width:0}
  .list-view .list-filter-row .list-chip,.list-view .list-filter-row details.list-chip,.list-view .list-chips > input,.list-view .list-chips > select{width:100%;min-height:44px;border-radius:12px}
  .list-view .list-filter-row details.list-chip > summary{min-height:44px;border-radius:12px}
  .list-view .list-filter-row details.list-chip[open]{border-color:var(--plum)}
  .list-view .list-filter-row .list-chip-caret{margin-left:auto}
  /* Panels open in place inside the sheet instead of floating. */
  .list-view .list-filter-row .list-panel{position:static;width:100%;max-height:none;box-shadow:none;border:0;
    border-top:1px solid var(--line);border-radius:0;margin-top:4px}
  .list-view .list-filter-row[data-sheet="sort"] .list-sort > summary{display:none}
  .list-view .list-filter-row[data-sheet="sort"] .list-sort{border:0}
  .list-view .list-filter-row[data-sheet="sort"] .list-sort .list-panel{border-top:0;margin-top:0}
  .list-view .list-filter-row .list-option{min-height:44px}
  .list-sheet-scrim{position:fixed;inset:0;z-index:59;background:rgba(36,16,33,.45)}
  body.list-sheet-open{overflow:hidden}

  /* Rows as cards: title top-left, status + ⋯ top-right, everything else
     as labelled lines below a hairline. Column order comes from the
     per-list nth-child rules <x-list-view> emits from `columns`. */
  .list-view .tbl-wrap{padding:12px 16px 0}
  .list-view .tbl-wrap tbody tr{display:flex;flex-wrap:wrap;align-items:flex-start;gap:0 10px;
    border:1px solid var(--line);border-radius:10px;padding:13px;margin-bottom:10px;background:#fff}
  .list-view .tbl-wrap tbody tr:nth-child(even),.list-view .tbl-wrap tbody tr:hover{background:#fff}
  .list-view .tbl-wrap tbody td{display:flex;flex:1 1 100%;width:auto;justify-content:space-between;align-items:flex-start;gap:12px;
    padding:5px 0;border:0!important;text-align:left!important;white-space:normal!important;font-weight:400;min-width:0}
  /* A labelled cell is a two-column grid: label left, value(s) right. A cell
     holding several blocks (phone + email, date + time) stacks them under
     one label instead of spreading them across the line. */
  .list-view .tbl-wrap tbody td[data-th]{display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:12px;row-gap:2px;justify-items:end;text-align:right!important}
  .list-view .tbl-wrap tbody td[data-th] > *{grid-column:2;max-width:100%;min-width:0;overflow-wrap:anywhere}
  /* An expandable block (Notification History's "View" content) takes the
     whole value column and reads left, instead of shrinking to its summary
     and spilling its body past the card edge. */
  .list-view .tbl-wrap tbody td[data-th] > details{justify-self:stretch;width:100%;text-align:left}
  .list-view .tbl-wrap tbody td[data-th] > details > div{max-width:100%!important}
  .list-view .tbl-wrap tbody td[data-th] > .row-menu{justify-self:end}
  .list-view .tbl-wrap tbody td[data-th]::before{content:attr(data-th);grid-column:1;grid-row:1;justify-self:start;padding-top:2px;
    font-weight:600;color:var(--muted);font-size:.7rem;text-transform:none;letter-spacing:0;text-align:left}
  .list-view .tbl-wrap tbody td:not([data-th]){justify-content:flex-start}
  .list-view .tbl-wrap tbody tr[hidden]{display:none}
  /* A list that declares no `columns` falls back to: first cell is the
     title, a lone status pill or the ⋯ menu goes top-right. */
  .list-view-auto .tbl-wrap tbody td:first-child{flex:1 1 0;min-width:0;width:auto;padding:0 0 6px;font-size:.92rem;font-weight:700;display:block;justify-items:start;text-align:left!important}
  .list-view-auto .tbl-wrap tbody td:first-child::before{display:none}
  .list-view-auto .tbl-wrap tbody td:has(> .pill:only-child),.list-view-auto .tbl-wrap tbody td:has(> .row-menu){flex:0 0 auto;padding:0;justify-content:flex-end;order:1}
  .list-view-auto .tbl-wrap tbody td:has(> .pill:only-child)::before,.list-view-auto .tbl-wrap tbody td:has(> .row-menu)::before{display:none}
  /* Hairline between the header line (orders 0-1) and the labelled lines (3). */
  .list-view .tbl-wrap tbody tr::after{content:"";order:2;flex:1 1 100%;height:1px;background:#f4edf2;margin:3px 0 4px}
  .list-view .tbl-wrap tbody td.num,.list-view .tbl-wrap tbody td.money{font-weight:700;font-size:.92rem}
  .list-view .tbl-wrap tbody td:empty{display:none}
  .list-view .tbl-wrap tbody td .btn{flex:0 0 auto}
  .list-view .tbl-wrap tbody td:not([data-th]) form.inline{display:inline-flex}
  .row-menu > summary{width:32px;height:32px;border-color:var(--line)}
  .list-view .empty{padding:40px 16px}
}

/* ---- Files card + upload modal (WI-589, Component Standard §8) ---- */
.record-files-modal{width:min(480px,92vw)}
.drop-zone{border:2px dashed #d7bcce;background:#faf6f9;border-radius:12px;padding:26px;text-align:center;cursor:pointer;outline-offset:2px}
.drop-zone.is-over{border-color:var(--magenta);background:#fdeef4}
.drop-zone-title{font-weight:700;font-size:.85rem;color:var(--ink)}
.drop-zone.is-over .drop-zone-title{color:#a3123a}
.drop-zone-sub{font-size:.8rem;color:var(--muted);margin-top:4px}
.drop-zone-browse{color:#a3123a;text-decoration:underline}
.drop-zone-mobile{display:none}
.upload-queue{display:flex;flex-direction:column;gap:10px;margin-top:12px}
.upload-queue:empty{display:none}
.upload-item{border:1.5px solid var(--line);border-radius:10px;padding:12px 14px}
.upload-head{display:flex;align-items:center;justify-content:space-between;gap:12px;min-width:0}
.upload-name{font-weight:600;font-size:.85rem;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.upload-meta{font-size:.78rem;color:var(--muted);white-space:nowrap;flex:none}
.upload-bar{height:4px;background:var(--line);border-radius:999px;margin-top:9px;overflow:hidden}
.upload-bar span{display:block;height:100%;background:var(--magenta);border-radius:999px;transition:width .15s}
.upload-link{font:600 .78rem inherit;color:#a3123a;background:none;border:none;cursor:pointer;padding:0;font-family:inherit}
.upload-item.upload-error{border-color:#f3c6d2;background:#fdeef4}
.upload-item.upload-error .upload-name{color:#a3123a}
.upload-msg{font-size:.78rem;color:var(--muted);margin-top:5px}
@media(max-width:767px){
  /* Mobile — upload sheet: rises from the bottom, actions stack full width. */
  dialog.record-files-modal{width:100vw;max-width:none;inset:auto 0 0 0;margin:0;border-radius:18px 18px 0 0;max-height:90vh;overflow-y:auto;
    padding:18px 16px calc(16px + env(safe-area-inset-bottom))}
  .record-files-modal .drop-zone{padding:22px}
  .record-files-modal .drop-zone-title,.record-files-modal .drop-zone-sub:not(.drop-zone-mobile){display:none}
  .record-files-modal .drop-zone-mobile{display:block;margin-top:0}
  .record-files-modal .actions{flex-direction:column-reverse;gap:8px}
  .record-files-modal .actions .btn{width:100%;min-height:44px}
}

/* ---- Files card: thumbnails + quick view (WI-594) ---- */
/* Card rows (WI-595): a passport-size tile (3:4, like a 35x45mm photo) with
   the file's name, description and meta stacked beside it; the row ⋯ menu
   sits top-right. One layout at every width — nothing is hidden. */
.file-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
.file-row{display:flex;align-items:flex-start;gap:16px;padding:14px 0;border-top:1px solid var(--line);min-width:0}
.file-row:first-child{border-top:0;padding-top:0}
.file-info{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:4px;padding-top:2px}
.file-name{font-weight:700;font-size:.95rem;overflow-wrap:anywhere}
.file-name a{color:var(--plum);text-decoration:none}
.file-name a:hover{color:var(--magenta);text-decoration:underline}
.file-desc{color:var(--ink);overflow-wrap:anywhere}
.file-meta{display:flex;flex-wrap:wrap;gap:4px 0;color:var(--muted);font-size:.82rem}
.file-meta span+span::before{content:"·";margin:0 8px;color:var(--muted)}
.file-actions{flex:none;margin-left:auto}
.file-tile{flex:none;width:96px;height:124px;border-radius:10px;border:1.5px solid var(--line);background:#faf6f9;overflow:hidden;
  display:inline-flex;align-items:center;justify-content:center;padding:0;cursor:pointer;color:var(--plum)}
.file-tile img{width:100%;height:100%;object-fit:cover;display:block}
.file-tile:hover{border-color:var(--magenta)}
.file-tile-static{cursor:default}
.file-tile-static:hover{border-color:var(--line)}
.file-ext{font-size:.8rem;font-weight:800;letter-spacing:.06em}
dialog.record-file-preview{width:min(960px,94vw);max-height:92vh;padding:16px;flex-direction:column;gap:12px}
dialog.record-file-preview[open]{display:flex}
.record-file-preview .preview-head{display:flex;align-items:center;justify-content:space-between;gap:12px;min-width:0}
.record-file-preview .preview-head h2{font-size:.95rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.record-file-preview .preview-actions{display:flex;align-items:center;gap:8px;flex:none}
.record-file-preview .list-sheet-close{border:0;background:var(--wash);width:36px;height:36px;border-radius:50%;font-size:1.2rem;color:var(--muted);cursor:pointer}
.record-file-preview .preview-body{flex:1 1 auto;min-height:200px;display:flex;align-items:center;justify-content:center;background:#faf6f9;border-radius:10px;overflow:auto}
.record-file-preview .preview-body img{max-width:100%;max-height:calc(92vh - 110px);object-fit:contain;display:block}
.record-file-preview .preview-body iframe{width:100%;height:calc(92vh - 110px);border:0;background:#fff}
@media(max-width:767px){
  dialog.record-file-preview{width:100vw;max-width:none;max-height:100vh;inset:0;margin:0;border-radius:0;padding:12px}
  .record-file-preview .preview-body img,.record-file-preview .preview-body iframe{max-height:calc(100vh - 90px);height:calc(100vh - 90px)}
  .file-tile{width:80px;height:104px}
  .file-row{gap:12px}
}
