: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;
  --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}
.top h1{font-size:1.15rem;color:var(--plum);font-weight:700;flex:1;min-width:0;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* 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)}}

.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;min-width:36px;
  min-height:36px;padding:8px 14px;border-radius:var(--radius-pill);border:1px solid var(--line);
  background:#fff;color:var(--plum);
  font-size:.85rem;font-weight:600;text-decoration:none;transition:border-color .15s,background .15s}
/* Page-number pills (and the "..." ellipsis) are a single short token, so
   they get symmetric padding to stay a true circle — the asymmetric
   8px/14px padding above is sized for "Prev"/"Next" text and stretched a
   digit into an oval. */
.pagination-link.page-num{width:36px;height:36px;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)}
.pagination-item.disabled .pagination-link{color:var(--muted);border-color:var(--line);cursor:not-allowed;opacity:.6}

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}
.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)}
/* Finance Trend table: header cells carry a two-line label + formula
   sub-caption (see admin/finance/index.blade.php), so the default th
   padding reads too cramped against both the toolbar above and the
   sub-caption text inside the cell. */
.fin-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), and class="hide-mobile" on a <th>/<td>
   to drop a lower-priority column from the card entirely. 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}
  .tbl-wrap td.hide-mobile,.tbl-wrap th.hide-mobile{display:none}

  /* 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}

/* 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}
.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}

.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}
.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-404). The generic .filters rule forces a
   170px min-width on every input, which would stretch the opt-out checkbox
   into a giant box — scope it back for this filter bar. */
#audience-filters input[type="checkbox"]{min-width:0;width:auto}
#audience-filters input[type="number"]{min-width:110px}
#audience-table td{vertical-align:middle}

/* 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{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}

/* 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}
.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{
  background:repeating-linear-gradient(45deg,#f1eef0,#f1eef0 6px,#e4dde2 6px,#e4dde2 12px);
  opacity:1;border:1px solid var(--line)}
#appointments-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{
  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{
  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: collapsible monthly breakdown ---------- */
/* 13 columns of cumulative figures is dense even for Admin — the 8 raw
   per-period columns (Investment, Cash, Cum. Revenue, CAPEX/Cum. CAPEX,
   OPEX/Cum. OPEX, Cum. Expenses) collapse behind this toggle, defaulting to
   just Month/Revenue/Total Expenses/Cum. Net Cash/Cum. Runway. */
.fin-table-card .fin-detail{display:none}
.fin-table-card.fin-expanded .fin-detail{display:table-cell}
/* Below 640px the table is already a stacked-card list (see the .tbl-wrap
   mobile fallback) — the breakdown toggle only makes sense against the real
   table, so it hides rather than fighting that layout for a handful of
   columns nobody asked to see on a phone. */
@media(max-width:640px){#fin-breakdown-toggle{display:none}}

/* ---------- 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}
/* Wireframe 1a: per-page control left, page nav right — not centered
   together, so the two controls read as independent, not one cluster. */
.list-footer{display:flex;align-items:center;justify-content:space-between;gap:20px;margin-top:18px;flex-wrap:wrap}
.list-footer .pager{margin-top:0}
.list-footer .pagination-nav{margin-top:0}
/* WI-284: per wireframe 1a, every list's pagination footer is a fixed
   wash-tinted background with a border-top divider — not a flip against the
   last row's stripe color. Same treatment on every list, always. */
.list-footer.zebra{background:var(--wash);border-top:1px solid var(--line);border-radius:0 0 var(--radius) var(--radius);padding:14px 16px;margin-top:0}
.per-page-select select{width:auto}

.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{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. */
  .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{flex:1 1 130px;min-width:130px}
  .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}
