/* =====================================================================
   tibr-shell.css — the appv3 page shell + house components (2026-09).

   One stylesheet, two brands. Palette and wordmark switch on
   <body data-brand="tibr|vyntworks">; everything else (density, hairlines,
   the grid, buttons, inputs) is shared. Design: flat, hairline borders,
   5px radii, tracked uppercase labels, tabular numerals, hover-revealed
   row actions. Nunito throughout.

   Prefix: ts-  (shell + components).  Colours are RGB triplets so we can
   write rgb(var(--ts-primary-500) / 0.15).
   ===================================================================== */

:root {
  --ts-sidebar-w: 13.5rem;
  --ts-chrome-h: 40px;
  --ts-r: 5px;
  --ts-font: 'Nunito', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --ts-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* ---- TIBR (default): wordmark orange + wordmark grey ------------------ */
body, body[data-brand="tibr"] {
  --ts-primary-50: 254 241 236;  --ts-primary-100: 253 218 204; --ts-primary-200: 251 181 155;
  --ts-primary-300: 248 142 103; --ts-primary-400: 244 115 63;  --ts-primary-500: 241 90 41;
  --ts-primary-600: 212 72 26;   --ts-primary-700: 173 57 19;   --ts-primary-800: 133 43 14;
  --ts-primary-900: 92 29 9;
  --ts-gray-50: 248 248 248;  --ts-gray-100: 241 241 242; --ts-gray-200: 220 221 222;
  --ts-gray-300: 190 191 193; --ts-gray-400: 152 153 156; --ts-gray-500: 115 116 119;
  --ts-gray-600: 88 89 91;    --ts-gray-700: 69 70 72;    --ts-gray-800: 51 52 54;
  --ts-gray-900: 34 35 36;    --ts-gray-950: 23 24 25;
  --ts-accent: 241 90 41;          /* active marker on the dark masthead */
  --ts-page-bg: #f6f6f6;
}

/* ---- Vyntworks: "Steel Navy & Chalk" ---------------------------------- */
body[data-brand="vyntworks"] {
  --ts-primary-50: 233 239 245;  --ts-primary-100: 217 227 238; --ts-primary-200: 183 201 220;
  --ts-primary-300: 143 176 212; --ts-primary-400: 95 132 171;  --ts-primary-500: 49 86 124;
  --ts-primary-600: 43 75 109;   --ts-primary-700: 34 61 89;    --ts-primary-800: 26 47 69;
  --ts-primary-900: 20 28 46;
  --ts-gray-50: 251 251 249;  --ts-gray-100: 241 242 238; --ts-gray-200: 221 224 218;
  --ts-gray-300: 195 200 194; --ts-gray-400: 154 161 174; --ts-gray-500: 107 115 130;
  --ts-gray-600: 76 85 104;   --ts-gray-700: 44 53 71;    --ts-gray-800: 31 39 56;
  --ts-gray-900: 20 28 46;    --ts-gray-950: 14 20 32;
  --ts-accent: 143 176 212;        /* light steel on the ink-navy masthead */
  --ts-page-bg: #f6f7f5;
}

:root { --ts-hair: rgb(20 28 46 / 0.09); --ts-hair-strong: rgb(20 28 46 / 0.16); }

/* ---- Base --------------------------------------------------------------- */
[v-cloak] { display: none !important; }
html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
body.ts-body {
  margin: 0; background: var(--ts-page-bg); color: rgb(var(--ts-gray-700));
  font-family: var(--ts-font); font-size: 0.875rem; line-height: 1.5;
}
/* Keep these at element specificity so .ts-area / .ts-mast-chip / .ts-item colours win. */
a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; }
.ts-body .mdi::before { line-height: 1; }

/* ---- Masthead ----------------------------------------------------------- */
.ts-chrome { position: fixed; top: 0; left: 0; right: 0; z-index: 50; }
.ts-areas {
  height: var(--ts-chrome-h); display: flex; align-items: stretch; gap: 0; padding: 0 0.5rem 0 0.25rem;
  background: rgb(var(--ts-gray-900)); border-bottom: 1px solid rgb(255 255 255 / 0.08);
}
.ts-areas-scroll { display: flex; align-items: stretch; flex: 1; min-width: 0; overflow-x: auto; scrollbar-width: none; }
.ts-areas-scroll::-webkit-scrollbar { display: none; }
/* When the Area strip overflows (tablet/phone) the scrollbar is hidden, so fade the
   clipped edge(s) as the hint that more tabs are off-screen. tibr-shell.js toggles
   the classes on scroll/resize and scrolls the active tab into view on load. */
/* 2026-10-05: wider, with a fully hidden outer band — the old 2.25rem ramp left most
   of a half-cut word ("3R INTELLIGENCE") readable, so it didn't read as a fade. */
.ts-areas-scroll { --ts-fade-l: linear-gradient(to right, transparent 0.75rem, #000 3.25rem); --ts-fade-r: linear-gradient(to left, transparent 0.75rem, #000 3.25rem); }
.ts-areas-scroll.ts-more-right { -webkit-mask-image: var(--ts-fade-r); mask-image: var(--ts-fade-r); }
.ts-areas-scroll.ts-more-left { -webkit-mask-image: var(--ts-fade-l); mask-image: var(--ts-fade-l); }
.ts-areas-scroll.ts-more-left.ts-more-right { -webkit-mask-image: var(--ts-fade-l), var(--ts-fade-r); mask-image: var(--ts-fade-l), var(--ts-fade-r); -webkit-mask-composite: source-in; mask-composite: intersect; }
.ts-area {
  display: flex; align-items: center; gap: 0.35rem; flex-shrink: 0; padding: 0 0.6rem;
  font-size: 0.62rem; font-weight: 600; letter-spacing: 0.09em; text-transform: uppercase; white-space: nowrap;
  color: rgb(var(--ts-gray-400)); border-bottom: 2px solid transparent; transition: color 0.15s, border-color 0.15s;
}
.ts-area .mdi { font-size: 0.9rem; }
.ts-area:hover { color: #fff; }
.ts-area--active { color: #fff; border-bottom-color: rgb(var(--ts-accent)); }
.ts-area--active .mdi { color: rgb(var(--ts-accent)); }
.ts-burger {
  display: flex; width: 34px; height: 100%; border: 0; background: transparent; color: rgb(var(--ts-gray-300));
  align-items: center; justify-content: center; font-size: 1.25rem; margin-right: 0.25rem;
}
.ts-burger:hover { color: #fff; }
.ts-mast-right { margin-left: auto; display: flex; align-items: center; gap: 0.3rem; padding-left: 0.5rem; flex-shrink: 0; }
.ts-mast-btn {
  width: 28px; height: 28px; border-radius: 6px; border: 0; background: transparent; color: rgb(var(--ts-gray-400));
  display: flex; align-items: center; justify-content: center; font-size: 1.05rem; position: relative;
}
.ts-mast-btn:hover { background: rgb(255 255 255 / 0.08); color: #fff; }
.ts-mast-avatar {
  width: 24px; height: 24px; border-radius: 99px; background: rgb(var(--ts-accent)); color: rgb(var(--ts-gray-900));
  font-size: 0.6rem; font-weight: 700; display: flex; align-items: center; justify-content: center; margin-left: 0.2rem;
}
body[data-brand="tibr"] .ts-mast-avatar { color: #fff; }
.ts-mast-chip {
  font-size: 0.6rem; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 700; padding: 0.15rem 0.45rem;
  border-radius: 4px; background: rgb(var(--ts-accent) / 0.18); color: rgb(var(--ts-accent)); margin-right: 0.35rem;
}
.ts-disabled { opacity: 0.45; pointer-events: none; }

/* ---- Sidebar ------------------------------------------------------------ */
.ts-sidebar {
  position: fixed; top: var(--ts-chrome-h); bottom: 0; left: 0; width: var(--ts-sidebar-w); z-index: 40;
  display: flex; flex-direction: column; background: #fff; border-right: 1px solid var(--ts-hair);
  transition: transform 0.2s ease;
}
.ts-sidebar-header { height: 3rem; flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; padding: 0 1rem; border-bottom: 1px solid var(--ts-hair); }
.ts-brand { display: flex; align-items: center; }
.ts-logo { height: 1.5rem; width: auto; display: block; }
.ts-wordmark { font-family: 'Newsreader', Georgia, 'Times New Roman', serif; font-weight: 600; font-size: 1.25rem; letter-spacing: -0.01em; line-height: 1; color: rgb(var(--ts-gray-900)); }
.ts-wordmark .tick { color: rgb(var(--ts-primary-500)); }
.ts-sidebar-close { display: none; border: 0; background: transparent; color: rgb(var(--ts-gray-500)); font-size: 1.2rem; width: 30px; height: 30px; border-radius: 5px; }
.ts-sidebar-close:hover { background: rgb(var(--ts-gray-100)); color: rgb(var(--ts-gray-900)); }

.ts-nav { flex: 1; overflow-y: auto; padding: 0.9rem 0.6rem; display: flex; flex-direction: column; gap: 0.05rem; }
.ts-item {
  display: flex; align-items: center; gap: 0.55rem; padding: 0.32rem 0.45rem; border-radius: var(--ts-r);
  font-size: 0.74rem; font-weight: 500; color: rgb(var(--ts-gray-700)); transition: background-color 75ms;
}
.ts-item .mdi { font-size: 1.05rem; color: rgb(var(--ts-gray-500)); flex-shrink: 0; width: 1.05rem; text-align: center; }
.ts-item span { flex: 1; min-width: 0; white-space: normal; line-height: 1.2; overflow-wrap: normal; } /* labels wrap, never truncate */
.ts-item:hover { background: rgb(var(--ts-gray-100)); }
.ts-item--active { background: rgb(var(--ts-primary-50)); box-shadow: inset 2px 0 0 rgb(var(--ts-primary-500)); border-radius: 0 var(--ts-r) var(--ts-r) 0; color: rgb(var(--ts-primary-700)); }
.ts-item--active .mdi { color: rgb(var(--ts-primary-700)); }
.ts-item--muted { color: rgb(var(--ts-gray-500)); font-weight: 400; }
.ts-group { margin-top: 0.55rem; }
.ts-group + .ts-group { margin-top: 0.35rem; }
.ts-group[open] { margin-bottom: 0.4rem; }
.ts-group-label {
  list-style: none; display: flex; align-items: center; justify-content: space-between; cursor: pointer; user-select: none;
  padding: 0.2rem 0.45rem; font-size: 0.58rem; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 600; color: rgb(var(--ts-gray-400));
}
.ts-group-label::-webkit-details-marker { display: none; }
.ts-group-label .mdi { font-size: 0.9rem; transition: transform 0.15s; }
.ts-group[open] > .ts-group-label .mdi { transform: rotate(180deg); }
.ts-group-label:hover { color: rgb(var(--ts-gray-700)); }
.ts-items { list-style: none; margin: 0.1rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.05rem; }
.ts-nav-foot { margin-top: auto; padding-top: 0.75rem; }

.ts-roundel { display: flex; align-items: center; gap: 0.4rem; margin: 0.2rem 0.4rem 0.45rem; padding: 0.45rem 0.25rem 0; border-top: 1px solid var(--ts-hair); }
.ts-roundel-avatar { flex-shrink: 0; width: 1.6rem; height: 1.6rem; border-radius: 99px; background: rgb(var(--ts-gray-900)); color: #fff; font-size: 0.6rem; font-weight: 700; display: flex; align-items: center; justify-content: center; }
.ts-roundel-meta { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.ts-roundel-name { font-size: 0.7rem; font-weight: 600; color: rgb(var(--ts-gray-800)); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ts-roundel-email { font-size: 0.6rem; color: rgb(var(--ts-gray-400)); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Tenant (company) name, between the user's name and email: one muted line, ellipsised. */
.ts-roundel-company { font-size: 0.62rem; font-weight: 600; color: rgb(var(--ts-gray-500)); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ts-roundel-logout button { border: 0; background: transparent; color: rgb(var(--ts-gray-400)); font-size: 1rem; display: flex; padding: 0.2rem; border-radius: 4px; }
.ts-roundel-logout button:hover { color: rgb(var(--ts-primary-600)); background: rgb(var(--ts-gray-100)); }

.ts-backdrop { position: fixed; inset: var(--ts-chrome-h) 0 0 0; background: rgb(20 28 46 / 0.45); z-index: 39; }
.ts-backdrop[hidden] { display: none; }

/* ---- Main column -------------------------------------------------------- */
.ts-main-ctn { padding-top: var(--ts-chrome-h); margin-left: var(--ts-sidebar-w); min-height: 100vh; transition: margin-left 0.2s ease; }
/* Desktop: the masthead burger collapses the sidebar off-canvas for screen real
   estate (state kept in localStorage by tibr-shell.js; body.ts-nav-collapsed is
   set before first paint by the inline script in partials.shell). */
@media (min-width: 960px) {
  body.ts-nav-collapsed .ts-sidebar { transform: translateX(-100%); }
  body.ts-nav-collapsed .ts-main-ctn { margin-left: 0; }
}
.ts-main { padding: 1rem 1.75rem 2.5rem; display: flex; flex-direction: column; gap: 0.9rem; }

/* Page header: breadcrumb (tracked caps) above the title; actions on the right */
.ts-header { display: flex; align-items: flex-end; justify-content: space-between; gap: 1rem; padding: 0 0 0.75rem; border-bottom: 1px solid var(--ts-hair); }
.ts-crumb { margin: 0 0 0.15rem; font-size: 0.66rem; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600; color: rgb(var(--ts-gray-400)); }
.ts-crumb span { opacity: 0.4; margin: 0 0.3rem; }
.ts-crumb b { color: rgb(var(--ts-gray-700)); font-weight: 600; }
.ts-crumb a:hover { color: rgb(var(--ts-primary-600)); }
.ts-title { margin: 0; font-size: 1.05rem; font-weight: 600; letter-spacing: -0.015em; color: rgb(var(--ts-gray-950)); line-height: 1.3; }
/* Wraps: a page with more than one header action (badge + two buttons, e.g.
   Terminology) ran 430px wide and side-scrolled the whole page at 390. */
.ts-header-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; flex-shrink: 0; }

/* ---- Buttons ------------------------------------------------------------ */
.ts-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.35rem; border: 0; border-radius: var(--ts-r);
  padding: 0.38rem 0.7rem; font-size: 0.74rem; font-weight: 600; line-height: 1.1rem; white-space: nowrap;
  transition: background-color 75ms, color 75ms; text-decoration: none;
}
.ts-btn .mdi { font-size: 0.95rem; }
.ts-btn:disabled { opacity: 0.5; cursor: default; }
.ts-btn-primary { background: rgb(var(--ts-gray-900)); color: #fff; }
.ts-btn-primary:hover:not(:disabled) { background: rgb(var(--ts-primary-700)); }
.ts-btn-gray { background: #fff; color: rgb(var(--ts-gray-800)); box-shadow: inset 0 0 0 1px var(--ts-hair-strong); }
.ts-btn-gray:hover:not(:disabled) { background: rgb(var(--ts-gray-50)); }
.ts-btn-ghost { background: transparent; color: rgb(var(--ts-gray-600)); }
.ts-btn-ghost:hover:not(:disabled) { background: rgb(var(--ts-gray-100)); color: rgb(var(--ts-gray-900)); }
.ts-btn-danger { background: #fff; color: #b3462e; box-shadow: inset 0 0 0 1px rgb(179 70 46 / 0.35); }
.ts-btn-danger:hover:not(:disabled) { background: #fbeeea; }
.ts-btn-sm { padding: 0.25rem 0.5rem; font-size: 0.68rem; }
.ts-icon-btn { width: 1.55rem; height: 1.55rem; border-radius: 4px; border: 0; background: transparent; color: rgb(var(--ts-gray-500)); display: inline-flex; align-items: center; justify-content: center; font-size: 0.95rem; }
.ts-icon-btn:hover:not(:disabled) { background: rgb(var(--ts-gray-100)); color: rgb(var(--ts-gray-900)); }
.ts-icon-btn.danger:hover:not(:disabled) { background: #fbeeea; color: #b3462e; }
.ts-icon-btn:disabled { opacity: 0.4; cursor: default; }
.ts-link { background: none; border: 0; padding: 0; font: inherit; font-size: 0.72rem; font-weight: 600; color: rgb(var(--ts-primary-600)); cursor: pointer; }
.ts-link:hover { text-decoration: underline; }

/* ---- Badges / notices / toast ------------------------------------------ */
.ts-badge { display: inline-flex; align-items: center; gap: 0.25rem; border-radius: 4px; padding: 0.05rem 0.4rem; font-size: 0.6rem; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600; line-height: 1.1rem; color: rgb(var(--ts-gray-600)); box-shadow: inset 0 0 0 1px var(--ts-hair-strong); white-space: nowrap; }
.ts-badge-success { background: #edf7f1; color: #1f7a52; box-shadow: inset 0 0 0 1px rgb(31 122 82 / 0.25); }
.ts-badge-info { background: rgb(var(--ts-primary-50)); color: rgb(var(--ts-primary-700)); box-shadow: inset 0 0 0 1px rgb(var(--ts-primary-500) / 0.25); }
.ts-notice { border: 1px solid var(--ts-hair); border-left: 3px solid rgb(var(--ts-primary-400)); background: #fff; border-radius: var(--ts-r); padding: 0.55rem 0.8rem; font-size: 0.74rem; color: rgb(var(--ts-gray-700)); }
.ts-notice-warn { border-left-color: #d97706; background: #fffbeb; color: #92400e; }
.ts-toast { position: fixed; top: calc(var(--ts-chrome-h) + 12px); right: 16px; z-index: 3100; padding: 0.5rem 0.9rem; border-radius: var(--ts-r); color: #fff; font-size: 0.78rem; font-weight: 600; box-shadow: 0 6px 18px rgb(20 28 46 / 0.2); }
.ts-toast-success { background: #1f7a52; }
.ts-toast-error { background: #b3462e; }
/* Added 2026-09-26 (Governance): a toast for a change that NARROWS what someone
   may do — a gate going on, an email sender list closing. Neither a success nor
   an error, and the old Vuetify snackbar had "warning" for exactly this. */
.ts-toast-warn { background: #8a5a00; }

/* ---- Cards / sections --------------------------------------------------- */
.ts-card { background: #fff; border: 1px solid var(--ts-hair); border-radius: var(--ts-r); }
.ts-card-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 0.55rem 0.85rem; border-bottom: 1px solid var(--ts-hair); }
.ts-card-title { margin: 0; font-size: 0.8rem; font-weight: 600; color: rgb(var(--ts-gray-950)); }
.ts-card-body { padding: 0.8rem 0.85rem; }
.ts-muted { color: rgb(var(--ts-gray-500)); }
.ts-note { font-size: 0.68rem; color: rgb(var(--ts-gray-400)); margin: 0; }
.ts-note b { color: rgb(var(--ts-gray-600)); font-weight: 600; }

/* ---- Form fields -------------------------------------------------------- */
.ts-label { display: block; font-size: 0.68rem; font-weight: 600; color: rgb(var(--ts-gray-700)); margin-bottom: 0.3rem; }
.ts-input, .ts-select, .ts-textarea {
  display: block; width: 100%; font: inherit; font-size: 0.78rem; color: rgb(var(--ts-gray-900)); background: #fff;
  border: 1px solid var(--ts-hair-strong); border-radius: var(--ts-r); padding: 0.4rem 0.6rem; outline: none;
}
.ts-input:focus, .ts-select:focus, .ts-textarea:focus { border-color: rgb(var(--ts-primary-500)); box-shadow: 0 0 0 2px rgb(var(--ts-primary-500) / 0.15); }
.ts-input::placeholder { color: rgb(var(--ts-gray-400)); }
.ts-input:disabled, .ts-select:disabled { background: rgb(var(--ts-gray-50)); color: rgb(var(--ts-gray-500)); }

/* ---- Editable grid ------------------------------------------------------ */
.ts-grid-ctn { background: #fff; border: 1px solid var(--ts-hair); border-radius: var(--ts-r); overflow: hidden; }
.ts-grid-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 0.6rem 0.85rem; border-bottom: 1px solid var(--ts-hair); }
.ts-grid-head p { margin: 0; font-size: 0.72rem; color: rgb(var(--ts-gray-500)); max-width: 72rem; line-height: 1.5; }
.ts-grid-head p a, .ts-grid-head p b { color: rgb(var(--ts-primary-600)); font-weight: 600; }
.ts-grid-head p b { color: rgb(var(--ts-gray-700)); }
.ts-grid-wrap { overflow-x: auto; }
.ts-grid { width: 100%; border-collapse: collapse; table-layout: fixed; }
.ts-grid th { text-align: left; padding: 0.4rem 0.6rem; font-size: 0.58rem; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 600; color: rgb(var(--ts-gray-500)); background: rgb(var(--ts-gray-50)); border-bottom: 1px solid var(--ts-hair); white-space: nowrap; }
.ts-grid th.num, .ts-grid td.num { text-align: right; }
.ts-grid tbody tr { border-bottom: 1px solid var(--ts-hair); }
.ts-grid tbody tr:hover { background: rgb(var(--ts-primary-50) / 0.45); }
.ts-grid td { padding: 0.15rem 0.35rem; vertical-align: middle; height: 2.1rem; }
.ts-cell { display: block; width: 100%; border: 1px solid transparent; background: transparent; border-radius: 3px; padding: 0.3rem 0.35rem; font: inherit; font-size: 0.78rem; color: rgb(var(--ts-gray-900)); outline: none; min-height: 1.9rem; }
.ts-cell::placeholder { color: rgb(var(--ts-gray-400)); }
.ts-cell:hover:not(:disabled) { border-color: var(--ts-hair); }
.ts-cell:focus { background: #fff; border-color: rgb(var(--ts-primary-500)); box-shadow: 0 0 0 2px rgb(var(--ts-primary-500) / 0.15); }
.ts-cell:disabled { color: rgb(var(--ts-gray-600)); }
.ts-cell.num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--ts-mono); font-size: 0.74rem; }
.ts-cell.num::-webkit-outer-spin-button, .ts-cell.num::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.ts-cell.num[type=number] { -moz-appearance: textfield; }
.ts-num-wrap { display: flex; align-items: center; justify-content: flex-end; gap: 0.25rem; }
.ts-num-wrap .unit { font-size: 0.68rem; color: rgb(var(--ts-gray-400)); padding-right: 0.35rem; }
.ts-default { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.66rem; font-weight: 600; color: rgb(var(--ts-gray-500)); padding-left: 0.35rem; background: none; border: 0; cursor: pointer; font-family: inherit; }
.ts-default:disabled { cursor: default; }
.ts-default .ring { width: 0.85rem; height: 0.85rem; border-radius: 99px; border: 1.5px solid var(--ts-hair-strong); display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
.ts-default.on .ring { border-color: rgb(var(--ts-primary-600)); background: rgb(var(--ts-primary-600)); }
.ts-default.on .ring::after { content: ''; width: 0.3rem; height: 0.3rem; border-radius: 99px; background: #fff; }
.ts-default.on { color: rgb(var(--ts-primary-700)); }
.ts-default .set { opacity: 0; transition: opacity 0.12s; }
.ts-grid tbody tr:hover .ts-default:not(:disabled) .set { opacity: 1; }
/* Row actions sit quiet at rest and come up full on hover/focus. They used to
   be opacity 0 until hover, which hid the only Save/Delete on 34 settings grids
   from anyone who did not think to hover (Payment Terms, 2026-09-26) and from
   touch screens wider than 960px, which cannot hover at all. */
.ts-row-actions { display: flex; justify-content: flex-end; gap: 0.15rem; opacity: 0.7; transition: opacity 0.12s; }
.ts-grid tbody tr:hover .ts-row-actions, .ts-grid tbody tr:focus-within .ts-row-actions, .ts-row-actions.always { opacity: 1; }
@media (hover: none) { .ts-row-actions { opacity: 1; } }
/* Section heading inside a grid: a tinted full-width row that labels the rows
   under it (registries that arrive grouped — Terminology, parameter sets). */
.ts-grid tr.ts-group, .ts-grid tbody tr.ts-group:hover { background: rgb(var(--ts-gray-50)); }
.ts-grid tr.ts-group td { height: auto; padding: 0.3rem 0.6rem; font-size: 0.58rem; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 600; color: rgb(var(--ts-gray-500)); }
.ts-grid tr.ts-add { background: rgb(var(--ts-gray-50) / 0.6); }
.ts-grid tr.ts-add:hover { background: rgb(var(--ts-gray-50)); }
.ts-grid tr.ts-add td { height: 2.3rem; }
.ts-grid tr.ts-add .ts-cell { border-style: dashed; border-color: var(--ts-hair); background: #fff; }
.ts-grid tr.ts-add .ts-cell:focus { border-style: solid; }
.ts-grid-empty { padding: 1.2rem; text-align: center; font-size: 0.74rem; color: rgb(var(--ts-gray-400)); }
.ts-grid-foot { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 0.45rem 0.85rem; font-size: 0.68rem; color: rgb(var(--ts-gray-500)); background: rgb(var(--ts-gray-50)); border-top: 1px solid var(--ts-hair); flex-wrap: wrap; }
.ts-grid-foot b { color: rgb(var(--ts-gray-700)); font-weight: 600; }
.ts-grid-foot .status { display: inline-flex; align-items: center; gap: 0.35rem; }
.ts-grid-foot .status i { width: 6px; height: 6px; border-radius: 99px; background: #1f7a52; display: inline-block; }
.ts-grid-foot .actions { display: flex; align-items: center; gap: 0.5rem; }

/* ---- Responsive --------------------------------------------------------- */
@media (max-width: 959.98px) {
  .ts-burger { display: flex; }
  .ts-sidebar { transform: translateX(-100%); box-shadow: none; width: min(18rem, 86vw); }
  body.ts-nav-open .ts-sidebar { transform: none; box-shadow: 0 0 40px rgb(20 28 46 / 0.25); }
  body.ts-nav-open { overflow: hidden; }
  .ts-sidebar-close { display: flex; align-items: center; justify-content: center; }
  .ts-main-ctn { margin-left: 0; }
  .ts-main { padding: 0.9rem 1rem 2rem; }
  .ts-item { padding: 0.5rem 0.55rem; font-size: 0.82rem; }
  .ts-item .mdi { font-size: 1.15rem; }
  .ts-group-label { padding: 0.35rem 0.55rem; font-size: 0.62rem; }
  .ts-row-actions { opacity: 1; }
  .ts-default .set { opacity: 1; }
  /* The burger is drawn 34px wide in a 40px masthead; give it a 40px hit area
     on tablets too (phones get the same from the tap-target block below). */
  .ts-burger { position: relative; }
  .ts-burger::before {
    content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: max(100%, 2.5rem); height: 2.5rem;
  }
}
/* Tablet: the seven Area tabs need ~709px at desktop spacing, so at 768–900 the
   strip overflowed by a few px and "Settings" faded out tucked against the "?"
   help button. Tighten tab padding + tracking so the full strip fits from ~730px
   up; below that it still scrolls with the edge fade.
   2026-10-16: an eighth tab (Sales) put the strip back over the line and Settings
   was clipped again at 820, so the tab icons go at tablet width — the labels and
   the active underline carry the strip, and that frees ~150px. */
@media (min-width: 600px) and (max-width: 959.98px) {
  .ts-area { padding: 0 0.42rem; letter-spacing: 0.05em; gap: 0.3rem; }
  .ts-area .mdi { display: none; }
  .ts-mast-right { padding-left: 0.75rem; }
}
@media (max-width: 599.98px) {
  .ts-header { flex-direction: column; align-items: flex-start; gap: 0.5rem; }
  .ts-mast-right .ts-mast-chip { display: none; }
  /* Grid → stacked cards: each row a card, each cell labelled by data-label */
  .ts-grid-wrap { overflow: visible; }
  .ts-grid, .ts-grid tbody, .ts-grid tr, .ts-grid td { display: block; width: 100%; }
  .ts-grid thead { display: none; }
  .ts-grid tbody tr { padding: 0.5rem 0.65rem 0.6rem; border-bottom: 1px solid var(--ts-hair); }
  .ts-grid tbody tr:hover { background: transparent; }
  .ts-grid td { height: auto; padding: 0.3rem 0; text-align: left !important; }
  .ts-grid td[data-label]::before { content: attr(data-label); display: block; font-size: 0.58rem; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 600; color: rgb(var(--ts-gray-500)); margin-bottom: 0.1rem; }
  .ts-grid td.num .ts-num-wrap { justify-content: flex-start; }
  /* 2.5rem, not 2.4 — measured 38px, 2px short of the 40px tap target on every
     converted grid (Tax Rates, Payment Terms, Project Types, Team Names too). */
  .ts-cell { border-color: var(--ts-hair); min-height: 2.5rem; font-size: 0.9rem; }
  .ts-cell.num { text-align: left; }
  .ts-row-actions { justify-content: flex-start; gap: 0.5rem; padding-top: 0.25rem; }
  .ts-icon-btn { width: 2.5rem; height: 2.5rem; font-size: 1.15rem; box-shadow: inset 0 0 0 1px var(--ts-hair-strong); }
  /* Text buttons clear 40px too — the padding alone leaves them at 30px, which
     is short of the tap target on a phone (header actions, grid Add, footer
     reset). Same treatment .ts-icon-btn already gets above. */
  .ts-btn { min-height: 2.5rem; }
  /* ...and across. min-height alone left a short label under the tap target:
     the grid's inline-add "Add" measured 37px wide at 390 on every page with
     an add row (Tax Rates, Project Managers). 2.5rem = 40px either way. */
  .ts-btn { min-width: 2.5rem; }
  /* The breadcrumb link's 40px tap target: see the .ts-crumb block further down. */
  /* 2.5rem = 40px, matching .ts-btn / .ts-icon-btn / .ts-cell. At 2.4rem this
     measured 38px on a phone — 2px under the tap target — on every grid that
     carries a default picker (Tax Rates). */
  .ts-default { min-height: 2.5rem; font-size: 0.8rem; }
  /* A section heading stays a heading in card view — a label, not a card. */
  .ts-grid tr.ts-group { padding: 0.55rem 0.65rem 0.15rem; border-bottom: 0; }
  .ts-grid tr.ts-group td { padding: 0; }
  .ts-grid tr.ts-add td { height: auto; }
  .ts-grid-foot { flex-direction: column; align-items: flex-start; }
}
@media (min-width: 960px) {
  .ts-sidebar-close { display: none; }
}

/* ---- Modals -------------------------------------------------------------- */
/* House dialog: a ts-card in an overlay. Scrolls its own body so a tall form
   (payment-plan stages) never pushes the footer buttons off screen, and drops
   to full width on phones. */
.ts-modal { position: fixed; inset: 0; z-index: 3000; display: flex; align-items: flex-start; justify-content: center; padding: 1rem; overflow-y: auto; background: rgb(20 28 46 / 0.45); }
.ts-modal-card { display: flex; flex-direction: column; width: 100%; max-width: 34rem; margin: auto; max-height: calc(100vh - 2rem); background: #fff; border: 1px solid var(--ts-hair); border-radius: var(--ts-r); }
.ts-modal-lg { max-width: 62rem; }
.ts-modal-head { flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 0.55rem 0.85rem; border-bottom: 1px solid var(--ts-hair); }
.ts-modal-title { margin: 0; font-size: 0.8rem; font-weight: 600; color: rgb(var(--ts-gray-950)); }
/* A title long enough to wrap squeezed the close button to 39px wide on a phone. */
.ts-modal-head .ts-icon-btn { flex-shrink: 0; }
.ts-modal-body { flex: 1; min-height: 0; overflow-y: auto; padding: 0.8rem 0.85rem; }
.ts-modal-foot { flex-shrink: 0; display: flex; align-items: center; justify-content: flex-end; gap: 0.5rem; padding: 0.55rem 0.85rem; border-top: 1px solid var(--ts-hair); background: rgb(var(--ts-gray-50)); }
.ts-modal-foot .spacer { margin-right: auto; }
.ts-modal-text { margin: 0; font-size: 0.78rem; line-height: 1.55; color: rgb(var(--ts-gray-700)); white-space: pre-line; }
@media (max-width: 599.98px) {
  .ts-modal { padding: 0.5rem; align-items: stretch; }
  .ts-modal-card { max-height: calc(100vh - 1rem); }
  .ts-modal-foot { flex-wrap: wrap; }
  .ts-modal-foot .ts-btn { min-height: 2.4rem; flex: 1 1 auto; justify-content: center; }
  /* The rule above outranks the generic .ts-btn min-height and left dialog
     buttons at 38px — 2px short of the tap target on every page with a house
     modal (My Company, Payment Plans, Brand Kit, Project Managers). */
  .ts-modal-foot .ts-btn { min-height: 2.5rem; }
  /* KothingEditor (Spec Library, the quote page's Edit for this quote) draws its
     toolbar at 30x28 — under the tap target. The tray already wraps, so bigger
     buttons just take another row. Has to out-rank the vendor's own
     `.kothing-editor .ke-menu-list li .ke-btn { min-width: 30px }`. */
  .ts-modal .kothing-editor .ke-menu-list li .ke-btn,
  .ts-modal .kothing-editor .ke-menu-list li .ke-btn-select { min-width: 2.5rem; min-height: 2.5rem; height: 2.5rem; line-height: 2.5rem; }
}

/* ---- Additions (2026-09-04) ---------------------------------------------- */
/* The Tailwind forms reset strips the native select arrow, so a single-choice
   .ts-select would read as a text input. (Multi-selects — the Tax Rates state
   picker — are a listbox and keep the plain box.) */
select.ts-select:not([multiple]) {
  padding-right: 1.6rem;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23737477' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.45rem center;
  background-size: 0.8rem;
}

/* A grid with too many columns to survive tablet width can opt into the phone
   card layout early: .ts-grid-md-cards stacks from 959.98px down (the <600
   rules in Responsive above already cover phones for every grid). */
@media (min-width: 600px) and (max-width: 959.98px) {
  .ts-grid-md-cards-wrap { overflow: visible; }
  .ts-grid.ts-grid-md-cards, .ts-grid.ts-grid-md-cards tbody,
  .ts-grid.ts-grid-md-cards tr, .ts-grid.ts-grid-md-cards td { display: block; width: 100%; }
  .ts-grid.ts-grid-md-cards thead { display: none; }
  .ts-grid.ts-grid-md-cards tbody tr { padding: 0.5rem 0.85rem 0.6rem; border-bottom: 1px solid var(--ts-hair); }
  .ts-grid.ts-grid-md-cards tbody tr:hover { background: transparent; }
  .ts-grid.ts-grid-md-cards td { height: auto; padding: 0.3rem 0; text-align: left !important; }
  .ts-grid.ts-grid-md-cards td[data-label]::before { content: attr(data-label); display: block; font-size: 0.58rem; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 600; color: rgb(var(--ts-gray-500)); margin-bottom: 0.1rem; }
  .ts-grid.ts-grid-md-cards td.num .ts-num-wrap { justify-content: flex-start; }
  .ts-grid.ts-grid-md-cards .ts-cell.num { text-align: left; }
  .ts-grid.ts-grid-md-cards .ts-row-actions { justify-content: flex-start; gap: 0.5rem; padding-top: 0.25rem; }
  .ts-grid.ts-grid-md-cards .ts-icon-btn { width: 2.5rem; height: 2.5rem; font-size: 1.15rem; box-shadow: inset 0 0 0 1px var(--ts-hair-strong); }
}

/* ---- Additions (2026-09-05) ---------------------------------------------- */
/* House switch: the on/off control for a single setting (Team Names' "Use
   Teams"). Flat like everything else — a hairline track that fills with the
   brand primary when on, no gradient, no shadow. Sits in a .ts-setting-row so
   the label/description and the switch line up inside a .ts-card-body. */
.ts-switch {
  position: relative; flex-shrink: 0; width: 2.15rem; height: 1.2rem; padding: 0; border: 0;
  border-radius: 99px; background: rgb(var(--ts-gray-300)); transition: background-color 120ms;
}
.ts-switch::after {
  content: ''; position: absolute; top: 2px; left: 2px; width: 0.85rem; height: 0.85rem;
  border-radius: 99px; background: #fff; transition: transform 120ms;
}
.ts-switch.on { background: rgb(var(--ts-primary-600)); }
.ts-switch.on::after { transform: translateX(0.95rem); }
.ts-switch:focus-visible { outline: 2px solid rgb(var(--ts-primary-500) / 0.5); outline-offset: 2px; }
.ts-switch:disabled { opacity: 0.5; cursor: default; }

/* A labelled setting line: text on the left, control on the right. */
.ts-setting-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.ts-setting-row .label { font-size: 0.78rem; font-weight: 600; color: rgb(var(--ts-gray-900)); }
.ts-setting-row .help { margin: 0.1rem 0 0; font-size: 0.72rem; color: rgb(var(--ts-gray-500)); line-height: 1.5; }
@media (max-width: 599.98px) {
  /* Bigger tap target on phones, same flat look. The track only needs to be
     24px tall to read well, so ::before extends the hit area to 44px without
     drawing anything — the visual stays flat, the thumb target clears 40px. */
  .ts-switch { width: 2.6rem; height: 1.5rem; }
  .ts-switch::after { width: 1.1rem; height: 1.1rem; top: 3px; left: 3px; }
  .ts-switch.on::after { transform: translateX(1.1rem); }
  .ts-switch::before {
    content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: calc(100% + 0.5rem); height: 2.75rem;
  }
}

/* ---- Additions (2026-09-06) ---------------------------------------------- */
/* The toast is confirmation, never a control — but anchored top-right it landed
   exactly on .ts-header-actions, so for its four seconds elementFromPoint over
   a page's primary button returned the toast and a second save click was
   swallowed. Move it to the bottom-right, which every appv3 page leaves empty
   (the old floating help launcher is display:none here), and make it
   click-through so it can never intercept anything again. */
.ts-toast { top: auto; bottom: 16px; pointer-events: none; }

/* Tap targets on phones, round two (found measuring Regional at 390x844).
   Last night's pass covered .ts-icon-btn, .ts-btn and .ts-crumb a; these were
   still short.

   1. The house form controls. .ts-cell (the grid's inline input) was raised to
      2.4rem for phones, but .ts-input / .ts-select / .ts-textarea — what an
      ordinary settings form is built from — stayed at 34px. Regional is seven
      of them, so they grow to a real 40px.
   2. The masthead and sidebar chrome. These are sized to the 40px masthead and
      look right as they are, so they keep their drawn size and gain a 40px
      hit area the way .ts-switch does — nothing moves, nothing is redrawn. */
@media (max-width: 599.98px) {
  .ts-input, .ts-select, .ts-textarea { min-height: 2.5rem; }

  .ts-area, .ts-burger, .ts-mast-btn, .ts-mast-avatar,
  .ts-sidebar-close, .ts-brand, .ts-roundel-logout button { position: relative; }
  .ts-area::before, .ts-burger::before, .ts-mast-btn::before, .ts-mast-avatar::before,
  .ts-sidebar-close::before, .ts-brand::before, .ts-roundel-logout button::before {
    content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: max(100%, 2.5rem); height: 2.5rem;
  }
}

/* ---- Additions (2026-09-06, My Company) ---------------------------------- */
/* Inline form validation. The house form had a label, an input and a note, but
   no way to say "this field is wrong" — every page that validates on save was
   about to invent its own red text. First used by My Company's add/edit
   dialog; Freelancer Login, FX Rates and API Connections have the same need. */
.ts-req { color: #b3462e; font-weight: 700; }
.ts-field-err { display: block; margin-top: 0.25rem; font-size: 0.66rem; font-weight: 600; color: #b3462e; }
.ts-input.is-err, .ts-select.is-err, .ts-textarea.is-err { border-color: #b3462e; }
.ts-input.is-err:focus, .ts-select.is-err:focus, .ts-textarea.is-err:focus {
  border-color: #b3462e; box-shadow: 0 0 0 2px rgb(179 70 46 / 0.15);
}

/* Tap targets, round three (found measuring My Company at 390x844). In-prose
   links — the "Regional" cross-reference in a grid's head paragraph, the links
   in a .ts-note — sit on a 16px line and cannot grow without wrecking the
   paragraph, so they take the .ts-crumb a treatment: the drawn link is
   untouched and gains a 40px-tall hit area. Kept to the link's own width so it
   never reaches sideways over the words either side of it. Shared: Tax Rates
   and Payment Plans carry the same links. */
@media (max-width: 599.98px) {
  .ts-grid-head p a, .ts-note a { position: relative; display: inline-block; }
  .ts-grid-head p a::before, .ts-note a::before {
    content: ''; position: absolute; top: 50%; left: 0; transform: translateY(-50%);
    width: 100%; height: 2.5rem;
  }
}

/* ---- Additions (2026-09-07, Brand Kit) ----------------------------------- */
/* A colour field. The native swatch sits beside the hex box and the two read as
   one control: same hairline, same radius, and the swatch matches the height of
   a .ts-input so a colour row lines up with the text rows either side of it.
   The house form could ask for a name, a number or a choice but had no way to
   ask for a colour — Document Themes and Document Styles want the same pair. */
.ts-color { display: flex; align-items: center; gap: 0.4rem; }
.ts-color input[type="color"] {
  flex-shrink: 0; width: 2.4rem; height: 2rem; padding: 2px; background: #fff; cursor: pointer;
  border: 1px solid var(--ts-hair-strong); border-radius: var(--ts-r);
}
.ts-color input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.ts-color input[type="color"]::-webkit-color-swatch { border: 0; border-radius: 3px; }
.ts-color input[type="color"]::-moz-color-swatch { border: 0; border-radius: 3px; }
.ts-color input[type="color"]:disabled { opacity: 0.5; cursor: default; }
.ts-color .ts-input { flex: 1; min-width: 0; font-family: var(--ts-mono); font-size: 0.74rem; }

/* A read-only colour chip: shows a colour inline beside its hex, e.g. a
   suggestion the user has not accepted yet. */
.ts-swatch {
  display: inline-block; flex-shrink: 0; width: 0.9rem; height: 0.9rem; border-radius: 3px;
  border: 1px solid var(--ts-hair-strong); vertical-align: -0.1rem;
}

/* Machine values in prose — a hex, an id, a path. */
.ts-mono { font-family: var(--ts-mono); font-size: 0.72rem; color: rgb(var(--ts-gray-700)); }

@media (max-width: 599.98px) {
  /* The swatch opens the OS colour picker, so it is a tap target like the box
     next to it. 2.5rem square, matching .ts-input's phone height. */
  .ts-color input[type="color"] { width: 2.5rem; height: 2.5rem; }
}

/* ---- Additions (2026-09-08) ---------------------------------------------- */
/* Stat tiles: a short row of read-only counters above a settings page (Quote
   Numbering's adoption snapshot). Flat like a .ts-card, tabular figures, and
   an auto-fill track so three tiles become one column on a phone without any
   per-page media query. AI Usage and the Governance pages want the same. */
.ts-stats {
  display: grid; gap: 0.6rem; margin-bottom: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}
.ts-stat { padding: 0.75rem 0.9rem; background: #fff; border: 1px solid var(--ts-hair); border-radius: var(--ts-r); }
.ts-stat .k { font-size: 0.62rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: rgb(var(--ts-gray-500)); }
.ts-stat .v {
  margin-top: 0.15rem; font-size: 1.4rem; font-weight: 600; line-height: 1.15;
  color: rgb(var(--ts-gray-900)); font-variant-numeric: tabular-nums;
}
.ts-stat .v small { font-size: 0.78rem; font-weight: 400; color: rgb(var(--ts-gray-500)); }
.ts-stat .s { margin: 0.15rem 0 0; font-size: 0.68rem; color: rgb(var(--ts-gray-500)); line-height: 1.45; }

/* Segmented control: two or three mutually exclusive choices for one setting
   (Manual / AI on each Quote Numbering card). One hairline box, the chosen
   segment fills; replaces Vuetify's v-btn-toggle. */
.ts-seg { display: inline-flex; flex-shrink: 0; border: 1px solid var(--ts-hair-strong); border-radius: var(--ts-r); overflow: hidden; }
.ts-seg button {
  min-height: 1.9rem; padding: 0.2rem 0.7rem; border: 0; background: #fff; cursor: pointer;
  font: inherit; font-size: 0.72rem; font-weight: 600; color: rgb(var(--ts-gray-600));
}
.ts-seg button + button { border-left: 1px solid var(--ts-hair-strong); }
.ts-seg button:hover:not(.on) { background: rgb(var(--ts-gray-50)); }
.ts-seg button.on { background: rgb(var(--ts-gray-900)); color: #fff; }
.ts-seg button:focus-visible { outline: 2px solid rgb(var(--ts-primary-500) / 0.5); outline-offset: -2px; }

/* A worked example of what a rule produces — the "next five" strip under each
   convention card. Machine output, so monospace and quietly tinted. */
.ts-preview { padding: 0.55rem 0.7rem; background: rgb(var(--ts-gray-50)); border: 1px solid var(--ts-hair); border-radius: var(--ts-r); }
.ts-preview .k { font-size: 0.62rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: rgb(var(--ts-gray-500)); }
.ts-preview .vals { margin-top: 0.25rem; font-family: var(--ts-mono); font-size: 0.72rem; color: rgb(var(--ts-gray-800)); overflow-wrap: anywhere; }
.ts-preview .vals .sep { color: rgb(var(--ts-gray-400)); }

@media (max-width: 599.98px) {
  /* Segments are tap targets like every other button on the page. */
  .ts-seg button { min-height: 2.5rem; min-width: 3.5rem; }
}

@media (max-width: 599.98px) {
  /* The settings accordion headers are the control that opens each group in
     the drawer, so they are tap targets like every item under them. They
     measured 33px on every converted page — the first phone measurement that
     opened the drawer and walked the sidebar caught it. */
  .ts-group-label { min-height: 2.5rem; }
}

/* ---- Chip picker -------------------------------------------------------- */
/* A type-to-filter list that reads back as chips. Sits in a grid cell like an
   .ts-cell does, so it hovers to an outline and focuses to the primary ring.
   The panel is position:fixed — the page places it off the control's rect so
   .ts-grid-wrap's overflow can never clip it. Added for Default Authoriser;
   Governance and Valuations want the same control. */
.ts-picker { position: relative; }
.ts-picker-control {
  display: flex; align-items: center; flex-wrap: wrap; gap: 0.25rem; width: 100%; min-height: 1.9rem;
  border: 1px solid transparent; border-radius: 3px; background: transparent; padding: 0.25rem 1.25rem 0.25rem 0.35rem;
  font: inherit; font-size: 0.78rem; color: rgb(var(--ts-gray-900)); text-align: left; cursor: pointer; position: relative;
}
.ts-picker-control:hover:not(:disabled) { border-color: var(--ts-hair); }
.ts-picker-control:focus-visible, .ts-picker.open .ts-picker-control {
  outline: none; background: #fff; border-color: rgb(var(--ts-primary-500)); box-shadow: 0 0 0 2px rgb(var(--ts-primary-500) / 0.15);
}
.ts-picker-control:disabled { opacity: 0.5; cursor: default; }
.ts-picker-control .caret { position: absolute; right: 0.3rem; top: 50%; transform: translateY(-50%); font-size: 0.9rem; color: rgb(var(--ts-gray-400)); }
.ts-picker.open .ts-picker-control .caret { transform: translateY(-50%) rotate(180deg); }
.ts-picker-ph { color: rgb(var(--ts-gray-400)); }
.ts-chip {
  display: inline-flex; align-items: center; gap: 0.15rem; border-radius: 3px; padding: 0.05rem 0.1rem 0.05rem 0.35rem;
  font-size: 0.7rem; font-weight: 600; color: rgb(var(--ts-gray-700)); background: #fff; box-shadow: inset 0 0 0 1px var(--ts-hair-strong);
}
.ts-chip button { border: 0; background: none; padding: 0 0.15rem; color: rgb(var(--ts-gray-400)); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; font-size: 0.8rem; }
.ts-chip button:hover { color: #b3462e; }
.ts-picker-panel {
  position: fixed; z-index: 1500; display: flex; flex-direction: column; background: #fff;
  border: 1px solid var(--ts-hair-strong); border-radius: var(--ts-r); box-shadow: 0 8px 24px rgb(20 28 46 / 0.16); overflow: hidden;
}
.ts-picker-search { display: flex; align-items: center; gap: 0.35rem; padding: 0.3rem 0.5rem; border-bottom: 1px solid var(--ts-hair); color: rgb(var(--ts-gray-400)); flex-shrink: 0; }
.ts-picker-search input { border: 0; outline: 0; width: 100%; font: inherit; font-size: 0.78rem; color: rgb(var(--ts-gray-900)); background: transparent; }
.ts-picker-list { overflow-y: auto; padding: 0.2rem; min-height: 0; }
.ts-picker-opt {
  display: flex; align-items: center; gap: 0.4rem; width: 100%; border: 0; background: none; padding: 0.35rem 0.4rem;
  border-radius: 3px; font: inherit; font-size: 0.78rem; color: rgb(var(--ts-gray-700)); text-align: left; cursor: pointer;
}
.ts-picker-opt:hover { background: rgb(var(--ts-gray-50)); }
.ts-picker-opt.on { color: rgb(var(--ts-primary-700)); font-weight: 600; }
.ts-picker-opt .mdi { color: rgb(var(--ts-gray-400)); font-size: 0.95rem; flex-shrink: 0; }
.ts-picker-opt.on .mdi { color: rgb(var(--ts-primary-600)); }
.ts-picker-empty { padding: 0.6rem; text-align: center; font-size: 0.72rem; color: rgb(var(--ts-gray-400)); }
/* In an inline add row the picker reads as "empty and waiting", like .ts-cell. */
.ts-grid tr.ts-add .ts-picker-control { border-style: dashed; border-color: var(--ts-hair); background: #fff; }
.ts-grid tr.ts-add .ts-picker.open .ts-picker-control { border-style: solid; }

@media (max-width: 599.98px) {
  /* Every part of the picker a finger lands on clears 40px: the control, each
     option, the search box and a chip's remove button. */
  .ts-picker-control { min-height: 2.5rem; font-size: 0.9rem; border-color: var(--ts-hair); }
  .ts-picker-opt { min-height: 2.5rem; font-size: 0.85rem; }
  /* The row was 40px but the <input> inside it only 35px, so a tap in the top
     or bottom 2.5px of the row focused nothing (measured on Settings →
     Approvals, 2026-10-12). The input now fills the row and the row keeps its
     height — the padding moves onto the input. */
  .ts-picker-search { min-height: 2.5rem; padding-top: 0; padding-bottom: 0; }
  .ts-picker-search input { min-height: 2.5rem; }
  .ts-chip { font-size: 0.8rem; padding-left: 0.5rem; }
  .ts-chip button { min-width: 2.5rem; min-height: 2.5rem; font-size: 1rem; }
}

/* ---- Tree (hierarchical list) ------------------------------------------
   Added 2026-09-09 for Settings › Default Quote Items, the first page whose
   content is a parent/child tree rather than a flat grid. Rows WRAP instead of
   truncating: on the old page every item name in the 1/3-width column was
   ellipsised to nothing at 1440, and the row controls were clipped off the
   right edge. Cost Category Templates and the Spec Library want the same list. */
.ts-tree {
  display: flex; flex-direction: column; gap: 0.25rem; padding: 0.3rem; overflow-y: auto; outline: none;
  border: 1px solid var(--ts-hair); border-radius: var(--ts-r); background: rgb(var(--ts-gray-50));
}
.ts-tree:focus-visible { box-shadow: 0 0 0 2px rgb(var(--ts-primary-500) / 0.3); }
.ts-tree-row {
  display: flex; align-items: center; flex-wrap: wrap; gap: 0.25rem; padding: 0.2rem 0.3rem;
  background: #fff; border: 1px solid var(--ts-hair); border-radius: 4px; cursor: pointer;
}
.ts-tree-row:hover { border-color: var(--ts-hair-strong); }
.ts-tree-row.sel { border-color: rgb(var(--ts-primary-500)); background: rgb(var(--ts-primary-50) / 0.5); }
.ts-tree-row.off .ts-tree-name { color: rgb(var(--ts-gray-400)); font-weight: 400; }
.ts-tree-rail { display: inline-flex; flex-shrink: 0; color: rgb(var(--ts-gray-300)); }
.ts-tree-rail > span { width: 0.9rem; height: 1.4rem; display: inline-flex; align-items: center; justify-content: center; font-size: 0.72rem; }
.ts-tree-rail > span.line { border-left: 1px solid var(--ts-hair); }
.ts-tree-name { flex: 1 1 7rem; min-width: 0; font-size: 0.76rem; font-weight: 600; color: rgb(var(--ts-gray-900)); overflow-wrap: anywhere; }
.ts-tree-actions { display: inline-flex; align-items: center; gap: 0.1rem; margin-left: auto; flex-shrink: 0; }
.ts-tree-empty { padding: 1.2rem 0.6rem; text-align: center; font-size: 0.74rem; color: rgb(var(--ts-gray-400)); }
.ts-tree-empty b { color: rgb(var(--ts-gray-600)); font-weight: 600; }
@media (max-width: 599.98px) {
  /* Give the name its own line so the 40px controls sit under it, never
     squeezing it back to an ellipsis. */
  .ts-tree-name { flex-basis: 100%; order: -1; padding: 0.15rem 0.1rem 0; }
  .ts-tree-actions { margin-left: auto; }
}

/* Key cap for a keyboard-shortcut hint (Alt + arrows on the tree). */
.ts-kbd {
  display: inline-block; min-width: 1.15rem; padding: 0 0.25rem; margin: 0 0.05rem;
  border: 1px solid var(--ts-hair-strong); border-bottom-width: 2px; border-radius: 3px; background: #fff;
  font-family: var(--ts-mono); font-size: 0.62rem; font-weight: 600; line-height: 1.15rem;
  color: rgb(var(--ts-gray-600)); text-align: center;
}

/* ---- Additions (2026-09-09) ---------------------------------------------- */
/* A <select> that lives in a grid cell. .ts-cell strips the box down to a
   transparent field that outlines on hover, but the Tailwind forms reset also
   strips the native arrow — so a picker read as plain text. Draw the same
   chevron .ts-select uses, sized for the cell. Team Names hand-rolled this as
   .tm-select; Cost Category Templates has four such columns and Valuations and
   the Spec Library want the same. */
select.ts-cell {
  padding-right: 1.5rem;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23737477' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.35rem center;
  background-size: 0.8rem;
}

/* ---- Additions (2026-09-10) ---------------------------------------------- */
/* A tick box for selecting rows in a grid. The native control is 13px — far
   under the 40px tap target — and a pseudo-element on an <input> is not
   reliable, so the box lives inside a label that IS the hit area (clicking the
   label toggles the input natively). Excel Quote Print Templates selects rows
   this way; Word Quote Print Templates and the other template pages do too. */
.ts-check {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.5rem; height: 1.5rem; margin: 0; cursor: pointer;
}
.ts-check input[type="checkbox"] {
  width: 0.95rem; height: 0.95rem; margin: 0; cursor: pointer;
  accent-color: rgb(var(--ts-primary-600));
}
@media (max-width: 599.98px) {
  .ts-check { width: 2.5rem; height: 2.5rem; }
  .ts-check input[type="checkbox"] { width: 1.15rem; height: 1.15rem; }
}

/* A fold: a titled section of a long form that opens and closes. Flat like
   everything else — a hairline rule and a tracked uppercase title, no card
   inside a card. The Excel print settings are 200 fields in five folds; the
   MCP and Furniture Import Mapping pages have the same shape. */
.ts-fold { border-top: 1px solid var(--ts-hair); }
.ts-fold:first-child { border-top: 0; }
.ts-fold-head {
  display: flex; width: 100%; align-items: center; justify-content: space-between; gap: 0.5rem;
  padding: 0.72rem 0; background: none; border: 0; font: inherit; text-align: left; cursor: pointer;
}
.ts-fold-head .t {
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  color: rgb(var(--ts-gray-900));
}
.ts-fold-head .mdi { font-size: 1.05rem; color: rgb(var(--ts-gray-400)); }
.ts-fold-head:hover .t, .ts-fold-head:hover .mdi { color: rgb(var(--ts-primary-600)); }
.ts-fold-head:focus-visible { outline: 2px solid rgb(var(--ts-primary-500) / 0.5); outline-offset: 2px; }
.ts-fold-body { padding: 0.15rem 0 1.1rem; }
@media (max-width: 599.98px) {
  .ts-fold-head { min-height: 2.5rem; }
}

/* ---- Additions (2026-09-12) ---------------------------------------------- */
/* A refused field inside an editable grid. `.ts-input.is-err` above only ever
   covered the boxed form controls, so a red field in a .ts-grid cell — the
   Scope Templates level and column grids, the PO Templates mappings — drew no
   border at all and only the message underneath said anything was wrong. */
.ts-cell.is-err { border-color: #b3462e; background: rgb(179 70 46 / 0.04); }
.ts-cell.is-err:focus { border-color: #b3462e; box-shadow: 0 0 0 2px rgb(179 70 46 / 0.15); }

/* ---- Additions (2026-09-17, Valuations) --------------------------------- */
/* A radio list: one choice from a few, each with a sentence or two saying what
   it does (Valuations' three ways to carry internal variations). .ts-seg only
   fits short labels; this is the same idea for options that need explaining.
   The whole row is the <label>, so the row is the hit area on a phone and
   clicking anywhere on it picks the option natively. The Tailwind forms reset
   is commented out in app.css, so the radio is native — accent-color takes it off the browser's blue and onto the brand. Sync Connections
   and Connections have radio groups of the same shape. */
.ts-choices { display: flex; flex-direction: column; gap: 0.4rem; }
.ts-choice {
  display: flex; align-items: flex-start; gap: 0.65rem; margin: 0; padding: 0.6rem 0.75rem; cursor: pointer;
  background: #fff; border: 1px solid var(--ts-hair); border-radius: var(--ts-r); transition: border-color 75ms, background-color 75ms;
}
.ts-choice:hover { border-color: var(--ts-hair-strong); }
.ts-choice.on { border-color: rgb(var(--ts-primary-500)); background: rgb(var(--ts-primary-50) / 0.5); }
.ts-choice:focus-within { box-shadow: 0 0 0 2px rgb(var(--ts-primary-500) / 0.15); }
.ts-choice.is-disabled { cursor: default; }
.ts-choice.is-disabled:hover:not(.on) { border-color: var(--ts-hair); }
.ts-choice input[type="radio"] {
  flex-shrink: 0; width: 0.95rem; height: 0.95rem; margin: 0.12rem 0 0; cursor: inherit;
  accent-color: rgb(var(--ts-primary-600));
}
.ts-choice input[type="radio"]:focus { box-shadow: none; outline: none; }
.ts-choice .t { display: block; font-size: 0.78rem; font-weight: 600; color: rgb(var(--ts-gray-900)); }
.ts-choice .d { display: block; margin-top: 0.15rem; font-size: 0.72rem; color: rgb(var(--ts-gray-600)); line-height: 1.5; }
@media (max-width: 599.98px) {
  .ts-choice { min-height: 2.5rem; padding: 0.7rem 0.75rem; }
  .ts-choice input[type="radio"] { width: 1.15rem; height: 1.15rem; margin-top: 0.05rem; }
  .ts-choice .t { font-size: 0.85rem; }
}
/* The breadcrumb links are ~16px of text — on a phone every converted page's
   one control under the 40px tap target. The link box itself is 40px tall:
   min-height 2.5rem, with negative vertical margins that hand back exactly the
   extra 24px (2.5rem - the 15.84px line), so the header does not grow and the
   title does not move. z-index keeps the title's text from taking the lower
   part of the tap. Replaces the 17 Sep ::before hit area, which caught taps
   but left the link measuring 16px to every tap-target check.
   (2026-10-11, measured on the Valuation page at 390.) */
@media (max-width: 599.98px) {
  .ts-crumb a {
    position: relative; z-index: 1;
    display: inline-flex; align-items: center;
    min-height: 2.5rem; padding: 0 0.25rem; margin: -0.755rem -0.25rem;
  }
}

/* A progress meter: how much of an allowance is used. Flat and hairline like
   everything else — a 6px track, a tinted fill, no gradient or shadow. Green
   until 80%, amber past it, red once the allowance is spent. Added for the
   AI Usage monthly cap (2026-09-24); additive, nothing above changes. */
.ts-meter { height: 6px; border-radius: 99px; background: rgb(var(--ts-gray-200)); overflow: hidden; }
.ts-meter > span { display: block; height: 100%; border-radius: 99px; background: #1f7a52; transition: width 0.2s ease; }
.ts-meter.warn > span { background: #b8860b; }
.ts-meter.over > span { background: #b3462e; }

/* ---- Additions (2026-10-04, sweep K2): shell tap targets on a phone ------
   Measured at 390x844 on every converted page: the masthead burger (34x39),
   the Area tabs (39 high), the Report and ? buttons (28x28), the avatar
   (24x24), the drawer's close (30x30) and the sidebar's Sign out (22x30)
   were all under the 40px tap target.

   The 2026-09-06 pass gave them a 40px ::before hit area instead of growing
   them, and that could never work here: the masthead is 40px tall, so a 40px
   hit area centred on a 39px control runs half a pixel off the top of the
   screen and stops dead at the bar's bottom edge — elementFromPoint measured
   39px of reachable height, never 40. Worse, side by side the Report, ? and
   avatar hit areas overlapped each other by ~7px, so a tap near the edge of
   one opened its neighbour.

   So on phones the bar grows to 48px and the three right-hand controls and
   the burger are drawn at a real 40px: separate, measurable targets instead
   of invisible overlapping ones. The Area tabs stretch to the bar and clear
   40px on their own. Everything is behind max-width: 599.98px, so desktop and
   tablet do not move; every page reads the bar's height from --ts-chrome-h,
   so nothing has to be told about it. */
@media (max-width: 599.98px) {
  :root { --ts-chrome-h: 48px; }
  .ts-burger { width: 40px; margin-right: 0.15rem; }
  .ts-mast-right { gap: 0.4rem; padding-left: 0.35rem; }
  .ts-mast-btn { width: 40px; height: 40px; font-size: 1.2rem; }
  /* The burger and the two icon buttons draw nothing at rest, so they can be a
     40px box outright. The avatar is a filled circle — a 40px one shouts on a
     phone — so it keeps a small face and takes the hit area the way .ts-switch
     does, with the gap beside it widened so the two no longer overlap. */
  .ts-burger::before, .ts-mast-btn::before { content: none; }
  .ts-mast-avatar { width: 28px; height: 28px; font-size: 0.64rem; margin-left: 0.2rem; }
  /* Sign out: a 40px square around the same 16px icon. */
  .ts-roundel-logout button { width: 40px; height: 40px; padding: 0; align-items: center; justify-content: center; }
}
/* The drawer's close button is only ever on screen below 960, where it is a
   finger target on a tablet as much as on a phone. The sidebar header is 48px
   tall, so 40px fits without moving the wordmark. */
@media (max-width: 959.98px) {
  .ts-sidebar-close { width: 40px; height: 40px; }
}
/* Waiting counts (Approvals): a small badge on a rail item and on the masthead inbox button. */
.ts-item .ts-count { margin-left: auto; min-width: 1.15rem; padding: 0 0.35rem; border-radius: 99px; background: #b3462e; color: #fff; font-size: 0.62rem; font-weight: 700; line-height: 1.15rem; text-align: center; }
.ts-mast-approvals { position: relative; }
.ts-mast-approvals .ts-count { position: absolute; top: 2px; right: 0; min-width: 1rem; padding: 0 0.25rem; border-radius: 99px; background: #e0613f; color: #fff; font-size: 0.58rem; font-weight: 700; line-height: 1rem; text-align: center; box-shadow: 0 0 0 2px rgb(var(--ts-gray-950, 17 24 39)); }

/* ---- Additions (2026-10-23, sweep K8): shell chrome on a tablet ----------
   K8 asked for the masthead avatar, the sidebar brand link and .ts-crumb a at
   390. Re-measured there on five converted pages (Home, the Quotes list, Add
   client, Tax Rates, the Settings hub) in both brands: all three already clear
   the 40px floor. The crumb got a real 40px box on 11 Oct (65x40, reach 66x41),
   and the avatar and the brand carry the 6 Sep ::before — confirmed with
   elementFromPoint rather than getBoundingClientRect, which is what the 08 Oct
   measurement read: avatar drawn 28x28 / reach 40x41, brand drawn 74x24 /
   reach 75x41, and no overlap with their neighbours (? ends at 344, the
   avatar's hit area starts at 348).

   What the re-measure found instead is that every one of those fixes stops at
   599.98px — and 600–959 is the other half of the touch band. That is the
   width where the sidebar is off-canvas, so the burger, the drawer, the brand,
   the close and the roundel are the only way to navigate, all of them under a
   finger. At 820 they measured: ? / Report / Approvals 28x28, avatar 24x24,
   crumb links 57x15, sidebar brand 74x24, Sign out 22x30.

   Treated the way the row prescribes, and so that nothing visible moves:
   - the masthead icon buttons and Sign out draw nothing at rest, so they
     become a real 40px box — only the hover tint grows;
   - the avatar, the brand and the crumb keep their drawn size and take a 40px
     hit area, sound here because nothing sits within 12px of any of them
     (checked above and below with elementFromPoint);
   - the avatar goes 24 -> 28px, as on a phone, so its hit area clears its
     neighbour's instead of overlapping it — the overlap K2 had to undo;
   - the crumb takes the 11 Oct treatment, not a ::before: a 40px inline-flex
     box whose negative margins hand back exactly the extra height, so the
     header does not grow and the title does not move.
   The masthead stays 40px tall and the sidebar header 48px, so no page
   reflows. Nothing here applies below 600 or at 960 and up. */
@media (min-width: 600px) and (max-width: 959.98px) {
  .ts-mast-right { gap: 0.4rem; }
  .ts-mast-btn { width: 40px; height: 40px; }
  .ts-mast-avatar { width: 28px; height: 28px; font-size: 0.64rem; margin-left: 0.2rem; }
  .ts-roundel-logout button { width: 40px; height: 40px; padding: 0; align-items: center; justify-content: center; }

  .ts-mast-avatar, .ts-brand { position: relative; }
  .ts-mast-avatar::before, .ts-brand::before {
    content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: max(100%, 2.5rem); height: 2.5rem;
  }

  .ts-crumb a {
    position: relative; z-index: 1;
    display: inline-flex; align-items: center;
    min-height: 2.5rem; padding: 0 0.25rem; margin: -0.755rem -0.25rem;
  }
}
