/* ==========================================================================
   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–6 are the foundation; 7 onward are the waves that landed on top of
   it, each appended in the order it shipped and numbered at the end. Source
   order is load-bearing (see the cascade note at the end of §6 and the one at
   the head of §17), so a section is never MOVED to tidy the sequence — the
   numbers follow the file, the file never follows the numbers.

      1. Tokens (:root)             2. Reset / base
      3. Layout                     4. Type
      5. Shell (base.html)          6. Components (the vocabulary)
      7. D5 additions — the app shell (left rail + top strip), consolidated
      8. Compat shims               9. Grouped side-nav (accordion folders)
     10. UX-polish additions (the list-page kit: search, sort, sticky, pager)
     11. Reports wave (R1)         12. Create-in-modal (flag CREATE_MODALS)
     13. Status kit                14. Status manager (Wave 3E)
     15. Board (Wave 3F)           16. AI help assistant (flag HELP_ASSISTANT)
     17. Signal rail — the one urgency device; the LAST to set a leading edge
     18. Field app — install, role strip, offline field screen
   ========================================================================== */

/* 1. TOKENS ---------------------------------------------------------------- */
:root{
  /* Brand — IDENTITY only (primary fills, active nav, key accents, H1/H2) */
  --brand:#E32024; --brand-700:#C60E1C; --brand-50:#FBE4E5;
  /* Ink that rides ON a saturated brand/danger fill. White is right in light; the dark
     theme flips it, because those fills cannot darken (see the dark block). */
  --on-brand:#ffffff; --on-danger:#ffffff;
  /* LINK role — deliberately not --brand. The page ground is a shade darker than a card,
     so one token cannot serve both: #E32024 is AA inside a .card (4.67:1) and fails on
     --surface (4.35:1). Identity keeps --brand; text-that-is-a-link comes through here. */
  --link:var(--brand-700); --link-hover:#9E0A16;
  /* Accent — Ice Blue, callout/info cards ONLY */
  --accent:#EBF5F5;
  /* 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;
  --shadow-sm:0 1px 2px rgba(24,43,58,.06);
  --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);
  /* The one shadow that casts UPWARD — a docked bar lifting off the content it covers. */
  --shadow-up:0 -2px 12px rgba(24,43,58,.08);
  /* .70 is the smallest alpha of --brand that clears the 3:1 graphic bar on BOTH grounds
     (min is .660 over --card, .680 over --surface). At .32 the ring composited to 1.4:1
     and was not an indicator at all. Only the opacity moved — the hue is still --brand. */
  --glow-red:0 8px 24px rgba(227,32,36,.28); --focus-ring:rgba(227,32,36,.70);
  /* Dimming layers. --scrim: dialog + drawer backdrops. --scrim-nav: the mobile rail.
     --scrim-media: the lightbox — heavier, and deliberately theme-INDEPENDENT, because a
     media viewer is the same dark room in both themes (its close chip stays white). */
  --scrim:rgba(28,27,26,.38); --scrim-nav:rgba(24,43,58,.5); --scrim-media:rgba(24,43,58,.82);
  /* 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-ink:rgba(255,255,255,.82);
  --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;
  /* Type ramp — 7 steps, one job each. Body is --text-base and is FIXED at 15px. */
  --text-micro:10px;   /* markers, never prose: counters, sort glyphs, tags inside a control */
  --text-xs:11px;      /* labels: uppercase micro-labels, timestamps, counts, legends, badges */
  --text-sm:13px;      /* dense UI that repeats per screen: table body, nav, menus, form labels */
  --text-base:15px;    /* body, page inputs, primary buttons, record titles */
  --text-lg:17px;      /* the heading step: dialog/drawer titles, article h2, .btn-xl */
  --text-xl:20px;      /* glyphs above the reading range: icon buttons, nav and notif emoji */
  --text-2xl:24px;     /* display: page titles at 700, data numerals at 800 */
  /* Tracking — the uppercase micro-label, and the optical correction for the display face */
  --track-caps:.14em; --track-display:-.02em;
  /* Info-tip helper box — a light brand wash so it lifts off --card in both themes.
     Day: ~brand-50 rose on white. Night: quiet burgundy on the navy card (not a danger fill). */
  --tip-bg: color-mix(in srgb, var(--brand) 12%, var(--card));
  --tip-line: color-mix(in srgb, var(--brand) 24%, var(--line));
}

/* Dark tokens — surfaces from the brand dark-blue family; red brightened on near-black. */
html[data-theme="dark"],
.applayout[data-theme="dark"]{
  /* --brand-700 is a step LIGHTER than --brand here, not darker. Two reasons: on an
     emissive ground "more light" reads as raised/pressed, and --brand-700 is also
     link-hover TEXT on --card, where every darker candidate falls under 4.5:1. The
     1.29:1 step is the ratified light pair (#E32024 → #C60E1C) mirrored. Before this
     the two tokens were the SAME hex and every hover/active state was invisible. */
  --brand:#FF4D4A; --brand-700:#FF7A72; --brand-50:rgba(255,77,74,.16);
  /* White fails on these fills (3.27:1 / 2.80:1) and the fills cannot darken — they
     double as link and error TEXT on --card. So the ink flips to a red-cast near-black. */
  --on-brand:#1A0A09; --on-danger:#1A0A09;
  --link:var(--brand); --link-hover:var(--brand-700);
  --accent:#132C38;
  /* --subtle lifted from #6A7680 (3.59:1 on --card): it paints .input::placeholder at
     15px, which is text, so 4.5:1 governs. Still dimmer than --muted — hierarchy holds.
     --muted lifted one step from #7F8D99 (4.27:1 on --accent): it paints .msg .meta on the
     outbound (.msg.outbound = --accent) bubble. #84919D is 4.51:1 there, 5.18:1 on --card.
     Pinned by apps/web/test_token_contrast.py. */
  --ink:#EEF2F5; --ink-700:#C2CCD4; --muted:#84919D; --subtle:#7C8993;
  --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-sm:0 1px 2px rgba(0,0,0,.5);
  --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);
  --shadow-up:0 -2px 12px rgba(0,0,0,.5);
  --glow-red:0 8px 24px rgba(255,77,74,.32); --focus-ring:rgba(255,77,74,.72);
  /* Scrims go deeper so a dialog still separates from an already-dark page. --scrim-media
     is deliberately NOT overridden — same dark room, both themes. */
  --scrim:rgba(0,0,0,.58); --scrim-nav:rgba(0,0,0,.62);
  --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);
  /* A touch more brand in the mix so the box still separates from --card (#141F29). */
  --tip-bg: color-mix(in srgb, var(--brand) 16%, var(--card));
  --tip-line: color-mix(in srgb, var(--brand) 32%, var(--line));
}

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:var(--text-base); 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(--link); text-decoration:none;}
a:hover{color:var(--link-hover); text-decoration:underline;}
h1,h2,h3,h4{color:var(--ink); margin:0; line-height:1.25;}
h1,h2{font-family:var(--font-display); font-weight:700;}
h1{font-size:var(--text-2xl);}   /* 24px — where .page-title already sits */
h2{font-size:var(--text-lg);}    /* 17px — the step that opens a surface (.modal-title) */
h3,h4{font-size:var(--text-base); font-weight:600;}   /* Work Sans has no 700 face; 600 is what renders */
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:var(--scrim-media); 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:var(--text-sm);}
.kv dd{margin:0; color:var(--ink-700); font-variant-numeric:tabular-nums;}

/* 4. TYPE ------------------------------------------------------------------ */
.page-title{font-size:var(--text-2xl); font-weight:700; color:var(--ink); letter-spacing:var(--track-display); font-family:var(--font-display);}
.page-sub{color:var(--muted); font-size:var(--text-base); margin-top:2px;}
.eyebrow{font-size:var(--text-xs); font-weight:700; letter-spacing:var(--track-caps); text-transform:uppercase; color:var(--link);}
/* 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:var(--text-base); font-weight:600; color:var(--ink);}
.small{font-size:var(--text-sm);}
.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:var(--text-base); letter-spacing:-.01em; overflow:hidden; text-overflow:ellipsis;}
.brand-word b{font-weight:700;}
.brand-word span{font-weight:400; color:var(--rail-muted);}
.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:var(--text-xl);
  position:relative; flex:0 0 auto;
}
.iconbtn:hover{background:var(--rail-hover);}
.bell-dot{
  position:absolute; top:5px; right:5px; min-width:16px; height:16px; padding:0 4px;
  border-radius:9px; background:var(--brand); color:var(--on-brand); font-size:var(--text-micro); 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:var(--text-xs);
}
.acct-name{display:none; font-size:var(--text-sm); 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:var(--text-xs);}
.acct-caret{color:var(--muted); font-size:var(--text-xs);}

/* 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 color-mix(in srgb, var(--brand) 22%, var(--card));
  font-weight:700; line-height:1; overflow:hidden;
}
.user-avatar--xs{width:22px; height:22px; font-size:var(--text-micro);}
.user-avatar--sm{width:26px; height:26px; font-size:var(--text-micro);}
.user-avatar--md{width:30px; height:30px; font-size:var(--text-xs);}
.user-avatar--lg{width:48px; height:48px; font-size:var(--text-lg);}
.user-avatar--xl{width:72px; height:72px; font-size:var(--text-2xl);}
.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);}
/* `.card-accent` is NOT declared here. It is scoped to `.auth-card` in §17 (SIGNAL RAIL):
   one brand moment, the sign-in card, and never a data surface. */
.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:var(--text-2xl); line-height:1;}
.nav-card .nc-title{font-weight:600; color:var(--ink); font-size:var(--text-base);}
.nav-card .nc-sub{color:var(--muted); font-size:var(--text-sm);}

/* 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:var(--text-xs); font-weight:700; letter-spacing:var(--track-caps); text-transform:uppercase; color:var(--muted);
}
.mg-head .mg-ic{display:inline-flex; color:var(--brand);}
.mg-sub{
  margin:10px 0 2px; font-size:var(--text-xs); font-weight:700; letter-spacing:var(--track-caps);
  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(--link);}
.mg-lt{display:flex; flex-direction:column; gap:1px; min-width:0;}
.mg-ll{font-weight:600; font-size:var(--text-base); color:var(--ink);}
.mg-link:hover .mg-ll{color:var(--link);}
.mg-ld{font-size:var(--text-sm); 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:var(--text-base); 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:var(--on-brand); --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);}
/* Destructive ghost — the quiet Delete that sits beside a card's Edit. The button
   vocabulary had a neutral ghost and a solid danger fill but nothing between them,
   so seven call sites hand-inlined color:var(--danger) onto .btn-ghost. Shell and
   border stay the ghost's; only the ink moves. AA as text in both themes: 6.57:1 on
   light --card and 5.97:1 on dark --card (6.13 / 6.63 on the --surface hover ground). */
.btn-ghost-danger{--bg:transparent; --fg:var(--danger); --bd:var(--line);}
.btn-ghost-danger:hover{--bg:var(--surface);}
.btn-danger{--bg:var(--danger); --fg:var(--on-danger); --bd:var(--danger);}
.btn-danger:hover{filter:brightness(.94);}
.btn-sm{min-height:36px; padding:0 12px; font-size:var(--text-sm);}
.btn-block{display:flex; width:100%;}

/* Forms */
.field{display:flex; flex-direction:column; gap:6px; margin-bottom:var(--s4);}
.field > label{font-size:var(--text-sm); 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-color:var(--card); color:var(--ink-700); font-size:var(--text-base); 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;}
/* One caret, never a tiled field. The `background` shorthand resets background-repeat
   to `repeat`; a later equal-specificity `.field select` shim used to do exactly that,
   and the dark-theme image-only override then stamped the 12×8 chevron across every
   dropdown (Type / Parent / Client on New location, and the same widget everywhere
   else). Longhands only; :not([multiple]) because a listbox is not a dropdown. */
select.input:not([multiple]),
.field select:not([multiple]){
  appearance:none; -webkit-appearance:none;
  background-color:var(--card);
  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; background-size:12px 8px;
  padding-right:34px;
}
/* The caret hex is baked into a data: URI — var() never resolves inside url() — so the
   dark theme needs its own copy. %2384919D is dark --muted (5.18:1 on --card). Repeat /
   position / size travel with the image so a later shorthand cannot leave the URI tiling. */
html[data-theme="dark"] select.input:not([multiple]),
.applayout[data-theme="dark"] select.input:not([multiple]),
html[data-theme="dark"] .field select:not([multiple]),
.applayout[data-theme="dark"] .field select:not([multiple]){
  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='%2384919D' d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 12px center; background-size:12px 8px;
}
.helptext{font-size:var(--text-sm); color:var(--muted);}
.page-title-row,.field-label,.card-title-row,.kv-label{
  display:flex; align-items:center; gap:6px; flex-wrap:wrap;
}
.page-title-row{align-items:flex-start; gap:8px;}
.page-title-row .page-title{min-width:0;}
.page-title-row .info-tip{margin-top:6px; flex:0 0 auto;}
.field-label > label,.kv-label > dt,.card-title-row > .card-title,.card-title-row > .eyebrow{
  margin:0;
}
.field > .field-label > label{font-size:var(--text-sm); font-weight:600; color:var(--ink);}
.info-tip{position:relative; display:inline-flex; align-items:center; vertical-align:middle;}
.info-tip-btn{
  display:inline-grid; place-items:center; width:18px; height:18px; padding:0;
  border:1px solid var(--line); border-radius:999px; background:transparent;
  color:var(--muted); cursor:help; flex:0 0 auto;
  transition:color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease),
    background-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
@media (pointer:coarse){
  .info-tip-btn{width:32px; height:32px;}
}
.info-tip-btn:hover,.info-tip.is-open .info-tip-btn,.info-tip:focus-within .info-tip-btn{
  color:var(--ink); border-color:var(--ink-700); background:var(--accent);
}
.info-tip-btn:focus-visible{box-shadow:0 0 0 3px var(--focus-ring);}
.info-tip-box{
  position:absolute; z-index:65; top:calc(100% + 6px); left:50%;
  transform:translateX(-50%); width:max-content; max-width:min(260px, calc(100vw - 32px));
  padding:8px 10px; border:1px solid var(--tip-line); border-radius:8px;
  background:var(--tip-bg); color:var(--ink-700); font-size:var(--text-sm); font-weight:400;
  line-height:1.45; letter-spacing:0; text-transform:none;
  box-shadow:var(--shadow-md);
  text-align:left; white-space:normal;
  opacity:0; visibility:hidden; pointer-events:none;
  transition:opacity var(--t-fast) var(--ease), visibility var(--t-fast) var(--ease);
}
.info-tip-box::before{
  content:""; position:absolute; top:-5px; left:50%; transform:translateX(-50%) rotate(45deg);
  width:8px; height:8px; background:var(--tip-bg); border-left:1px solid var(--tip-line);
  border-top:1px solid var(--tip-line);
}
.field-label .info-tip-box,.card-title-row .info-tip-box,.kv-label .info-tip-box,.card-head .info-tip-box{
  top:50%; left:calc(100% + 8px); transform:translateY(-50%);
}
.field-label .info-tip-box::before,.card-title-row .info-tip-box::before,
.kv-label .info-tip-box::before,.card-head .info-tip-box::before{
  top:50%; left:-5px; transform:translateY(-50%) rotate(-45deg);
}
.info-tip-box a{font-weight:600;}
.info-tip-box code{font-size:var(--text-xs);}
@media (hover:hover) and (pointer:fine){
  html:not(.info-tip-js) .info-tip:hover .info-tip-box,
  html:not(.info-tip-js) .info-tip:focus-within .info-tip-box{opacity:1; visibility:visible; pointer-events:auto;}
}
.info-tip.is-open .info-tip-box,
.info-tip.info-tip--fixed.is-open .info-tip-box{opacity:1; visibility:visible; pointer-events:auto;}
.info-tip.info-tip--fixed .info-tip-box{
  position:fixed; transform:none; left:auto; top:auto;
}
.info-tip.info-tip--fixed .info-tip-box::before{display:none;}
.errorlist{list-style:none; margin:0; padding:0; color:var(--danger); font-size:var(--text-sm); 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:var(--text-xs); 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:var(--text-sm); 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:var(--text-base); color:var(--ink-700);}
.alert .e{flex:0 0 auto; font-size:var(--text-lg); 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:color-mix(in srgb, currentColor 12%, transparent);}

/* 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:var(--text-sm);}
/* RATIFIED EXCEPTION: th keeps .04em, NOT --track-caps — micro-label tracking on a
   white-space:nowrap header widens every column and turns straight into h-scroll. */
table.table th{
  text-align:left; font-size:var(--text-xs); 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:600; font-size:var(--text-lg);}

/* 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:var(--text-sm); 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:var(--text-sm); 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;}
/* Touch: every menu row is a 44px target (account, appearance, record More, Add, the status
   manager's row menu). A mouse keeps the denser desktop rows. The taller rows would push the
   sticky top bar's menus past the bottom of a short phone, where the page cannot scroll them
   into view, so those menus stop at the screen edge and scroll inside (Sign out stays
   reachable). The vh line is the fallback for browsers without dvh. */
@media (pointer:coarse){
  .menu-pop .menu-item{min-height:44px;}
  .topstrip .menu-pop{max-height:calc(100vh - 72px);
    max-height:calc(100dvh - 72px - env(safe-area-inset-top)); overflow-y:auto;
    overscroll-behavior:contain;}
}

/* Thread */
.thread{display:flex; flex-direction:column; gap:var(--s3);}
.msg{max-width:78%; padding:10px 13px; border-radius:var(--radius); font-size:var(--text-base); border:1px solid var(--line); background:var(--card);}
.msg .meta{font-size:var(--text-xs); 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 var(--rail-divider);
}
.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:var(--text-xs); font-weight:700; letter-spacing:var(--track-caps); text-transform:uppercase;
  color:var(--rail-faint); 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:var(--rail-ink);
  font-size:var(--text-sm); 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:var(--text-xl); 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:var(--on-brand); font-size:var(--text-xs); font-weight:700;
  display:grid; place-items:center; line-height:1;
}
.side-foot{display:none; padding:var(--s3); border-top:1px solid var(--rail-divider);}

/* 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:var(--text-sm);}
.livepill .lp-time{color:var(--ink); font-weight:600; font-size:var(--text-sm); 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:var(--text-sm); 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);}
.weatherbtn{display:inline-flex; align-items:center; gap:6px; min-height:40px; padding:0 10px; border-radius:var(--radius-sm); border:1px solid var(--line); background:var(--surface); color:var(--ink); font-family:inherit; font-size:var(--text-sm); 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);}
.weatherbtn:hover{border-color:var(--ink); background:var(--card);}
.weatherbtn .wb-ic{display:inline-flex; width:17px; height:17px; color:var(--ink-700);}
.weatherbtn .wb-ic[data-kind="sun"]{color:var(--warn);}
.weatherbtn .wb-ic[data-kind="moon"]{color:var(--info);}
.weatherbtn .wb-ic[data-kind="rain"],
.weatherbtn .wb-ic[data-kind="storm"],
.weatherbtn .wb-ic[data-kind="drizzle"]{color:var(--info);}
.weatherbtn .wb-temp{font-variant-numeric:tabular-nums; font-weight:600; letter-spacing:-.01em;}
.weather-pop{min-width:214px; max-width:240px;}
.weather-place{padding:6px 10px 2px;}
.weather-now{display:flex; align-items:center; gap:10px; padding:4px 10px 8px;}
.weather-now .wn-ic{display:inline-flex; width:28px; height:28px; color:var(--ink-700); flex:0 0 auto;}
.weather-now .wn-ic[data-kind="sun"]{color:var(--warn);}
.weather-now .wn-ic[data-kind="moon"]{color:var(--info);}
.weather-now .wn-temp{font-size:var(--text-2xl); font-weight:700; line-height:1; font-variant-numeric:tabular-nums; color:var(--ink);}
.weather-now .wn-label{color:var(--ink-700); margin-top:2px;}
.weather-meta{padding:0 10px 6px; line-height:1.35;}
.weather-days{display:flex; flex-direction:column; gap:0;}
.weather-day{display:grid; grid-template-columns:5.2em 18px 1fr; gap:8px; align-items:center; padding:5px 10px; font-size:var(--text-sm); color:var(--ink-700);}
.weather-day .wd-name{font-weight:600; color:var(--ink);}
.weather-day .wd-ic{display:inline-flex; width:16px; height:16px; color:var(--ink-700);}
.weather-day .wd-range{text-align:right; font-variant-numeric:tabular-nums; white-space:nowrap;}
.weather-attr{padding:4px 10px 6px; text-decoration:none; color:var(--muted);}
.weather-attr:hover{color:var(--ink-700); text-decoration:underline;}
.mode-auto-tag{font-size:var(--text-micro); font-weight:700; letter-spacing:var(--track-caps); 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:600; color:var(--ink); font-size:var(--text-base); 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/.weatherbtn, 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:var(--scrim-nav);
  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;}
.ph-actions .st-head{display:flex; align-items:center; gap:10px; flex-wrap:wrap;}
.ph-actions .st-head .badge{font-size:var(--text-sm); padding:6px 12px;}
.ph-actions .st-head .btn-primary,
.ph-actions .st-head .st-head-action{min-height:44px; padding:10px 18px; font-size:var(--text-base); font-weight:700;}

/* 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-family:var(--font-display); font-size:var(--text-2xl); font-weight:800; color:var(--ink); letter-spacing:var(--track-display); font-variant-numeric:tabular-nums; line-height:1.1;}
.stat .l{font-size:var(--text-xs); color:var(--muted); text-transform:uppercase; letter-spacing:var(--track-caps); margin-top:4px;}
.stat .d{font-size:var(--text-xs); margin-top:6px; color:var(--muted);}
/* The tile's rail is the SIGNAL RAIL — declared once in §17, which also aliases
   `.stat.is-alert` onto it. Only the numeral tint lives here: it intensifies the same
   reading unit rather than adding a second device. */
.stat.is-alert .n{color: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:var(--shadow-up);
}
.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:var(--surface); box-shadow:var(--shadow-lg); font-size:var(--text-base); 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:var(--text-base); 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:var(--text-sm); 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:var(--text-base); 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:600; color:var(--ink); font-size:var(--text-base);}
.rowcard .rc-kv{display:flex; flex-wrap:wrap; gap:4px 18px; font-size:var(--text-sm);}
.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;}

/* 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:var(--text-xs); font-weight:700; letter-spacing:var(--track-caps); 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:var(--text-sm); font-weight:600; color:var(--ink); font-variant-numeric:tabular-nums;}
.cal-cell.is-out .cal-date{color:var(--subtle);}   /* de-emphasized but AA text, 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:var(--text-xs); 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:var(--text-xs); 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:var(--text-xs); font-weight:700; letter-spacing:var(--track-caps); 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:600; color:var(--ink); font-size:var(--text-base);}
.cal-agenda .cal-row .ci span{font-size:var(--text-xs); 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); align-items:center;}
.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:var(--text-sm); 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:var(--text-base); 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:var(--text-sm); 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:var(--text-base); cursor:pointer; line-height:1; padding:2px 4px;}
.cal-pop .cp-title{font-size:var(--text-base); font-weight:700; color:var(--ink); line-height:1.3;}
.cal-pop .cp-kv{display:flex; gap:8px; font-size:var(--text-sm); 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:var(--text-xs); color:var(--muted); margin-bottom:3px;}
.cal-pop .cp-check ul{margin:0; padding-left:18px; font-size:var(--text-sm); 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:var(--scrim);}
.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:var(--text-base); cursor:pointer; line-height:1; padding:2px 4px;}
.cal-modal .cp-title{font-size:var(--text-base); 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:var(--text-sm); font-weight:600; color:var(--ink);}
.cal-modal .cm-sched{font-size:var(--text-sm); color:var(--ink-700);}
.cal-modal .cm-actions{display:flex; flex-wrap:wrap; gap:7px; margin-top:4px;}

/* 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 is the SIGNAL RAIL
   (§17) — `.notif-row.is-urgent` is one of its aliases, so the rail itself is not
   redeclared here; only the danger-tinted icon chip below is local. 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:var(--text-xs); font-weight:700; letter-spacing:var(--track-caps); 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-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:var(--text-xl); 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:var(--text-base); 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:var(--text-sm); 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:var(--text-xs); 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:var(--text-lg); line-height:1;}
.permrow .pr-label{flex:1 1 auto; min-width:0; font-size:var(--text-sm); 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:var(--text-sm); color:var(--muted); font-weight:600;}

/* 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:var(--text-micro);}
.actor-avatar-md{width:34px; height:34px; font-size:var(--text-xs);}
.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:var(--text-xs); 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:var(--surface); font-size:var(--text-xs); 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:var(--text-sm);}
.history-time{font-size:var(--text-xs); 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:var(--text-xs); font-weight:700; letter-spacing:var(--track-caps); 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:var(--text-sm);}
.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:var(--text-sm); display:flex; align-items:center; gap:8px; min-width:0;}
.maprow .mr-src .mono{font-size:var(--text-xs); 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:var(--text-sm); 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,.fieldgroup > .card-title-row{margin-bottom:var(--s4); display:block;}
.fieldgroup > .card-title-row{display:flex;}
.fieldgroup > .card-title-row .eyebrow{margin-bottom:0;}

/* 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:var(--text-base); font-weight:600; color:var(--ink);}
.checkrow .cr-sub{font-size:var(--text-sm); 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:var(--text-lg); font-weight:600; 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, color-mix(in srgb, var(--ink) 6%, transparent) 1px, transparent 1px) 0 0/100% 22px,
    linear-gradient(90deg, color-mix(in srgb, var(--ink) 6%, transparent) 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 color-mix(in srgb, var(--ink) 25%, transparent));}

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

/* ---- Named replacements for the inline-style corpus ----------------------
   1,931 static style="" attributes sat across 284 of 386 templates; what follows
   are the repeats that earned a name. Two kinds: components the library was
   actually missing, and the few layout mechanics that genuinely recur.
   Deliberately the LAST thing in section 6. Every rule below has to out-cascade a
   component default of EQUAL specificity — .page-head's margin-bottom, .grid's
   gap, .stack's gap, .field's margin-bottom, .actionbar's negative margin — and
   equal-specificity ties are settled by source order alone. Move this block
   earlier and .block-gap, .gap-* and .nomargin silently stop working.
   The 20 style="" attributes that interpolate a template value stay inline. */

/* The card IS the table's frame; a wrap inside it must not draw a second one.
   86 wraps hand-rolled border:0;box-shadow:none;border-radius:0 to say so — the
   same three declarations .modal .card and .drawer .card already use. Opt-in
   rather than a blanket `.card > .table-wrap`: 8 of the 86 are not a card's
   direct child (render fragments, .card-pad.stack wrappers), so a child rule
   would strand them, and the two boxed preview tables in custom_fields/
   change_type.html sit directly in a .card.card-pad and KEEP their frame. */
.table-wrap.flush{border:0; box-shadow:none; border-radius:0;}

/* A card that is itself a link. a{text-decoration:none} in section 2 already
   covers the resting state, so the 39 inline copies existed only to beat
   a:hover's underline — which is all this restates. Colour is deliberately not
   touched: .rowcard/.stat set none, so the anchor keeps --link, and folding
   color:inherit in here would repaint 39 live headings. */
a.rowcard, a.rowcard:hover, a.stat, a.stat:hover{text-decoration:none;}
/* The bare <a> that wraps a rowcard's innards on 19 list pages — that one does
   want inherited ink and a block box, because .rc-head/.rc-kv style themselves. */
.cardlink{display:block; color:inherit; text-decoration:none;}
.cardlink:hover{text-decoration:none;}

/* A <form> whose only job is to carry one button inside a row or a table cell.
   Stays display:inline, not inline-flex: the 27 call sites sit in <td>s and in
   .row/.actionbar flex parents, where inline is what they already render as. */
.form-inline{display:inline;}

/* Right-aligned, unwrapped cell — amounts, counts, dates, row-action columns.
   NOT folded into td.num: .num also switches on tabular-nums, which would change
   how 196 existing cells render. */
table.table td.cell-right, table.table th.cell-right{text-align:right; white-space:nowrap;}
/* The bulk-select checkbox column — th and td together on 8 list tables. */
table.table th.col-check, table.table td.col-check{width:26px;}

/* Two-column field grid. .grid's gap:var(--s4) applies to ROWS as well, and the
   .field children already carry margin-bottom:var(--s4), so the vertical rhythm
   doubles up; 34 grids zeroed the row axis by hand. */
.grid-form{gap:0 var(--s4);}

/* The form-card reading cap section 3's own LAYOUT note already ratifies
   ("form cards 560–720px"). 560 is the width with volume; 640 and 720 are a
   handful each and stay inline rather than grow a width ladder. */
.card-narrow{max-width:560px;}

/* Layout mechanics — four names, not a utility grid. Each is a single repeat
   measured in the templates, not a slot in a combinatorial scale. */
/* flex:1 on 140 elements across 98 templates, mostly the bare <div> that takes
   up the slack in a .row. Distinct from .spacer (flex:1 1 auto): basis 0 makes
   siblings share the line evenly no matter how wide their content is. */
.grow{flex:1;}
/* Margin reset for the elements section 2 does NOT already flatten — .field
   (margin-bottom:var(--s4)), <form>, <dl>, <span>, and .actionbar's negative
   pull-out. <p> and <h1>–<h4> are margin:0 in the base reset already, so those
   inline copies are deleted outright rather than re-stated here. */
.nomargin{margin:0;}
/* Page rhythm — the gap BELOW a block that is followed by another block. --s4 is
   the ordinary step; --s5 is the looser one used under a stat row or page head. */
.block-gap{margin-bottom:var(--s4);}
.block-gap-lg{margin-bottom:var(--s5);}
/* Density for the flex primitives (.stack, .row, .row-wrap, .grid), which is where
   every one of these call sites lives. The scale absorbs the off-scale strays:
   12px IS --s3, and 6px reads as --s2 in a vertical stack. Two it does NOT
   absorb — gap:var(--s4) on a .stack is already the default and is deleted, not
   classed; 6px on a label row is optical alignment against the checkbox and
   stays inline. */
.gap-tight{gap:var(--s2);}
.gap-mid{gap:var(--s3);}

/* ---- Wave 4: the components hiding in the compound repeats ---------------
   Wave 3 named the mechanics it could name without inventing anything, and
   stopped. What it left behind read as noise only because nobody had said out
   loud what these things ARE. Each rule below is one product thing, checked
   against every one of its call sites before it got a name, built from the
   values already in that markup — nothing here moves a pixel. Same cascade
   caveat as the block above: these have to sit after .row, .row-wrap, .small,
   .muted and .field, which means after everything else in section 6. */

/* The action strip along the foot of a rowcard — the phone twin of a list
   table's row-actions column, on 16 list pages. Row actions, the status picker
   and the bulk checkbox share it. It belongs to the .rc-* family (.rc-head,
   .rc-kv) that already dresses the rest of the card, so it is scoped the same
   way. The margin sits ON TOP of .rowcard's own 10px gap, on purpose: the strip
   stands further off the body than the body's lines stand off each other. */
.rowcard .rc-actions{
  margin-top:var(--s2); display:flex; align-items:center;
  gap:var(--s2); justify-content:flex-end;
}
/* The "Select" checkbox that opens the bulk lane from a card. margin-right:auto
   is the whole point — it pins the checkbox to the far left of that strip while
   the actions stay right, which is why the strip is a flex row and not a
   text-align. 6px rather than --s2 is the optical gap against the checkbox
   glyph that Wave 3 already ruled off-scale; inside a named component a literal
   is honest (.rowcard's own gap is 10px). */
.rowcard .rc-select{
  display:flex; align-items:center; gap:6px; margin-right:auto;
  font-size:var(--text-sm); color:var(--muted);
}

/* A .row / .row-wrap whose two ends are pushed apart. It is what .card-head
   does with `.card-title{margin-right:auto}` and .page-head does with
   `.ph-actions{margin-left:auto}`, named for the 19 rows that have neither a
   card head nor a page head to hang it on: a settings card's name-and-controls
   line, a peek's title row, a checkrow with its value on the right. A modifier,
   not a primitive — it says nothing about display, gap or wrapping, so it
   composes with .row, .row-wrap, .checkrow and .gap-tight instead of restating
   them. */
.row-split{justify-content:space-between;}

/* The explanatory line UNDER the thing it explains — a card's name, a form
   control, a bulk-result row, an integration's status. 23 of them spelled it
   `class="muted small"` plus a 4px top margin; that is one component, and
   .helptext (the same two type declarations, no margin) is its sibling for the
   inside of a .field, where .field's own gap does the spacing instead.
   Deliberately ONE offset: the 2px and 6px copies of the same element are
   drift, and a class per value would be the margin ladder this stylesheet
   has spent two waves refusing to grow. */
.subnote{margin-top:4px; font-size:var(--text-sm); color:var(--muted);}

/* The name of the thing a settings row configures — an SLA policy, a webhook
   endpoint, a report schedule, an integration, an assistant toggle. A text role
   the library was missing, in the family of .card-title / .eyebrow / .small
   rather than a weight utility: there is no 600 twin and no ladder here,
   because the five small bold sub-labels ("Roles", "Signing secret") are a
   different role and stay as they are. That this role is 700 while .card-title
   is 600 IS an inconsistency; naming it is what makes it visible, and settling
   it is a design decision rather than a sweep. */
.setting-name{font-weight:700;}

/* Text the user typed, shown with the line breaks they typed — notes,
   descriptions, precautions, corrective actions, a captured sync error. Ten
   read-only bodies across eight modules. Not folded into .muted: eight of them
   are muted and two are not, and the wrapping is the point either way. */
.freetext{white-space:pre-wrap;}

/* The "Photos (n)" attachment disclosure on a list row — the summary line and
   the paperclip inside it, which is why the icon rule lives here rather than
   beside .btn-ic. `list-style:none` is all the marker suppression these six
   call sites have ever had; no ::marker or ::-webkit-details-marker rule is
   added, because that would be a change rather than a name. The icon is 12px
   where .btn's is 16px on purpose — it sits in --text-sm running text, not on
   a control. */
.attach-toggle{cursor:pointer; list-style:none; font-size:var(--text-sm); color:var(--muted);}
.attach-toggle .btn-ic{width:12px; height:12px; display:inline-block; vertical-align:-2px;}

/* ---- 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:var(--surface); 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 color-mix(in srgb, var(--surface) 18%, transparent);
  }
}

/* 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;}
  /* The strip's own gap only (.page-context takes the slack): `>` keeps this off the
     .spacer labels inside its account and Appearance menus. */
  .topstrip > .spacer{display:none;}
  .modebtn{width:40px; padding:0; justify-content:center;}
  .modebtn [data-theme-mode-label], .modebtn .mode-auto-tag{display:none;}
  .weatherbtn{width:40px; padding:0; justify-content:center;}
  .weatherbtn .wb-temp{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.)
   background-color, never the `background` shorthand: the shorthand resets the
   select caret's background-repeat to `repeat` and tiles the 12×8 chevron. */
.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-color:var(--card); color:var(--ink-700); font-size:var(--text-base); 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:var(--text-xs); text-transform:uppercase; letter-spacing:var(--track-caps); color:var(--muted); font-weight:600;}
.kpi .n{font-family:var(--font-display); font-size:var(--text-2xl); font-weight:800; line-height:1.1; color:var(--ink); font-variant-numeric:tabular-nums; letter-spacing:var(--track-display);}
.kpi .s{font-size:var(--text-xs); 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:var(--text-2xl); line-height:1;}
.nav-card .t{font-weight:600; color:var(--ink); font-size:var(--text-base);}
.nav-card .d{color:var(--muted); font-size:var(--text-sm);}
.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:var(--text-xs); 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:var(--text-base); flex-basis:20px;}
.side-child.is-active{color:#fff;}
.side-subgroup{
  font-size:var(--text-micro); font-weight:700; letter-spacing:var(--track-caps); 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:var(--text-micro); font-weight:700; letter-spacing:var(--track-caps); 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 color-mix(in srgb, var(--surface) 18%, transparent); 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:var(--text-xs); color:var(--surface);
    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;}
}

/* ==========================================================================
   10. 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_help, 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);}
table.table th[aria-sort="none"] .th-sort::after{
  content:"↕"; opacity:0; font-size:var(--text-micro); line-height:1; transition:opacity .12s ease;
}
table.table th[aria-sort="none"] .th-sort:hover::after{opacity:.45;}
.sort-ind{font-size:var(--text-micro); 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:var(--text-sm);}
.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:var(--text-sm); 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:var(--text-xs); 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:var(--text-micro);}
.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:var(--text-sm);}
.list-command-panel-head a{font-size:var(--text-xs); 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:var(--text-xs); font-weight:700;}
.list-filter-field>.input{width:100%; min-height:32px; margin-bottom:6px; font-size:var(--text-sm); 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:var(--text-sm); 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:var(--text-xs); font-weight:600;}
.list-filter-dates .input{min-height:32px; padding:4px 7px; font-size:var(--text-sm);}
.list-filter-more{margin:0 0 10px; border-top:1px solid var(--line); padding-top:8px;}
.list-filter-more>summary{cursor:pointer; color:var(--link); font-size:var(--text-sm); 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:var(--text-xs); 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:var(--text-sm); 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:var(--text-micro); text-transform:uppercase; letter-spacing:var(--track-caps);}
.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:var(--text-sm);}
.list-sort-option:hover{background:var(--surface);}
.list-sort-option>a{padding:3px 5px; border-radius:5px; color:var(--ink-700); font-size:var(--text-xs); 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:var(--text-xs); font-weight:700;}
.list-view-save .input{min-height:34px; padding:6px 8px; font-size:var(--text-sm);}
.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_help.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:var(--text-sm);}
.page-help summary{cursor:pointer; color:var(--link); font-weight:600; width:fit-content;}
.page-help summary:hover{color:var(--link-hover); 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:var(--text-lg); 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(--link);}
.kb-article-title{font-weight:600; font-size:var(--text-base);}
.kb-article-arrow{color:var(--muted);}
.kb-toc{padding:12px 16px; margin-bottom:var(--s3); font-size:var(--text-sm);}
.kb-toc a{display:block; padding:2px 0; color:var(--link); 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:var(--text-base); line-height:1.65;}
.kb-article h2{font-size:var(--text-lg); 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:var(--text-base); 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(--link); 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:var(--text-sm); 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:var(--text-sm); 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);}

/* ==========================================================================
   11. 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:600; color:var(--ink); font-size:var(--text-base); 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:var(--text-xs); font-weight:600;
  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);}

/* ==========================================================================
   12. 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:var(--scrim);}
.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:var(--text-base); cursor:pointer; line-height:1; padding:4px 6px;}
.modal .modal-title{font-size:var(--text-lg); 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:var(--scrim);}
.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:var(--text-base); 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:var(--text-xs); font-weight:700; letter-spacing:var(--track-caps); 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:var(--text-sm);}
.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:var(--text-sm); 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;
}
/* .cell-right must survive a resized column. The td rule above is (0,3,2) and beats
   `table.table td.cell-right` (0,2,2), so a numeric cell that used to hold one line
   started wrapping the moment colmgr.js added .colmgr-sized. The inline style this
   class replaced won that fight by being inline; this restores the outcome. */
.table-wrap.colmgr-sized table.table td.cell-right{white-space:nowrap;}
.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));}
}
@media (min-width:720px){
  /* Desktop: sort lives on column headers, not a toolbar button. */
  .colmgr--sort{display:none;}
}

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

/* ==========================================================================
   13. 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:var(--text-sm); 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;} }

/* --- 14. 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:var(--text-sm); 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:var(--text-sm); 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:var(--text-sm); 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:var(--text-base); 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:var(--text-xs); text-transform:uppercase; letter-spacing:var(--track-caps); 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;} }

/* --- 15. 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:var(--text-sm); 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:var(--text-sm); 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:var(--text-xs); 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); 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:600; font-size:var(--text-base);
  line-height:1.35; text-decoration:none;}
.bd-card-title:hover{color:var(--brand-700);}
.bd-ref{font-size:var(--text-xs); color:var(--muted);}
.bd-meta{display:flex; align-items:center; gap:6px; min-width:0; flex-wrap:wrap;}
/* RATIFIED EXCEPTION: .02em, NOT --track-caps — two data-supplied initials centred in a
   24px circle; caps tracking adds a trailing space a flex centre cannot take back. */
.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:var(--text-micro); 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:var(--text-xs);}
.bd-due{margin-left:auto; color:var(--muted); font-size:var(--text-xs); white-space:nowrap;}
.bd-due.is-overdue{color:var(--danger); font-weight:600;}
.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:var(--text-sm);}
.bd-more{display:block; padding:9px 12px 11px; border-top:1px solid var(--line);
  color:var(--brand-700); font-size:var(--text-sm); 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:var(--text-micro); text-transform:uppercase; letter-spacing:var(--track-caps);}
.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;}}

/* --- 16. 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:var(--on-brand);
  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:var(--text-lg); 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:var(--text-sm); 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:var(--text-sm); 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:var(--text-xs);}
/* 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:var(--text-xs);
  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:var(--text-xs); 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:var(--text-sm);}

.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:var(--text-xs);}

@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;}}

/* Jira-style issue chrome — watch/share/⋯ on the light toolbar, one sidebar card,
   + Add instead of empty ledger cards, compact media/cost sections. */
.issue-toolbar{display:flex; align-items:center; gap:4px;}
.issue-toolbar .issue-tool-btn{
  width:auto; min-width:44px; height:44px; padding:0 10px; color:var(--ink-700);
  gap:6px; display:inline-flex; align-items:center; justify-content:center;
  font-size:var(--text-sm); font-weight:600; background:transparent;
}
.issue-toolbar .issue-tool-btn:hover{background:var(--surface); color:var(--ink);}
.issue-tool-label{white-space:nowrap;}
/* Compact AI chip in the issue chrome (Linear / Jira). Opens a small briefing popover. */
.ai-brief{position:relative; display:inline-flex; align-items:center;}
.ai-brief-btn{
  display:inline-flex; align-items:center; gap:5px; height:32px; padding:0 10px 0 8px;
  border-radius:999px; border:1px solid var(--line); background:var(--card);
  color:var(--ink-700); font-size:var(--text-sm); font-weight:600; cursor:pointer;
  line-height:1;
}
.ai-brief-btn .btn-ic{color:var(--brand);}
.ai-brief-btn:hover, .ai-brief-btn.is-open{
  border-color:var(--brand); color:var(--brand-700); background:var(--brand-50);
}
.ai-brief-btn:disabled{opacity:.7; cursor:wait;}
.ai-brief-btn:focus-visible{outline:2px solid var(--brand); outline-offset:2px;}
.ai-brief-pop{
  position:absolute; right:0; top:calc(100% + 8px); z-index:45;
  width:min(380px, calc(100vw - 24px)); max-height:min(360px, 70vh);
  display:flex; flex-direction:column;
  background:var(--card); border:1px solid var(--line); border-radius:var(--radius);
  box-shadow:var(--shadow-lg); overflow:hidden;
}
.ai-brief-pop[hidden]{display:none;}
.ai-brief-head{
  display:flex; align-items:center; gap:8px; padding:10px 10px 8px 14px;
  border-bottom:1px solid var(--line); flex:0 0 auto;
}
.ai-brief-head .btn-ic{color:var(--brand);}
.ai-brief-title{font-weight:700; font-size:var(--text-sm); color:var(--ink); flex:1 1 auto;}
.ai-brief-body{padding:12px 14px; overflow-y:auto; flex:1 1 auto;}
.ai-brief-pending{display:flex; align-items:center; gap:8px; min-height:48px;}
.ai-brief-html{font-size:var(--text-sm); line-height:1.55; color:var(--ink-700);}
.ai-brief-html > :first-child{margin-top:0;}
.ai-brief-html > :last-child{margin-bottom:0;}
.ai-brief-html p{margin:0 0 10px;}
.ai-brief-html ul, .ai-brief-html ol{margin:0 0 10px; padding-left:1.2em;}
.ai-brief-html li{margin:0 0 4px;}
.ai-brief-foot{
  display:flex; align-items:center; gap:8px; padding:8px 14px 10px;
  border-top:1px solid var(--line); flex:0 0 auto; font-size:var(--text-xs);
}
.ai-brief-foot .muted{flex:1 1 auto;}
.ai-brief-again{
  border:0; background:transparent; color:var(--brand-700); font-size:var(--text-xs);
  font-weight:600; cursor:pointer; padding:4px 0;
}
.ai-brief-again:hover{text-decoration:underline;}
@media (max-width:719px){
  .ai-brief-pop{
    position:fixed; left:12px; right:12px; top:auto; width:auto;
    bottom:calc(12px + env(safe-area-inset-bottom)); max-height:min(70vh, 480px);
  }
}
@media print{.ai-brief{display:none !important;}}
.issue-sidebar-card{padding:0; overflow:hidden;}
.issue-sidebar-block{padding:var(--s4); border-bottom:1px solid var(--line);}
.issue-sidebar-block:last-child,
.issue-sidebar-card > .record-attribution{border-bottom:0;}
.issue-sidebar-card .record-attribution{
  box-shadow:none; border:0; border-radius:0; background:transparent; padding:var(--s4);
}
.issue-add{position:relative; display:flex; flex-wrap:wrap; align-items:center; gap:8px;}
.issue-add-btn{
  display:inline-flex; align-items:center; gap:6px; min-height:36px; padding:0 12px;
  border-radius:var(--radius-sm); border:1px dashed var(--line); background:var(--card);
  color:var(--ink-700); font-weight:600; font-size:var(--text-sm); cursor:pointer;
}
.issue-add-btn:hover{background:var(--surface); border-color:var(--brand); color:var(--brand-700);}
.issue-add-pop{left:0; right:auto; min-width:220px;}
.issue-addon{
  flex:1 1 100%; padding:var(--s3); border:1px solid var(--line); border-radius:var(--radius);
  background:var(--card);
}
.issue-section{margin:0;}
.issue-section-head{
  display:flex; align-items:center; justify-content:space-between; gap:8px;
  margin-bottom:var(--s2);
}
.issue-media-grid{grid-template-columns:repeat(auto-fill, minmax(160px,1fr)); gap:var(--s3);}
.issue-media-item{
  background:var(--card); border:1px solid var(--line); border-radius:var(--radius); overflow:hidden;
}

/* Slack-style comment composer + @mention chips */
.slack-composer{position:relative; border:1px solid var(--line); border-radius:var(--radius);
  background:var(--card); overflow:visible;}
.slack-fmt{display:flex; flex-wrap:wrap; gap:2px; padding:6px 8px; border-bottom:1px solid var(--line);
  background:var(--surface);}
.slack-fmt-btn{border:0; background:transparent; color:var(--ink-700); min-width:28px; height:28px;
  border-radius:var(--radius-sm); font-size:var(--text-sm); font-weight:700; cursor:pointer;
  display:inline-flex; align-items:center; justify-content:center;}
.slack-fmt-btn:hover,.slack-ico:hover,.slack-ico.is-on{background:var(--brand-50); color:var(--brand-700);}
.slack-ta{border:0 !important; box-shadow:none !important; border-radius:0 !important;
  resize:vertical; min-height:72px;}
.slack-bar{display:flex; align-items:center; justify-content:space-between; gap:8px;
  padding:6px 8px; border-top:1px solid var(--line);}
.slack-bar-left,.slack-bar-right{display:flex; align-items:center; gap:2px;}
.slack-ico{border:0; background:transparent; color:var(--ink-700); width:32px; height:32px;
  border-radius:var(--radius-sm); cursor:pointer; display:inline-flex; align-items:center;
  justify-content:center; font-size:var(--text-sm); font-weight:700;}
.slack-send{min-height:32px;}
.slack-pop{position:absolute; z-index:20; left:8px; right:8px; bottom:48px; max-height:220px;
  overflow:auto; background:var(--card); border:1px solid var(--line); border-radius:var(--radius);
  box-shadow:var(--shadow-lg);}
.slack-emoji{display:flex; flex-wrap:wrap; gap:2px; padding:8px;}
.slack-emoji-btn{border:0; background:transparent; font-size:var(--text-xl); width:32px; height:32px;
  cursor:pointer; border-radius:var(--radius-sm);}
.slack-emoji-btn:hover{background:var(--surface);}
.slack-mention-row{display:flex; flex-direction:column; align-items:flex-start; gap:1px; width:100%;
  border:0; background:transparent; text-align:left; padding:8px 10px; cursor:pointer;}
.slack-mention-row:hover{background:var(--surface);}
.slack-mention-name{font-weight:600; font-size:var(--text-sm); color:var(--ink);}
.mention{display:inline; font-weight:600; color:var(--brand-700); background:var(--brand-50);
  border-radius:4px; padding:0 4px;}
.msg p{margin:0 0 6px;}
.msg p:last-child{margin-bottom:0;}
.msg ul,.msg ol{margin:4px 0 6px; padding-left:1.2em;}
.msg pre,.msg code{font-size:var(--text-sm);}
.msg pre{overflow:auto; background:var(--surface); padding:8px; border-radius:var(--radius-sm);}
.slack-excerpt{margin:0; padding:8px 12px; border-left:3px solid var(--brand); background:var(--surface);
  color:var(--ink-700); font-size:var(--text-sm);}


/* --- 17. SIGNAL RAIL — one status device, one edge, three tones + absence ---
   Collapses the two 3px accents that disagreed with each other. `.card-accent` (a
   decorative TOP edge, worn by six cards doing three unrelated jobs — pure decoration on
   four of them, "the assistant is live" on a fifth, "this shift row is expanded" on the
   sixth) retires to ONE job: the sign-in card, a brand moment with no data on it. It never
   appears on a data surface again. `.card.is-urgent`, `.stat.is-alert` and
   `.notif-row.is-urgent` were three copies of one declaration; they become aliases below.

   TONE = HOW MUCH THIS DEMANDS OF YOU, never what kind of thing it is:
     .signal-danger  past SLA · overdue · escalated · expired · down
     .signal-warn    due soon · at reorder · expiring
     .signal-brand   a queue addressed to YOU by name — the rarest tone, not the commonest
     (no class)      reference and navigation — most of the app, deliberately
   Reference gets NO rail rather than a grey one. A stripe every card wears means "card",
   not "demand", and the device is spent the moment it is universal. "Somebody must act on
   this" is not brand: every open work order is waiting on someone, so admitting that
   reading gives every card a rail within a quarter.

   WHERE IT IS LEGAL: where records are COMPARED and the surface is a CARD — list rowcards,
   board cards, KPI tiles, the notification feed. A detail page has no "many" to pick out
   of, so it gets a sentence (`.alert-danger`) instead. A table row is not a card: the
   `.table-wrap` radius clips the first and last rail, and the desktop table already carries
   urgency as a WORD in its SLA column, which is exactly what the phone rowcard lacked.

   COLOUR IS NEVER THE CARRIER (WCAG 1.4.1). The rail is an INDEX to a signal, never the
   signal itself. It is legal only on a surface that NAMES ITS OWN STATE IN WORDS — the KPI
   tile's `.l` already reads "Past SLA" / "AMC expiring soon" / "Low-stock items" / "Open
   incidents"; the escalated rowcard prints "⏰ escalated"; the board card prints its due
   date in --danger through `.bd-due.is-overdue`. That word IS the compliance, not
   decoration: strip the labels and the rail becomes colour-only. Position is the third
   carrier — railed surfaces are hoisted above unrailed ones, and reading order survives
   greyscale, a screen reader and every colour-vision deficiency.

   NOTHING HERE ANIMATES. An alarm that pulses is an alarm somebody covers with a sticky
   note — so the blanket prefers-reduced-motion rule earlier in this file is satisfied by
   construction, not by a second media query.

   BOTH THEMES BY CONSTRUCTION: every tone is a role token the dark :root block already
   redefines (--danger, --warn, --brand), so not one rule below is theme-conditional. A 3px
   stroke is a graphic object, so 3:1 governs; measured against the two grounds a railed
   surface sits on (--card, --surface) all six clear it with room to spare —
     light   --danger 6.57 / 6.13 · --warn 6.03 / 5.62 · --brand 4.67 / 4.35
     dark    --danger 5.97 / 6.63 · --warn 9.32 / 10.35 · --brand 5.10 / 5.66
   (WCAG 2.x relative luminance, same method as the palette's own AA fixes).

   The LAST section in the file to set a leading-edge border, on purpose. `.signal-*` is
   (0,1,0) — the same weight as `.card`, `.stat`, `.rowcard` and `.bd-card` — so only
   source order gives it the inline-start edge. §18 (the field app) comes after it and must
   never set `border`, `border-left` or `border-inline-start` on anything that could wear
   `.signal-*`: a later (0,1,0) rule that does takes the edge off a railed card.
   apps/web/test_field_pwa.py refuses every border property that reaches that edge, except
   on the never-railed selectors listed in its EDGE_GUARD_EXEMPT_SELECTORS.
   Written as `border-inline-start`, following the logical-property usage already in the
   responsive block. 3px, not wider: widening is what you do when you do not trust the
   rarity, and the rarity is the mechanism. */
.signal-danger{border-inline-start:3px solid var(--danger);}
.signal-warn{border-inline-start:3px solid var(--warn);}
.signal-brand{border-inline-start:3px solid var(--brand);}

/* Danger is the ONLY tone that touches type. A 24px Montserrat 800 numeral in --danger is
   the top of the scale, and the type-ramp wave already ratified the data numeral as display
   type's second job. Warn and brand numerals stay --ink: painting them would put tone on a
   third channel, drag --warn (#805D00, a dark olive) under the 4.5:1 TEXT bar, and flatten
   the danger→warn step that is the whole point. Twin of the `.stat.is-alert .n` rule above,
   kept so the ~20 templates still on `.stat.is-alert` are untouched by this wave. */
.stat.signal-danger .n{color:var(--danger);}

/* ALIASES — the shipped class names, now reaching the surfaces they always claimed.
   `.rowcard.is-urgent` has been DEAD CSS since the rowcards collapse: only `.card.is-urgent`
   and `.notif-row.is-urgent` ever had rules and neither matches a bare `.rowcard`. The gap
   is written out in full in work_items/list.html and pinned by
   test_work_items_ux_consistency.py. `.rowcards` renders only below 720px, where there are
   no columns and no position to spend — so this one selector is the only hierarchy a
   supervisor in a basement has ever had on a phone. Renaming ~30 templates is not this
   wave's job; new code writes `.signal-*`. */
.card.is-urgent,
.rowcard.is-urgent,
.notif-row.is-urgent,
.stat.is-alert{border-inline-start:3px solid var(--danger);}

/* `.card-accent` scoped to its one surviving job — the sign-in card (auth_base.html). The
   scoping IS the enforcement: a stray `card-accent` on a data card now renders nothing, so
   the device cannot drift back unnoticed the way it did before. `.auth-card` carries no
   other rule in this stylesheet, so the selector costs nothing. */
.auth-card.card-accent{border-top:3px solid var(--brand);}

/* Reference band — the dashboard's two narrative cards stop stacking as more identical
   full-width rectangles below the numbers. auto-fit, not `1fr 1fr`: either card is
   independently gated, so a fixed two-column grid would strand a lone card at half width.
   Hierarchy bought with position instead of colour.

   `min(320px,100%)`, not a bare 320px: `.container` is 16px-padded, so a 320px-wide phone
   leaves 288px of content and a hard 320px track minimum would push the PAGE into a
   horizontal scroll. The min() clamps the track to the container instead. `align-items:start`
   because these two cards have unrelated heights and stretching the short one is a lie about
   how much is in it. */
.band-2{display:grid; gap:var(--s4); grid-template-columns:repeat(auto-fit,minmax(min(320px,100%),1fr)); align-items:start;}

/* Cancelled PPM visits stay visible without successful-completion colouring. */
.cal-event.is-cancelled{background:var(--surface);color:var(--muted);}
.cal-legend .leg-cancelled .sw,.badge-dot.is-cancelled{background:var(--muted);}

/* --- 18. FIELD APP — install, role strip, offline field screen ---
   The installable field PWA: the install affordance and the one-time role strip on
   signed-in pages, the field status card on My work, and the offline field screen
   (web/offline_field.html) the service worker serves when the home page, My work or a QR
   landing cannot reach the network. It comes after §17, so no rule here may set `border`,
   `border-left` or `border-inline-start`: the signal rail keeps the leading edge only by
   source order, and the field screen's conflict and job cards wear `.signal-warn`.
   apps/web/test_field_pwa.py refuses every border property that could reach the leading
   edge (`border` and `border-inline` with their colour, style and width longhands,
   `border-left*` and `border-inline-start*`) from this section's first rule to the end of
   the file. The only exceptions are selectors listed in EDGE_GUARD_EXEMPT_SELECTORS in
   that file: elements that can never wear `.signal-*` (dialogs, sheets, button resets),
   one reason per entry.
   Tokens only — no raw colour, no prefers-color-scheme (dark mode is data-theme, as
   everywhere else) — and every tap target is at least 44px. Guards find these rules by
   selector, never by this title. */

/* Offline field screen — the generic "You're offline" block (offline_field.html), and the
   logo of the plain offline page (offline.html). */
.field-offline{align-items:center; max-width:380px;}
.field-offline-logo{height:44px; width:auto;}
/* Two lockups, one shown: the colour logo on the light ground, the design system's white
   lockup once theme-boot.js has set data-theme="dark". Both are in sw.js PRECACHE. */
.field-offline-logo.is-ondark,
[data-theme="dark"] .field-offline-logo:not(.is-ondark){display:none;}
[data-theme="dark"] .field-offline-logo.is-ondark{display:block;}

/* Field sign-out guard (web/partials/field_signout_dialog.html · web/field-sync.js). A native
   <dialog> opened with showModal(): the browser owns the top layer, focus and Escape, so no
   z-index is declared. The :not([open]) rule keeps a browser without <dialog> support from
   rendering it inline. Same frame as .pwa-sheet: 440px wide, 24px sides. Phones get a bottom
   sheet at the create-modal breakpoint (§12), with every button full width, Open offline view
   included. */
[data-field-signout-dialog]{width:min(440px, 100vw - 32px); max-height:85vh; overflow-y:auto;
  margin:auto; padding:var(--s4) var(--s5) var(--s5); border:1px solid var(--line);
  border-radius:var(--radius); background:var(--card); color:var(--ink); box-shadow:var(--shadow-lg);}
[data-field-signout-dialog]:not([open]){display:none;}
[data-field-signout-dialog]::backdrop{background:var(--scrim);}
[data-field-signout-dialog] .field-signout-title{font-size:var(--text-lg); font-weight:700;
  line-height:1.3; color:var(--ink); margin:0 0 var(--s2);}
/* The two count lines carry the hidden attribute, so they declare no display. */
[data-field-signout-dialog] .field-signout-line{margin:0 0 var(--s3);}
[data-field-signout-dialog] .field-signout-copy{margin:0 0 var(--s4);}
[data-field-signout-dialog] .field-signout-actions{display:flex; flex-wrap:wrap;
  justify-content:flex-end; gap:var(--s2);}
@media (max-width:719px){
  [data-field-signout-dialog]{width:100%; max-width:100%; margin:auto 0 0;
    border-radius:var(--radius) var(--radius) 0 0;
    padding-bottom:calc(var(--s5) + env(safe-area-inset-bottom));}
  [data-field-signout-dialog] .field-signout-actions .btn,
  [data-field-signout-dialog] .field-signout-line .btn{flex:1 1 100%;}
}
@media print{
  [data-field-signout-dialog]{display:none !important;}
}

/* Field role-switch guard (web/partials/field_roleswitch_dialog.html · web/field-sync.js). The
   same native <dialog> frame as the sign-out guard above, kept as its own rules so each guard's
   token test reads one block: 440px wide, a bottom sheet with full-width buttons on phones. */
[data-field-roleswitch-dialog]{width:min(440px, 100vw - 32px); max-height:85vh; overflow-y:auto;
  margin:auto; padding:var(--s4) var(--s5) var(--s5); border:1px solid var(--line);
  border-radius:var(--radius); background:var(--card); color:var(--ink); box-shadow:var(--shadow-lg);}
[data-field-roleswitch-dialog]:not([open]){display:none;}
[data-field-roleswitch-dialog]::backdrop{background:var(--scrim);}
[data-field-roleswitch-dialog] .field-signout-title{font-size:var(--text-lg); font-weight:700;
  line-height:1.3; color:var(--ink); margin:0 0 var(--s2);}
/* The count line carries the hidden attribute, so it declares no display. */
[data-field-roleswitch-dialog] .field-signout-line{margin:0 0 var(--s3);}
[data-field-roleswitch-dialog] .field-signout-copy{margin:0 0 var(--s4);}
[data-field-roleswitch-dialog] .field-signout-actions{display:flex; flex-wrap:wrap;
  justify-content:flex-end; gap:var(--s2);}
@media (max-width:719px){
  [data-field-roleswitch-dialog]{width:100%; max-width:100%; margin:auto 0 0;
    border-radius:var(--radius) var(--radius) 0 0;
    padding-bottom:calc(var(--s5) + env(safe-area-inset-bottom));}
  [data-field-roleswitch-dialog] .field-signout-actions .btn{flex:1 1 100%;}
}
@media print{
  [data-field-roleswitch-dialog]{display:none !important;}
}

/* Offline field screen (web/offline_field.html · field-shell.js). The PORTAL archetype: a
   phone-first column of cards modelled on web/console/_card.html, rendered by Alpine from the
   jobs saved on the phone, with no base.html chrome. It reuses .card, .btn, .badge, .alert,
   .checkrow, .stockbar and .input unchanged and declares only what the kit lacks. Token-built,
   so the light and dark data-theme blocks already cover it. Nothing here sets a border on the
   leading edge: a signal tone on the conflicts card or a conflicted job keeps the §17 rail.
   Every control meets the 44px floor through .btn, .input and .checkrow; the header chip is a
   status readout, not a control. */
.fs-shell{display:flex; flex-direction:column; gap:var(--s4); max-width:40rem; margin:0 auto;
  padding:calc(var(--s5) + env(safe-area-inset-top)) var(--s4) calc(var(--s6) + env(safe-area-inset-bottom));}
/* The field view is the page's second <main>; it carries hidden while the generic block shows. */
.fs-shell[hidden]{display:none;}
.fs-head{margin-bottom:0; flex-wrap:wrap;}
.fs-head > .badge{flex:0 0 auto; padding:4px 12px; font-size:var(--text-sm);}
.fs-h{font-family:var(--font); font-weight:600;}
.fs-ic{display:inline-flex; align-items:center; justify-content:center; width:16px; height:16px; flex:0 0 auto;}
.fs-tight{gap:4px;}
.fs-block{display:block;}
.fs-self-start{align-self:flex-start;}
.fs-in-sm{max-width:9rem;}
.fs-in-md{max-width:14rem;}
.fs-in-lg{width:calc(100% - 20px - var(--s3)); max-width:18rem; margin-top:4px;
  margin-inline-start:calc(20px + var(--s3));}
.fs-item-label{min-width:10rem;}
.fs-done .cr-title{text-decoration:line-through; color:var(--muted);}
.fs-conflict + .fs-conflict{padding-top:var(--s3); border-top:1px solid var(--line);}

/* §18 — install affordance (web/partials/pwa_install.html, pwa-install.js) and first-run role
   strip (web/partials/role_strip.html, role-strip.js). Token-built. Neither surface asks
   anything of the reader — both state a fact about the app — so neither carries a tone or a
   rail, and no rule here touches a surface the rail in section 17 decorates. */
/* Both ship with the `hidden` attribute and their scripts reveal them. `.btn`, `.btn-block`,
   `.menu-item` and `.role-strip` all set `display`, which beats the UA's [hidden] rule, so the
   attribute is re-asserted here at two-attribute weight. */
[data-pwa-install][hidden],
[data-role-strip][hidden]{display:none;}

/* Sign-in pages: the install button sits under the auth card, at the card's width. The
   column spaces its children with a flex gap, so the wrapper drops out of the layout until
   pwa-install.js reveals the trigger (the steps dialog has already moved to <body> by then). */
.auth-install{width:100%; max-width:400px;}
.auth-install:not(:has([data-pwa-install]:not([hidden]))){display:none;}

/* Role strip — the Ice Blue callout ground (--accent is reserved for callout/info), copy that
   wraps on a phone, and a 44px dismiss. The icon and the dismiss line up with the copy's FIRST
   line (align-items:flex-start): the icon drops (44 - 18) / 2 and the copy is padded by
   (44px - one 1.45 line) / 2, so line one centres on the dismiss and a one-line strip keeps
   its height. Sits at the top of <main>, above the flash messages. */
.role-strip{display:flex; align-items:flex-start; gap:var(--s3); margin:0 0 var(--s4);
  padding:var(--s1) var(--s1) var(--s1) var(--s4); border-radius:var(--radius);
  background:var(--accent); color:var(--ink-700); font-size:var(--text-sm); line-height:1.45;}
.role-strip-ic{flex:0 0 auto; display:inline-flex; width:18px; height:18px; margin-top:13px;
  color:var(--brand);}
.role-strip-text{flex:1 1 auto; min-width:0; padding-block:calc((44px - 1.45em) / 2);}
.role-strip-text b{color:var(--ink); font-weight:600;}
.role-strip-dismiss{flex:0 0 auto; width:44px; height:44px; padding:0; border:0;
  border-radius:var(--radius-sm); background:transparent; color:var(--muted); cursor:pointer;
  display:grid; place-items:center;}
.role-strip-dismiss:hover{background:color-mix(in srgb, currentColor 12%, transparent); color:var(--ink);}

/* .pwa-sheet — the iPhone Add to Home Screen steps: a native <dialog> opened with showModal().
   The top layer paints it above every overlay, so it needs no z-index. A centred card from
   720px, a bottom sheet with a full-width Done below it (the same breakpoint as .modal). The
   :not([open]) rule keeps a browser without <dialog> support (iOS before 15.4) from rendering
   the steps inline; there pwa-install.js toggles the open attribute itself. */
.pwa-sheet{width:min(440px, 100vw - 32px); max-width:none; max-height:85vh; margin:auto;
  padding:var(--s4) var(--s5) var(--s5); border:1px solid var(--line); border-radius:var(--radius);
  background:var(--card); color:var(--ink); box-shadow:var(--shadow-lg);}
.pwa-sheet:not([open]){display:none;}
.pwa-sheet::backdrop{background:var(--scrim);}
.pwa-sheet-title{margin:0 0 var(--s3); font-size:var(--text-lg); font-weight:700; line-height:1.3;
  color:var(--ink);}
.pwa-sheet-actions{display:flex; justify-content:flex-end; gap:var(--s2); margin-top:var(--s4);}
.pwa-steps{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:var(--s3);}
.pwa-steps li{display:flex; align-items:center; gap:var(--s3); font-size:var(--text-base);
  color:var(--ink-700);}
.pwa-step-ic{flex:0 0 auto; width:44px; height:44px; display:grid; place-items:center;
  border-radius:var(--radius-sm); background:var(--surface); color:var(--brand);}
.pwa-steps-after{margin-top:var(--s3);}
@media (max-width:719px){
  .pwa-sheet{width:100%; margin:auto 0 0; border-radius:var(--radius) var(--radius) 0 0;
    padding-bottom:calc(var(--s5) + env(safe-area-inset-bottom));}
  .pwa-sheet-actions .btn{flex:1 1 100%;}
}

/* .po-track — a purchase order's progress after it is sent (Zoho Books link): Raised → Approved
   → PO issued → Shared with vendor → Billed → Paid. One rule per step: a done step carries the
   ok ink, the step it is waiting on the brand rule, a rejection the danger rule. Steps wrap onto
   a second row on phones rather than scrolling sideways. */
.po-track{display:flex; flex-wrap:wrap; gap:var(--s2) var(--s3); list-style:none; margin:0; padding:0;}
.po-track li{flex:1 1 110px; min-width:0; border-top:3px solid var(--line); padding-top:var(--s2);}
.po-track .t-label{display:block; font-weight:600; font-size:var(--text-sm);}
.po-track .t-detail{display:block; font-size:var(--text-xs); color:var(--muted); overflow-wrap:anywhere;}
.po-track li.is-done{border-top-color:var(--ok);}
.po-track li.is-done .t-label{color:var(--ok-ink);}
.po-track li.is-current{border-top-color:var(--brand);}
.po-track li.is-stopped{border-top-color:var(--danger);}
.po-track li.is-stopped .t-label{color:var(--danger);}
.po-track li.is-todo .t-label{color:var(--muted);}
