/* ==========================================================================
   PropSquare FMS — apps/web/static/web/app.css
   ONE curated stylesheet. Plain classes + CSS custom properties.
   No build step, no framework. Compose colors ONLY from the tokens below.
   --------------------------------------------------------------------------
   Order:  1. Tokens (:root)     2. Reset / base
           3. Layout             4. Type
           5. Shell (base.html)  6. Components (the vocabulary)
           7. ====== D5 ADDITIONS (consolidated, declared, token-built) ======
   ========================================================================== */

/* 1. TOKENS ---------------------------------------------------------------- */
:root{
  /* Brand — IDENTITY only (primary, links, active nav, key accents, H1/H2) */
  --brand:#E32024; --brand-600:#E52B2B; --brand-700:#C60E1C; --brand-50:#FBE4E5;
  /* Accent — Ice Blue, callout/info cards ONLY */
  --accent:#EBF5F5; --accent-deep:#DCEDED;
  /* Text */
  --ink:#182B3A; --ink-700:#252423; --muted:#45545F; --subtle:#5F6B74;
  /* Surface */
  --surface:#F6F7F7; --line:#E2E5E6; --card:#ffffff;
  /* Semantic */
  /* Semantic ink is AA on its own tint: --ok-ink 5.62:1 on --ok-bg, --warn-ink 5.36:1 on
     --warn-bg. The same value doubles as border/dot/icon colour (well past the 3:1 graphic
     bar), so status chips need no per-component overrides. */
  --ok:#146C45;   --ok-bg:#E4F3EC; --ok-ink:#146C45;
  --warn:#805D00; --warn-bg:#FBF1D8; --warn-ink:#805D00;
  --danger:#B42318; --danger-bg:#FEE4E2;   /* DEEPER red — error/destructive/overdue only */
  --info:#182B3A; --info-bg:#EBF5F5;
  /* Radius / elevation */
  --radius:12px; --radius-sm:8px; --radius-xs:4px;
  --shadow:0 1px 2px rgba(24,43,58,.06), 0 1px 3px rgba(24,43,58,.08);
  --shadow-md:0 4px 12px rgba(24,43,58,.08), 0 2px 4px rgba(24,43,58,.06);
  --shadow-lg:0 12px 32px rgba(24,43,58,.12), 0 4px 8px rgba(24,43,58,.06);
  --glow-red:0 8px 24px rgba(227,32,36,.28); --focus-ring:rgba(227,32,36,.32);
  /* Motion — brand curve */
  --t-fast:120ms; --t-med:200ms; --t-slow:320ms; --ease:cubic-bezier(.2,.6,.2,1);
  /* Left rail — dark ink shell + PropSquare-red accent (nav design pass 2026-07) */
  --rail-bg:#131E28; --rail-muted:rgba(255,255,255,.56); --rail-faint:rgba(255,255,255,.55);
  --rail-hover:rgba(255,255,255,.07); --rail-active:rgba(227,32,36,.16);
  --rail-divider:rgba(255,255,255,.10); --rail-focus:#7FB2FF;
  /* Type */
  --font:"Work Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-display:"Montserrat", var(--font);
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  /* Chart series — brand-derived */
  --chart-1:#E32024; --chart-2:#182B3A; --chart-3:#1F8A5B; --chart-4:#C98A00; --chart-5:#C60E1C; --chart-6:#74808A;
  /* Spacing rhythm — 4px base */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px; --s6:32px; --s7:48px;
}

/* Dark tokens — surfaces from the brand dark-blue family; red brightened on near-black. */
html[data-theme="dark"],
.applayout[data-theme="dark"]{
  --brand:#FF4D4A; --brand-700:#FF4D4A; --brand-50:rgba(255,77,74,.16);
  --accent:#132C38; --accent-deep:#0F2530;
  --ink:#EEF2F5; --ink-700:#C2CCD4; --muted:#7F8D99; --subtle:#6A7680;
  --surface:#0C141C; --line:#243039; --card:#141F29;
  --ok:#3DBE7E; --ok-bg:rgba(61,190,126,.16); --ok-ink:#6FD6A4;
  --warn:#EFB94F; --warn-bg:rgba(239,185,79,.15); --warn-ink:#F3CA79;
  --danger:#FF6B5C; --danger-bg:rgba(255,107,92,.16);
  --info:#7FB2CC; --info-bg:rgba(127,178,204,.15);
  --shadow:0 1px 2px rgba(0,0,0,.5), 0 1px 3px rgba(0,0,0,.55);
  --shadow-md:0 4px 14px rgba(0,0,0,.55), 0 2px 5px rgba(0,0,0,.5);
  --shadow-lg:0 10px 30px rgba(0,0,0,.6), 0 3px 8px rgba(0,0,0,.5);
  --glow-red:0 8px 24px rgba(255,77,74,.32); --focus-ring:rgba(255,77,74,.40);
  --chart-1:#FF4D4A; --chart-2:#8FB6CE; --chart-3:#3DBE7E; --chart-4:#EFB94F; --chart-5:#E32024; --chart-6:#94A1AB;
  --rail-bg:#070C11; --rail-hover:rgba(255,255,255,.06); --rail-active:rgba(255,77,74,.18);
}

html[data-theme="dark"].theme-dim-status .badge-ok,
.applayout[data-theme="dark"].theme-dim-status .badge-ok{background:var(--ok-bg); color:var(--ok-ink);}
html[data-theme="dark"].theme-dim-status .badge-warn,
.applayout[data-theme="dark"].theme-dim-status .badge-warn{background:var(--warn-bg); color:var(--warn-ink);}

/* 2. RESET / BASE ---------------------------------------------------------- */
*{box-sizing:border-box;}
html{-webkit-text-size-adjust:100%;}
body{
  margin:0; font-family:var(--font); font-size:15px; line-height:1.5;
  color:var(--ink-700); background:var(--surface);
  -webkit-font-smoothing:antialiased;
  transition:background-color var(--t-slow) var(--ease), color var(--t-slow) var(--ease);
}
.card,.sidebar,.topstrip,.stat,.table-wrap,.input,.chip,.btn,.rowcard,.calwrap,.cal-cell,.nav-card,.notif-list,.msg,.acct{
  transition:background-color var(--t-slow) var(--ease), border-color var(--t-slow) var(--ease), color var(--t-slow) var(--ease), box-shadow var(--t-slow) var(--ease);
}
a{color:var(--brand); text-decoration:none;}
a:hover{color:var(--brand-700); text-decoration:underline;}
h1,h2,h3,h4{color:var(--ink); margin:0; line-height:1.25;}
h1,h2{font-family:var(--font-display);}
p{margin:0;}
img{max-width:100%; display:block;}
button{font-family:inherit; font-size:inherit;}
:focus-visible{outline:2px solid var(--brand); outline-offset:2px; border-radius:4px;}
/* Screen-reader-only text — visually hidden, still announced (labels next to inputs etc.). */
.sr-only{
  position:absolute !important; width:1px !important; height:1px !important;
  padding:0 !important; margin:-1px !important; overflow:hidden !important;
  clip:rect(0,0,0,0) !important; white-space:nowrap !important; border:0 !important;
}
.skip-link{
  position:fixed; z-index:100; top:8px; left:8px; padding:9px 13px;
  border-radius:var(--radius-sm); background:var(--card); color:var(--ink);
  box-shadow:var(--shadow-lg); transform:translateY(calc(-100% - 16px));
  transition:transform var(--t-fast) var(--ease);
}
.skip-link:focus{transform:translateY(0);}

/* 3. LAYOUT ---------------------------------------------------------------- */
/* Fluid app shell (ratified 2026-07-21): no max-width — this is a dense ops tool, so
   lists/dashboards fill the viewport like Jira. Inner surfaces that want a reading
   column cap themselves (form cards 560–720px, .kb-article 760px). Mobile is untouched:
   below 720px the rail is a drawer and the old 1180px cap never bound there anyway. */
.container{width:100%; padding:var(--s5) var(--s4);}
.stack{display:flex; flex-direction:column; gap:var(--s4);}
.row{display:flex; align-items:center; gap:var(--s3);}
.row-wrap{display:flex; flex-wrap:wrap; gap:var(--s3); align-items:center;}
.spacer{flex:1 1 auto;}
.grid{display:grid; gap:var(--s4);}
.grid-cards{display:grid; gap:var(--s4); grid-template-columns:repeat(auto-fill, minmax(240px,1fr));}
/* Click-to-enlarge <details> lightbox — shared by web/layouts/gallery.html and
   web/partials/media_gallery.html (M2). A closed <details> hides the overlay; opening it fixes
   a dimmed full-viewport layer over the page. Click anywhere on the overlay to close. */
.grid-cards details > .lightbox{
  position:fixed; inset:0; z-index:80; display:flex; align-items:center; justify-content:center;
  padding:var(--s5); background:rgba(24,43,58,.82); cursor:zoom-out;
}
.grid-cards details > .lightbox img{
  max-width:96vw; max-height:92vh; width:auto; height:auto;
  border-radius:var(--radius); box-shadow:var(--shadow-lg); background:var(--card);
}
.lightbox-close{
  position:fixed; top:max(12px, env(safe-area-inset-top)); right:12px;
  display:grid; place-items:center; width:44px; height:44px; padding:0;
  border:1px solid rgba(255,255,255,.6); border-radius:999px;
  background:rgba(24,43,58,.88); color:#fff; cursor:pointer;
}
.grid-cards details summary::-webkit-details-marker{display:none;}
.toolbar{display:flex; flex-wrap:wrap; align-items:center; gap:var(--s3); margin-bottom:var(--s4);}
.detail-grid{display:grid; gap:var(--s4); grid-template-columns:1fr;}
.kv{display:grid; grid-template-columns:auto 1fr; gap:var(--s2) var(--s4); align-items:baseline;}
.kv dt{color:var(--muted); font-size:13px;}
.kv dd{margin:0; color:var(--ink-700); font-variant-numeric:tabular-nums;}

/* 4. TYPE ------------------------------------------------------------------ */
.page-title{font-size:24px; font-weight:700; color:var(--ink); letter-spacing:-.02em; font-family:var(--font-display);}
.page-sub{color:var(--muted); font-size:15px; margin-top:2px;}
.eyebrow{font-size:12px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--brand);}
/* UX-006 — linked breadcrumb trail inside the eyebrow slot (web/partials/breadcrumbs.html).
   Ancestor levels stay brand-colored + underline on hover; the separator is de-emphasized. */
.eyebrow a{color:inherit; text-decoration:none;}
.eyebrow a:hover{text-decoration:underline;}
.eyebrow .bc-sep{color:var(--muted); font-weight:500;}
.card-title{font-size:16px; font-weight:650; color:var(--ink);}
.small{font-size:13px;}
.muted{color:var(--muted);}
.mono{font-family:var(--mono); font-variant-numeric:tabular-nums;}

/* 5. SHELL (base.html chrome) --------------------------------------------- */
/* The shell LAYOUT (the persistent left side-nav + light top strip) lives in
   the D5 "App shell" block below. These are the shared lockup + account/icon
   primitives reused by the dark rail (.brand) and the light top strip (.acct,
   .iconbtn). The old horizontal .topnav/.mobnav rules were retired in Pass 2. */
.brand{display:flex; align-items:center; gap:10px; color:#fff; font-weight:700; white-space:nowrap; min-width:0;}
.brand:hover{text-decoration:none; color:#fff;}
.brand-mark{height:28px; width:auto; flex:0 0 auto; display:block;}
.brand-word{font-size:16px; letter-spacing:-.01em; overflow:hidden; text-overflow:ellipsis;}
.brand-word b{font-weight:700;}
.brand-word span{font-weight:400; color:rgba(255,255,255,.62);}
.iconbtn{
  width:44px; height:44px; border-radius:var(--radius-sm); border:0; cursor:pointer;
  background:transparent; color:#fff; display:grid; place-items:center; font-size:18px;
  position:relative; flex:0 0 auto;
}
.iconbtn:hover{background:rgba(255,255,255,.12);}
.bell-dot{
  position:absolute; top:5px; right:5px; min-width:16px; height:16px; padding:0 4px;
  border-radius:9px; background:var(--brand); color:#fff; font-size:10px; font-weight:700;
  display:grid; place-items:center; line-height:1; border:2px solid var(--card);
}
/* Account switcher — lives in the LIGHT top strip, so it reads on light. */
.acct{
  display:flex; align-items:center; gap:8px; cursor:pointer; color:var(--ink-700);
  padding:4px 10px 4px 4px; border-radius:999px; border:1px solid var(--line);
  background:var(--card); min-height:44px;
}
.acct:hover{background:var(--surface);}
.acct-avatar{
  width:30px; height:30px; border-radius:50%; flex:0 0 auto;
  background:var(--brand-50); color:var(--brand-700); display:grid; place-items:center;
  font-weight:700; font-size:12px;
}
.acct-name{display:none; font-size:13px; line-height:1.15; text-align:left;}
.acct-name b{display:block; font-weight:600; color:var(--ink);}
.acct-name span{color:var(--muted); font-size:11px;}
.acct-caret{color:var(--muted); font-size:11px;}

/* Global user avatars + profile surfaces. Photo bytes are served through the authenticated
   avatar endpoint; the same square renders generated initials when no photo exists. */
.user-avatar-link{display:inline-flex; flex:0 0 auto; border-radius:50%; text-decoration:none;}
.user-avatar-link:hover{text-decoration:none; box-shadow:0 0 0 2px var(--focus-ring);}
.user-avatar{
  display:grid; place-items:center; flex:0 0 auto; border-radius:50%; object-fit:cover;
  background:var(--brand-50); color:var(--brand-700); border:1px solid rgba(227,32,36,.12);
  font-weight:700; line-height:1; overflow:hidden;
}
.user-avatar--xs{width:22px; height:22px; font-size:9px;}
.user-avatar--sm{width:26px; height:26px; font-size:10px;}
.user-avatar--md{width:30px; height:30px; font-size:12px;}
.user-avatar--lg{width:48px; height:48px; font-size:17px;}
.user-avatar--xl{width:72px; height:72px; font-size:24px;}
.profile-form-card{max-width:680px;}
.profile-name-grid{display:grid; grid-template-columns:1fr; gap:0 var(--s3);}
.profile-phone-grid{display:grid; grid-template-columns:minmax(150px, .85fr) minmax(0, 1.15fr); gap:var(--s2);}
.profile-custom-code{margin-top:var(--s2); max-width:280px;}
.profile-custom-code label{display:block; font-size:var(--text-sm); font-weight:600; margin-bottom:4px;}
.profile-photo-control{display:flex; align-items:flex-start; gap:var(--s3);}
.profile-photo-control input[type="file"]{padding:8px;}
.profile-remove-photo{margin-top:var(--s2);}
.profile-form-errors ul{margin:4px 0 0; padding-left:18px;}
.profile-heading{display:flex; align-items:center; gap:var(--s4); min-width:0;}
.profile-heading > div{min-width:0;}
.profile-heading .page-title,.profile-heading .page-sub{overflow-wrap:anywhere;}
.profile-detail-card{max-width:680px;}
.profile-kv{grid-template-columns:minmax(100px, 150px) 1fr;}
@media (min-width:600px){.profile-name-grid{grid-template-columns:1fr 1fr;}}
@media (max-width:480px){
  .profile-phone-grid{grid-template-columns:1fr;}
  .profile-pagehead{align-items:flex-start;}
  .profile-heading{align-items:flex-start;}
}

/* 6. COMPONENTS ------------------------------------------------------------ */

/* Cards */
.card{background:var(--card); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow);}
/* Signature brand flourish — opt-in */
.card-accent{border-top:3px solid var(--brand);}
.card-pad{padding:var(--s4);}
.card-head{
  display:flex; align-items:center; gap:var(--s3);
  padding:var(--s3) var(--s4); border-bottom:1px solid var(--line);
}
.card-head .card-title{margin-right:auto;}
.nav-card{
  display:flex; flex-direction:column; gap:6px; padding:var(--s4);
  background:var(--card); border:1px solid var(--line); border-radius:var(--radius);
  box-shadow:var(--shadow); color:var(--ink-700); transition:border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.nav-card:hover{border-color:var(--brand); box-shadow:var(--shadow-lg); text-decoration:none; transform:translateY(-1px);}
.nav-card .e{font-size:24px; line-height:1;}
.nav-card .nc-title{font-weight:650; color:var(--ink); font-size:15px;}
.nav-card .nc-sub{color:var(--muted); font-size:13px;}

/* Dashboard "Explore modules" tree — hierarchical map of the software (mirrors the
   side-rail's nav_groups). Cards of grouped module links; replaces the flat "Jump to" grid. */
.tree-grid{display:grid; gap:var(--s4); grid-template-columns:repeat(auto-fill, minmax(260px,1fr)); align-items:start;}
.module-group{
  background:var(--card); border:1px solid var(--line); border-radius:var(--radius);
  box-shadow:var(--shadow); padding:var(--s4);
}
.mg-head{
  display:flex; align-items:center; gap:8px; margin-bottom:8px;
  font-size:12px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--muted);
}
.mg-head .mg-ic{display:inline-flex; color:var(--brand);}
.mg-sub{
  margin:10px 0 2px; font-size:11px; font-weight:700; letter-spacing:.14em;
  text-transform:uppercase; color:var(--muted); opacity:.85;
}
.mg-list{display:flex; flex-direction:column;}
.mg-link{
  display:flex; align-items:flex-start; gap:10px; padding:8px;
  border-radius:var(--radius-sm); color:var(--ink-700);
}
.mg-link:hover{background:var(--surface); text-decoration:none;}
.mg-link .mg-lic{display:inline-flex; color:var(--muted); margin-top:1px; flex:0 0 auto;}
.mg-link:hover .mg-lic{color:var(--brand);}
.mg-lt{display:flex; flex-direction:column; gap:1px; min-width:0;}
.mg-ll{font-weight:600; font-size:14px; color:var(--ink);}
.mg-link:hover .mg-ll{color:var(--brand);}
.mg-ld{font-size:12px; color:var(--muted); line-height:1.35;}

/* Buttons */
.btn{
  --bg:var(--card); --fg:var(--ink-700); --bd:var(--line);
  display:inline-flex; align-items:center; justify-content:center; gap:7px;
  min-height:44px; padding:0 16px; border-radius:var(--radius-sm);
  border:1px solid var(--bd); background:var(--bg); color:var(--fg);
  font-size:14px; font-weight:600; cursor:pointer; white-space:nowrap;
  transition:background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.btn:hover{text-decoration:none; box-shadow:var(--shadow);}
.btn .btn-ic{display:inline-flex; width:16px; height:16px; flex:0 0 auto;}
.btn:disabled,.btn[aria-disabled="true"]{opacity:.55; cursor:not-allowed; box-shadow:none;}
.btn-primary{--bg:var(--brand); --fg:#fff; --bd:var(--brand);}
.btn-primary:hover{--bg:var(--brand-700); --bd:var(--brand-700); box-shadow:var(--glow-red);}
.btn-ghost{--bg:transparent; --fg:var(--ink-700); --bd:var(--line);}
.btn-ghost:hover{--bg:var(--surface);}
.btn-danger{--bg:var(--danger); --fg:#fff; --bd:var(--danger);}
.btn-danger:hover{filter:brightness(.94);}
.btn-sm{min-height:36px; padding:0 12px; font-size:13px;}
.btn-block{display:flex; width:100%;}

/* Forms */
.field{display:flex; flex-direction:column; gap:6px; margin-bottom:var(--s4);}
.field > label{font-size:13px; font-weight:600; color:var(--ink);}
.field > label .req{color:var(--danger); margin-left:2px;}
.input{
  width:100%; min-height:44px; padding:10px 12px;
  border:1px solid var(--line); border-radius:var(--radius-sm);
  background:var(--card); color:var(--ink-700); font-size:15px; font-family:inherit;
  transition:border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.input::placeholder{color:var(--subtle);}
.input:focus{outline:0; border-color:var(--brand); box-shadow:0 0 0 3px var(--focus-ring);}
textarea.input{min-height:104px; resize:vertical; line-height:1.5;}
select.input{appearance:none; background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%2345545F' d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E"); background-repeat:no-repeat; background-position:right 12px center; padding-right:34px;}
.helptext{font-size:13px; color:var(--muted);}
.errorlist{list-style:none; margin:0; padding:0; color:var(--danger); font-size:13px; display:flex; flex-direction:column; gap:2px;}
.errorlist[hidden]{display:none;}   /* author display:flex would otherwise beat the UA [hidden] rule */
.field.has-error .input{border-color:var(--danger); box-shadow:0 0 0 3px var(--danger-bg);}

/* Badges */
.badge{
  display:inline-flex; align-items:center; gap:5px; padding:3px 9px;
  border-radius:999px; font-size:12px; font-weight:600; line-height:1.4;
  background:var(--surface); color:var(--ink-700); border:1px solid var(--line);
  white-space:nowrap;
}
.badge-ok{background:var(--ok-bg); color:var(--ok-ink); border-color:transparent;}
.badge-warn{background:var(--warn-bg); color:var(--warn-ink); border-color:transparent;}
.badge-danger{background:var(--danger-bg); color:var(--danger); border-color:transparent;}
.badge-info{background:var(--info-bg); color:var(--info); border-color:transparent;}

/* Chips (filters) */
.chip{
  display:inline-flex; align-items:center; gap:6px; min-height:36px; padding:0 13px;
  border-radius:999px; border:1px solid var(--line); background:var(--card);
  color:var(--ink-700); font-size:13px; font-weight:600; cursor:pointer; white-space:nowrap;
  transition:background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.chip:hover{border-color:var(--ink); text-decoration:none;}
.chip .count{color:var(--muted); font-weight:600;}
.chip.is-active{background:var(--brand-50); border-color:var(--brand); color:var(--brand-700);}
.chip.is-active .count{color:var(--brand-700);}

/* Alerts */
.alert{display:flex; gap:10px; padding:12px 14px; border-radius:var(--radius); border:1px solid var(--line); background:var(--card); font-size:14px; color:var(--ink-700);}
.alert .e{flex:0 0 auto; font-size:16px; line-height:1.4;}
.alert-danger{background:var(--danger-bg); border-color:transparent; color:var(--danger);}
.alert-info{background:var(--info-bg); border-color:transparent; color:var(--info);}
.alert-warn{background:var(--warn-bg); border-color:transparent; color:var(--warn-ink);}
.alert-ok{background:var(--ok-bg); border-color:transparent; color:var(--ok-ink);}
/* UX-011 — dismiss ✕ on a flash alert (base.html messages loop). Small, borderless,
   inherits the alert's own ink so it reads correctly across all four alert variants. */
.alert-dismiss{
  flex:0 0 auto; width:22px; height:22px; padding:0; border:0; border-radius:6px;
  background:transparent; color:inherit; opacity:.65; cursor:pointer;
  display:grid; place-items:center; margin:-2px -4px -2px 0;
}
.alert-dismiss:hover{opacity:1; background:rgba(0,0,0,.06);}

/* Tables */
/* overflow-x:auto by default: a table wider than the wrap (tablet / split-screen widths)
   scrolls sideways instead of silently clipping its right-hand columns. overflow-y:clip
   keeps the card radius. Trade (same one .colmgr-scroll already makes): pairing auto with
   clip makes the wrap a scroll container, so the sticky thead sticks to the wrap rather
   than the page. */
/* position:relative anchors the colmgr full-height resize guide overlay. */
.table-wrap{position:relative; border:1px solid var(--line); border-radius:var(--radius); overflow-x:auto; overflow-y:clip; background:var(--card); box-shadow:var(--shadow);}
table.table{width:100%; border-collapse:collapse; font-size:14px;}
table.table th{
  text-align:left; font-size:12px; font-weight:700; letter-spacing:.04em; text-transform:uppercase;
  color:var(--muted); padding:11px 14px; border-bottom:1px solid var(--line); background:var(--surface);
  white-space:nowrap;
}
table.table td{padding:13px 14px; border-bottom:1px solid var(--line); color:var(--ink-700); vertical-align:middle;}
table.table tr:last-child td{border-bottom:0;}
/* Jira-style gridlines: verticals join the existing row borders. Tokens keep both themes. */
table.table th, table.table td{border-right:1px solid var(--line);}
table.table th:last-child, table.table td:last-child{border-right:0;}
/* Long free-text list cells. The full value ships to the browser (so widening the column
   with the chooser reveals more of it, and the title attribute shows all of it on hover);
   this caps how much width one long value can demand under table-layout:auto and trims the
   overflow with an ellipsis. Must be a block: max-width on a td itself is ignored under
   auto layout. */
.cell-clip{display:block; max-width:34ch; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
table.table td.num{font-variant-numeric:tabular-nums; text-align:right;}
/* Rows highlight on hover but no longer navigate as a whole: a row-wide click made the
   connected values inside it (location, vendor, asset…) unreachable, so the record is
   opened from its identity cells and every connected value links to its own page. */
.rowlink{transition:background var(--t-fast) var(--ease);}
.rowlink:hover{background:var(--surface);}
.rowlink a{font-weight:600; color:var(--ink);}
/* Identity links get the same quiet resting underline as .cell-link (row-action anchors
   are .btn and stay underline-free). */
.rowlink a:not(.btn){text-decoration:underline; text-underline-offset:2px;
  text-decoration-color:color-mix(in srgb, currentColor 35%, transparent);}
.rowlink a:not(.btn):hover, .rowlink a:not(.btn):focus-visible{text-decoration-color:currentColor;}
/* A connected record inside a cell — a quiet resting underline marks it as navigable
   (WCAG 1.4.1: not hover-only); hover/keyboard focus bring up the full link treatment. */
.cell-link{color:inherit; text-decoration:underline; text-underline-offset:2px;
  text-decoration-color:color-mix(in srgb, currentColor 35%, transparent);}
.cell-link:hover, .cell-link:focus-visible{color:var(--brand-700); text-decoration:underline;
  text-decoration-color:currentColor;}
.empty{
  text-align:center; padding:var(--s7) var(--s4); color:var(--muted);
  display:flex; flex-direction:column; align-items:center; gap:var(--s3);
}
.empty .e{font-size:34px;}
.empty .empty-title{color:var(--ink); font-weight:650; font-size:16px;}

/* Menu (Alpine) — opens on TAP */
.menu{position:relative; display:inline-block;}
.menu-btn{
  min-height:36px; padding:0 12px; border-radius:var(--radius-sm); border:1px solid var(--line);
  background:var(--card); color:var(--ink-700); font-weight:600; font-size:13px; cursor:pointer;
  display:inline-flex; align-items:center; gap:6px;
}
.menu-btn:hover{background:var(--surface);}
.menu-pop{
  position:absolute; right:0; top:calc(100% + 6px); z-index:40; min-width:190px;
  background:var(--card); border:1px solid var(--line); border-radius:var(--radius);
  box-shadow:var(--shadow-lg); padding:6px; display:flex; flex-direction:column; gap:2px;
}
.menu-pop[hidden]{display:none;}
.menu-item{
  display:flex; align-items:center; gap:9px; padding:9px 10px; border-radius:var(--radius-sm);
  color:var(--ink-700); font-size:14px; cursor:pointer; background:transparent; border:0; text-align:left; width:100%;
}
.menu-item:hover{background:var(--surface); text-decoration:none; color:var(--ink-700);}
.menu-item.is-danger{color:var(--danger);}
.menu-item.is-danger:hover{background:var(--danger-bg);}
.menu-sep{height:1px; background:var(--line); margin:4px 2px;}

/* Thread */
.thread{display:flex; flex-direction:column; gap:var(--s3);}
.msg{max-width:78%; padding:10px 13px; border-radius:var(--radius); font-size:14px; border:1px solid var(--line); background:var(--card);}
.msg .meta{font-size:12px; color:var(--muted); margin-bottom:3px; display:flex; gap:8px; align-items:baseline;}
.msg .meta b{color:var(--ink); font-weight:600;}
.msg.inbound{align-self:flex-start; border-left:3px solid var(--info); background:var(--card);}
.msg.outbound{align-self:flex-end; background:var(--accent); border-color:transparent;}

/* ==========================================================================
   7. D5 ADDITIONS — consolidated, declared, token-built. One class, many screens.
   ========================================================================== */

/* App shell — persistent LEFT side-nav (dark rail) + light top strip.
   Replaces the retired horizontal .topnav/.mobnav. Mobile-first: below md the
   rail is an off-canvas DRAWER opened by ☰ in the top strip (scrim + Esc +
   tap-outside close); from md it is a fixed rail the content offsets against,
   with an icon-only COLLAPSE state remembered in localStorage. Token-built. */
.applayout{display:flex; min-height:100vh;}

/* The rail */
.sidebar{
  position:fixed; inset:0 auto 0 0; width:248px; z-index:60;
  background:var(--rail-bg); color:#fff; display:flex; flex-direction:column;
  padding-top:env(safe-area-inset-top);
  transform:translateX(-100%); transition:transform var(--t-med) var(--ease), width var(--t-fast) var(--ease), background-color var(--t-slow) var(--ease);
}
.side-head{
  display:flex; align-items:center; gap:8px; min-height:64px;
  padding:12px 16px; border-bottom:1px solid rgba(255,255,255,.10);
}
.side-head .brand{flex:1; min-width:0;}
.drawer-close{color:#fff; flex:0 0 auto;}
.side-nav{flex:1; overflow-y:auto; overflow-x:hidden; padding:var(--s3); display:flex; flex-direction:column; gap:2px;}
.side-group{
  font-size:11px; font-weight:700; letter-spacing:.14em; text-transform:uppercase;
  color:rgba(255,255,255,.55); padding:14px 12px 4px;
}
.side-link{
  display:flex; align-items:center; gap:12px; min-height:44px; padding:0 12px;
  border-radius:var(--radius-sm); color:rgba(255,255,255,.82);
  font-size:14px; font-weight:500; white-space:nowrap; cursor:pointer; position:relative;
  background:transparent; border:0; width:100%; text-align:left; font-family:inherit;
}
.side-link:hover{background:var(--rail-hover); color:#fff; text-decoration:none;}
.side-link:focus-visible{outline:2px solid var(--rail-focus); outline-offset:-2px;}
/* Active row — translucent red fill + inset identity-red bar (nav design pass) */
.side-link.is-active{background:var(--rail-active); color:#fff; font-weight:600; box-shadow:inset 3px 0 0 var(--brand);}
.side-link .e{font-size:18px; line-height:1; flex:0 0 26px; text-align:center;}
.side-link .side-label{flex:1; overflow:hidden; text-overflow:ellipsis;}
/* Pending-count pill on a side-nav link (e.g. Requests, #138) — reads on the dark rail. */
.side-badge{
  flex:0 0 auto; min-width:18px; height:18px; padding:0 5px; border-radius:9px;
  background:var(--brand); color:#fff; font-size:11px; font-weight:700;
  display:grid; place-items:center; line-height:1;
}
.side-foot{display:none; padding:var(--s3); border-top:1px solid rgba(255,255,255,.10);}

/* Live status pill + appearance toggle in the top strip */
.livepill{display:none; align-items:center; gap:9px; padding:7px 14px 7px 12px; border-radius:999px; border:1px solid var(--ok); border:1px solid color-mix(in srgb, var(--ok) 42%, transparent); background:transparent; background:color-mix(in srgb, var(--ok) 12%, transparent); white-space:nowrap; line-height:1;}
.livepill .lp-dot{position:relative; width:8px; height:8px; border-radius:50%; background:var(--ok); flex:0 0 auto;}
.livepill .lp-dot::after{content:""; position:absolute; inset:-4px; border-radius:50%; border:1px solid var(--ok); animation:lp-pulse 2.2s ease-out infinite;}
.livepill .lp-live{color:var(--ok-ink); font-weight:700; font-size:13.5px;}
.livepill .lp-time{color:var(--ink); font-weight:600; font-size:13.5px; font-variant-numeric:tabular-nums;}
@keyframes lp-pulse{0%{transform:scale(1); opacity:.55;} 70%{transform:scale(2); opacity:0;} 100%{opacity:0;}}
.modebtn{display:inline-flex; align-items:center; gap:8px; min-height:40px; padding:0 13px; border-radius:var(--radius-sm); border:1px solid var(--line); background:var(--surface); color:var(--ink); font-family:inherit; font-size:13.5px; font-weight:600; cursor:pointer; white-space:nowrap; transition:background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);}
.modebtn:hover{border-color:var(--ink); background:var(--card);}
.modebtn .mb-ic{display:inline-flex; width:17px; height:17px;}
.modebtn.is-day .mb-ic{color:var(--warn);}
.modebtn.is-night .mb-ic{color:var(--info);}
.mode-auto-tag{font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--muted); border:1px solid var(--line); border-radius:5px; padding:1px 5px; margin-left:1px;}
.theme-pop .theme-opt-ic{display:inline-flex; width:16px; height:16px; color:var(--brand); flex:0 0 auto;}
.theme-pop .theme-opt-check{display:none; width:15px; height:15px; color:var(--brand); flex:0 0 auto;}
.theme-pop .menu-item.is-current .theme-opt-check{display:inline-flex;}
.menu-item .mi-ic{display:inline-flex; width:16px; height:16px; color:var(--brand); flex:0 0 auto;}
.menu-item.is-danger .mi-ic{color:inherit;}

/* Content column */
.appmain{flex:1; min-width:0; display:flex; flex-direction:column; min-height:100vh;}
.topstrip{
  position:sticky; top:0; z-index:40; display:flex; align-items:center; gap:var(--s3);
  min-height:56px; padding:6px var(--s4); padding-top:calc(6px + env(safe-area-inset-top));
  background:var(--card); border-bottom:1px solid var(--line);
}
.topstrip .iconbtn{color:var(--ink-700);}
.topstrip .iconbtn:hover{background:var(--surface);}
.page-context{
  font-weight:650; color:var(--ink); font-size:15px; min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.drawer-btn{display:grid;}
/* Tiny phones: the strip's fixed-width children (~410px worth) would overflow the viewport,
   so tighten the gap one more step and let everything fit without body-level horizontal
   scroll. The rest of the phone treatment (square icon-only .modebtn, hidden mode label,
   compacted .acct) lives in the max-width:719px branch further down, which also wins the
   cascade at this width. */
@media (max-width:479px){
  .topstrip{gap:var(--s1);}
}

/* Scrim behind the mobile drawer */
.side-scrim{
  position:fixed; inset:0; z-index:55; background:rgba(24,43,58,.5);
  width:100%; height:100%; padding:0; border:0; border-radius:0;
  opacity:0; pointer-events:none; transition:opacity var(--t-med) var(--ease);
}
.applayout.drawer-open .sidebar{transform:translateX(0);}
.applayout.drawer-open .side-scrim{opacity:1; pointer-events:auto;}
/* Below md the CLOSED drawer must leave the tab order / AT tree — transform alone keeps
   its ~30 offscreen links focusable. visibility flips after the slide-out finishes
   (0s transition delayed by --t-med) and instantly on open. */
@media (max-width:719px){
  .sidebar{visibility:hidden;
    transition:transform var(--t-med) var(--ease), visibility 0s linear var(--t-med),
      background-color var(--t-slow) var(--ease);}
  .applayout.drawer-open .sidebar{visibility:visible;
    transition:transform var(--t-med) var(--ease), visibility 0s,
      background-color var(--t-slow) var(--ease);}
}

/* Page header rhythm — eyebrow → title → actions. Used on every staff page. */
.page-head{display:flex; align-items:flex-start; gap:var(--s4); margin-bottom:var(--s5);}
.page-head > :first-child{flex:1 1 auto; min-width:0;}
.page-head .ph-actions{margin-left:auto; display:flex; gap:var(--s2); flex-wrap:wrap;}

/* KPI stat tiles — dashboards, reports, portals */
.statkit{display:flex; gap:12px; flex-wrap:wrap;}
.stat{flex:1 1 150px; padding:14px 16px; background:var(--card); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow);}
.stat .n{font-size:26px; font-weight:700; color:var(--ink); letter-spacing:-.02em; font-variant-numeric:tabular-nums; line-height:1.1;}
.stat .l{font-size:12px; color:var(--muted); text-transform:uppercase; letter-spacing:.14em; margin-top:4px;}
.stat .d{font-size:12px; margin-top:6px; color:var(--muted);}
/* is-alert must out-shout brand red: deeper danger hue + weight + left rail + ⏰ emoji in markup */
.stat.is-alert{border-left:3px solid var(--danger);}
.stat.is-alert .n{color:var(--danger);}

/* Lifecycle stepper — status progress from the badge palette */
.stepper{display:flex; align-items:center; gap:6px; flex-wrap:wrap; font-size:12px;}
.step{color:var(--muted); display:inline-flex; align-items:center; gap:6px;}
.step::after{content:"\203A"; color:var(--line); margin-left:6px;}
.step:last-child::after{content:"";}
.step.is-done{color:var(--ok);}
.step.is-current{color:var(--brand-700); font-weight:700;}

/* Urgency accent — left rail for escalated / overdue records (pairs with ⏰/🔴 + weight in markup) */
.card.is-urgent{border-left:3px solid var(--danger);}

/* Sticky action bar — primary action in the thumb zone on phones; static from md up */
.actionbar{
  display:flex; gap:8px; position:sticky; bottom:0; z-index:20;
  background:var(--card); border-top:1px solid var(--line);
  padding:10px 12px calc(10px + env(safe-area-inset-bottom)); margin:0 -12px -12px;
  box-shadow:0 -2px 12px rgba(24,43,58,.08);
}
.actionbar .btn{flex:1;}

/* HTMX in-flight cue — fade the swapping region + show a spinner. Universal loading idiom.
   pointer-events:none is load-bearing, not tidiness: opacity:0 hides the "Working…" label
   but keeps its box, and inside a status chip on a 375px rowcard that invisible box lands
   on top of the row's own View / Edit controls and swallows the tap. A status message is
   never interactive, in flight or idle, so it never wants the pointer. */
.htmx-indicator{opacity:0; pointer-events:none; transition:opacity var(--t-fast) var(--ease);}
.htmx-request .htmx-indicator{opacity:1;}
.htmx-request.htmx-indicator{opacity:1;}
.is-swapping{opacity:.55; pointer-events:none;}
.spin{display:inline-block; width:15px; height:15px; border:2px solid var(--line); border-top-color:var(--brand); border-radius:50%; animation:ps-spin .7s linear infinite; vertical-align:-2px;}
@keyframes ps-spin{to{transform:rotate(360deg);}}

/* Toast — transient confirmation after an HTMX action (e.g. "Saved", "Marked read") */
.toast-wrap{position:fixed; left:50%; bottom:calc(18px + env(safe-area-inset-bottom)); transform:translateX(-50%); z-index:90; display:flex; flex-direction:column; gap:8px; width:min(420px,calc(100% - 24px));}
.toast{display:flex; align-items:center; gap:10px; padding:12px 14px; border-radius:var(--radius); background:var(--ink); color:#fff; box-shadow:var(--shadow-lg); font-size:14px; animation:toast-in var(--t-med) var(--ease);}
/* A toast that carries a follow-up link (bulk "View details") keeps the link and its
   dismiss control at the trailing edge, inheriting the tone's own foreground so the
   contrast pairing is decided once, by the tone rule, not per control. */
.toast .toast-action{margin-left:auto; color:inherit; font-weight:600; text-decoration:underline; white-space:nowrap;}
.toast .toast-x{border:0; background:none; color:inherit; opacity:.75; cursor:pointer; padding:4px 6px; margin:-4px -6px -4px 0; border-radius:6px; font-size:15px; line-height:1;}
.toast .toast-x:hover{opacity:1;}

/* Inline single-field edit (partials/inline_field.html + inline_editor.html).
   The read state is the value plus a low-contrast pencil that only gains weight on
   hover/focus — the field must read as a value first and a control second, or a dense
   detail page turns into a wall of affordances. The pencil keeps a 44px hit box on
   touch without taking 44px of layout, so an inline field still sits on one text line. */
.ip-field{display:inline-flex; align-items:center; gap:6px; flex-wrap:wrap;}
.ip-read{display:inline-flex; align-items:center; justify-content:center; color:var(--muted);
  border-radius:6px; padding:2px; opacity:.55; transition:opacity var(--t-fast) var(--ease), color var(--t-fast) var(--ease);}
.ip-read:hover,.ip-read:focus-visible{opacity:1; color:var(--brand-700); background:var(--brand-50);}
.ip-field:hover .ip-read{opacity:1;}
@media (hover:none){.ip-read{opacity:1; position:relative;}
  .ip-read::after{content:""; position:absolute; inset:-12px; }}
.ip-editing{display:inline-flex; align-items:center; gap:6px; flex-wrap:wrap;}
.ip-editing .ip-input{width:auto; min-width:140px; max-width:100%;}
.ip-ok,.ip-cancel{display:inline-flex; align-items:center; justify-content:center; min-width:32px; min-height:32px;
  border:1px solid var(--line); background:var(--card); color:var(--muted); border-radius:6px; cursor:pointer;}
.ip-ok{color:var(--ok-ink); border-color:var(--ok-bg);}
.ip-ok:hover,.ip-cancel:hover{background:var(--surface); color:var(--ink);}
.ip-error{color:var(--danger); font-size:13px; flex-basis:100%;}
@media (max-width:719px){.ip-ok,.ip-cancel{min-width:44px; min-height:44px;}}

/* Floating bulk-actions bar (partials/bulk_toolbar.html). Hidden until rows are ticked
   (Alpine x-show on n), then slides up bottom-center like a toast — Jira-style. Sits
   under backdrops/modals/toasts (79/80) and above docked bars (20). */
.bulkbar{
  position:fixed; left:0; right:0; margin-inline:auto; width:fit-content;
  max-width:min(92vw, 800px); bottom:calc(18px + env(safe-area-inset-bottom)); z-index:70;
  background:var(--card); border:1px solid var(--line); border-radius:var(--radius);
  box-shadow:var(--shadow-lg); padding:10px 14px; animation:toast-in var(--t-med) var(--ease);
}
.bulkbar .bulk-count{font-weight:700; color:var(--brand-700); white-space:nowrap;}
.bulkbar .input{width:auto;}  /* .input is 100%-wide in forms; here it must sit inline */
.bulkbar .bulk-x{border:0; background:none; color:var(--muted); cursor:pointer; padding:4px 6px; border-radius:6px; font-size:15px; line-height:1;}
.bulkbar .bulk-x:hover{color:var(--ink); background:var(--surface);}
@media (max-width:719px){
  .bulkbar{max-width:calc(100vw - 16px);}
}
.toast.is-danger{background:var(--danger);}
.toast.is-warning{background:var(--warn-bg); color:var(--warn-ink);}
.toast.is-info{background:var(--info-bg); color:var(--info);}
.toast.is-success{background:var(--ok-bg); color:var(--ok-ink);}
@keyframes toast-in{from{opacity:0; transform:translateY(8px);}}

/* Data-list → cards collapse. Below md, .table-wrap[data-collapse] hides the table and shows .rowcards. */
.rowcards{display:flex; flex-direction:column; gap:var(--s3);}
.rowcard{background:var(--card); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow); padding:var(--s4); display:flex; flex-direction:column; gap:10px;}
.rowcard .rc-head{display:flex; align-items:flex-start; gap:var(--s3);}
.rowcard .rc-head .rc-title{font-weight:650; color:var(--ink); font-size:15px;}
.rowcard .rc-kv{display:flex; flex-wrap:wrap; gap:4px 18px; font-size:13px;}
.rowcard .rc-kv b{color:var(--muted); font-weight:600; margin-right:6px;}
.rowcard .rc-kv span{color:var(--ink-700); font-variant-numeric:tabular-nums;}
.rowcards{display:none;}                 /* desktop: hidden; phone branch flips it on */

/* Chip strip — long filter rows scroll horizontally on phones (chips may scroll; tables may not) */
.chipstrip{display:flex; gap:var(--s2); overflow-x:auto; -webkit-overflow-scrolling:touch; scrollbar-width:none; padding-bottom:2px;}
.chipstrip::-webkit-scrollbar{display:none;}

/* Callout card — Ice Blue accent for info/explanatory cards ONLY (default cards stay white) */
.card.is-callout{background:var(--accent); border-color:transparent;}

/* Print sheet (QR labels) */
.label-sheet{display:grid; grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); gap:10px;}
.label{border:1px solid var(--line); border-radius:var(--radius-sm); padding:10px; text-align:center; display:flex; flex-direction:column; gap:6px; align-items:center;}
.qr-stub{width:88px; height:88px; border-radius:6px; background:repeating-conic-gradient(var(--ink) 0% 25%, #fff 0% 50%) 50%/12px 12px; }

/* PPM month-grid / schedule calendar — NEW for the PPM section (Pass 4).
   DECLARED here + back-referenced in the component library (Design-System D5).
   A 7-col month grid of scheduled PPM occurrences; event chips reuse the badge
   palette (info=scheduled, warn=due, danger=overdue, ok=done). Token-built.
   Mobile-first: the dense grid is a DESKTOP affordance (mouse); below md the
   calwrap[data-collapse] hides the grid and shows the comfortable .cal-agenda
   stack (full-width ≥44px rows) — same table→cards philosophy as data-collapse. */
.calwrap{border:1px solid var(--line); border-radius:var(--radius); overflow:hidden; background:var(--card); box-shadow:var(--shadow);}
.cal-dow{display:grid; grid-template-columns:repeat(7,1fr); background:var(--surface); border-bottom:1px solid var(--line);}
.cal-dow span{padding:9px 10px; font-size:11px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--muted);}
.cal-grid{display:grid; grid-template-columns:repeat(7,1fr);}
.cal-cell{min-height:108px; padding:7px 7px 8px; border-right:1px solid var(--line); border-bottom:1px solid var(--line); display:flex; flex-direction:column; gap:5px;}
.cal-cell:nth-child(7n){border-right:0;}
.cal-grid > .cal-cell:nth-last-child(-n+7){border-bottom:0;}
.cal-cell.is-out{background:var(--surface);}
.cal-cell.is-today{background:var(--brand-50);}
.cal-date{font-size:13px; font-weight:600; color:var(--ink); font-variant-numeric:tabular-nums;}
.cal-cell.is-out .cal-date{color:var(--subtle);}   /* de-emphasized but ≥3:1, both themes */
.cal-cell.is-today .cal-date{color:var(--brand-700);}
.cal-event{display:flex; align-items:center; gap:5px; padding:4px 7px; border-radius:6px; font-size:12px; font-weight:600; line-height:1.25; background:var(--info-bg); color:var(--info); cursor:pointer; border:0; width:100%; text-align:left; font-family:inherit; overflow:hidden;}
.cal-event span{overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.cal-event:hover{filter:brightness(.97);}
.cal-event.is-due{background:var(--warn-bg); color:var(--warn-ink);}
.cal-event.is-overdue{background:var(--danger-bg); color:var(--danger);}
.cal-event.is-done{background:var(--ok-bg); color:var(--ok-ink);}
.cal-more{font-size:11px; color:var(--muted); font-weight:600; padding:1px 7px;}
/* Agenda fallback (phone) — comfortable ≥44px tappable day rows */
.cal-agenda{display:none; flex-direction:column; gap:var(--s3);}
.cal-agenda .cal-day{display:flex; flex-direction:column; gap:8px;}
.cal-agenda .cal-day-label{font-size:12px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--muted);}
.cal-agenda .cal-day-label.is-today{color:var(--brand-700);}
.cal-agenda .cal-row{display:flex; align-items:center; gap:10px; min-height:48px; padding:8px 12px; border-radius:var(--radius-sm); border:1px solid var(--line); background:var(--card); cursor:pointer; color:var(--ink-700);}
.cal-agenda .cal-row:hover{background:var(--surface); text-decoration:none;}
.cal-agenda .cal-row .ci{flex:1; min-width:0;}
.cal-agenda .cal-row .ci b{display:block; font-weight:650; color:var(--ink); font-size:14px;}
.cal-agenda .cal-row .ci span{font-size:12px; color:var(--muted);}
.badge-dot{width:10px; height:10px; border-radius:3px; flex:0 0 auto; background:var(--info);}
.badge-dot.is-due{background:var(--warn);}
.badge-dot.is-overdue{background:var(--danger);}
.badge-dot.is-done{background:var(--ok-ink);}

/* PPM calendar — the three NEW components layered on the cal-* grid/agenda above:
   interactive legend-filter, toolbar month label + grid/agenda toggle, and the
   click-to-open event popover (bottom-sheet on phone). Token-built; Alpine-driven. */
.cal-legend{display:flex; flex-wrap:wrap; gap:8px; margin-bottom:var(--s3);}
.cal-legend .leg{display:inline-flex; align-items:center; gap:7px; padding:5px 11px; border:1px solid var(--line); border-radius:999px; background:var(--card); font-size:12px; font-weight:600; color:var(--ink-700); cursor:pointer; font-family:inherit;}
.cal-legend .leg .sw{width:11px; height:11px; border-radius:3px; flex:0 0 auto;}
.cal-legend .leg-scheduled .sw{background:var(--info);}
.cal-legend .leg-due .sw{background:var(--warn);}
.cal-legend .leg-overdue .sw{background:var(--danger);}
.cal-legend .leg-done .sw{background:var(--ok-ink);}
.cal-legend .leg[aria-pressed="false"]{opacity:.42;}
.cal-legend .leg:hover{background:var(--surface);}
.cal-toolbar-label{font-size:15px; font-weight:700; color:var(--ink); font-variant-numeric:tabular-nums; letter-spacing:-.01em;}
.seg{display:inline-flex; border:1px solid var(--line); border-radius:var(--radius-sm); overflow:hidden; background:var(--card);}
.seg button{border:0; background:transparent; padding:0 14px; min-height:36px; font-size:13px; font-weight:600; color:var(--ink-700); cursor:pointer; font-family:inherit;}
.seg button + button{border-left:1px solid var(--line);}
.seg button.is-active{background:var(--brand-50); color:var(--brand-700);}
/* Desktop grid⇄agenda swap (the phone swap lives in the max-width:719 block) */
@media (min-width:720px){
  .calwrap.is-agenda .cal-grid,
  .calwrap.is-agenda .cal-dow{display:none;}
  .calwrap.is-agenda .cal-agenda{display:flex; padding:var(--s3);}
}
.cal-pop{position:fixed; z-index:80; width:min(320px, 100vw - 24px); background:var(--card); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow-lg); padding:14px 15px; display:flex; flex-direction:column; gap:8px;}
.cal-pop .cp-head{display:flex; align-items:center; justify-content:space-between; gap:8px;}
.cal-pop .cp-x{border:0; background:transparent; color:var(--muted); font-size:15px; cursor:pointer; line-height:1; padding:2px 4px;}
.cal-pop .cp-title{font-size:15px; font-weight:700; color:var(--ink); line-height:1.3;}
.cal-pop .cp-kv{display:flex; gap:8px; font-size:13px; color:var(--ink-700);}
.cal-pop .cp-kv b{flex:0 0 76px; color:var(--muted); font-weight:600;}
.cal-pop .cp-check b{display:block; font-size:12px; color:var(--muted); margin-bottom:3px;}
.cal-pop .cp-check ul{margin:0; padding-left:18px; font-size:13px; color:var(--ink-700); display:flex; flex-direction:column; gap:2px;}
.cal-pop .cp-actions{display:flex; flex-wrap:wrap; gap:7px; margin-top:5px; padding-top:10px; border-top:1px solid var(--line);}
.cal-pop .cp-actions .btn, .cal-pop .cp-actions form{flex:0 0 auto;}
/* PPM calendar v2 (PPM_CALENDAR_V2) — asset-grouped chips open a CENTERED day-detail modal
   (scrim + card listing each underlying visit) instead of the anchored popover. Reuses the
   .cal-pop card tokens; scrim sits just under the modal (toasts/lightbox live at z 80).
   Phone: drops to a bottom sheet exactly like .cal-pop (see the max-width:719 block). */
.cal-modal-backdrop{position:fixed; inset:0; z-index:79; background:rgba(28,27,26,.38);}
.cal-modal{position:fixed; z-index:80; left:50%; top:50%; transform:translate(-50%,-50%); width:min(560px, 100vw - 24px); max-height:85vh; overflow-y:auto; background:var(--card); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow-lg); padding:14px 15px; display:flex; flex-direction:column; gap:8px;}
.cal-modal .cp-head{display:flex; align-items:center; justify-content:space-between; gap:8px;}
.cal-modal .cp-x{border:0; background:transparent; color:var(--muted); font-size:15px; cursor:pointer; line-height:1; padding:2px 4px;}
.cal-modal .cp-title{font-size:15px; font-weight:700; color:var(--ink); line-height:1.3;}
.cal-modal .cm-date{margin-top:-4px;}
.cal-modal .cm-list{display:flex; flex-direction:column; gap:10px; margin-top:4px;}
.cal-modal .cm-row{border:1px solid var(--line); border-radius:var(--radius-sm); padding:10px 12px; display:flex; flex-direction:column; gap:4px;}
.cal-modal .cm-row-head{display:flex; align-items:center; justify-content:space-between; gap:8px;}
.cal-modal .cm-ref{font-size:13px; font-weight:600; color:var(--ink);}
.cal-modal .cm-sched{font-size:13px; color:var(--ink-700);}
.cal-modal .cm-actions{display:flex; flex-wrap:wrap; gap:7px; margin-top:4px;}

/* Clock-in/out timeline strip — NEW for the Attendance section (Pass 6).
   DECLARED here + back-referenced in the component library (Design-System D5).
   A horizontal day timeline for one person: the scheduled shift WINDOW (ice-blue
   band) with the ACTUAL worked segment overlaid (ok=on time, warn=late), hour
   ticks, and IN/OUT/NOW pins. Token-built; reuses the badge colour semantics.
   Positions are author-set inline %s (left/width) computed against the strip's
   own time span — same approach as the calendar's cell math. Mobile-first: the
   strip is fluid (100% wide) and never needs the table→cards swap. */
.clockstrip{display:flex; flex-direction:column; gap:var(--s3);}
.cs-meta{display:flex; flex-wrap:wrap; gap:6px 16px; font-size:13px; color:var(--muted);}
.cs-meta b{color:var(--ink); font-weight:600; font-variant-numeric:tabular-nums;}
.cs-bar{position:relative; height:38px; margin:24px 0 6px; border-radius:var(--radius-sm); background:var(--surface); border:1px solid var(--line);}
.cs-window{position:absolute; top:0; bottom:0; background:var(--accent); border-left:1px dashed var(--info); border-right:1px dashed var(--info);}
.cs-fill{position:absolute; top:6px; bottom:6px; border-radius:5px; background:var(--ok-bg); border:1px solid var(--ok);}
.cs-fill.is-late{background:var(--warn-bg); border-color:var(--warn);}
.cs-fill.is-absent{background:var(--danger-bg); border-color:var(--danger);}
.cs-tick{position:absolute; top:-22px; font-size:11px; color:var(--muted); transform:translateX(-50%); font-variant-numeric:tabular-nums; white-space:nowrap;}
.cs-tick::after{content:""; position:absolute; left:50%; top:17px; width:1px; height:10px; background:var(--line);}
.cs-pin{position:absolute; top:-7px; bottom:-7px; width:2px; background:var(--ink); border-radius:2px;}
.cs-pin.is-late{background:var(--warn);}
.cs-pin.is-now{background:var(--brand);}
.cs-pin .cs-lab{position:absolute; top:-22px; left:50%; transform:translateX(-50%); white-space:nowrap; font-size:11px; font-weight:700; color:var(--ink);}
.cs-pin.is-late .cs-lab{color:var(--warn-ink);}
.cs-pin.is-now .cs-lab{color:var(--brand-700);}
.cs-legend{display:flex; flex-wrap:wrap; gap:6px 14px; font-size:12px; color:var(--muted);}
.cs-legend .k{display:inline-flex; align-items:center; gap:6px;}
.cs-legend .sw{width:12px; height:12px; border-radius:3px; border:1px solid var(--line);}
.cs-legend .sw.ok{background:var(--ok-bg); border-color:var(--ok);}
.cs-legend .sw.late{background:var(--warn-bg); border-color:var(--warn);}
.cs-legend .sw.win{background:var(--accent); border-color:var(--info);}

/* Notification feed — NEW for the Notifications section (Pass 7).
   DECLARED here + back-referenced in the component library (Design-System D5).
   The grouped-day alert feed behind the 🔔 bell: day-group labels (Today /
   Earlier) over .notif-row list items with an UNREAD treatment (leading brand
   dot + bold ink title) that READ rows lose (muted, dot hidden). Each row pairs
   a neutral type-icon chip (reuses the emoji icon vocabulary), a deep-linked
   source ref (.notif-src → brand link to its WO/Ticket/PPM/Vendor/Attendance),
   a relative timestamp and a per-row kebab of actions. Urgency reuses the same
   semantics as .card.is-urgent (deeper danger hue + ⏰/🔴 + left rail + weight):
   .notif-row.is-urgent. Token-built; mobile-first (fluid — never needs the
   table→cards swap, it is already a card-like list). */
.notiffeed{display:flex; flex-direction:column; gap:var(--s5);}
.notif-group{display:flex; flex-direction:column; gap:var(--s2);}
.notif-group-label{font-size:11px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--muted); padding:0 2px;}
.notif-list{display:flex; flex-direction:column; background:var(--card); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow); overflow:hidden;}
.notif-row{display:flex; align-items:flex-start; gap:var(--s3); padding:var(--s3) var(--s4); min-height:64px; border-bottom:1px solid var(--line); position:relative;}
.notif-row:last-child{border-bottom:0;}
.notif-row.is-urgent{border-left:3px solid var(--danger);}     /* same semantic as .card.is-urgent */
.notif-dot{flex:0 0 auto; width:9px; height:9px; border-radius:50%; background:var(--brand); margin-top:14px;}
.notif-row.is-read .notif-dot{visibility:hidden;}
.notif-ico{flex:0 0 auto; width:40px; height:40px; border-radius:var(--radius-sm); display:grid; place-items:center; font-size:19px; background:var(--surface); margin-top:2px;}
.notif-row.is-urgent .notif-ico{background:var(--danger-bg);}
.notif-main{flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:4px;}
.notif-title{font-size:14px; font-weight:700; color:var(--ink); line-height:1.35; text-wrap:pretty;}
.notif-row.is-read .notif-title{font-weight:600; color:var(--ink-700);}
.notif-sub{display:flex; flex-wrap:wrap; align-items:baseline; gap:3px 9px; font-size:13px; color:var(--muted);}
.notif-src{font-weight:600;}     /* brand link colour inherited from a{} */
.notif-aside{flex:0 0 auto; display:flex; flex-direction:column; align-items:flex-end; gap:6px;}
.notif-time{font-size:12px; color:var(--muted); white-space:nowrap; font-variant-numeric:tabular-nums;}

/* Role-permissions grid — NEW for the Members section (Pass 8).
   DECLARED here + back-referenced in the component library (Design-System D5).
   A per-surface allow/scope list on a member's profile that REFLECTS the D2 role
   nav matrix: one .permrow per surface (emoji icon + label + an access badge).
   Granted rows carry an .badge-ok "Full" / neutral .badge "View" pill; denied
   rows get .permrow.is-denied (muted label + a muted "—"). Token-built; reuses
   the badge palette + the .acct-avatar treatment for member rows elsewhere.
   Mobile-first: fluid single column — never needs the table→cards swap. */
.permlist{display:flex; flex-direction:column;}
.permrow{display:flex; align-items:center; gap:var(--s3); min-height:44px; padding:7px 0; border-bottom:1px solid var(--line);}
.permrow:last-child{border-bottom:0;}
.permrow .pr-ico{flex:0 0 26px; text-align:center; font-size:17px; line-height:1;}
.permrow .pr-label{flex:1 1 auto; min-width:0; font-size:14px; font-weight:500; color:var(--ink-700);}
.permrow.is-denied .pr-ico{opacity:.4;}
.permrow.is-denied .pr-label{color:var(--muted);}
.permrow .pr-access{flex:0 0 auto;}
.permrow .pr-denied{font-size:13px; color:var(--muted); font-weight:600;}

/* Before→after diff block — NEW for the Audit section (Pass 9).
   DECLARED here + back-referenced in the component library (Design-System D5).
   On an audit event DETAIL, the immutable record of WHICH fields changed: one
   .diffrow per field (uppercase .diff-field label) carrying the OLD value
   (.diff-old — struck-through danger pill = "was") joined by a → .diff-arrow to
   the NEW value (.diff-new — ok pill = "now"). Added fields may omit .diff-old;
   removed fields omit .diff-new. Reuses the badge colour semantics (danger=was,
   ok=now); the audit FEED itself reuses .notif-group/.notif-list/.notif-row with
   an .acct-avatar actor in place of .notif-ico. Token-built; mobile-first — the
   change row wraps, never needs the table→cards swap. */
.difflist{display:flex; flex-direction:column;}
.diffrow{display:flex; flex-direction:column; gap:7px; padding:13px 0; border-bottom:1px solid var(--line);}
.diffrow:first-child{padding-top:0;}
.diffrow:last-child{border-bottom:0; padding-bottom:0;}
.diff-field{font-size:11px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--muted);}
.diff-change{display:flex; align-items:center; gap:10px; flex-wrap:wrap;}
.diff-val{
  display:inline-flex; align-items:center; gap:6px; padding:5px 11px; border-radius:var(--radius-sm);
  font-size:14px; font-weight:600; font-variant-numeric:tabular-nums;
  border:1px solid var(--line); background:var(--surface); color:var(--ink-700);
}
.diff-old{background:var(--danger-bg); border-color:transparent; color:var(--danger); text-decoration:line-through; text-decoration-thickness:1px;}
.diff-new{background:var(--ok-bg); border-color:transparent; color:var(--ok-ink);}
.diff-arrow{flex:0 0 auto; color:var(--muted); font-size:15px; line-height:1;}

/* Record attribution + Jira-style History ----------------------------------
   Shared by Location, Asset and Work-item detail pages. Actor stamps expose
   their exact timestamp on hover/focus/tap while keeping the default line
   compact; the immutable history itself remains a simple mobile-first list. */
.actor-avatar-link{display:inline-flex; flex:0 0 auto; border-radius:50%; text-decoration:none; vertical-align:middle;}
.actor-avatar-link:hover{text-decoration:none; box-shadow:0 0 0 2px var(--focus-ring);}
.actor-avatar{
  display:inline-grid; place-items:center; flex:0 0 auto; overflow:hidden; border-radius:50%;
  object-fit:cover; background:var(--brand-50); color:var(--brand-700); font-weight:700;
  border:1px solid color-mix(in srgb, var(--brand) 18%, var(--line)); vertical-align:middle;
}
.actor-avatar-sm{width:24px; height:24px; font-size:9px;}
.actor-avatar-md{width:34px; height:34px; font-size:11px;}
.actor-avatar.is-former{filter:saturate(.55); background:var(--surface); color:var(--muted);}
.record-attribution{
  display:flex; flex-direction:column; gap:8px; padding:var(--s3); background:var(--card);
  border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow);
}
.audit-stamp{position:relative; display:inline-flex; max-width:100%; color:var(--muted); outline:none;}
.audit-stamp-line{display:flex; align-items:center; flex-wrap:wrap; gap:5px; font-size:12px; line-height:1.4;}
.audit-actor-name{color:var(--ink-700); font-weight:600;}
.audit-stamp-tooltip{
  position:absolute; z-index:30; left:0; bottom:calc(100% + 7px); display:none; width:max-content;
  max-width:min(320px, 80vw); padding:6px 9px; border-radius:6px; background:var(--ink);
  color:#fff; font-size:11px; line-height:1.35; box-shadow:var(--shadow-lg); pointer-events:none;
}
.audit-stamp:hover .audit-stamp-tooltip,
.audit-stamp:focus .audit-stamp-tooltip,
.audit-stamp:focus-within .audit-stamp-tooltip{display:block;}
.record-history{overflow:visible;}
.history-list{list-style:none; margin:0; padding:0 var(--s4);}
.history-entry{display:grid; grid-template-columns:34px minmax(0, 1fr); gap:var(--s3); padding:var(--s4) 0; border-bottom:1px solid var(--line);}
.history-entry:last-child{border-bottom:0;}
.history-marker{position:relative; align-self:start;}
.history-marker::after{content:""; position:absolute; top:39px; bottom:calc(-1 * var(--s4) - 1px); left:16px; width:1px; background:var(--line);}
.history-entry:last-child .history-marker::after{display:none;}
.history-body{min-width:0;}
.history-heading{display:flex; align-items:baseline; justify-content:space-between; flex-wrap:wrap; gap:4px var(--s3); color:var(--ink-700); font-size:13px;}
.history-time{font-size:12px; white-space:nowrap;}
.history-changes{display:flex; flex-direction:column; gap:7px; margin:10px 0 0;}
.history-change{display:grid; grid-template-columns:minmax(88px, .45fr) minmax(0, 1fr); gap:8px var(--s3); align-items:baseline;}
.history-change dt{font-size:11px; font-weight:700; letter-spacing:.05em; text-transform:uppercase; color:var(--muted);}
.history-change dd{display:flex; align-items:center; flex-wrap:wrap; gap:7px; min-width:0; margin:0; font-size:12px;}
.history-old,.history-new{max-width:100%; overflow-wrap:anywhere; padding:2px 6px; border-radius:5px; background:var(--surface); color:var(--ink-700);}
.history-old{text-decoration:line-through; color:var(--muted);}
.history-new{background:var(--ok-bg); color:var(--ok-ink);}
.history-arrow{color:var(--muted);}
.history-no-fields{margin:8px 0 0;}
@media (max-width:479px){
  .history-change{grid-template-columns:1fr; gap:3px;}
  .history-heading{align-items:flex-start; flex-direction:column;}
}

/* CSV import — file dropzone + column-mapping rows (Import section · FORM/WIZARD · Pass 10).
   DECLARED here + back-referenced in the component library (Design-System D5).
   .dropzone: the file-pick affordance (dashed well, ≥44px, brand on hover, ok-tint when filled).
   .maprow: one detected CSV column → target field; stacks on phone, 3-col (src → target) ≥480. */
.dropzone{display:flex; flex-direction:column; align-items:center; gap:6px; text-align:center;
  padding:var(--s5) var(--s4); border:2px dashed var(--line); border-radius:var(--radius);
  background:var(--surface); color:var(--muted); cursor:pointer; min-height:44px; transition:border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);}
.dropzone:hover{border-color:var(--brand); color:var(--ink-700);}
.dropzone .e{font-size:30px; line-height:1;}
.dropzone.has-file{border-style:solid; border-color:var(--ok); background:var(--ok-bg); color:var(--ink-700);}
.dropzone.has-file .e{color:var(--ok);}
.maprow{display:grid; grid-template-columns:1fr; gap:6px var(--s3); align-items:center; padding:11px 0; border-bottom:1px solid var(--line);}
.maprow:last-child{border-bottom:0;}
.maprow .mr-src{font-weight:600; color:var(--ink); font-size:14px; display:flex; align-items:center; gap:8px; min-width:0;}
.maprow .mr-src .mono{font-size:12px; color:var(--muted); font-weight:500;}
.maprow .mr-arrow{display:none; color:var(--muted);}
.maprow .field{margin-bottom:0;}
@media (min-width:480px){ .maprow{grid-template-columns:1fr auto 1.15fr;} .maprow .mr-arrow{display:inline;} }

/* SLA override rows — priority → response / resolution targets (Settings · FORM · Pass 10).
   DECLARED here + back-referenced in the component library (Design-System D5).
   Stacks on phone; 3-col (priority + two number inputs) from ≥480. Token-built. */
.slarow{display:grid; grid-template-columns:1fr 1fr; gap:6px var(--s3); align-items:end; padding:var(--s3) 0; border-bottom:1px solid var(--line);}
.slarow:last-child{border-bottom:0;}
.slarow .sla-pri{grid-column:1 / -1; font-weight:600; font-size:14px; color:var(--ink-700); display:flex; align-items:center; gap:8px;}
.slarow .field{margin-bottom:0;}
.slarow .field > label{font-weight:500; color:var(--muted);}
@media (min-width:480px){ .slarow{grid-template-columns:1.1fr 1fr 1fr; align-items:end;} .slarow .sla-pri{grid-column:auto; padding-bottom:10px;} }

/* Grouped form sections — labelled blocks of .field rows inside one FORM card (Settings · Pass 10).
   DECLARED here + back-referenced in the component library (Design-System D5).
   A hairline + breathing space separates groups; the first group has no top rule. */
.fieldgroup{padding-top:var(--s5); margin-top:var(--s5); border-top:1px solid var(--line);}
.fieldgroup:first-of-type{padding-top:0; margin-top:0; border-top:0;}
.fieldgroup > .eyebrow{margin-bottom:var(--s4); display:block;}

/* Toggle/option row — a labelled checkbox setting (Settings, import options · Pass 10).
   DECLARED here + back-referenced in the component library (Design-System D5). ≥44px tap target. */
.checkrow{display:flex; align-items:flex-start; gap:var(--s3); min-height:44px; padding:8px 0; cursor:pointer;}
.checkrow input{flex:0 0 auto; width:20px; height:20px; margin-top:2px; accent-color:var(--brand);}
.checkrow .cr-body{flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:2px;}
.checkrow .cr-title{font-size:14px; font-weight:600; color:var(--ink);}
.checkrow .cr-sub{font-size:13px; color:var(--muted); line-height:1.45;}

/* Extra-large primary action — the thumb-zone check-in/out button (Attendance · Mark · Pass 10).
   DECLARED here + back-referenced in the component library (Design-System D5). Pairs with .btn-block. */
.btn-xl{min-height:62px; font-size:17px; font-weight:650; border-radius:var(--radius);}

/* Map placeholder — a calm static stand-in for a captured-location map (Attendance Mark, Scan · Pass 10).
   DECLARED here + back-referenced in the component library (Design-System D5). Ice-blue grid + pin. */
.mapstub{position:relative; width:100%; height:130px; border-radius:var(--radius-sm); border:1px solid var(--line); overflow:hidden;
  background:
    linear-gradient(0deg, rgba(24,43,58,.05) 1px, transparent 1px) 0 0/100% 22px,
    linear-gradient(90deg, rgba(24,43,58,.05) 1px, transparent 1px) 0 0/22px 100%,
    var(--accent);
  display:grid; place-items:center;}
.mapstub .pin{font-size:28px; line-height:1; filter:drop-shadow(0 2px 3px rgba(24,43,58,.25));}

/* Curated PORTAL top bar — the phone-first self-service shell (resident / vendor / technician).
   Replaces the retired horizontal .topnav: a dark brand strip carrying the lockup + bell + .acct
   ONLY — never the staff side-nav. Sticky, safe-area aware. Back-ref: portal.html shell +
   auth.html offline chrome. Token-built; the white .brand/.iconbtn + light .acct read on --ink. */
.portalbar{position:sticky; top:0; z-index:40; background:var(--ink); box-shadow:var(--shadow);
  padding-top:env(safe-area-inset-top);}
.portalbar-inner{max-width:560px; margin:0 auto; display:flex; align-items:center; gap:var(--s3);
  min-height:56px; padding:6px var(--s4);}
.portalbar .brand{flex:1; min-width:0;}

/* Thread composer — docked reply input for curated message threads (resident ticket detail +
   Chat-3 vendor comment thread). Sticky safe-area bottom bar: the text input grows, Send stays
   auto-width. Mirrors .actionbar's thumb-zone semantics but for a text-entry row. Token-built. */
.threadbar{display:flex; gap:var(--s2); align-items:center; position:sticky; bottom:0; z-index:20;
  background:var(--card); border-top:1px solid var(--line);
  padding:var(--s3) var(--s4) calc(var(--s3) + env(safe-area-inset-bottom));
  box-shadow:0 -2px 12px rgba(24,43,58,.08);}
.threadbar .input{flex:1; min-width:0; min-height:44px;}

/* Stock-level gauge — the ONE new class this pass (Inventory list rows + item detail · Chat 3).
   DECLARED here + back-referenced in the component library (Design-System D5). A token-built
   fill bar over a track, with a reorder-point tick. Salience is by TREATMENT not hue: at/under the
   reorder point it flips .is-low (danger fill) and the row pairs a ⏰/🔴 emoji + danger badge — so it
   out-shouts brand red on a red-heavy screen (BRAND-RED DISCIPLINE). One class, list + detail. */
.stockbar{position:relative; height:10px; border-radius:999px; background:var(--surface);
  border:1px solid var(--line); overflow:hidden; min-width:84px;}
.stockbar .sb-fill{position:absolute; inset:0 auto 0 0; background:var(--ok); border-radius:999px; transition:width var(--t-med) var(--ease);}
.stockbar.is-low .sb-fill{background:var(--danger);}
.stockbar .sb-mark{position:absolute; top:0; bottom:0; width:2px; background:var(--ink-700); opacity:.42;}  /* reorder point */
.stockbar.is-lg{height:16px; border-radius:var(--radius-sm);}

/* ---- RESPONSIVE (mobile-first; min-width against the fixed scale) -------- */

/* sm ≥480 — minor spacing bumps */
@media (min-width:480px){
  .container{padding:var(--s5) var(--s5);}
}

/* md ≥720 — rail becomes fixed & visible, content offsets, drawer retires, tables visible */
@media (min-width:720px){
  .sidebar{transform:none;}
  .appmain{margin-left:248px;}
  .drawer-btn{display:none;}
  .side-scrim{display:none;}
  .side-foot{display:block;}
  .acct-name{display:block;}
  .livepill{display:inline-flex;}
  .detail-grid{grid-template-columns:1.7fr 1fr; align-items:start;}
  .grid-2{grid-template-columns:1fr 1fr;}

  /* Icon-only COLLAPSE state (toggle remembered in localStorage) */
  .applayout.is-collapsed .sidebar{width:72px;}
  .applayout.is-collapsed .appmain{margin-left:72px;}
  .applayout.is-collapsed .brand-word,
  .applayout.is-collapsed .side-label,
  .applayout.is-collapsed .side-group,
  .applayout.is-collapsed .side-sep{display:none;}
  .applayout.is-collapsed .side-head{justify-content:center; padding:12px 8px;}
  .applayout.is-collapsed .side-link{justify-content:center; padding:0;}
  .applayout.is-collapsed .side-link .e{flex:0 0 auto;}
  /* labels reappear as a hover flyout when the rail is collapsed — also on keyboard focus
     (UX-011: a pinned link was reachable by Tab but its label only ever showed on :hover). */
  .applayout.is-collapsed .side-link:hover .side-label,
  .applayout.is-collapsed .side-link:focus .side-label{
    display:block; position:absolute; left:calc(100% + 10px); top:50%; transform:translateY(-50%);
    background:var(--ink); color:#fff; padding:7px 11px; border-radius:var(--radius-sm);
    box-shadow:var(--shadow-lg); white-space:nowrap; z-index:70; font-weight:600;
    border:1px solid rgba(255,255,255,.14);
  }
}

/* Phone branch for collapsing tables: show cards, hide table — BELOW md only */
@media (max-width:719px){
  .topstrip{gap:var(--s2); padding-inline:var(--s2);}
  .topstrip .page-context{flex:1 1 auto;}
  .topstrip .spacer{display:none;}
  .modebtn{width:40px; padding:0; justify-content:center;}
  .modebtn [data-theme-mode-label], .modebtn .mode-auto-tag{display:none;}
  .acct{padding-inline:4px;}
  .acct-name, .acct-caret{display:none;}
  .table-wrap[data-collapse]{border:0; background:none; box-shadow:none; overflow:visible;}
  .table-wrap[data-collapse] table.table{display:none;}
  .table-wrap[data-collapse] .rowcards{display:flex;}
  .table-wrap[data-collapse][data-mobile-scroll]{
    border:1px solid var(--line); background:var(--card); box-shadow:var(--shadow);
    overflow-x:auto; -webkit-overflow-scrolling:touch;
  }
  .table-wrap[data-collapse][data-mobile-scroll] table.table{display:table; min-width:680px;}
  /* PPM calendar: hide the dense grid, show the comfortable agenda stack */
  .calwrap[data-collapse]{border:0; background:none; box-shadow:none; overflow:visible;}
  .calwrap[data-collapse] .cal-dow,
  .calwrap[data-collapse] .cal-grid{display:none;}
  .calwrap[data-collapse] .cal-agenda{display:flex;}
  .toolbar .btn{width:100%;}                 /* primary list action full-width on phone */
  .page-head{flex-direction:column; gap:var(--s3);}
  .page-head .ph-actions{margin-left:0; width:100%;}
  .page-head .ph-actions .btn{flex:1;}
  /* PPM calendar: popover becomes a bottom sheet; the desktop view toggle is hidden */
  .cal-pop{inset:auto 0 0 0; left:0; right:0; top:auto; width:auto; border-radius:var(--radius) var(--radius) 0 0; padding-bottom:calc(14px + env(safe-area-inset-bottom));}
  /* PPM calendar v2: the centered modal drops to the same bottom sheet */
  .cal-modal{inset:auto 0 0 0; left:0; right:0; top:auto; transform:none; width:auto; border-radius:var(--radius) var(--radius) 0 0; padding-bottom:calc(14px + env(safe-area-inset-bottom));}
  .seg--views{display:none;}
}

/* lg ≥820 — detail-grid already splits at md; reaffirm + table density */
@media (min-width:820px){
  .detail-grid{grid-template-columns:1.7fr 1fr;}
}

/* xl ≥1080 — full data density, multi-column dashboards */
@media (min-width:1080px){
  .container{padding:var(--s6) var(--s5);}
  .grid-cards{grid-template-columns:repeat(auto-fill, minmax(252px,1fr));}
}

@media (prefers-reduced-motion:reduce){
  *{animation-duration:.001ms !important; transition-duration:.001ms !important;}
}

/* Print — the PPM calendar month plan (facility teams print it). Hide the shell
   and interactive chrome, flatten to full width, force chip colours, one month
   per page. */
/* Print rules are scoped to the calendar page (body.page-ppm-calendar) so they
   never leak into other pages' print output. Overflow/filtered chips are forced
   visible: the printed month plan must show every visit, not the collapsed or
   legend-filtered on-screen subset. */
@media print{
  .page-ppm-calendar .sidebar, .page-ppm-calendar .side-scrim, .page-ppm-calendar .topstrip,
  .page-ppm-calendar .toolbar, .page-ppm-calendar .ph-actions, .page-ppm-calendar .cal-legend,
  .page-ppm-calendar .seg, .page-ppm-calendar .cal-more, .page-ppm-calendar .cal-pop,
  .page-ppm-calendar .cal-modal, .page-ppm-calendar .cal-modal-backdrop,
  .page-ppm-calendar .page-help, .page-ppm-calendar .alert .btn{display:none !important;}
  .page-ppm-calendar .applayout, .page-ppm-calendar .appmain, .page-ppm-calendar .container{display:block !important; margin:0 !important; padding:0 !important; max-width:none !important;}
  .page-ppm-calendar .calwrap{box-shadow:none !important; border-color:#000;}
  .page-ppm-calendar .calwrap.is-agenda .cal-grid, .page-ppm-calendar .calwrap.is-agenda .cal-dow{display:grid !important;}
  .page-ppm-calendar .cal-grid{display:grid !important;}
  .page-ppm-calendar .cal-agenda{display:none !important;}
  .page-ppm-calendar .cal-event{display:flex !important; -webkit-print-color-adjust:exact; print-color-adjust:exact;}
  .page-ppm-calendar .cal-cell{break-inside:avoid; page-break-inside:avoid;}
}

/* ==========================================================================
   8. COMPAT SHIMS — token-built bridges so screens not yet migrated to the new
   component markup still render correctly during the rollout. Each is justified
   in one line; all are retired as their section pass lands. No raw hex.
   ========================================================================== */

/* Django renders bound fields as bare <input>/<select>/<textarea> inside .field
   (no .input class). Mirror the .input treatment onto them so EVERY form stays
   styled app-wide without touching each widget. (Same rules as .input.) */
.field > input, .field > select, .field > textarea,
.field input:not([type=checkbox]):not([type=radio]):not([type=file]),
.field select, .field textarea{
  width:100%; min-height:44px; padding:10px 12px;
  border:1px solid var(--line); border-radius:var(--radius-sm);
  background:var(--card); color:var(--ink-700); font-size:15px; font-family:inherit;
  transition:border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.field textarea{min-height:104px; resize:vertical; line-height:1.5;}
.field input:focus, .field select:focus, .field textarea:focus{
  outline:0; border-color:var(--brand); box-shadow:0 0 0 3px var(--focus-ring);
}
.field input[type=checkbox], .field input[type=radio]{width:auto; min-height:0; accent-color:var(--brand);}
.field.has-error input, .field.has-error select, .field.has-error textarea{
  border-color:var(--danger); box-shadow:0 0 0 3px var(--danger-bg);
}

/* .kpi — pre-additions stat tile (reports/mis, assets/vendors/meters/inventory
   detail). Bridges to the .stat look until each is moved to .statkit/.stat. */
.kpi{display:flex; flex-direction:column; gap:4px;}
.kpi .l{font-size:12px; text-transform:uppercase; letter-spacing:.14em; color:var(--muted); font-weight:600;}
.kpi .n{font-size:26px; font-weight:700; line-height:1.1; color:var(--ink); font-variant-numeric:tabular-nums; letter-spacing:-.02em;}
.kpi .s{font-size:12px; color:var(--muted);}

/* .filters — legacy filter-chip row (14 list pages). Behaves like a wrapped chip
   group until each list moves to .chipstrip. */
.filters{display:flex; flex-wrap:wrap; gap:var(--s2); align-items:center;}

/* .nav-card legacy inner classes (home dashboard uses .ico/.t/.d). Map to the
   new .e/.nc-title/.nc-sub treatment until the Dashboard pass. */
.nav-card .ico{font-size:24px; line-height:1;}
.nav-card .t{font-weight:650; color:var(--ink); font-size:15px;}
.nav-card .d{color:var(--muted); font-size:13px;}
.nav-card.is-soon{opacity:.6; pointer-events:none;}

/* Account-menu state and role labels. */
.menu-item.is-current{background:var(--brand-50); color:var(--brand-700); font-weight:600;}
.menu-item .role{margin-left:auto; font-size:11px; color:var(--muted);}

/* Offline page centering + legacy responsive visibility helpers. */
.center-screen{min-height:70vh; display:grid; place-items:center; text-align:center; padding:var(--s5);}
.only-mobile{display:none;}
@media (max-width:719px){ .hide-mobile{display:none;} .only-mobile{display:flex;} }

/* ==========================================================================
   9. GROUPED SIDE-NAV — pinned set + single-open accordion folders.
   Design pass 2026-07 (PropSquare Nav Rail export) ported onto the app's
   .side-* classes: .side-fold (header) · .side-foldbody (animated body) ·
   .side-foldbody-inner (overflow-clipped grid child) · .side-child · .side-subgroup.
   ========================================================================== */
.side-sep{height:1px; margin:10px 10px; background:var(--rail-divider);}

/* Folder header reuses .side-link; the SVG chevron is pushed right by .side-label{flex:1}. */
.side-chev{
  flex:0 0 auto; width:16px; height:16px; color:var(--rail-muted);
  transition:transform var(--t-med) var(--ease);
}
.side-fold-wrap.is-open > .side-fold .side-chev{transform:rotate(90deg); color:#fff;}
.side-fold-wrap.is-open > .side-fold{color:#fff;}

/* Accordion body — animated height via grid-template-rows 0fr→1fr; the inner wrapper
   clips its overflow so children slide in/out smoothly (no max-height guessing). */
.side-foldbody{display:grid; grid-template-rows:0fr; transition:grid-template-rows var(--t-med) var(--ease);}
.side-fold-wrap.is-open > .side-foldbody{grid-template-rows:1fr;}
.side-foldbody-inner{overflow:hidden; min-height:0; display:flex; flex-direction:column; gap:2px; padding-top:2px;}

/* Child rows: indented under the folder, muted until hover/active. */
.side-child{padding-left:38px; color:var(--rail-muted);}
.side-child:hover{color:#fff;}
.side-child .e{font-size:15px; flex-basis:20px;}
.side-child.is-active{color:#fff;}
.side-subgroup{
  font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase;
  color:var(--rail-faint); padding:8px 10px 2px 38px;
}

/* "operator" pill on the Admin folder header. */
.side-tag{
  flex:0 0 auto; font-size:9px; font-weight:700; letter-spacing:.14em; text-transform:uppercase;
  color:var(--rail-faint); border:1px solid var(--rail-divider); border-radius:4px; padding:2px 5px;
}

/* Admin folder (Operator) is visually fenced off from the operational groups. */
.side-fold-wrap--admin{margin-top:16px;}
.side-fold-wrap--admin::before{content:""; display:block; height:1px; background:var(--rail-divider); margin:0 10px 8px;}

/* Flyout title — only shown inside the collapsed icon-rail's hover flyout (below). */
.flyout-title{display:none;}

@media (min-width:720px){
  /* Collapsed icon-rail: drop inline chevron / sub-labels / tag, centre the folder icon,
     and reveal each folder's children as a hover/focus flyout panel to the right. */
  .applayout.is-collapsed .side-chev,
  .applayout.is-collapsed .side-tag,
  .applayout.is-collapsed .side-subgroup{display:none;}
  .applayout.is-collapsed .side-fold{justify-content:center; padding:0; gap:0;}
  .applayout.is-collapsed .side-fold-wrap{position:relative;}
  /* The flyout panels / label tooltips are abs-positioned INSIDE the rail's scroll
     container, which clips them at the 72px edge — let them escape while collapsed
     (the icon rail's short stack doesn't need its own scrollbar; expanded keeps
     overflow-y:auto untouched). */
  .applayout.is-collapsed .side-nav{overflow:visible;}
  /* Tap parity: Safari/iPadOS don't focus a <button> on click, so :focus-within never
     fires there — the accordion's .is-open (set by the existing click handler) also
     reveals the flyout. */
  .applayout.is-collapsed .side-fold-wrap.is-open > .side-foldbody{
    opacity:1; visibility:visible; transform:translateX(0);
  }
  .applayout.is-collapsed .side-fold-wrap--admin{margin-top:12px;}
  .applayout.is-collapsed .side-fold-wrap--admin::before{margin:0 12px 8px;}
  /* Body becomes an absolutely-positioned flyout: always laid out (rows:1fr), gated by
     opacity/visibility on hover so it fades in cleanly. */
  .applayout.is-collapsed .side-foldbody{
    position:absolute; left:calc(100% + 6px); top:0; grid-template-rows:1fr; min-width:210px;
    background:var(--ink); border-radius:12px; padding:8px;
    box-shadow:var(--shadow-lg); border:1px solid rgba(255,255,255,.14); z-index:70;
    opacity:0; visibility:hidden; transform:translateX(-6px);
    transition:opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease), visibility var(--t-fast) var(--ease);
  }
  .applayout.is-collapsed .side-fold-wrap:hover > .side-foldbody,
  .applayout.is-collapsed .side-fold-wrap:focus-within > .side-foldbody{
    opacity:1; visibility:visible; transform:translateX(0);
  }
  /* Inside the flyout: show the folder title, left-align children, re-show labels/badges. */
  .applayout.is-collapsed .side-foldbody .flyout-title{
    display:block; font-weight:700; font-size:12px; color:#fff;
    padding:6px 10px 8px; border-bottom:1px solid var(--rail-divider); margin-bottom:6px;
  }
  .applayout.is-collapsed .side-foldbody .side-child{justify-content:flex-start; padding:8px 10px; gap:10px;}
  .applayout.is-collapsed .side-foldbody .side-subgroup{display:block; padding:8px 10px 2px;}
  .applayout.is-collapsed .side-foldbody .side-label,
  .applayout.is-collapsed .side-foldbody .side-link:hover .side-label{
    display:block; position:static; transform:none; left:auto; top:auto;
    background:none; box-shadow:none; border:0; padding:0; white-space:nowrap; font-weight:500;
  }
  /* The folder header's own single-label tooltip is redundant with the flyout (which already
     opens via :hover/:focus-within above) — hide it in both interaction modes. */
  .applayout.is-collapsed .side-fold:hover .side-label,
  .applayout.is-collapsed .side-fold:focus .side-label{display:none;}
}

/* ==========================================================================
   8. UX-POLISH ADDITIONS — list-page kit (search, sortable + sticky headers,
   pager, page help). Token-built; consumed by web/partials/{list_search,
   sortable_th, pagination, page_header, export_import_buttons}.html and
   apps/web/listing.py. One vocabulary for every module list.
   ========================================================================== */

/* Sticky table headers. Since .table-wrap defaults to overflow-x:auto (horizontal
   scroll for wide tables), the wrap is a scroll container and the thead sticks to the
   wrap's own scrollport rather than the page — harmless when the wrap doesn't scroll
   vertically. Opaque card background + z-index keep rows beneath. */
.table-wrap table.table thead th{position:sticky; top:0; z-index:2; background:var(--card);}

/* Sortable column header link (sortable_th.html). Inherits the th type ramp;
   the active column picks up brand ink + a small ▲/▼ indicator. */
table.table th .th-sort{display:inline-flex; align-items:center; gap:4px; color:inherit;}
table.table th .th-sort:hover{color:var(--ink); text-decoration:none;}
table.table th[aria-sort="ascending"] .th-sort,
table.table th[aria-sort="descending"] .th-sort{color:var(--brand-700);}
.sort-ind{font-size:10px; line-height:1; color:var(--brand-700);}

/* Pager bar (pagination.html) — the audit-list toolbar rhythm as a component. */
.pager{display:flex; flex-wrap:wrap; align-items:center; gap:var(--s3); margin-top:var(--s4);}

/* Jira/Excel-style list command bar (list_search + list_filter_menu + list_view_menu).
   The one compact row replaces the old wall of status/kind chips while preserving ordinary GET
   forms for JS-off use. Native details makes every popover keyboard reachable without a widget
   dependency. */
.list-commandbar{display:flex; flex:1 1 640px; flex-wrap:wrap; align-items:center; gap:var(--s2); min-width:0;}
.list-search{display:flex; align-items:center; gap:var(--s2); flex:1 1 220px; min-width:180px; max-width:420px;}
.list-search .input{min-height:36px; padding:6px 12px; font-size:13px;}
.list-command-menu{position:relative; flex:0 0 auto;}
.list-command-menu>summary{
  list-style:none; display:inline-flex; align-items:center; gap:6px; min-height:36px; padding:6px 11px;
  border:1px solid var(--line); border-radius:var(--radius-sm); background:var(--card); color:var(--ink-700);
  font-size:13px; font-weight:600; cursor:pointer; user-select:none;
}
.list-command-menu>summary::-webkit-details-marker{display:none;}
.list-command-menu>summary::after{content:"⌄"; color:var(--muted); font-size:12px; line-height:1;}
.list-command-menu[open]>summary{border-color:var(--brand); color:var(--brand-700); box-shadow:0 0 0 2px var(--focus-ring);}
.list-command-count{display:grid; place-items:center; min-width:17px; height:17px; padding:0 4px; border-radius:999px; background:var(--brand-50); color:var(--brand-700); font-size:10px;}
.list-command-current{max-width:130px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--muted); font-weight:500;}
.list-command-panel{
  position:absolute; top:calc(100% + 6px); left:0; z-index:45; width:min(560px, calc(100vw - 28px));
  max-height:min(68vh, 620px); overflow:auto; padding:12px; border:1px solid var(--line); border-radius:var(--radius);
  background:var(--card); box-shadow:var(--shadow-lg, var(--shadow));
}
.list-view-menu .list-command-panel,.list-more-menu .list-command-panel{left:auto; right:0; width:min(330px, calc(100vw - 28px));}
.list-command-panel-head{display:flex; align-items:center; justify-content:space-between; gap:var(--s3); margin-bottom:10px; font-size:13px;}
.list-command-panel-head a{font-size:12px; font-weight:600;}
.list-filter-fields{display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px; margin-bottom:10px;}
.list-filter-field{min-width:0; margin:0; padding:8px; border:1px solid var(--line); border-radius:8px;}
.list-filter-field legend{padding:0 3px; color:var(--ink); font-size:12px; font-weight:700;}
.list-filter-field>.input{width:100%; min-height:32px; margin-bottom:6px; font-size:12px; padding:5px 8px;}
.list-filter-options{display:flex; flex-direction:column; gap:4px; max-height:154px; overflow:auto;}
.list-filter-options label{display:flex; align-items:center; gap:6px; min-width:0; color:var(--ink-700); font-size:12px; cursor:pointer;}
.list-filter-options label span{overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.list-filter-dates{display:grid; gap:6px;}
.list-filter-dates label{display:grid; gap:3px; color:var(--muted); font-size:11px; font-weight:600;}
.list-filter-dates .input{min-height:32px; padding:4px 7px; font-size:12px;}
.list-filter-more{margin:0 0 10px; border-top:1px solid var(--line); padding-top:8px;}
.list-filter-more>summary{cursor:pointer; color:var(--brand); font-size:12px; font-weight:700;}
.list-filter-more[open]>summary{margin-bottom:8px;}
.list-active-filters{order:3; display:flex; flex:1 0 100%; flex-wrap:wrap; gap:6px; padding-top:2px;}
.list-active-filters a{color:var(--ink-700); font-size:12px; text-decoration:none; border-bottom:1px solid var(--line);}
.list-active-filters a:hover{color:var(--brand-700); border-color:var(--brand);}
.list-command-actions{display:flex; align-items:center; gap:var(--s2);}
.list-command-actions .colmgr-bar{display:contents; padding:0;}
.list-view-links{display:flex; flex-direction:column; gap:2px; margin-bottom:12px;}
.list-view-links a{display:flex; align-items:center; justify-content:space-between; gap:8px; padding:7px 8px; border-radius:7px; color:var(--ink-700); font-size:13px; text-decoration:none;}
.list-view-links a:hover,.list-view-links a.is-current{background:var(--surface); color:var(--brand-700);}
.list-view-links small{color:var(--muted); font-size:10px; text-transform:uppercase; letter-spacing:.06em;}
.list-sort-options{display:flex; flex-direction:column; gap:4px;}
.list-sort-option{display:grid; grid-template-columns:minmax(0,1fr) auto auto; align-items:center; gap:6px; padding:7px 8px; border-radius:7px; font-size:13px;}
.list-sort-option:hover{background:var(--surface);}
.list-sort-option>a{padding:3px 5px; border-radius:5px; color:var(--ink-700); font-size:11px; text-decoration:none;}
.list-sort-option>a:hover,.list-sort-option>a.is-current{background:var(--brand-50); color:var(--brand-700); font-weight:700;}
.list-view-save{display:grid; gap:8px; border-top:1px solid var(--line); padding-top:10px;}
.list-view-save>label{display:grid; gap:4px; color:var(--muted); font-size:11px; font-weight:700;}
.list-view-save .input{min-height:34px; padding:6px 8px; font-size:13px;}
.list-view-save [data-list-view-message]{min-height:1.1em; color:var(--muted);}
.list-view-save [data-list-view-message].is-error{color:var(--danger);}
.list-more-panel{display:flex; flex-direction:column; align-items:stretch; gap:4px;}
.list-more-panel>.btn,.list-more-panel a.btn{justify-content:flex-start; width:100%;}
.list-more-panel .export-range{width:100%;}
.list-more-panel .export-range>summary{justify-content:flex-start; width:100%;}
.list-inline-create{display:none; scroll-margin-top:var(--s4);}
.list-inline-create:target,.list-inline-create.is-open{display:block;}
@media (max-width:719px){
  .list-commandbar{flex-basis:100%; gap:8px;}
  .list-search{flex:1 1 100%; max-width:none;}
  .list-command-actions{margin-left:0;}
  .list-command-menu>summary{min-height:40px;}
  .list-command-menu[open]>.list-command-panel{
    position:fixed; inset:auto 0 0 0; z-index:78; width:auto; max-height:78vh; border-radius:var(--radius) var(--radius) 0 0;
    padding:16px 14px calc(16px + env(safe-area-inset-bottom));
  }
  .list-filter-fields{grid-template-columns:1fr;}
  .list-filter-options{max-height:190px;}
  .list-commandbar .btn{width:auto;}
}

/* Collapsible "What is this page for?" help (page_header.html, copy from
   apps/web/module_help.py via the page_meta context processor). */
.page-help{margin-top:var(--s2); max-width:640px; font-size:13px;}
.page-help summary{cursor:pointer; color:var(--brand); font-weight:600; width:fit-content;}
.page-help summary:hover{color:var(--brand-700); text-decoration:underline;}
.page-help[open] summary{margin-bottom:var(--s1);}
.page-help p{color:var(--muted); line-height:1.55;}
.page-help-more{font-weight:600; white-space:nowrap;}

/* Help Center (/help/ — web/help/*.html): the docs/kb knowledge base rendered
   in-app by apps/web/helpcenter.py. Index cards + article typography + TOC. */
.kb-index-card{margin-bottom:var(--s3);}
.kb-cat-head{display:flex; align-items:baseline; justify-content:space-between; gap:var(--s2); margin-bottom:var(--s1);}
.kb-cat-title{font-size:15px; font-weight:700; margin:0;}
.kb-article-list{display:flex; flex-direction:column;}
.kb-article-row{display:flex; align-items:center; justify-content:space-between; gap:var(--s2);
  padding:9px 10px; margin:0 -10px; border-radius:8px; text-decoration:none; color:inherit;
  border-bottom:1px solid var(--line);}
.kb-article-row:last-child{border-bottom:0;}
.kb-article-row:hover{background:var(--surface);}
.kb-article-row:hover .kb-article-title{color:var(--brand);}
.kb-article-title{font-weight:600; font-size:13.5px;}
.kb-article-arrow{color:var(--muted);}
.kb-toc{padding:12px 16px; margin-bottom:var(--s3); font-size:13px;}
.kb-toc a{display:block; padding:2px 0; color:var(--brand); text-decoration:none; width:fit-content;}
.kb-toc a:hover{text-decoration:underline;}
.kb-related{margin-top:var(--s3);}
.kb-pager{display:flex; justify-content:space-between; gap:var(--s2); margin-top:var(--s3);}

.kb-article{max-width:760px; font-size:14px; line-height:1.65;}
.kb-article h2{font-size:17px; font-weight:700; margin:1.6em 0 .5em; padding-top:.35em; border-top:1px solid var(--line);}
.kb-article h2:first-child{margin-top:.2em; border-top:0; padding-top:0;}
.kb-article h3{font-size:14.5px; font-weight:700; margin:1.3em 0 .4em;}
.kb-article p{margin:.55em 0;}
.kb-article ul,.kb-article ol{margin:.55em 0 .55em 1.4em; padding:0;}
.kb-article li{margin:.3em 0;}
.kb-article li>ul,.kb-article li>ol{margin-top:.2em; margin-bottom:.2em;}
.kb-article a{color:var(--brand); font-weight:600; text-decoration:none;}
.kb-article a:hover{text-decoration:underline;}
.kb-article strong{font-weight:700;}
.kb-article code{font-family:var(--mono); font-size:12.5px; background:var(--surface);
  border:1px solid var(--line); border-radius:5px; padding:1px 5px;}
.kb-article pre{background:var(--surface); border:1px solid var(--line); border-radius:8px;
  padding:10px 12px; overflow-x:auto;}
.kb-article pre code{background:none; border:0; padding:0;}
.kb-article blockquote{margin:.7em 0; padding:6px 12px; border-left:3px solid var(--brand);
  color:var(--muted); background:var(--surface); border-radius:0 8px 8px 0;}
.kb-article table{width:100%; border-collapse:collapse; margin:.7em 0; font-size:13px; display:block; overflow-x:auto;}
.kb-article th,.kb-article td{text-align:left; padding:7px 10px; border:1px solid var(--line); vertical-align:top;}
.kb-article th{background:var(--surface); font-weight:700;}
.kb-article hr{border:0; border-top:1px solid var(--line); margin:1.2em 0;}
.kb-article em:last-child{color:var(--muted);}

/* ==========================================================================
   9. REPORTS wave (R1) — charts, delta chips, clickable stat tiles.
   Appended block; tokens only, no raw hex. See apps/web/reports_common.py.
   ========================================================================== */

/* Chart figure — a card wrapping a fixed-height canvas + caption. */
.chartbox{margin:0; padding:14px 16px; background:var(--card); border:1px solid var(--line);
  border-radius:var(--radius); box-shadow:var(--shadow);}
.chartbox figcaption{font-weight:650; color:var(--ink); font-size:14px; margin-bottom:8px;}
.chartbox .small{margin-top:8px;}
.chart-canvas{position:relative; min-height:260px;}

/* Period-over-period delta chip on a stat tile. up→ok, down→danger (direction, not judgement). */
.delta-chip{display:inline-flex; align-items:center; gap:2px; font-size:12px; font-weight:650;
  padding:1px 7px; border-radius:999px; vertical-align:middle; margin-left:6px;
  background:var(--surface); color:var(--muted); font-variant-numeric:tabular-nums;}
.delta-chip.up{background:var(--ok-bg); color:var(--ok-ink);}
.delta-chip.down{background:var(--danger-bg); color:var(--danger);}

/* Clickable stat tile (drill-through) — keep the .stat look, add an anchor affordance. */
a.stat{text-decoration:none; color:inherit; display:block; transition:border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);}
a.stat:hover{border-color:var(--brand); box-shadow:var(--shadow-lg); transform:translateY(-1px);}

/* Non-interactive "account-wide" note chip in the report toolbar. */
.chip.is-muted{background:transparent; border-style:dashed; color:var(--muted); cursor:default;}
.chip.is-muted:hover{border-color:var(--line);}

/* ==========================================================================
   10. CREATE-IN-MODAL (WS-D, flag CREATE_MODALS) — the "＋ New …" list anchors
   open the create form in a dialog over the list (web/partials/modal_shell.html
   swapped into base.html's #modal). Token-built; deliberately does NOT touch the
   .cal-modal block.

   Stacking plan for the overlay family (toasts stay highest at 90):
       .drawer-backdrop 79  <  .drawer 80  <  .modal-backdrop 81  <  .modal 82
   The modal sits ABOVE the drawer because the interaction contract (§1.7) allows a
   drawer to open exactly one modal — a typed delete confirmation from inside an edit
   panel — and inplace.js's overlay stack permits that one pairing and no other.
   Equal z-index would not have been enough: base.html renders #modal BEFORE #drawer,
   so at a tie the later sibling paints on top and the dialog would open behind the
   panel that asked for it. Same failure the toast/z-index fix already corrected once.
   ========================================================================== */
.modal-backdrop{position:fixed; inset:0; z-index:81; background:rgba(28,27,26,.38);}
.modal{position:fixed; z-index:82; left:50%; top:50%; transform:translate(-50%,-50%);
  width:min(560px, 100vw - 24px); max-height:85vh; overflow-y:auto; background:var(--card);
  border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow-lg);
  padding:16px 18px;}
/* Wider dialog for the bigger create forms (work items, permits, SLA, roles…) — the view
   opts in via modal_render(..., wide=True); phones still get the bottom sheet below. */
.modal--wide{width:min(720px, 100vw - 24px);}
.modal .modal-x{position:absolute; top:10px; right:10px; border:0; background:transparent;
  color:var(--muted); font-size:15px; cursor:pointer; line-height:1; padding:4px 6px;}
.modal .modal-title{font-size:16px; font-weight:700; color:var(--ink); line-height:1.3;
  margin:0 24px 10px 0;}
/* The reused form fragment arrives wrapped in its page card — flatten it so the dialog
   itself is the card (no box-in-a-box border/shadow, no double padding). */
.modal .card{border:0; box-shadow:none; border-radius:0; background:transparent;}
.modal .card-pad{padding:0;}
/* Busy state: htmx puts .htmx-request on the posting form (the submit button is already
   disabled via hx-disabled-elt). */
.modal form.htmx-request{opacity:.7;}
.modal form.htmx-request button[type=submit]{cursor:progress;}

/* Shared in-place drawer (web/partials/drawer_shell.html). The form keeps its canonical
   full-page route; this is only the enhanced surface that leaves the list visible behind it. */
.drawer-backdrop{position:fixed; inset:0; z-index:79; background:rgba(28,27,26,.38);}
.drawer{position:fixed; top:0; right:0; bottom:0; z-index:80; width:min(460px,94vw);
  background:var(--card); border-left:1px solid var(--line); box-shadow:var(--shadow-lg);
  overflow-y:auto; padding:16px 18px;}
.drawer .drawer-x{position:absolute; top:8px; right:8px; width:44px; height:44px; padding:0;
  display:grid; place-items:center; border:0; border-radius:var(--radius-sm); background:transparent;
  color:var(--muted); font-size:15px; cursor:pointer; line-height:1;}
.drawer .drawer-x:hover{background:var(--surface); color:var(--ink);}
.drawer .card{border:0; box-shadow:none; border-radius:0; background:transparent;}
.drawer .card-pad{padding:0;}
.drawer form.htmx-request{opacity:.7;}
.drawer form.htmx-request button[type=submit]{cursor:progress;}
/* Phone: bottom sheet, exactly like .cal-modal (same breakpoint as the responsive pass). */
@media (max-width:719px){
  .modal{inset:auto 0 0 0; left:0; right:0; top:auto; transform:none; width:auto;
    border-radius:var(--radius) var(--radius) 0 0;
    padding-bottom:calc(16px + env(safe-area-inset-bottom));}
  .drawer{inset:auto 0 0 0; top:auto; width:auto; max-height:80vh; border-left:0;
    border-top:1px solid var(--line); border-radius:var(--radius) var(--radius) 0 0;
    padding-bottom:calc(16px + env(safe-area-inset-bottom));}
}
/* Print-hide: an open dialog must never leak into any page's print output. */
@media print{
  .modal, .modal-backdrop, .drawer, .drawer-backdrop{display:none !important;}
}

/* ---- Column chooser (static/web/colmgr.js) --------------------------------
   Injected "Columns" button + panel over every managed list table: show/hide,
   reorder, pin-left (sticky), per-column widths. Desktop only — the phone
   rowcards branch hides it with the table. Tokens keep light + dark correct. */
.colmgr{position:relative; display:inline-flex; flex:0 0 auto;}
.colmgr-bar{display:flex; justify-content:flex-end; padding:8px 10px 0;}
.colmgr-btn{gap:6px;}
.colmgr-btn.is-custom::after{content:""; width:6px; height:6px; border-radius:50%; background:var(--brand-700); align-self:center;}
.colmgr-pop{position:absolute; right:0; top:calc(100% + 6px); z-index:40; width:272px; max-height:min(60vh, 480px); overflow:auto; background:var(--card); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow-lg, var(--shadow)); padding:10px;}
.colmgr-head{display:flex; align-items:center; justify-content:space-between; margin-bottom:6px; color:var(--ink);}
/* "Built-in" / "Custom" group labels in the Add-columns section (emitted by colmgr.js) —
   the panel's muted uppercase micro-label style. */
.colmgr-sub{font-size:11px; font-weight:700; letter-spacing:.14em; text-transform:uppercase;
  color:var(--muted); padding:6px 4px 2px;}
.colmgr-list{list-style:none; margin:0; padding:0; display:flex; flex-direction:column;}
.colmgr-item{display:flex; align-items:center; gap:6px; padding:5px 4px; border-radius:8px;}
.colmgr-item.is-dragging{background:var(--surface); box-shadow:var(--shadow);}
.colmgr-item label{display:flex; align-items:center; gap:8px; flex:1; min-width:0; cursor:pointer; color:var(--ink-700); font-size:14px;}
.colmgr-item label span{overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.colmgr-grip{border:0; background:none; cursor:grab; color:var(--muted); padding:2px; border-radius:6px; touch-action:none; flex:0 0 auto;}
.colmgr-grip:active{cursor:grabbing;}
.colmgr-pin{border:0; background:none; color:var(--muted); cursor:pointer; padding:3px; border-radius:6px; flex:0 0 auto;}
.colmgr-pin:hover{color:var(--ink);}
.colmgr-pin[aria-pressed="true"]{color:var(--brand-700); background:var(--brand-50);}
.colmgr-foot{margin-top:8px;}
/* Header resize handle — th is position:sticky (top), so the absolute child anchors to it.
   Sits fully inside the th: sized headers clip their overflow, and right:-4px would cut it. */
.colmgr-rz{position:absolute; top:0; right:0; width:9px; height:100%; cursor:col-resize; user-select:none; touch-action:none; z-index:5;}
.colmgr-rz:hover, .colmgr-rz.is-active{background:color-mix(in srgb, var(--brand-700) 30%, transparent);}
/* Resting affordance: a faint grab bar appears as soon as the HEADER is hovered (or focus
   is inside it — th-sort link / column caret), before the 9px handle itself is found. */
.colmgr-rz::after{content:""; position:absolute; top:22%; bottom:22%; right:3px; width:2px;
  border-radius:2px; background:var(--subtle); opacity:0; transition:opacity var(--t-fast) var(--ease);}
th:hover .colmgr-rz::after, th:focus-within .colmgr-rz::after{opacity:.55;}
/* Full-height resize guide — appears when the pointer nears a column boundary anywhere
   in the table (not just the header), Jira-style. 9px hit area, 2px visible bar. */
.colmgr-guide{position:absolute; top:0; bottom:0; width:9px; margin-left:-4px; cursor:col-resize; user-select:none; touch-action:none; z-index:6; display:none;}
.colmgr-guide::after{content:""; position:absolute; top:0; bottom:0; left:3px; width:2px; background:color-mix(in srgb, var(--brand-700) 45%, transparent); border-radius:2px;}
.colmgr-guide.is-active::after{background:var(--brand-700);}
/* Drop indicator while dragging a header sideways to reorder — solid accent, and the
   bar is not grabbable (the drag owns the pointer via capture on the table). */
.colmgr-guide.is-drop{pointer-events:none;}
.colmgr-guide.is-drop::after{background:var(--brand); width:3px; left:3px;}
table.table th.is-dragging{opacity:.55; cursor:grabbing;}
/* Per-column menu opener — a small caret that fades in when the header is hovered. */
.colmgr-caret{border:0; background:none; color:var(--muted); cursor:pointer; padding:1px 2px; border-radius:5px; opacity:0; transition:opacity var(--t-fast) var(--ease); vertical-align:middle;}
th:hover .colmgr-caret, .colmgr-caret:focus-visible, .colmgr-caret[aria-expanded="true"]{opacity:1;}
.colmgr-caret:hover{color:var(--ink); background:var(--surface);}
/* Column context menu — same surface as .colmgr-pop, positioned at the pointer. */
.colmgr-menu{position:fixed; z-index:40; min-width:184px; background:var(--card); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow-lg, var(--shadow)); padding:6px;}
.colmgr-menu button{display:flex; align-items:center; gap:8px; width:100%; border:0; background:none; text-align:left; padding:7px 10px; border-radius:8px; font-size:14px; color:var(--ink-700); cursor:pointer;}
.colmgr-menu button:hover{background:var(--surface); color:var(--ink);}
.colmgr-menu button.is-current{color:var(--brand-700); font-weight:600;}
.colmgr-menu .colmgr-menu-sep{height:1px; background:var(--line); margin:5px 4px;}
/* Pinned (sticky-left) columns. Horizontal scrolling is now the .table-wrap default, so
   this only restates it for the custom-layout case (kept as the explicit contract
   .colmgr-scroll's JS relies on); the trade either way is that top-sticky headers stick
   to the wrap rather than the page. */
.table-wrap.colmgr-scroll{overflow-x:auto;}
/* User-resized columns: cell content may wrap onto up to THREE lines, then trims with
   an ellipsis (unsized tables stay one-line via .cell-clip above). The clamp cannot sit
   on the td itself — max-height is inert on table cells and re-displaying a td breaks
   table layout — so it lives on the .cell-clip block every long-text list cell carries;
   short cells (dates, names, badges) just wrap naturally. overflow:hidden on the td is
   what keeps a nowrap .badge inside its own column instead of painting over the next.
   Headers stay single-line. The td width comes from colmgr.js (inline), so .cell-clip
   drops its 34ch cap and lets the drag width govern. */
.table-wrap.colmgr-sized table.table{table-layout:fixed;}
.table-wrap.colmgr-sized table.table td{
  white-space:normal;
  overflow-wrap:break-word;
  overflow:hidden;
  vertical-align:top;
}
.table-wrap.colmgr-sized table.table th{
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}
.table-wrap.colmgr-sized table.table .cell-clip{
  max-width:none;
  white-space:normal;
  display:-webkit-box;
  -webkit-box-orient:vertical;
  -webkit-line-clamp:3;
  line-clamp:3;
  overflow:hidden;
}
td.colmgr-stick{position:sticky; z-index:1; background:var(--card);}
.table-wrap table.table thead th.colmgr-stick{z-index:3;}
.rowlink:hover td.colmgr-stick{background:var(--surface);}
@media (max-width:719px){
  /* Columns is meaningless in the rowcards branch, but SORT still works standalone (it
     navigates the same ?sort= URLs as the now-hidden headers) — keep it reachable so
     phones can re-order lists. colmgr.js tags the Sort host with .colmgr--sort. */
  .colmgr, .colmgr-bar{display:none;}
  .list-command-actions .colmgr{display:inline-flex;}
  .colmgr--sort{display:inline-flex;}
  .colmgr-bar:has(.colmgr--sort){display:flex;}
  .list-command-actions .colmgr-pop{position:fixed; inset:auto 0 0 0; z-index:78; width:auto; max-height:72vh;
    border-radius:var(--radius) var(--radius) 0 0; padding:14px 12px calc(14px + env(safe-area-inset-bottom));}
}

/* --- Status chip category dot (Wave 2D) --- */
/* Workflow-resolved status chips (web_extras.status_badge) lead with a small category dot:
   todo→muted, in_progress→info, done→ok, cancelled→muted-danger (opacity keeps it quieter than
   a true danger signal — warn/amber stays reserved for SLA/overdue). Fallback badges (no
   workflow / uncataloged code) render dot-less, so this block only ever styles resolved chips. */
.st-dot{width:7px; height:7px; border-radius:999px; display:inline-block; flex:0 0 auto;
        background:var(--muted);}
.st-dot--todo{background:var(--muted);}
.st-dot--in_progress{background:var(--info);}
.st-dot--done{background:var(--ok);}
.st-dot--cancelled{background:var(--danger); opacity:.55;}

/* ==========================================================================
   11. STATUS KIT (Wave 3A, graduated) — the status picker kit.
   Chip → caret trigger → portal'd transition menu (status.js moves the
   fetched .st-menu to document.body, position:fixed, viewport-clamped — the
   same mechanics as .colmgr-menu, because .table-wrap clips on both axes
   (overflow-x:auto / overflow-y:clip) and would cut an in-flow menu off) →
   optional screen modal (reuses the .modal shell). Token-built, light+dark.
   ========================================================================== */
.st-host{display:inline-flex; align-items:center; max-width:100%; min-width:0; vertical-align:middle;}
.st-trigger{display:inline-flex; align-items:center; gap:4px; border:0; background:transparent;
  padding:2px 4px; margin:-2px -4px; border-radius:var(--radius-sm); cursor:pointer;
  max-width:100%; min-width:0; font:inherit; color:inherit; text-align:left;}
.st-trigger:hover, .st-trigger:focus-visible{background:var(--surface);}
.st-trigger:focus-visible{outline:2px solid var(--brand); outline-offset:1px;}
.rowcard .st-trigger{min-height:44px; min-width:44px;}
/* Caret fades in on hover/focus/open — colmgr-caret's idiom. */
.st-caret{width:14px; height:14px; flex:0 0 auto; color:var(--muted); opacity:0;
  transition:opacity var(--t-fast) var(--ease);}
.st-trigger:hover .st-caret, .st-trigger:focus-visible .st-caret,
.st-trigger[aria-expanded="true"] .st-caret{opacity:1;}
/* Touch: the caret can't wait for a hover that never comes, and the trigger grows to a
   ≥44px target. This matters most on boards, where the picker is the ONLY touch path to
   move a card (drag is pointer:fine-only). */
@media (hover:none), (pointer:coarse){
  /* .bd-card .st-caret sets a dimmed resting opacity further down; match its specificity
     so touch still gets the fully-opaque caret on boards too. */
  .st-caret, .bd-card .st-caret{opacity:1;}
  .st-trigger{min-width:44px; min-height:44px; justify-content:center;}
}
.st-menu-slot{display:none;}

/* The portal'd menu (position/coords come from status.js on desktop). z 75: above the
   floating bulk bar (70) — an open menu must never render under it — and below dialog
   scrims/dialogs (79/80). Click-outside/scroll both close it, so it can't orphan. */
.st-menu{position:fixed; z-index:75; min-width:220px; max-width:320px;
  max-height:min(60vh, 420px); overflow:auto; background:var(--card);
  border:1px solid var(--line); border-radius:var(--radius);
  box-shadow:var(--shadow-lg, var(--shadow)); padding:6px;}
.st-menu-head{display:flex; align-items:center; justify-content:space-between; gap:10px;
  padding:6px 10px 8px; border-bottom:1px solid var(--line); margin-bottom:4px;}
.st-item{display:flex; align-items:center; justify-content:space-between; gap:12px; width:100%;
  border:0; background:none; text-align:left; padding:8px 10px; border-radius:8px;
  font:inherit; font-size:14px; color:var(--ink-700); cursor:pointer; text-decoration:none;}
.st-item:hover, .st-item:focus-visible{background:var(--surface); color:var(--ink); text-decoration:none;}
.st-item:focus-visible{outline:2px solid var(--brand); outline-offset:-2px;}
.st-item .badge{flex:0 0 auto;}
.st-item-name{flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
/* Primary option: brand inline-start rule + weight (Jira's emphasized next step). */
.st-item.is-primary{font-weight:600; box-shadow:inset 3px 0 0 var(--brand);}
/* Cancel-category options: danger on hover ONLY (grouped last after .menu-sep). */
.st-item.is-cancel:hover, .st-item.is-cancel:focus-visible{
  color:var(--danger); background:color-mix(in srgb, var(--danger) 9%, transparent);}
.st-empty{padding:14px 12px; text-align:center;}
.st-foot{border-top:1px solid var(--line); margin-top:4px; padding:8px 10px 4px;}
.st-menu .menu-sep{margin:4px 2px;}

/* Detail head (status_head.html): picker + primary action side by side. */
.st-head{display:inline-flex; align-items:center; gap:10px; max-width:100%; min-width:0;}
.st-head-note{white-space:nowrap;}

/* Screen modal internals (rendered inside the .modal shell). */
.st-screen-chips{display:flex; align-items:center; gap:8px; flex-wrap:wrap;
  margin-bottom:var(--s4);}
.st-arrow{color:var(--muted);}
.st-confirm{margin:0 0 var(--s4); color:var(--ink-700);}

/* Phone ≤719px: the menu becomes a bottom sheet (status.js skips inline coords, so these
   rules govern) — mirrors .modal's sheet treatment incl. the safe-area inset. */
@media (max-width:719px){
  .st-menu{inset:auto 0 0 0; width:auto; max-width:none; max-height:70vh;
    border-radius:var(--radius) var(--radius) 0 0; border-bottom:0;
    padding-bottom:calc(10px + env(safe-area-inset-bottom));}
  .st-item{min-height:44px;}
}
/* Print-hide: an open menu must never leak into print output. */
@media print{ .st-menu{display:none !important;} }

/* --- 12. STATUS MANAGER (Wave 3E) --- */
/* The single-page workflow editor (web/custom_statuses/manage.html): status rail +
   per-status outgoing-transition cards + the transition drawer. Drawer = the .modal
   family: fixed right panel ≥720px, bottom sheet below. Token-built, light+dark. */
.csm-grid{display:grid; grid-template-columns:1fr; gap:var(--s4); align-items:start;}
@media (min-width:1024px){ .csm-grid{grid-template-columns:minmax(340px, 420px) 1fr;} }
.csm-span{grid-column:1 / -1;}
/* Rail */
.csm-rail-head{display:flex; align-items:center; justify-content:space-between; padding:12px 14px 0;}
.csm-cap{white-space:nowrap;}
.csm-rail-list{list-style:none; margin:0; padding:8px; display:flex; flex-direction:column; gap:2px;}
.csm-row{display:flex; align-items:center; gap:8px; padding:6px 8px; border-radius:10px; flex-wrap:wrap;}
.csm-row:hover{background:var(--surface);}
.csm-row.is-dragging{background:var(--surface); box-shadow:var(--shadow); opacity:.92;}
.csm-grip{border:0; background:none; cursor:grab; color:var(--muted); padding:4px; border-radius:6px; touch-action:none; flex:0 0 auto; min-width:32px; min-height:32px;}
.csm-grip:active{cursor:grabbing;}
.csm-chip{flex:0 0 auto;}
.csm-code{overflow:hidden; text-overflow:ellipsis; max-width:12ch; white-space:nowrap;}
.csm-lock{display:inline-flex; align-items:center; gap:3px;}
.csm-row-end{margin-left:auto; display:inline-flex; align-items:center; gap:6px; flex-wrap:wrap;}
.csm-mini{padding:4px 8px; font-size:12.5px; min-height:0;}
.csm-cat{max-width:130px;}
/* Colour swatch strip — one dot per WorkflowStatus.Color, current ringed. */
.csm-colors{display:inline-flex; gap:5px; align-items:center;}
.csm-dot{width:28px; height:28px; border-radius:50%; border:1px solid var(--line); cursor:pointer; padding:0; flex:0 0 auto;}
.csm-dot--neutral{background:var(--muted);}
.csm-dot--info{background:var(--info);}
.csm-dot--warn{background:var(--warn);}
.csm-dot--ok{background:var(--ok);}
.csm-dot--danger{background:var(--danger);}
.csm-dot.is-current{outline:2px solid var(--brand); outline-offset:1px;}
@media (hover:none), (pointer:coarse){
  .csm-grip{min-width:44px; min-height:44px;}
  .csm-dot{width:36px; height:36px;}
}
.csm-initial-label{display:inline-flex; align-items:center; gap:4px; cursor:pointer; white-space:nowrap;}
.csm-updown form{display:inline;}
.csm-menu-pop{min-width:172px;}
.csm-add-status{border-top:1px solid var(--line); padding:12px 14px 14px; margin-top:8px;}
.csm-code-adv summary{cursor:pointer;}
.csm-archived{border-top:1px solid var(--line); padding:12px 14px 14px;}
.csm-arch-row{display:flex; align-items:center; justify-content:space-between; gap:8px; padding:4px 0;}
.csm-orphans{margin:10px 14px 0;}
.csm-recover{display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin-top:6px;}
/* Transitions pane */
.csm-pane{display:flex; flex-direction:column; gap:var(--s4); min-width:0;}
.csm-pane-head{display:flex; align-items:center; justify-content:space-between;}
.csm-tcard-head{display:flex; align-items:center; gap:8px; padding:12px 14px; border-bottom:1px solid var(--line);}
.csm-trows{display:flex; flex-direction:column; padding:6px;}
.csm-trow{display:flex; align-items:center; gap:8px; width:100%; text-align:left; border:0; background:none; padding:8px 10px; border-radius:8px; cursor:pointer; color:var(--ink-700); font-size:14px; flex-wrap:wrap;}
.csm-trow:hover{background:var(--surface);}
.csm-trow-name{font-weight:600; color:var(--ink);}
.csm-trow-meta{margin-left:auto; display:inline-flex; gap:6px; align-items:center; white-space:nowrap;}
.csm-trow-empty{margin:4px 10px 6px;}
.csm-add{padding:2px 10px 8px;}
.csm-add summary{cursor:pointer; color:var(--brand-700); font-size:13.5px; list-style:none;}
.csm-add summary::-webkit-details-marker{display:none;}
.csm-add-form{display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin-top:8px;}
.csm-add-form select{max-width:190px;}
.csm-add-form input[name="name"]{max-width:180px;}
/* Validation banner + orphan lists */
.csm-validation{margin-bottom:var(--s4);}
.csm-vlist{margin:4px 0 0 18px;}
.csm-valid-ok{display:flex; align-items:center; gap:6px; margin:0 0 var(--s4);}
/* The transition drawer — .modal family: right panel ≥720px, bottom sheet below. */
.csm-drawer{position:fixed; top:0; right:0; bottom:0; z-index:70; width:min(460px, 94vw); background:var(--card); border-left:1px solid var(--line); box-shadow:var(--shadow-lg); overflow-y:auto; padding:16px 18px;}
.csm-drawer:empty{display:none;}
.csm-panel-head{display:flex; align-items:flex-start; gap:10px; margin-bottom:var(--s3);}
.csm-panel-x{margin-left:auto; border:0; background:transparent; color:var(--muted); font-size:15px; cursor:pointer; line-height:1; padding:4px 6px; flex:0 0 auto;}
.csm-whocan{margin:0 0 var(--s3);}
.csm-sect{border-top:1px solid var(--line); padding-top:var(--s3); margin-top:var(--s3);}
.csm-sect-title{font-size:11px; text-transform:uppercase; letter-spacing:.12em; color:var(--muted); font-weight:700; margin:0 0 8px;}
.csm-rolegrid{display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); gap:4px 10px;}
.csm-check{display:inline-flex; align-items:center; gap:6px; cursor:pointer; color:var(--ink-700);}
.csm-fieldrow{display:flex; align-items:center; gap:8px; padding:3px 0;}
.csm-fieldrow .csm-req{margin-left:auto; color:var(--muted); white-space:nowrap;}
.csm-fieldhead{margin:8px 0 2px; font-weight:600;}
.csm-rulerow{display:flex; align-items:center; justify-content:space-between; gap:8px; padding:3px 0;}
.csm-rulerow .mono{overflow-wrap:anywhere;}
.csm-danger-btn{color:var(--danger); display:inline-flex; align-items:center; gap:6px;}
.csm-danger-btn:hover{background:var(--danger-bg);}
/* Drawer busy state mirrors .modal's. */
.csm-drawer form.htmx-request{opacity:.7;}
.csm-drawer form.htmx-request button[type=submit]{cursor:progress;}
@media (max-width:719px){
  .csm-drawer{inset:auto 0 0 0; top:auto; width:auto; max-height:80vh; border-left:0; border-top:1px solid var(--line); border-radius:var(--radius) var(--radius) 0 0; padding-bottom:calc(16px + env(safe-area-inset-bottom));}
}
@media print{ .csm-drawer{display:none !important;} }

/* --- 13. BOARD (Wave 3F) --- */
/* Workflow Kanban: horizontally scrollable status lanes, bounded server-side to 40 cards per
   lane. All colours come from the existing status/category and surface tokens (light + dark). */
.seg--views a{display:inline-flex; align-items:center; gap:6px; min-height:36px; padding:0 12px;
  color:var(--ink-700); text-decoration:none; font-size:13px; font-weight:600;}
.seg--views a + a{border-left:1px solid var(--line);}
.seg--views a:hover{background:var(--surface); color:var(--ink); text-decoration:none;}
.seg--views a.is-active{background:var(--brand-50); color:var(--brand-700);}
.bd-toolbar{align-items:center;}
.board{display:flex; align-items:flex-start; gap:var(--s3); width:100%; overflow-x:auto;
  padding:2px 2px var(--s4); overscroll-behavior-x:contain; scrollbar-gutter:stable;}
.bd-col{position:relative; flex:0 0 300px; width:300px; max-height:calc(100vh - 220px);
  display:flex; flex-direction:column; background:var(--surface); border:1px solid var(--line);
  border-top:3px solid var(--muted); border-radius:var(--radius); overflow:hidden;
  transition:opacity var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);}
.bd-col[data-category="in_progress"]{border-top-color:var(--info);}
.bd-col[data-category="done"]{border-top-color:var(--ok);}
.bd-col[data-category="cancelled"]{border-top-color:color-mix(in srgb,var(--danger) 55%,var(--line));}
.bd-col-head{position:sticky; top:0; z-index:2; display:flex; align-items:center; gap:8px;
  min-height:44px; padding:10px 12px; background:var(--surface);
  border-bottom:1px solid var(--line);}
.bd-col-title{font-size:13px; font-weight:700; color:var(--ink); min-width:0; overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap;}
.bd-count{margin-left:auto; display:inline-flex; align-items:center; justify-content:center;
  min-width:24px; min-height:22px; padding:1px 7px; border-radius:999px;
  background:var(--card); border:1px solid var(--line); color:var(--muted);
  font-size:12px; font-variant-numeric:tabular-nums;}
.bd-cards{min-height:92px; padding:8px; overflow-y:auto; display:flex; flex-direction:column; gap:8px;}
.bd-card{position:relative; display:flex; flex-direction:column; gap:9px; padding:11px;
  background:var(--card); border:1px solid var(--line); border-radius:10px;
  box-shadow:var(--shadow-sm,0 1px 2px rgba(0,0,0,.06)); color:var(--ink);
  transition:box-shadow var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease),
    transform var(--t-fast) var(--ease);}
.bd-card:hover{border-color:color-mix(in srgb,var(--brand) 35%,var(--line)); box-shadow:var(--shadow);}
.bd-card:focus-visible{outline:2px solid var(--brand); outline-offset:2px;}
.bd-card-top{display:flex; flex-direction:column; gap:3px; min-width:0;}
.bd-card-title{display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2;
  line-clamp:2; overflow:hidden; color:var(--ink); font-weight:650; font-size:14px;
  line-height:1.35; text-decoration:none;}
.bd-card-title:hover{color:var(--brand-700);}
.bd-ref{font-size:11.5px; color:var(--muted);}
.bd-meta{display:flex; align-items:center; gap:6px; min-width:0; flex-wrap:wrap;}
.bd-avatar{display:inline-flex; align-items:center; justify-content:center; width:24px; height:24px;
  border-radius:50%; background:var(--brand-50); color:var(--brand-700); border:1px solid var(--line);
  font-size:10px; font-weight:700; letter-spacing:.02em;}
.bd-meta-text{min-width:0; max-width:110px; overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap; color:var(--muted); font-size:12px;}
.bd-due{margin-left:auto; color:var(--muted); font-size:11.5px; white-space:nowrap;}
.bd-due.is-overdue{color:var(--danger); font-weight:650;}
.bd-card-status{display:flex; align-items:center; min-height:26px; padding-top:7px;
  border-top:1px solid var(--line);}
/* Drag/status affordance at rest: on cards the picker caret is visible without hover, so
   touch + reduced-motion users (for whom drag is disabled) can see the move path. */
.bd-card .st-caret{opacity:.6;}
.bd-empty{padding:20px 10px; color:var(--muted); text-align:center; font-size:12.5px;}
.bd-more{display:block; padding:9px 12px 11px; border-top:1px solid var(--line);
  color:var(--brand-700); font-size:12.5px; font-weight:600; text-decoration:none;}
.bd-more:hover{background:var(--card); text-decoration:none;}
.bd-col.is-readonly .bd-col-head::after{content:"Read only"; margin-left:2px; color:var(--muted);
  font-size:10px; text-transform:uppercase; letter-spacing:.08em;}
.bd-col.is-illegal{opacity:.42;}
.bd-col.is-over{opacity:1; box-shadow:0 0 0 2px var(--brand),var(--shadow);}
.bd-placeholder{min-height:82px; border:2px dashed var(--brand); border-radius:10px;
  background:color-mix(in srgb,var(--brand) 8%,transparent);}
.bd-ghost{position:fixed; inset:0 auto auto 0; z-index:90; margin:0; pointer-events:none;
  opacity:.94; box-shadow:var(--shadow-lg); transform-origin:top left; will-change:transform;}
.bd-card.is-settled{animation:bd-settle .3s var(--ease);}
.bd-card.is-snapback{animation:bd-snapback .28s var(--ease);}
@keyframes bd-settle{from{transform:scale(.97); box-shadow:0 0 0 2px var(--ok);}to{transform:none;}}
@keyframes bd-snapback{35%{transform:translateX(-5px);}70%{transform:translateX(3px);}100%{transform:none;}}
@media (pointer:fine){
  .bd-card{cursor:grab;}
  .bd-card:active{cursor:grabbing;}
  .bd-card a,.bd-card button{cursor:pointer;}
}
@media (max-width:719px){
  .seg.seg--views{display:inline-flex;}
  .seg--views{margin-right:auto;}
  .board{scroll-snap-type:x mandatory;}
  .bd-col{flex-basis:min(86vw,320px); width:min(86vw,320px); max-height:none;
    scroll-snap-align:start;}
  .bd-cards{overflow-y:visible;}
}
@media (prefers-reduced-motion:reduce){
  .bd-col,.bd-card{transition:none;}
  .bd-card.is-settled,.bd-card.is-snapback{animation:none;}
}
@media print{.board,.seg--views{display:none !important;}}

/* --- 14. AI HELP ASSISTANT (flag HELP_ASSISTANT) --- */
/* Tokens only, so light + dark come free from the :root / [data-theme=dark] sets above. The
   drawer reuses the .csm-drawer recipe (right panel ≥720px, bottom sheet below) with a flex
   column so the composer stays pinned while the thread scrolls. */
.ai-fab{position:fixed; right:18px; bottom:calc(18px + env(safe-area-inset-bottom)); z-index:70;
  width:52px; height:52px; border-radius:50%; border:0; background:var(--brand); color:#fff;
  display:grid; place-items:center; cursor:pointer; box-shadow:var(--shadow-lg);
  transition:transform .12s ease, background .12s ease;}
.ai-fab:hover{background:var(--brand-700); transform:translateY(-1px);}
.ai-fab:focus-visible{outline:2px solid var(--brand); outline-offset:3px;}

.ai-drawer{position:fixed; top:0; right:0; bottom:0; z-index:70; width:min(420px, 94vw);
  background:var(--card); border-left:1px solid var(--line); box-shadow:var(--shadow-lg);
  display:flex; flex-direction:column; padding:16px 18px;}
.ai-head{display:flex; align-items:flex-start; gap:10px; margin-bottom:var(--s3); flex:0 0 auto;}
.ai-title{font-weight:700; font-size:15px; color:var(--ink);}
.ai-thread{flex:1 1 auto; overflow-y:auto; display:flex; flex-direction:column; gap:var(--s2);
  padding-right:2px;}
.ai-intro{margin:0 0 var(--s3);}
.ai-chips{display:flex; flex-wrap:wrap; gap:6px;}
.ai-chip{border:1px solid var(--line); background:var(--surface); color:var(--ink-700);
  border-radius:999px; padding:5px 11px; font-size:12px; font-weight:600; cursor:pointer;
  text-align:left;}
.ai-chip:hover{border-color:var(--brand); color:var(--brand-700);}

.ai-msg{border-radius:var(--radius); padding:9px 12px; font-size:13px; line-height:1.55;
  max-width:92%; overflow-wrap:anywhere;}
.ai-msg.outbound{margin-left:auto; background:var(--brand-50); color:var(--ink);}
.ai-msg.inbound{background:var(--surface); color:var(--ink-700);}
.ai-msg.inbound > :first-child{margin-top:0;}
.ai-msg.inbound > :last-child{margin-bottom:0;}
.ai-msg.inbound ul,.ai-msg.inbound ol{margin:6px 0; padding-left:18px;}
.ai-msg.inbound code{font-size:12px;}
/* Take-me-there buttons: an in-app deep link in a reply renders as a tappable chip, while a
   Help Center citation stays a plain link. Pure CSS on the href — no client-side parsing. */
.ai-msg.inbound a[href^="/"]{display:inline-flex; align-items:center; gap:6px; margin:3px 4px 3px 0;
  border:1px solid var(--brand); border-radius:999px; padding:3px 11px; font-size:12px;
  font-weight:600; color:var(--brand-700); text-decoration:none;}
.ai-msg.inbound a[href^="/"]:hover{background:var(--brand-50); text-decoration:none;}
.ai-msg.inbound a[href^="/help/"]{display:inline; margin:0; border:0; border-radius:0; padding:0;
  font-size:inherit; font-weight:inherit; color:var(--brand-700); text-decoration:underline;}
.ai-msg.inbound a[href^="/help/"]:hover{background:transparent;}

.ai-cites{display:flex; flex-wrap:wrap; gap:6px; margin:4px 0 0;}
.ai-cite{border:1px solid var(--line); background:var(--card); color:var(--muted);
  border-radius:999px; padding:3px 10px; font-size:11px; font-weight:600; text-decoration:none;}
.ai-cite:hover{border-color:var(--brand); color:var(--brand-700); text-decoration:none;}
.ai-error{border:1px solid var(--danger); background:var(--danger-bg); color:var(--danger);
  border-radius:var(--radius-sm); padding:8px 11px; font-size:12px;}

.ai-dots{display:inline-flex; gap:4px;}
.ai-dots i{width:6px; height:6px; border-radius:50%; background:var(--muted);
  animation:ai-blink 1.2s infinite ease-in-out;}
.ai-dots i:nth-child(2){animation-delay:.15s;}
.ai-dots i:nth-child(3){animation-delay:.3s;}
@keyframes ai-blink{0%,80%,100%{opacity:.25;}40%{opacity:1;}}

.ai-composer{display:flex; gap:8px; align-items:center; flex:0 0 auto;
  border-top:1px solid var(--line); padding-top:var(--s3); margin-top:var(--s3);}
.ai-composer .input{flex:1 1 auto;}
.ai-foot{flex:0 0 auto; margin:6px 0 0; font-size:11px;}

@media (max-width:719px){
  .ai-drawer{inset:auto 0 0 0; top:auto; width:auto; height:min(78vh, 560px); border-left:0;
    border-top:1px solid var(--line); border-radius:var(--radius) var(--radius) 0 0;
    padding-bottom:calc(16px + env(safe-area-inset-bottom));}
}
@media (prefers-reduced-motion:reduce){
  .ai-fab{transition:none;}
  .ai-dots i{animation:none; opacity:.6;}
}
@media print{.ai-fab,.ai-drawer{display:none !important;}}
