/* ============================================================
   ella hub — no-sidebar overrides + top-nav shell.
   Loaded AFTER brand.css + app.css. Removes the left sidebar,
   moves the wordmark logo into the topbar with a horizontal nav,
   and styles the notification/user dropdowns, flashes and forms.
   ============================================================ */

/* ---- `hidden` means hidden ----
   The browser's own rule for the attribute is `display: none` at author-origin
   weight, so ANY declaration that sets display beats it — including an inline
   `style="display:flex"` on the very element being hidden. Three places had
   already patched this one selector at a time (`.chan-row[hidden]` and friends),
   each after the same bug: JS sets `.hidden = true`, nothing happens, and the
   panel that was supposed to disappear stays on screen looking deliberate.
   Once, here, for everything. */
[hidden] { display: none !important; }

/* ---- left sidebar is ON — app.css drives the topbar offset + main margin ---- */
/* NO max-width, and that is the fix rather than an omission.
 *
 * This capped at 1560px and the wide-screen rules below raised the cap to
 * 1780px, which reads as "fill more on a big screen" and is the opposite: a cap
 * that gets looser is still a cap. On a 3600px display the console sat in a
 * 1780px column with 400px of empty grey either side, while ella Converse, ella
 * Draw, ella Jobs and ella Secure v2 — which never had the rule — used the whole
 * width. Two consoles out of six, and the two an administrator lives in.
 *
 * The type scale below stays: more room is a reason to set text slightly larger,
 * which is what those rules were actually for. */
.content-wrapper { max-width: none; }

/* ---- topbar wordmark ---- */
.topbar .wordmark { font-size: 20px; text-decoration: none; }
.topbar .wordmark-mark { width: 26px; height: 26px; }
/* "hub" as brand-gradient TEXT — no pill, anywhere the wordmark appears */
.wordmark .hub,
.topbar .wordmark .hub,
.wordmark.on-dark .hub {
  background: var(--gradient);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
  padding: 0; border-radius: 0; box-shadow: none;
  font-size: 1em; letter-spacing: -.04em; text-transform: lowercase; font-weight: 800;
}
.topnav a.active { background: #F3E9FF; color: var(--purple-deep); }
.topnav a.active i { opacity: 1; }

/* search — EXACTLY like ella-converse: 210px input, expands to 280 on focus, #F7F7F7, 13px muted icon */
.topbar-search { position: relative; margin-right: 14px; }
.topbar-search input {
  width: 210px; height: 38px; padding: 0 12px 0 34px; border: 1px solid var(--border);
  border-radius: 999px; background: #F7F7F7; color: var(--ink); font: inherit; font-size: 14px;
  transition: width .18s, box-shadow .15s, background .15s;
}
.topbar-search input::placeholder { color: var(--slate-light); opacity: 1; }
.topbar-search input:focus {
  outline: none; width: 280px; background: #fff;
  border-color: var(--primary); box-shadow: 0 0 0 3px rgba(172,67,255,.14);
}
.topbar-search-ic {
  position: absolute; left: 13px; top: 50%; transform: translateY(-50%);
  color: var(--slate-light); font-size: 13px; pointer-events: none; transition: color .15s ease;
}
.topbar-search:has(input:focus) .topbar-search-ic { color: var(--primary-ink); }

/* search results dropdown (converse layout) */
.search-results {
  position: absolute; right: 0; top: 46px; width: 380px; max-width: min(90vw, 380px);
  max-height: 70vh; overflow-y: auto; background: #fff; border: 1px solid var(--border);
  border-radius: 12px; box-shadow: var(--shadow-lg); z-index: 1400; padding: 6px;
}
.search-group-label { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--slate-light); padding: 10px 10px 4px; display: flex; align-items: center; gap: 7px; }
.search-item { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 8px; text-decoration: none; color: var(--ink); }
.search-item:hover, .search-item.active { background: rgba(172, 67, 255, .08); }
.search-item .si-ic { width: 30px; height: 30px; border-radius: 9px; flex: none; display: grid; place-items: center; font-size: 13px; }
.search-item .si-body { min-width: 0; display: flex; flex-direction: column; line-height: 1.3; }
.search-item .si-title { font-weight: 600; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.search-item .si-sub { font-size: 12px; color: var(--slate-light); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.search-empty { padding: 18px; text-align: center; color: var(--slate-light); font-size: 13px; }

/* ---- clickable rows (ella-secure / converse pattern). Buttons live in brand.css. ---- */
.notif-dt tbody tr.row-link { cursor: pointer; }
.notif-dt tbody tr.row-link:hover td { background: rgba(172, 67, 255, .05); }

/* ---- disabled buttons (mark-all-read etc.) ---- */
.btn:disabled, .btn.is-disabled { opacity: .5; cursor: default; pointer-events: none; }
.dropdown-head button:disabled, .dropdown-head button.is-disabled { color: var(--slate-light); opacity: .55; cursor: default; pointer-events: none; }

table.dataTable tbody tr:hover { background: var(--bg); } tr.row-link:hover { background: var(--bg); }

.modal-dl { display: grid; grid-template-columns: 130px 1fr; gap: 10px 16px; font-size: 14px; }
.modal-dl dt { color: var(--slate-light); font-weight: 600; }
.modal-dl dd { margin: 0; word-break: break-word; }
.row-actions .row-actions-toggle:disabled { opacity: .5; cursor: default; color: var(--slate-light); background: #fff; }
/* Positioned by `placeRowMenu()` in admin/layout.php: `position: fixed` with measured
   `top`/`left`, so the menu escapes any scrolling container it happens to sit
   inside and can flip above its button when there is no room below. An
   absolutely-positioned menu is clipped by an `overflow` ancestor whatever its
   z-index says, and on a long list it opened off the bottom of the window.
   `top`/`left` are supplied inline; what is here is how it is drawn. */
.row-actions-menu { position: fixed; top: auto; right: auto; bottom: auto; left: auto; z-index: 1400; background: #fff; border: 1px solid var(--border); border-radius: var(--radius); box-shadow: 0 12px 32px rgba(16,24,40,.16), 0 0 0 1px rgba(16,24,40,.04); padding: 4px; min-width: 180px; overflow-y: auto; display: none; }
/* Opening upward is a different shape, not only a different place. */
.row-actions.opens-up .row-actions-menu { box-shadow: 0 -12px 32px rgba(16,24,40,.16), 0 0 0 1px rgba(16,24,40,.04); }
.row-actions.opens-up .row-actions-toggle[aria-expanded="true"] i { transform: rotate(0deg); }
.row-actions-menu-item, .row-actions-menu form { display: block; margin: 0; padding: 0; background: none; border: 0; width: 100%; }
.row-actions-menu-item, .row-actions-menu form button { display: flex; align-items: center; gap: 8px; width: 100%; padding: 8px 12px; font-family: inherit; font-size: 13px; color: var(--ink); text-align: left; background: transparent; border: 0; border-radius: 8px; cursor: pointer; text-decoration: none; }
.row-actions-menu-item:hover, .row-actions-menu form button:hover { background: var(--bg); }
.row-actions-menu-item i, .row-actions-menu form button i { width: 14px; color: var(--slate-light); }
.row-actions-menu-item.danger, .row-actions-menu form button.danger { color: var(--danger-ink); }
.row-actions-menu-item.danger i, .row-actions-menu form button.danger i { color: var(--danger-ink); }
table.dataTable td:has(> .row-actions) { overflow: visible; }

/* help + bell — EXACTLY like ella-converse: muted #6b7280, 18px; help→blue, bell→primary.
   Converse sets margin-right:14px inline on the help link → uniform 24px gaps. */
.topbar-help {
  color: var(--slate-light); font-size: 18px; line-height: 1; text-decoration: none;
  display: inline-flex; align-items: center; margin-right: 14px; transition: color .15s ease;
}
.topbar-cog {
  display: inline-flex; align-items: center; background: transparent; border: 0; padding: 4px 6px;
  color: var(--slate-light); font-size: 18px; line-height: 1; cursor: pointer; transition: color .15s ease;
}
.topbar-cog:hover { color: #F97316; }

.notif-menu { margin-right: 14px; }
.notif-toggle {
  position: relative; display: inline-flex; align-items: center; background: transparent; border: 0;
  padding: 4px 6px; color: var(--slate-light); font-size: 18px; line-height: 1; cursor: pointer;
  transition: color .15s ease;
}
.notif-toggle .notif-badge {
  position: absolute; top: -3px; right: -4px; min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: 999px; background: #C9202D; color: #fff; font-size: 12px; font-weight: 700;
  line-height: 17px; text-align: center;
}

/* user toggle: name with the role tier stacked underneath it */
.user-toggle { padding: 6px 10px 6px 14px; gap: 10px; font-size: 15px; }
.user-toggle .who { display: flex; flex-direction: column; align-items: flex-end; line-height: 1.2; }
.user-toggle .who .n { font-weight: 600; font-size: 14px; color: var(--ink); max-width: 180px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-toggle .who .r { font-size: 12px; font-weight: 600; color: var(--slate-light);
  text-transform: uppercase; letter-spacing: .04em; }
.user-toggle .user-name { font-weight: 500; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); }
@media (max-width: 540px) { .user-toggle .user-name { display: none; } }

/* ---- dropdowns (notifications + user) ---- */
.notif-menu, .user-menu { position: relative; }
.dropdown {
  position: absolute; right: 0; top: 52px; width: 360px; background: #fff;
  border: 1px solid var(--border); border-radius: 14px; box-shadow: var(--shadow-lg);
  display: none; z-index: 1000; overflow: hidden;
}
.dropdown.open { display: block; }
.dropdown-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; border-bottom: 1px solid var(--border); }
.dropdown-head strong { font-size: 14px; }
.dropdown-head button { border: 0; background: transparent; color: var(--purple-deep); font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer; }
/* ---- notification filter: one dot per platform ---------------------------
   The dots are 12px, which is small, so three things carry the meaning and not
   one: the colour, a tooltip naming the platform, and — once a filter is on —
   the heading saying which. Two of the six sit below 3:1 against white, so a
   hairline ring gives every dot an edge regardless of how pale it is.

   "All" is hollow. A row of six solid dots with the current one merely brighter
   is a state somebody has to decode; a ring with nothing in it reads as "no
   filter" immediately, and it is the only shape in the row that is not a
   platform. */
.notif-filter { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 10px 16px; border-bottom: 1px solid var(--border); }
.notif-filter .nf-count { margin-left: auto; font-size: 12px; color: var(--slate-light); }
/* Inside a card the row is the panel's own, not the dropdown's: no side padding
   (the card already has 18px) and a lighter rule under it. */
.panel > .notif-filter { padding: 2px 0 10px; border-bottom: 1px solid var(--border); margin-bottom: 10px; }
.nf-none { padding: 18px 0; text-align: center; }
/* Severity, in a word, only where it changes what somebody does. It is a chip
   rather than a tint on the platform's icon, so the two never compete: the icon
   says WHICH platform, this says HOW URGENT. */
.sev-chip { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
  border-radius: 999px; padding: 2px 7px; }
.sev-chip.is-warning { background: var(--warning-bg, #FEF3C7); color: #B45309; }
.sev-chip.is-danger  { background: var(--danger-bg, #FEE2E2); color: var(--danger-ink); }
/* The heading is a name, never a name plus a number — the panel is 354px wide
   and the number put "Mark all read" onto two lines. */
.dropdown-head strong { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nf-dot { width: 13px; height: 13px; border-radius: 50%; padding: 0; cursor: pointer;
  flex: none; background: var(--c, var(--slate-light));
  border: 1px solid rgba(11,11,18,.16);
  outline-offset: 2px; transition: transform .12s ease, box-shadow .12s ease; }
.nf-dot:hover { transform: scale(1.18); }
/* Selected is a RING around the dot, not a size change — a dot that grows moves
   its neighbours, and the row twitches every time somebody changes filter. */
.nf-dot.is-on { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--c, var(--slate)); }
.nf-dot.nf-all { background: transparent; border: 2px solid var(--border-strong); --c: var(--slate); }
.nf-dot.nf-all.is-on { border-color: var(--slate); }
.nf-dot.nf-hub { --c: var(--slate-light); }
/* There is deliberately no "nothing from this one" state. Dimming the platforms
   absent from the latest ten was tried and removed: it made four of the six
   nearly invisible, so the row stopped being a set of colours anybody could
   learn — which is the one job it has. Whether a filter finds anything is
   answered by pressing it, in words. */

.notif-list { max-height: min(60vh, 360px); overflow-y: auto; }
.notif-footer { display: block; padding: 11px 16px; text-align: center; border-top: 1px solid var(--border); color: var(--purple-deep); font-size: 13px; font-weight: 600; text-decoration: none; }
.notif-footer:hover { background: var(--bg); }
.notif-row { display: flex; gap: 10px; padding: 12px 16px; border-bottom: 1px solid var(--border); text-decoration: none; color: inherit; }
.notif-row:last-child { border-bottom: 0; }
.notif-row:hover { background: var(--bg); }
.notif-row .sev { width: 8px; height: 8px; border-radius: 50%; margin-top: 5px; flex: none; }
.notif-row > span:last-child { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.notif-row .t { display: block; font-weight: 700; font-size: 13px; line-height: 1.35; }
.notif-row .m { display: block; font-size: 12px; color: var(--slate-light); }
/* The platform's colour, beside its name — so the filter row above is legible
   as "these dots mean these rows" without anything having to say so. */
.notif-row .pd { display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  margin-right: 5px; vertical-align: baseline; border: 1px solid rgba(11,11,18,.16); }
.notif-empty { padding: 22px 16px; text-align: center; color: var(--slate-light); font-size: 13px; }

.user-menu .user-dropdown { min-width: 220px; }
.user-dropdown ul { list-style: none; margin: 0; padding: 6px; }
.user-dropdown li a { display: flex; align-items: center; gap: 10px; padding: 9px 12px; border-radius: 8px; color: var(--ink); text-decoration: none; font-size: 15px; font-weight: 500; }
.user-dropdown li a i { width: 16px; color: var(--slate-light); }
.user-dropdown li a:hover { background: var(--bg); }
.user-dropdown .u-head { padding: 10px 12px 8px; border-bottom: 1px solid var(--border); margin-bottom: 4px; }
.user-dropdown .u-head .n { font-weight: 600; font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-dropdown .u-head .r { font-size: 12px; color: var(--slate-light); margin-top: 1px; }

/* ---- flash messages ---- */
.flash { border-radius: 12px; padding: 12px 16px; font-size: 14px; font-weight: 500; margin-bottom: 18px; border: 1px solid transparent; }
.flash.ok { background: var(--success-bg); color: var(--success-ink); border-color: color-mix(in srgb, var(--success) 30%, #fff); }
.flash.err { background: var(--danger-bg); color: var(--danger-ink); border-color: color-mix(in srgb, var(--danger) 30%, #fff); }

/* ---- section headers reused ---- */
.page-title { font-size: 1.65rem; font-weight: 700; letter-spacing: -.01em; }

/* ---- generic form fields on content pages ---- */
.field-inline { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
/* `.inp` was this estate's plain, non-floating control. It is `.form-control`
   and `.form-select` now, styled once with the floating ones above — so a
   filter bar and a form field can no longer drift apart, which they had: the
   plain select drew the OS chevron and the floating one drew ours, and the two
   appeared side by side on the same screen.

   A multiple or sized select renders as a list box and gets no chevron; that
   rule lives with `.form-select` above. */

/* ---- entitlement pills in the access detail ---- */
.ent { width: 30px; height: 30px; border-radius: 9px; display: inline-grid; place-items: center;
  border: 1px solid var(--border); background: var(--bg); color: var(--slate-light); }
.ent.on { color: #fff; border: 0; background: var(--gradient); }

/* ---- centered auth card (magic-link / set-password / mfa) ---- */
.auth-center { min-height: 100vh; display: grid; place-items: center; padding: 32px;
  background:
    radial-gradient(120% 90% at 8% 0%, rgba(172,67,255,.10), transparent 46%),
    radial-gradient(120% 110% at 100% 100%, rgba(67,255,187,.09), transparent 48%),
    var(--bg); }
.auth-box { width: 100%; max-width: 420px; padding: 34px 32px; text-align: left; }
.auth-box .wordmark { justify-content: center; margin-bottom: 20px; font-size: 26px; }
.auth-box h1 { font-size: 21px; text-align: center; }
.auth-box .sub { color: var(--slate); font-size: 14px; text-align: center; margin: 8px 0 22px; }

/* ---- QR + recovery codes ---- */
.qr-wrap { text-align: center; margin: 8px 0 14px; }
.qr-wrap img { border: 1px solid var(--border); border-radius: 14px; padding: 10px; background: #fff; }
.reccode-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 14px 0 18px; }
.reccode { background: var(--bg); border: 1px solid var(--border); border-radius: 9px; padding: 9px 10px; text-align: center; letter-spacing: 1px; font-size: 14px; font-weight: 600; }
/* "Downloaded" confirmation on the recovery-codes save button. */
#dlCodes.is-done { color: var(--success-ink); border-color: color-mix(in srgb, var(--success) 40%, #fff); background: var(--success-bg, #DCFCE7); }

/* ---- large screens: more ROOM, not different type ----
 *
 * These blocks used to change the base font size as well: this console stepped
 * it up to 16.5px and then 17px, ella Platform stepped it DOWN to 15px, and the
 * other four consoles have no such block at all and stay at 16px. Three base
 * sizes across six consoles, and every icon and menu label sized in `em` on top
 * of them — which is why the same sidebar measured 42px tall in ella Draw, 45 in
 * ella Platform and 48 in ella Jobs.
 *
 * A base size that changes with the viewport is also a thing nobody can predict
 * while writing a rule: "15px" in one file means something different from "15px"
 * in the next, and a bump written against one base silently becomes a shrink
 * when the base moves. The hub's own comment here recorded that happening.
 *
 * So the type scale is the same at every width in every console, and what a wide
 * screen buys is width — which is now uncapped, and is the part that was
 * actually missing. */
@media (min-width: 1500px) {
  .tiles { gap: 20px; }
}
@media (min-width: 1900px) {
  .tiles { grid-template-columns: repeat(4, 1fr); }
}

/* ---- tablet ---- */
@media (max-width: 900px) {
  .content-wrapper { padding: 20px; }
}

/* ---- mobile: single column, one service card per row ---- */
@media (max-width: 680px) {
  .topbar { padding: 0 14px; }
  .content-wrapper { padding: 16px; }
  .tiles { grid-template-columns: 1fr !important; }
  .dash { grid-template-columns: 1fr !important; }
  .user-toggle .who { display: none; }
  .dropdown { width: calc(100vw - 32px); right: -8px; }
  .note { flex-wrap: wrap; }
  .note .act { margin-left: 0; align-items: flex-start; }
}

/* The bespoke `.searchable-select` lived here — a trigger button, a popover, a
   search box and a `<ul>` of options, about eighty lines of CSS behind 435 lines
   of JavaScript. Every dropdown in the console is a `<select>` enhanced by Tom
   Select now, and its styling is the block further down this file.

   The ".ss-new" pill it defined has moved with it: a just-created option is
   marked `data-ss-new` and the pill is drawn from that. */

/* ---- quick create: "+ New …" inside a picker, and "+" beside a plain <select> ----
   The row inside a picker is Tom Select's `.create`, styled with the rest of its
   dropdown further down. What is left here is the "+" button that sits beside a
   control rather than inside it. */

.qc-add-wrap { display:inline-flex; flex:0 0 auto; }
/* Matches the 54px floating-label field it sits beside; 42px next to a bare control. */
.qc-add { display:inline-flex; align-items:center; justify-content:center; width:42px; height:42px; flex:0 0 42px;
  background:#fff; color:var(--primary); border:1px solid var(--border-strong); border-radius:10px; cursor:pointer;
  font-size:13px; transition:border-color .12s ease, background .12s ease, color .12s ease; }
.field-row > .qc-add-wrap .qc-add { width:54px; height:54px; flex-basis:54px; }
.qc-add:hover { border-color:var(--primary-light); background:rgba(172,67,255,.08); }
.qc-add:focus-visible { outline:none; border-color:var(--primary-light); box-shadow:0 0 0 3px rgba(172,67,255,.16); }

/* ── `.field-row`: the same grid, without touching 46 view files ───────────
 *
 * It was `display: flex` with `flex: 1 1 auto` children, which means every
 * field was sized by its own contents — the seven unrelated widths described
 * beside `.form-grid` in estate.css. In ella Platform it was worse in a way
 * that is worth writing down: the child rule named `.field`, the vocabulary
 * Bootstrap replaced, so after the migration it matched NOTHING and the fields
 * had no flex rule at all. A row of four then sized itself entirely by string
 * length, which is why "Entry method" ended up in an 84px box.
 *
 * `auto-fit` with a 210px floor gives every field in a row the SAME width and
 * wraps when they no longer fit, so a row of two is two halves, a row of four
 * is four quarters, and none of them is ever too narrow for its own label. It
 * needs no change to any view, which is why it is here: `.form-grid` is what
 * new work uses and what a converted screen moves to, and this is what the
 * screens that have not been converted yet get in the meantime.
 *
 * `align-items: start`, not `flex-end`: a field with a hint under it is taller
 * than one without, and bottom-aligning them staggered the controls. */
.field-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr));
  gap: 20px 16px;
  align-items: start;
}
.field-row > .form-floating, .field-row > .field { margin: 0; min-width: 0; }
/* A row that is a control plus its "+" button is not a row of equal fields.
   The button is 54px and the control takes the rest. */
.field-row:has(> .qc-add-wrap) { grid-template-columns: minmax(0, 1fr) 54px; align-items: start; }
.field-row + .field-row { margin-top: 20px; }

/* The quick-create modal opens on top of whatever modal launched it. */
/* `.qc-overlay` no longer sets a z-index of its own. Stacking is general now —
   any modal opened on top of another is raised by `show.bs.modal` in the layout
   — and two mechanisms for one thing is how they disagree. */
/* Colour picker inside a quick-create modal — a swatch, not a full-width field. */
.qc-swatch { display:flex; align-items:center; gap:10px; }
.qc-swatch input[type=color] { width:56px; height:42px; padding:3px; background:#fff;
  border:1px solid var(--border-strong); border-radius:10px; cursor:pointer; }

/* ---- Sign in with Google / Microsoft ---- */
.or-sep { display:flex; align-items:center; gap:12px; margin:20px 0 14px; color:var(--slate-light); font-size:12px;
  text-transform:uppercase; letter-spacing:.06em; }
.or-sep::before, .or-sep::after { content:""; flex:1; height:1px; background:var(--border); }
.sso-btn { display:flex; align-items:center; justify-content:center; gap:10px; margin-bottom:8px; font-weight:600; }
.sso-btn i { font-size:16px; }
.sso-google i { color:#EA4335; }
.sso-microsoft i { color:#00A4EF; }
.sso-btn:hover { border-color:var(--border-strong); background:var(--bg); }

/* ---- A value the user can't change: text, not a disabled-looking input ---- */
.fixed-value { display:flex; flex-direction:column; gap:2px; padding:9px 12px; border-radius:10px;
  background:var(--bg); border:1px solid var(--border); }
.fixed-value .fv-label { font-size: 12px; font-weight:600; text-transform:uppercase; letter-spacing:.04em; color:var(--slate-light); }
.fixed-value .fv-value { font-size:14px; font-weight:600; color:var(--ink); }
.fixed-value .fv-hint { font-size: 12px; color:var(--slate-light); }
.field-row > .fixed-value, .fixed-value { flex:1 1 auto; min-width:0; }

/* ---- job meta badges: one visual language for stage / progress / launch / risk ---- */
.jm-stage { display:inline-block; font-size: 12px; font-weight:600; color:var(--slate); background:var(--bg);
  border:1px solid var(--border); border-radius:999px; padding:2px 9px; white-space:nowrap; }
.jm-prog { display:inline-flex; align-items:center; gap:7px; white-space:nowrap; }
.jm-bar { display:inline-block; width:52px; height:5px; border-radius:999px; background:var(--border); overflow:hidden; }
.jm-bar > span { display:block; height:100%; background:var(--primary); border-radius:999px; }
.jm-prog.is-done .jm-bar > span { background:var(--success); }
.jm-prog.is-late .jm-bar > span { background:var(--danger); }
.jm-num { font-size: 12px; font-weight:600; color:var(--slate-light); }
.jm-launch { display:inline-flex; align-items:center; gap:5px; font-size: 12px; font-weight:600;
  color:var(--slate); white-space:nowrap; }
.jm-launch i { font-size:10px; opacity:.7; }
.jm-launch.is-soon { color:var(--warning-ink); }
.jm-launch.is-past { color: var(--danger-ink); }
.jm-risk { display:inline-flex; align-items:center; gap:5px; font-size: 12px; font-weight:700;
  text-transform:uppercase; letter-spacing:.03em; border-radius:999px; padding:2px 8px; white-space:nowrap; }
.jm-risk i { font-size:9px; }
.jm-risk.is-high { background:var(--danger-bg,#FEE2E2); color: var(--danger-ink); }
.jm-risk.is-med { background:#FEF3C7; color:#B45309; }
.statstrip .s.is-warn .v { color: var(--ink); }
.statstrip .s.is-warn { box-shadow: inset 0 3px 0 var(--warning); }
.kcard .kmeta { display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin:8px 0 2px; }
/* A control that reads as a link. `button.linkish` matters as much as the anchor:
   an action that is not navigation should still be a <button>, and it was picking
   up none of this — so "Run this again" and "Keep everything the same" rendered as
   ordinary prose that happened to respond to a click. */
a.linkish, button.linkish { color:inherit; text-decoration:none; border-bottom:1px dotted var(--border-strong); }
a.linkish:hover, button.linkish:hover { color:var(--primary); border-bottom-color:var(--primary); }
button.linkish { font-family:inherit; font-weight:inherit; background:none;
  border-left:0; border-right:0; border-top:0; padding:0; cursor:pointer; }
/* Size is inherited EXCEPT where .small has already said otherwise. A blanket
   `font:inherit` here would out-specify `.small` (0,2,1 beats 0,1,0) and silently
   enlarge every small link-button in the app — which is exactly what it did. */
button.linkish:not(.small) { font-size:inherit; }

/* The browser's default textarea face is monospace, which reads as a code box
   sitting in the middle of a form. Inputs and selects already inherit. */
textarea { font-family:inherit; font-size:14px; line-height:1.45; }

/* ---- job channel: compose tools, attachments, embeds ---- */
.chan-composer { position:relative; }
.chan-tool { flex:none; width:34px; height:34px; border:0; background:transparent; border-radius:9px;
  color:var(--slate-light); cursor:pointer; font-size:15px; display:grid; place-items:center; }
.chan-tool:hover { background:var(--bg); color: var(--primary-ink); }
.chan-gif-lbl { font-size: 12px; font-weight:800; letter-spacing:.03em; border:1.5px solid currentColor;
  border-radius:5px; padding:1px 3px; line-height:1; }
.chan-attach-preview { display:flex; align-items:center; gap:8px; padding:8px 14px; border-top:1px solid var(--border);
  background:var(--bg); font-size:12.5px; }
.chan-attach-preview .cap-name { color:var(--slate); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.chan-attach-preview .cap-x { margin-left:auto; border:0; background:transparent; font-size:18px; line-height:1;
  color:var(--slate-light); cursor:pointer; }
.chan-attach-preview .cap-x:hover { color: var(--danger-ink); }

.chan-files, .chan-embeds { display:flex; flex-wrap:wrap; gap:8px; margin-top:8px; }
.chan-img { display:block; border-radius:10px; overflow:hidden; border:1px solid var(--border); max-width:220px; }
.chan-img img { display:block; width:100%; height:auto; max-height:220px; object-fit:cover; }
.chan-file-chip { display:inline-flex; align-items:center; gap:8px; padding:7px 11px; border-radius:9px;
  border:1px solid var(--border); background:#fff; color:var(--ink); text-decoration:none; font-size:12.5px; max-width:260px; }
.chan-file-chip:hover { border-color:var(--primary-light); }
.chan-file-chip i { color:var(--slate-light); }
.chan-file-chip span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-weight:600; }
.chan-file-chip em { font-style:normal; color:var(--slate-light); flex:none; }
.chan-msg .txt a { color:var(--primary); text-decoration:underline; word-break:break-word; }
.chan-msg.mine .txt a { color:#fff; }

.chan-pop { position:absolute; bottom:calc(100% + 8px); left:12px; z-index:60; max-width:calc(100vw - 40px); background:#fff; border:1px solid var(--border);
  border-radius:12px; box-shadow:var(--shadow-lg); padding:10px; }
#chanEmojiPop { display:grid; grid-template-columns:repeat(10,1fr); gap:2px; width:320px; }
#chanEmojiPop[hidden], .chan-pop[hidden] { display:none; }
#chanEmojiPop .emo { border:0; background:transparent; font-size:19px; line-height:1; padding:5px; border-radius:7px; cursor:pointer; }
#chanEmojiPop .emo:hover { background:var(--bg); }
.chan-gifpop { width:360px; }
.gif-head { display:flex; align-items:center; gap:8px; margin-bottom:8px; }
.gif-head input { flex:1; padding:7px 10px; font:inherit; font-size:13px; border:1px solid var(--border);
  border-radius:8px; outline:none; }
.gif-head input:focus { border-color:var(--primary-light); box-shadow:0 0 0 3px rgba(172,67,255,.14); }
.gif-by { font-size: 12px; font-weight:700; text-transform:uppercase; color:var(--slate-light); }
.gif-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:6px; max-height:260px; overflow-y:auto; }
.gif-grid .gif { border:0; padding:0; background:var(--bg); border-radius:8px; overflow:hidden; cursor:pointer; }
.gif-grid .gif img { display:block; width:100%; height:76px; object-fit:cover; }
.gif-note { font-size:12px; color:var(--slate-light); padding:8px 2px 0; }

a.chip-link { text-decoration:none; }
a.chip-link:hover { border-color:var(--primary-light); color: var(--primary-ink); }

.filterbar { display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap;
  padding:10px 14px; margin-bottom:14px; border-radius:11px; background:#F3E9FF; color:var(--purple-deep);
  border:1px solid rgba(172,67,255,.22); font-size:13.5px; }

/* ---- capacity bars ---- */
.cap-bar { display:inline-block; width:150px; height:8px; border-radius:999px; background:var(--border);
  overflow:hidden; vertical-align:middle; margin-right:8px; }
.cap-bar > span { display:block; height:100%; background:var(--success); border-radius:999px; }
.cap-bar.is-tight > span { background:var(--warning,#D97706); }
.cap-bar.is-over > span { background:var(--danger); }
.cap-pct { font-size:12px; font-weight:700; color:var(--slate); }
.cap-pct.is-tight { color:var(--warning-ink); }
.cap-pct.is-over { color: var(--danger-ink); }

/* ---- notification preference list ---- */
.prefs { list-style:none; margin:0 0 20px; padding:0; display:flex; flex-direction:column; gap:8px; }
.pref { border:1px solid var(--border); border-radius:11px; background:#fff; transition:border-color .12s ease; }
.pref:hover { border-color:var(--border-strong); }
.pref.is-locked { background:var(--bg); }
/* The switch sits on the right, away from the text, so the row reads
   "this is the setting … and here is its state" rather than the tick-first
   order a form implies. */
.pref { display:flex; align-items:center; gap:16px; padding:12px 14px; }
.pref > .switch { flex:0 0 auto; margin-left:auto; }
.pref-body { display:flex; flex-direction:column; gap:2px; min-width:0; flex:1 1 auto; }
.pref-label { font-weight:600; font-size:14px; color:var(--ink); display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.pref.is-locked .pref-label { color:var(--slate); }
/* --slate-light is 4.26:1 on --border. A pill puts its own grey behind the
   text, so the page-level grey is not the one to use on it. */
.pref-pin { font-size: 12px; font-weight:700; text-transform:uppercase; letter-spacing:.04em;
  color:var(--slate); background:var(--border); padding:2px 7px; border-radius:999px; }
.pref-pin i { font-size:9px; }
.pref-desc { font-size:12.5px; color:var(--slate-light); line-height:1.45; }

/* "What's different this time?" — the clone assistant's panel.
 *
 * It is a distinct thing sitting inside a form, so it needs to look like one.
 * `.panel` does not do that here: the rule that gives it a card background lives
 * in admin.css — a file this console does not have (it was 2,699 lines of
 * never-loaded CSS and has been removed) — so the class quietly
 * contributes nothing but padding. */
.ask-panel { background: var(--surface-2, #f7f7fa); border: 1px solid var(--border);
  border-radius: 12px; padding: 12px; }
.ask-log { display: none; flex-direction: column; gap: 6px; margin-bottom: 8px;
  max-height: 220px; overflow-y: auto; }
.ask-log .them { padding: 8px 10px; border-radius: 8px; background: #fff;
  border: 1px solid var(--border); }
.ask-log .me { text-align: right; }
.sidebar-flyout-arrow { margin-left: 8px; font-size: max(12px, .7em); opacity: .6; }
.sidebar-menu-item.has-flyout.flyout-active > .sidebar-menu-link::before {
  content:""; position:absolute; left:0; top:0; bottom:0; width:3px; background:var(--mint); box-shadow:0 0 14px var(--mint);
}
.sidebar-flyout {
  position: fixed; left: var(--sidebar-width); top: 0; margin-left: 5px; min-width: 220px; padding: 8px;
  background:#0E0E16; border:1px solid rgba(255,255,255,.09); border-radius: 12px;
  box-shadow: 0 16px 44px rgba(0,0,0,.5); opacity:0; visibility:hidden; transform:translateX(-6px);
  transition: opacity .14s ease, transform .14s ease, visibility .14s; z-index: 1300;
}
.sidebar-flyout a.active { background:rgba(172,67,255,.30); color:#fff; }

/* ---- toasts (flash notices) ---- */
.toast-wrap { position: fixed; top: 74px; right: 20px; z-index: 2000; display:flex; flex-direction:column; gap:10px; max-width: 380px; }
.toast {
  display:flex; align-items:flex-start; gap:11px; background:#fff; border:1px solid var(--border);
  border-left: 4px solid var(--slate-light); border-radius:12px; padding:13px 15px; box-shadow: var(--shadow);
  animation: toastIn .25s cubic-bezier(.2,.8,.3,1); font-size:13.5px;
}
.toast.ok { border-left-color: var(--success); }
.toast.err { border-left-color: var(--danger); }
.toast.info { border-left-color: var(--info); }
.toast.warn { border-left-color: var(--warning); }
.toast .ti { width:22px; height:22px; border-radius:50%; display:grid; place-items:center; font-size:12px; color:#fff; flex:none; margin-top:1px; }
.toast.ok .ti { background: var(--success); } .toast.err .ti { background: var(--danger); } /* The glyph sits on a solid fill in white, and white on #ffc107 is barely a
   shape. #B45309 is the ink this codebase already uses for warnings on amber
   (.sev-chip.is-warning, .jm-risk.is-med), so the toast reads amber at the
   border and stays legible in the middle. */
.toast.warn .ti { background: #B45309; }
.toast.info .ti { background: var(--info); }
.toast .tx { flex:1; color:var(--ink); line-height:1.4; }
.toast .tc { border:0; background:transparent; color:var(--slate-light); cursor:pointer; font-size:15px; line-height:1; padding:2px; }
.toast.hide { animation: toastOut .25s ease forwards; }
@keyframes toastIn { from { transform: translateX(30px); opacity:0; } }
@keyframes toastOut { to { transform: translateX(30px); opacity:0; } }

/* ---- inline help ("i" info dot + tooltip) ---- */
/* `.ihelp` is in estate.css now — every console explains things the same
   way, so the mark that means "there is an explanation here" is one mark. */

/* ---- Deliver: job files (attachments) ---- */
.filelist .fl-items { list-style:none; margin:0 0 12px; padding:0; display:flex; flex-direction:column; gap:6px; }
.fl-item { display:flex; align-items:center; gap:11px; padding:9px 11px; border:1px solid var(--border);
  border-radius:10px; background:#fff; transition:border-color .12s ease, box-shadow .12s ease; }
.fl-item:hover { border-color:var(--border-strong); box-shadow:var(--shadow-sm); }
.fl-item .fl-ic { width:32px; height:32px; flex:none; border-radius:9px; display:grid; place-items:center;
  background:var(--bg); color:var(--slate); font-size:14px; }
.fl-item .fl-body { flex:1; min-width:0; display:flex; flex-direction:column; gap:2px; }
.fl-item .fl-name { font-weight:600; font-size:13.5px; color:var(--ink); text-decoration:none;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.fl-item .fl-name:hover { color:var(--primary); text-decoration:underline; }
.fl-item .fl-meta { font-size: 12px; color:var(--slate-light); }
.fl-item .fl-del { margin:0; flex:none; }
.fl-item .fl-delbtn { border:0; background:transparent; color:var(--slate-light); cursor:pointer;
  padding:6px 7px; border-radius:8px; font-size:12px; line-height:1; }
.fl-item .fl-delbtn:hover { color: var(--danger-ink); background:var(--danger-bg,#FEE2E2); }

/* The upload row. The real <input type=file> is hidden behind its styled label. */
.fl-add { display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin:0; }
.fl-add .fl-input { position:absolute; width:1px; height:1px; opacity:0; overflow:hidden; }
.fl-add .fl-input:focus-visible + label { border-color:var(--primary-light); box-shadow:0 0 0 3px rgba(172,67,255,.16); }
.fl-add label { margin:0; cursor:pointer; }

/* Files shown under the Brief / Estimate forms rather than on their own tab. */
.fl-section { margin-top:18px; padding-top:16px; border-top:1px solid var(--border); }
.fl-section-head { font-size: 12px; font-weight:700; text-transform:uppercase; letter-spacing:.05em;
  color:var(--slate-light); margin-bottom:10px; }
.filelist.is-compact .fl-item { padding:7px 10px; }
.filelist.is-compact .fl-item .fl-ic { width:28px; height:28px; font-size:13px; }

/* ---- Deliver: kanban board + stage stepper ---- */
.board { display:flex; gap:14px; overflow-x:auto; padding-bottom:10px; align-items:flex-start; }
.col { flex:0 0 300px; background:#EEEEF4; border-radius:14px; padding:10px; }
.col-head { display:flex; align-items:center; gap:8px; padding:5px 6px 11px; font-weight:700; font-size:13px; }
.col-head .n { font-size: 12px; color:var(--slate-light); margin-left:auto; }
.col-head .k { width:9px; height:9px; border-radius:50%; flex:none; }
.kcard { display:block; background:#fff; border:1px solid var(--border); border-radius:11px; padding:12px; margin-bottom:9px; box-shadow:var(--shadow-sm); color:inherit; transition:.13s; }
.kcard:hover { box-shadow:var(--shadow); border-color:var(--border-strong); transform:translateY(-1px); }
.kcard .ref { font-size: 12px; font-weight:700; color:var(--primary-dark); }
.kcard .ttl { font-weight:600; font-size:13.5px; margin:3px 0 8px; line-height:1.3; }
.kcard .foot { display:flex; align-items:center; justify-content:space-between; margin-top:9px; gap:8px; }
.col-empty { text-align:center; color:var(--slate-light); font-size:12px; padding:14px 6px; }
.prog { height:5px; border-radius:3px; background:#E7E7F0; overflow:hidden; margin-top:8px; }
.prog span { display:block; height:100%; background:var(--gradient); }

/* stage stepper (job workspace) */
/* One line. It used to wrap, and fourteen stages over two rows with the chevrons
   breaking mid-sequence looked like a bug. Anything outside the window around the
   current stage is folded away (see job.php); expanding shows the lot and lets it
   scroll rather than wrap. */
.stepper { display:flex; align-items:center; flex-wrap:nowrap; gap:0; overflow-x:auto; scrollbar-width:thin; min-width:0; }
.stepper > * { flex:none; }
/* Folded by default; `data-expanded` on the rail reveals them. */
.stepper:not([data-expanded]) .step-hideable { display:none; }
/* The two end chips. A button, because it does something — and it disappears
   once everything is shown, so it never claims there is more when there is not.

   `margin:0 9px` rather than 0, and that is the whole fix for a rail that read
   "Negotiation11 to go". The separators carry the spacing in this row, and the
   separator beside a fold chip is itself `.step-hideable` — so when the rail is
   folded the chip lost its only gap and butted straight against the last stage
   label. The margin matches `.arr`, so folded and expanded space identically. */
.stepper .step-fold {
  display:inline-flex; align-items:center; gap:7px;
  background:transparent; border:0; padding:0; margin:0 9px; cursor:pointer;
  font-family:inherit; font-size:12px; font-weight:600; color:var(--slate-light);
}
.stepper .step-fold:hover { color:var(--ink); text-decoration:underline; }
.stepper[data-expanded] .step-fold { display:none; }
.stepper .step { display:flex; align-items:center; gap:7px; font-size:12px; color:var(--slate-light); font-weight:600; }
.stepper .step .bub { width:22px; height:22px; border-radius:50%; display:grid; place-items:center; font-size: 12px; background:#EEF0F4; color:var(--slate-light); }
.stepper .step.done .bub { background:var(--success); color:#fff; }
.stepper .step.active { color:var(--ink); } .stepper .step.active .bub { background:var(--gradient); color:#fff; box-shadow:0 0 0 4px rgba(172,67,255,.16); }
.stepper .arr { color:var(--border-strong); margin:0 9px; }

/* ---- Deliver: job tasks ---- */
.jlane { margin-bottom:16px; }
.jlane-head { display:flex; align-items:center; gap:10px; font-weight:700; font-size:13px; margin-bottom:8px; }
.jlane-head .bar { width:4px; height:16px; border-radius:2px; flex:none; }
.jtask { display:flex; align-items:flex-start; gap:12px; padding:11px 14px; border:1px solid var(--border); border-radius:12px; background:#fff; margin-bottom:8px; }
.jtask:hover { border-color:var(--border-strong); }
/* Arriving from a cross-reference: say which row was meant, then get out of the
   way. A permanent highlight would still be there tomorrow, meaning nothing. */
.flash-target { animation: flash-target 1.8s ease-out 1; }
@keyframes flash-target {
    0%, 55% { box-shadow: 0 0 0 3px var(--primary-light, #E9D5FF); border-color: var(--primary, #7C3AED); }
    100%    { box-shadow: 0 0 0 3px transparent; }
}
@media (prefers-reduced-motion: reduce) { .flash-target { animation: none; box-shadow: 0 0 0 3px var(--primary-light, #E9D5FF); } }
.jtask .cbx { width:20px; height:20px; border-radius:6px; border:2px solid var(--border-strong); flex:none; display:grid; place-items:center; color:#fff; cursor:pointer; background:#fff; padding:0; margin-top:1px; }
.jtask .cbx.done { background:var(--success); border-color:var(--success); }
.jtask .jbody { flex:1; min-width:0; }
.jtask .tt { font-weight:600; font-size:13.5px; }
.jtask.done .tt { color:var(--slate-light); text-decoration:line-through; }
.jtask .sub { font-size: 12px; color:var(--slate-light); margin-top:2px; }
.jtask .jmeta { display:flex; align-items:center; gap:10px; flex:none; }
/* A task that can't start yet. Amber rather than red: it isn't wrong, it's
   waiting — the thing to chase is whatever it names, not this task. */
.blocked-chip { display:inline-flex; align-items:center; gap:5px; font-size: 12px; font-weight:700;
  color:var(--warning-ink); background:var(--warning-bg); padding:3px 9px; border-radius:var(--radius-pill);
  white-space:nowrap; cursor:default; }
.blocked-chip i { font-size:9px; }
/* Template editor: which item an item waits for. */
.dep-chip { display:inline-flex; align-items:center; gap:5px; font-size: 12px; color:var(--slate);
  background:var(--bg); border:1px solid var(--border); padding:2px 8px; border-radius:var(--radius-pill);
  max-width:180px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.dep-chip i { font-size:9px; color:var(--slate-light); }
.supply-chip { font-size: 12px; font-weight:700; color:var(--primary-dark); background:#F3E4FF; padding:3px 9px; border-radius:var(--pill); white-space:nowrap; }
.jdue { font-size:12px; font-weight:600; color:var(--slate); white-space:nowrap; }

/* ---- what happened on a task ----
   The standing instruction from the template, then the running notes. Notes are
   the field these logs actually live in, so they are quiet when empty and open
   the moment there is anything to read. */
.jtask .jdesc { font-size:12px; color:var(--slate); margin-top:4px; white-space:normal; }
.jnotes { margin-top:6px; }
.jnotes > summary { font-size: 12px; color:var(--slate-light); cursor:pointer; list-style:none;
  display:inline-flex; align-items:center; gap:6px; padding:2px 0; user-select:none; }
.jnotes > summary::-webkit-details-marker { display:none; }
.jnotes > summary:hover { color:var(--primary-deep, var(--purple-deep)); }
.jnotes > summary i { font-size:10px; }
.jnote { border-left:2px solid var(--border); padding:4px 0 4px 10px; margin-top:6px; }
.jnote-meta { font-size: 12px; color:var(--slate-light); display:flex; align-items:center; gap:6px; }
.jnote-meta strong { color:var(--slate); font-weight:700; }
.jnote-body { font-size:12.5px; white-space:pre-wrap; margin-top:2px; }
.jnote-del { background:none; border:0; padding:0 2px; color:var(--slate-light); cursor:pointer;
  font-size: 12px; opacity:0; transition:opacity .12s; }
.jnote:hover .jnote-del { opacity:1; }
.jnote-del:hover { color: var(--danger-ink); }
.jnote-add { display:flex; gap:6px; margin-top:8px; }
.jnote-add input { flex:1; min-width:0; font:inherit; font-size:12.5px; padding:5px 9px;
  border:1px solid var(--border); border-radius:8px; background:#fff; }
.jnote-add input:focus { outline:none; border-color:var(--border-strong); }
.note-chip { font-size: 12px; font-weight:700; color:var(--slate); background:var(--bg);
  border:1px solid var(--border); padding:2px 8px; border-radius:var(--radius-pill); white-space:nowrap; }
.note-chip i { font-size:9px; color:var(--slate-light); }
.jdue.over { color: var(--danger-ink); } .jdue.soon { color:var(--warning-ink); }
.javatar { width:24px; height:24px; border-radius:50%; background:var(--gradient); color:#06131A; display:grid; place-items:center; font-weight:800; font-size: 12px; flex:none; }
.jcheck { margin-top:8px; display:flex; flex-direction:column; gap:5px; }
.jcheck label { font-size:12.5px; color:var(--slate); display:flex; gap:8px; align-items:center; cursor:pointer; }
.jcheck input { cursor:pointer; }
.jcheck label.done span { text-decoration:line-through; color:var(--slate-light); }
.iconbtn-sm { border:0; background:transparent; color:var(--slate-light); cursor:pointer; padding:4px; font-size:13px; }
.iconbtn-sm:hover { color:var(--ink); }
.flagchips { display:flex; gap:6px; flex-wrap:wrap; margin-top:10px; }

/* ella Deliver — per-job real-time channel (chat) */
.chan-head { display:flex; align-items:center; justify-content:space-between; padding:12px 16px; border-bottom:1px solid var(--border); font-size:14px; }
.chan-status { display:flex; align-items:center; gap:6px; font-size: 12px; color:var(--slate-light); text-transform:uppercase; letter-spacing:.04em; }
.chan-status .dot { width:8px; height:8px; border-radius:50%; background:var(--slate-light); box-shadow:0 0 0 3px rgba(138,144,166,.15); }
.chan-status.live .dot { background:var(--success,#0E9C73); box-shadow:0 0 0 3px rgba(14,156,115,.18); }
.chan-log { height:min(52vh,460px); overflow-y:auto; padding:16px; display:flex; flex-direction:column; gap:12px; background:var(--surface-2,#fafafb); }
.chan-empty { margin:auto; text-align:center; color:var(--slate-light); }
.chan-empty i { font-size:26px; color:var(--primary-light); }
.chan-empty p { margin:10px 0 0; font-size:13px; }
.chan-msg { display:flex; gap:10px; align-items:flex-start; max-width:88%; }
.chan-msg.mine { flex-direction:row-reverse; align-self:flex-end; }
.chan-msg .av { flex:0 0 auto; width:30px; height:30px; border-radius:50%; background:var(--primary,#7C3AED); color:#fff; font-size: 12px; font-weight:700; display:flex; align-items:center; justify-content:center; }
.chan-msg.mine .av { background:var(--slate,#475569); }
.chan-msg .bub { background:#fff; border:1px solid var(--border); border-radius:12px; padding:8px 12px; box-shadow:0 1px 2px rgba(16,24,40,.04); }
.chan-msg.mine .bub { background:var(--primary-050,#F3EEFF); border-color:#E4D8FF; }
.chan-msg .meta { font-size: 12px; color:var(--slate-light); margin-bottom:2px; }
.chan-msg .meta .who { font-weight:700; color:var(--ink); }
.chan-msg .txt { font-size:14px; line-height:1.5; word-wrap:break-word; overflow-wrap:anywhere; }
.chan-typing { height:18px; padding:0 16px; font-size:12px; color:var(--slate-light); font-style:italic; }
.chan-compose { display:flex; gap:8px; align-items:flex-end; padding:12px 16px; border-top:1px solid var(--border); }
.chan-compose textarea { flex:1; resize:none; border:1px solid var(--border); border-radius:10px; padding:9px 12px; font:inherit; font-size:14px; line-height:1.4; max-height:140px; background:#fff; }
.chan-compose textarea:focus { outline:none; border-color:var(--primary,#7C3AED); box-shadow:0 0 0 3px rgba(124,58,237,.12); }
.chan-compose .btn { flex:0 0 auto; height:38px; }

/* ella Deliver — delivery overview (KPIs + Gantt) */
.kpi-row { display:grid; grid-template-columns:repeat(auto-fit,minmax(min(150px, 100%), 1fr)); gap:12px; margin-top:6px; }
/* ── The stat card, in the hub's own markup ──────────────────────────────────
   The same component the other five consoles call `.stat-card`: a neutral card
   with a 3px rule along the top, and the rule is the only thing that carries a
   state.

   It used to carry the state on the NUMBER — `.is-warn .v` in amber ink,
   `.is-danger .v` in red. Five figures in a row came out in three colours, and
   the reader's job was to notice which of "5, 5, 64, 2, 62" were the coloured
   ones. A figure is the thing being read; it stays --ink at 19.6:1 and the
   colour moves to an edge that is not competing with it. */
.kpi { position:relative; overflow:hidden; background:var(--surface,#fff); border:1px solid var(--border);
       border-radius:12px; padding:14px 16px; }
/* Clipped by the card's own radius: a 3px box cannot honour a 12px one, so a
   rule given its own radius came out a capsule overhanging the card. */
.kpi::before { content:""; position:absolute; top:0; left:0; right:0; height:3px;
               background:var(--accent); }
.kpi .v { font-size:26px; font-weight:800; line-height:1; color:var(--ink); }
.kpi .l { font-size:12px; color:var(--slate-light); margin-top:6px; text-transform:uppercase; letter-spacing:.03em; }
.kpi.is-attention::before { background:var(--warning); }
.kpi.is-critical::before  { background:var(--danger); }
.kpi.is-positive::before  { background:var(--success); }
/* A KPI that links somewhere (the dashboard's ella Deliver card). */
a.kpi { display:block; color:inherit; text-decoration:none; transition:border-color .13s ease, box-shadow .13s ease, transform .13s ease; }
a.kpi:hover { border-color:var(--border-strong); box-shadow:var(--shadow-sm); transform:translateY(-1px); }

/* ---- ella Deliver card (dashboard) — the hub's own module, so it links inward ---- */
.deliver-card { padding:20px 22px; margin-bottom:24px; }
.deliver-card .dc-head { display:flex; align-items:flex-start; gap:14px; }
.deliver-card .dc-glyph { width:46px; height:46px; flex:none; border-radius:12px; display:grid; place-items:center;
  font-size:20px; color:#fff; background:var(--gradient); }
.deliver-card .dc-titles { flex:1; min-width:0; }
.deliver-card .dc-titles h2 { margin:0; font-size:17px; font-weight:800; letter-spacing:-.02em; }
.deliver-card .dc-titles h2 a { color:var(--ink); text-decoration:none; }
.deliver-card .dc-titles h2 a:hover { color: var(--primary-ink); }
.deliver-card .dc-titles h2 span { background:var(--gradient); -webkit-background-clip:text; background-clip:text;
  -webkit-text-fill-color:transparent; color:transparent; }
.deliver-card .dc-titles p { margin:4px 0 0; color:var(--slate); font-size:13px; line-height:1.5; }
.deliver-card .dc-cta { flex:none; white-space:nowrap; }
.deliver-card .dc-kpis { margin-top:16px; }
.deliver-card .dc-links { display:flex; flex-wrap:wrap; gap:8px; margin-top:16px; }

@media (max-width: 680px) {
  .deliver-card .dc-head { flex-wrap:wrap; }
  .deliver-card .dc-cta { width:100%; justify-content:center; }
}

.gantt { font-size:13px; }
.gantt-head, .gantt-row { display:flex; align-items:stretch; border-bottom:1px solid var(--border); }
.gantt-head { background:var(--surface-2,#fafafb); }
.gantt-label { flex:0 0 190px; padding:9px 14px; display:flex; flex-direction:column; justify-content:center; gap:1px; border-right:1px solid var(--border); overflow:hidden; }
.gantt-label .ellip { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:170px; }
.gantt-track { position:relative; flex:1; min-height:38px; }
.gantt-head .gantt-track { min-height:30px; }
.gantt-tick { position:absolute; top:0; bottom:0; border-left:1px dashed transparent; }
.gantt-tick span { position:absolute; top:8px; left:4px; font-size: 12px; color:var(--slate-light); white-space:nowrap; }
.gantt-grid { position:absolute; top:0; bottom:0; width:0; border-left:1px dashed var(--border); opacity:.6; }
/* The same weekly gridlines a ROW needs, as one repeating background instead of
   a <div> per week per job. The header keeps its .gantt-grid elements because it
   also carries the dated labels; a row carries nothing but the rule. */
.gantt-ruled { background-image: repeating-linear-gradient(to right,
        var(--border) 0, var(--border) 1px, transparent 1px, transparent var(--gantt-week, 100%)); }
.gantt-today { position:absolute; top:0; bottom:0; width:0; border-left:2px solid var(--danger,#DC2626); opacity:.55; z-index:2; }
.gantt-bar { position:absolute; top:50%; transform:translateY(-50%); height:16px; background:#D8CCF7; border:1px solid #B79CEC; border-radius:8px; overflow:hidden; z-index:1; }
.gantt-bar.over { background:#F6D2CE; border-color:#E9A69E; }
.gantt-bar .fill { display:block; height:100%; background:var(--primary,#7C3AED); }
.gantt-bar.over .fill { background:var(--danger,#DC2626); }
.gantt-launch { position:absolute; top:50%; transform:translate(-50%,-50%); font-size: 12px; color:var(--primary,#7C3AED); z-index:3; }
@media (max-width:720px){ .gantt-label { flex-basis:120px; } .gantt-label .ellip { max-width:104px; } }

/* ella Deliver — public magic-link supply page */
.supply-wrap { min-height:100vh; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:16px; padding:32px 16px; background:linear-gradient(160deg,#F6F3FF 0%,#FBF7F4 100%); }
.supply-card { width:100%; max-width:520px; background:#fff; border:1px solid var(--border); border-radius:18px; box-shadow:0 10px 40px rgba(16,24,40,.08); padding:30px 30px 26px; }
.supply-brand { margin-bottom:22px; }
.supply-head h1 { font-size:22px; margin:12px 0 8px; }
.supply-head .muted { font-size:14px; line-height:1.55; }
.supply-form { margin-top:20px; display:flex; flex-direction:column; gap:16px; }
.supply-file { display:flex; align-items:center; gap:10px; border:1.5px dashed var(--border); border-radius:12px; padding:14px 16px; cursor:pointer; font-size:14px; transition:border-color .15s,background .15s; }
.supply-file:hover { border-color:var(--primary,#7C3AED); background:#FAF7FF; }
.supply-file input { flex:1; font-size:13px; }
.btn-lg { padding:12px 18px; font-size:15px; justify-content:center; }
.supply-fine { font-size:12px; text-align:center; margin:2px 0 0; }
.supply-state { text-align:center; padding:12px 0 6px; }
.supply-state .ic { width:64px; height:64px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:28px; margin:0 auto 16px; }
.supply-state .ic.ok { background:var(--success-bg,#E6F6EF); color: var(--success-ink); }
.supply-state .ic.err { background:var(--danger-bg,#FDECEA); color: var(--danger-ink); }
.supply-state h1 { font-size:21px; margin:0 0 8px; }
.supply-state p { color:var(--slate-light); font-size:14px; line-height:1.55; margin:0; }
.supply-foot { font-size:12px; }

/* ella Deliver — request-supply modal (copy / email / SMS modes) */
.supply-modes { display:flex; gap:6px; margin:0 0 16px; border-bottom:1px solid var(--border); }
.supply-mode { border:0; background:transparent; padding:8px 12px; font:inherit; font-size:13px; color:var(--slate-light); cursor:pointer; border-bottom:2px solid transparent; margin-bottom:-1px; display:inline-flex; align-items:center; gap:6px; }
.supply-mode:hover:not(:disabled) { color:var(--ink); }
.supply-mode.active { color:var(--primary); border-bottom-color:var(--primary); font-weight:600; }
.supply-mode:disabled { opacity:.4; cursor:not-allowed; }

/* ============================================================
   Channels — the cross-job conversation list, @-mentions,
   and the dashboard note
   ============================================================ */

/* Sidebar count reuses app.css's .pip. Purple when something names you, so the
   two states are distinguishable without reading the number. */
.sidebar-menu-link .pip.pip-mention { background: var(--primary); }
/* Collapsed to icons there is no room for a number, and .pip's margin-left:auto
   would push it off the edge — so it becomes a dot on the icon instead. */
.sidebar:not(:hover):not(.expanded) .sidebar-menu-link .pip {
  position: absolute; top: 8px; left: 30px; margin: 0;
  min-width: 9px; width: 9px; height: 9px; padding: 0;
  border-radius: 50%; font-size: 0; overflow: hidden;
  box-shadow: 0 0 0 2px var(--ink);
}

.chan-list-tools { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-bottom: 14px; }
.chan-search { position: relative; flex: 1 1 320px; min-width: 240px; }
.chan-search i { position: absolute; left: 13px; top: 50%; transform: translateY(-50%); color: var(--slate-light); font-size: 13px; pointer-events: none; }
.chan-search input { width: 100%; padding-left: 36px; }
.chan-filters { display: flex; gap: 6px; flex: 0 0 auto; }
.chan-filter {
  border: 1px solid var(--border-strong); background: #fff; border-radius: var(--radius-pill);
  padding: 7px 14px; font: inherit; font-size: 13px; color: var(--slate); cursor: pointer;
  display: inline-flex; align-items: center; gap: 7px; transition: .12s ease;
}
.chan-filter:hover { border-color: var(--slate-light); color: var(--ink); }
.chan-filter.active { background: var(--primary); border-color: var(--primary); color: #fff; }
.chan-filter .cf-n { font-weight: 700; font-size: 12px; opacity: .75; }

.chan-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.chan-row { border: 1px solid var(--border); border-radius: var(--radius-sm); background: #fff; transition: border-color .12s ease, box-shadow .12s ease; }
.chan-row:hover { border-color: var(--border-strong); box-shadow: 0 2px 10px rgba(17,17,26,.06); }
/* Unread earns a left edge and a white background; read rows recede. */
.chan-row.is-unread { border-left: 3px solid var(--primary); }
.chan-row:not(.is-unread) { background: var(--bg); }
.chan-row[hidden] { display: none; }
.chan-row-link { display: flex; align-items: flex-start; gap: 16px; padding: 13px 16px; text-decoration: none; color: inherit; }
.chan-row-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.chan-row-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.chan-ref { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; font-weight: 700; color: var(--primary-dark); }
.chan-title { font-weight: 600; font-size: 14.5px; color: var(--ink); }
.chan-row.is-unread .chan-title { font-weight: 700; }
.chan-mention-pill {
  display: inline-flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 700;
  background: #F3E9FF; color: var(--purple-deep); padding: 1px 8px; border-radius: var(--radius-pill);
}
.chan-row-sub { font-size: 12.5px; color: var(--slate-light); }
.chan-dot { margin: 0 6px; }
.chan-last { font-size: 13px; color: var(--slate); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 2px; }
.chan-last strong { color: var(--ink); font-weight: 600; }
.chan-last.is-quiet { color: var(--slate-light); font-style: italic; }
.chan-row-meta { flex: 0 0 auto; display: flex; flex-direction: column; align-items: flex-end; gap: 4px; text-align: right; }
.chan-unread { background: var(--primary); color: #fff; font-size: 12px; font-weight: 700; padding: 2px 8px; border-radius: var(--radius-pill); }
.chan-when { font-size: 12px; color: var(--slate-light); white-space: nowrap; }
.chan-total { font-size: 12px; color: var(--slate-light); white-space: nowrap; }
.chan-empty-search { text-align: center; padding: 26px 0; }
/* ---- editing / deleting your own message ---- */
/* The meta line becomes a row so the tools sit hard right, clear of the name and
   timestamp. They fade in on hover — a thread should read as text, not as a
   column of buttons — and reuse the app's own .iconbtn-sm rather than inventing
   a channel-only button. */
.chan-msg .meta { display: flex; align-items: center; gap: 6px; }
.chan-msg-tools {
  display: inline-flex; gap: 1px; margin-left: auto; padding-left: 12px;
  opacity: 0; transition: opacity .12s ease;
}
.chan-msg:hover .chan-msg-tools,
.chan-msg:focus-within .chan-msg-tools { opacity: 1; }   /* keyboard users never hover */
.chan-msg-tools .iconbtn-sm { font-size: 12px; padding: 3px 5px; border-radius: 6px; line-height: 1; }
.chan-msg-tools .iconbtn-sm:hover { background: rgba(11,11,18,.06); }
.chan-msg.mine .chan-msg-tools .iconbtn-sm:hover { background: rgba(122,31,208,.10); color: var(--purple-deep); }
.chan-edited { cursor: help; text-decoration: underline dotted; text-underline-offset: 2px; }

/* A deleted message keeps its place — the replies around it still refer to it. */
.chan-msg.is-deleted .bub { opacity: .75; }
.chan-tomb { font-style: italic; color: var(--slate-light); display: flex; align-items: center; gap: 7px; }
.chan-tomb i { font-size: 11px; }

.chan-edit { display: flex; flex-direction: column; gap: 8px; min-width: 260px; }
.chan-edit-box {
  width: 100%; font: inherit; font-size: 14px; line-height: 1.45; color: var(--ink);
  background: #fff; border: 1px solid var(--primary-light); border-radius: 9px; padding: 9px 11px;
  resize: vertical; outline: none; box-shadow: 0 0 0 3px rgba(172,67,255,.14);
}
.chan-edit-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* A rendered @mention inside a message. Yours is filled in, so you can see at a
   glance which of the names in a thread is you. */
.chan-mention { background: #F3E9FF; color: var(--purple-deep); font-weight: 600; padding: 0 4px; border-radius: 4px; }
.chan-mention.is-me { background: var(--primary); color: #fff; }

/* The @-picker above the composer. */
.mention-pop {
  position: absolute; bottom: calc(100% + 6px); left: 12px; z-index: 60; min-width: 240px; max-width: 320px;
  background: #fff; border: 1px solid var(--border); border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
  max-height: 240px; overflow-y: auto; padding: 5px;
}
.mention-pop[hidden] { display: none; }
.mention-opt { display: flex; flex-direction: column; gap: 1px; padding: 7px 10px; border-radius: 7px; cursor: pointer; }
.mention-opt.is-active, .mention-opt:hover { background: rgba(172,67,255,.10); }
.mention-opt .mo-name { font-size: 13.5px; font-weight: 600; color: var(--ink); }
.mention-opt .mo-mail { font-size: 12px; color: var(--slate-light); }
.mention-empty { padding: 12px 10px; font-size: 13px; color: var(--slate-light); text-align: center; }

/* Dashboard note. */
.mention-note { border: 1px solid var(--primary-light); background: #FBF6FF; border-radius: var(--radius); padding: 16px 18px; margin-bottom: 22px; }
.mention-note-head { display: flex; align-items: center; gap: 11px; margin-bottom: 10px; }
.mention-note-head h2 { margin: 0; font-size: 15px; font-weight: 700; color: var(--ink); flex: 1 1 auto; }
.mention-note-icon {
  width: 30px; height: 30px; border-radius: 50%; background: var(--primary); color: #fff;
  display: flex; align-items: center; justify-content: center; font-size: 13px; flex: 0 0 auto;
}
.mention-note-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.mention-note-list a {
  display: grid; grid-template-columns: auto auto 1fr; gap: 4px 10px; align-items: baseline;
  padding: 8px 11px; background: #fff; border: 1px solid var(--border); border-radius: var(--radius-sm);
  text-decoration: none; color: inherit; transition: border-color .12s ease;
}
.mention-note-list a:hover { border-color: var(--primary-light); }
.mn-who { font-weight: 700; font-size: 13px; color: var(--ink); }
.mn-where { font-size: 12px; color: var(--slate-light); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.mn-body { grid-column: 1 / -1; font-size: 13px; color: var(--slate); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 640px) {
  /* Stretch, not flex-start: stacked, the text column sized itself to its
     one-line preview and ran 150px off a phone before the ellipsis could apply. */
  .chan-row-link { flex-direction: column; align-items: stretch; gap: 8px; }
  .chan-row-meta { flex-direction: row; align-items: center; gap: 10px; }
}

.away-chip { display:inline-flex; align-items:center; gap:5px; font-size: 12px; font-weight:700;
  color:#1E3A8A; background:var(--info-bg); padding:2px 8px; border-radius:var(--radius-pill);
  margin-left:7px; white-space:nowrap; cursor:default; }
.away-chip i { font-size:9px; }

/* ---- working calendar ---- */
.cal-month { border: 1px solid var(--border); border-radius: var(--radius-sm); background: #fff; margin-bottom: 12px; overflow: hidden; }
.cal-month-head { padding: 9px 14px; background: var(--bg); border-bottom: 1px solid var(--border);
  font-size: 13px; font-weight: 700; color: var(--ink); }
.cal-row { display: flex; align-items: center; gap: 12px; padding: 9px 14px; border-bottom: 1px solid var(--border); font-size: 14px; }
.cal-row:last-child { border-bottom: 0; }
.cal-date { flex: 0 0 92px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; color: var(--slate); }
.cal-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-div { flex: 0 0 auto; font-size: 12px; font-weight: 700; padding: 2px 9px; border-radius: var(--radius-pill);
  background: var(--info-bg); color: #1E3A8A; }
.cal-div.is-company { background: #F3E9FF; color: var(--purple-deep); }
/* The nation we actually observe, when a day is not kept UK-wide. */
.cal-div.is-ours { background: var(--ok-bg, #E9FBF3); color: var(--ok, #0A7050); }
.cal-divs { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; }
.cal-note { flex: 0 0 auto; font-style: italic; }
/* A day another nation takes off and we do not: listed for the full picture,
   dimmed because it moves nothing here. */
.cal-row.is-elsewhere .cal-date,
.cal-row.is-elsewhere .cal-label { color: var(--slate-light); }
.cal-src { flex: 0 0 auto; }

/* ---- confirmation dialog ----
   Its styling is entirely in `estate.css` now. The markup is
   `_confirm_dialog.php` — one file, byte-identical in all six consoles — so its
   CSS is one block in one file too. Six private copies is how ella Converse and
   ella Draw ended up with a dialog that laid itself out sideways: both still
   carried an older `.confirm-body { display: flex }` that the newer centred
   rules never cancelled. */

/* A value that can't be changed — locked-on notifications. Not the same as "off". */
.switch .form-check-input:disabled { opacity: .5; }
.switch:has(input:disabled) { cursor: not-allowed; }

/* ---- supply link lifecycle ---- */
/* On a task row: a live link and how far it has got. "Sent, never opened" and
   "opened days ago, still nothing" are different problems with different fixes,
   and neither shows up anywhere else on the page. */
.link-chip { display:inline-flex; align-items:center; gap:5px; font-size: 12px; font-weight:700;
  padding:3px 9px; border-radius:var(--radius-pill); white-space:nowrap; cursor:default; }
.link-chip i { font-size:9px; }
.link-chip.waiting { background:var(--bg); color:var(--slate); border:1px solid var(--border-strong); }
.link-chip.opened  { background:var(--info-bg); color:#1E3A8A; }
.link-chip.lapsing { background:var(--warning-bg); color:var(--warning-ink); }

.supply-sub { font-size:12px; font-weight:700; text-transform:uppercase; letter-spacing:.05em;
  color:var(--slate-light); margin:20px 0 10px; }
.supply-live { border:1px solid var(--border-strong); border-radius:var(--radius-sm); padding:14px; background:var(--bg); }
.supply-live-head { display:flex; align-items:baseline; gap:10px; flex-wrap:wrap; }
.supply-live-state { font-size:13px; font-weight:700; }
.supply-live-state.is-opened  { color: var(--info-ink); }
.supply-live-state.is-waiting { color:var(--slate); }
.supply-live-actions { display:flex; align-items:center; gap:8px; margin-top:10px; flex-wrap:wrap; }

.supply-history { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:6px; }
.supply-history li { display:flex; align-items:center; gap:10px; font-size:13px; color:var(--slate);
  padding:7px 10px; border:1px solid var(--border); border-radius:var(--radius-sm); }
.sh-state { font-size: 12px; font-weight:700; padding:1px 8px; border-radius:var(--radius-pill); flex:0 0 auto; }
.sh-used    { background:var(--success-bg); color:#08543E; }
.sh-expired { background:var(--bg); color:var(--slate); border:1px solid var(--border-strong); }
.sh-revoked { background:var(--danger-bg); color:#7F1D1D; }
.sh-detail { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* ella — nice file uploader (progress %, image thumb, file-type icon) */
.uploader { border:1.5px dashed var(--border); border-radius:12px; padding:16px; transition:border-color .15s, background .15s; cursor:pointer; }
.uploader.drag { border-color:var(--primary,#7C3AED); background:#FAF7FF; }
.uploader input[type=file] { display:none; }
.uploader-empty { display:flex; align-items:center; gap:10px; color:var(--slate-light); font-size:14px; justify-content:center; }
.uploader-empty i { font-size:18px; color:var(--primary-light); }
.uploader-file { display:flex; align-items:center; gap:12px; }
.uploader-thumb { flex:0 0 auto; width:46px; height:46px; border-radius:9px; overflow:hidden; display:flex; align-items:center; justify-content:center; background:var(--surface-2,#f1f2f6); border:1px solid var(--border); }
.uploader-thumb img { width:100%; height:100%; object-fit:cover; display:block; }
.uploader-thumb i { font-size:22px; }
/* A FILE TYPE IS NOT A STATUS. These sat on the same reds and greens the alerts
   use, so a PDF in a list read as an error and a spreadsheet read as a success —
   and when `--danger` became #DC2626 the PDF icon became the danger token
   exactly, which is what `ReadableTextTest` caught. Same four conventions
   (PDF red, Word blue, Excel green, image violet) in the estate's own family,
   none of which is a status token. */
.ft-pdf { color:#A34434; } .ft-doc { color:#2A63B0; } .ft-xls { color:#2F7D57; } .ft-img { color:#7847CE; }
/* #B45309, not #D97706: 5.0:1 against the row rather than 3.2:1, and still
   amber beside the two greys it sits with. */
.ft-zip { color:#B45309; } .ft-txt { color:#64748B; } .ft-file { color: var(--slate-light); }
.uploader-meta { flex:1; min-width:0; }
.uploader-name { font-size:13.5px; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.uploader-sub { font-size:12px; color:var(--slate-light); margin-top:2px; }
.uploader-remove { flex:0 0 auto; border:0; background:transparent; color:var(--slate-light); cursor:pointer; font-size:15px; padding:4px; }
.uploader-remove:hover { color: var(--danger-ink); }
.uploader-prog { height:6px; border-radius:4px; background:var(--surface-2,#e7e7f0); overflow:hidden; margin-top:8px; }
.uploader-prog span { display:block; height:100%; width:0; background:var(--gradient,var(--primary)); transition:width .12s linear; }

/* ── AI provenance ───────────────────────────────────────────────────────────
   Everything a model wrote is labelled, in one style, wherever it appears.
   Deliberately quiet: this is a label, not a warning. It should be readable at
   a glance and ignorable once you've taken it in — a banner people learn to
   scroll past would defeat the point. */
.ai-badge {
    display:inline-flex; align-items:center; gap:5px; flex:none;
    padding:2px 8px; border-radius:999px;
    font-size: 12px; font-weight:700; letter-spacing:.02em; text-transform:uppercase;
    background:var(--primary-bg, rgba(124,58,237,.10));
    color:var(--primary, #7C3AED);
    border:1px solid rgba(124,58,237,.28);
}
.ai-badge i { font-size:10px; }

.ai-note {
    display:flex; align-items:flex-start; gap:9px;
    margin:0 0 12px; padding:9px 11px; border-radius:8px;
    background:rgba(124,58,237,.05);
    border:1px solid rgba(124,58,237,.16);
    font-size:12.5px; line-height:1.5; color:var(--slate, #475569);
}
.ai-note .ai-prov {
    display:inline-block; margin-left:6px; padding:1px 6px; border-radius:5px;
    background:rgba(127,127,127,.12); font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
    font-size: 12px; color:var(--slate-light, #64748B); cursor:help;
}

@media print { .ai-note { break-inside: avoid; } }

/* ── Delivery board: filters, drag, schedule chip ───────────────────────────
   Cards carry two signals — how the job is moving (.ksched) and how the work
   inside it is going (the risk chip in .kmeta). They're kept visually distinct
   so a glance can tell which kind of problem a card has. */
.board-bar { display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin:0 0 14px; }
.board-filters { display:inline-flex; gap:4px; background:#EEEEF4; padding:3px; border-radius:9px; }
.board-filters .filter-btn {
    border:0; background:transparent; cursor:pointer; font:inherit; font-size:12.5px; font-weight:600;
    color:var(--slate); padding:5px 12px; border-radius:7px; transition:.12s;
}
.board-filters .filter-btn:hover { color:var(--ink); }
.board-filters .filter-btn.active { background:#fff; color:var(--primary); box-shadow:var(--shadow-sm); }
.board-search {
    flex:1; min-width:200px; max-width:340px; padding:7px 11px; font:inherit; font-size:13px;
    border:1px solid var(--border); border-radius:9px; background:#fff;
}

.kcard { position:relative; }
.kcard-body { display:block; color:inherit; text-decoration:none; }
.kcard[draggable="true"] { cursor:grab; }
.kcard.is-dragging { opacity:.45; cursor:grabbing; }
.kcard.is-saving   { opacity:.6; pointer-events:none; }
.col.over .col-drop { outline:2px dashed var(--primary-light, #C4B5FD); outline-offset:3px; border-radius:10px; }
.col-drop { min-height:36px; }

.ksched { margin:8px 0 0; }
.kcard-edit {
    margin-left:auto; border:0; background:transparent; cursor:pointer; padding:3px 5px;
    color:var(--slate-light); border-radius:6px; font-size: 12px; transition:.12s;
}
.kcard-edit:hover { background:rgba(127,127,127,.12); color: var(--primary-ink); }
.kcard .foot { display:flex; align-items:center; gap:8px; }

@media (hover:none) {
    /* Dragging is unreliable on touch, so make the alternative obvious rather
       than leaving people poking at a card that won't move. */
    .kcard-edit { color: var(--primary-ink); }
}

/* ── Access matrix ────────────────────────────────────────────────────────
   A person per row, a client per column. Kept narrow-celled on purpose: the
   grid is only useful if a whole agency fits on one screen, so the cells carry
   a short word and the meaning lives in the legend and the tooltip. */
.access-matrix th, .access-matrix td { white-space: nowrap; }
.access-matrix td .chip { min-width: 62px; justify-content: center; }
/* Somebody who reaches nothing is the row worth spotting from across the desk. */
.access-matrix tr.is-stranded > td:first-child { box-shadow: inset 3px 0 0 var(--warning); }
.access-matrix tr.is-stranded { background: var(--warning-bg); }

/* The label above a control that cannot carry its own.
   A `.form-floating` has a label inside the box; a chip select, a file row or a
   group of switches has nothing to put one in, and every call site was reaching
   for `<span class="muted small">` — which is the HINT's styling used as a
   label, so the name of the field read smaller and fainter than the sentence
   explaining it. One class, sized and coloured like a floated label. */
.field-label {
  display: block;
  margin: 0 0 6px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--slate);
  line-height: 1.3;
}

/* ── Chip select ──────────────────────────────────────────────────────────
   A searchable picker that builds a list of chips. Replaces <select multiple>,
   which hides everything past its own height and needs ⌘/Ctrl to add a second
   value — so the thing you have chosen is exactly what you cannot see. */
.chip-select { display: flex; flex-direction: column; gap: 10px; }
.cs-chips { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; min-height: 30px; }

.cs-chip {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 5px 6px 5px 12px; border-radius: 999px;
    background: var(--primary-bg, #F3EEFF); border: 1px solid var(--primary-light, #A78BFA);
    color: var(--ink, #0B0B12); font-size: 13px; font-weight: 600; line-height: 1.2;
}
.cs-chip-label { white-space: nowrap; }

/* Deliberately a real button rather than a bare ×: it is the only destructive
   control in the component, so it gets a hit area and a focus ring. */
.cs-x {
    display: inline-flex; align-items: center; justify-content: center;
    width: 18px; height: 18px; flex: none; padding: 0;
    border: 0; border-radius: 50%; cursor: pointer;
    background: transparent; color: var(--slate-light, #8A90A6);
    font-size: 15px; line-height: 1;
    transition: background .12s, color .12s;
}
.cs-x:hover { background: var(--danger, #DC2626); color: #fff; }
.cs-x:focus-visible { outline: 2px solid var(--primary, #7C3AED); outline-offset: 1px; }

.cs-empty { padding: 4px 0; }
/* The picker sits under the chips and stays a sensible width — full-bleed it
   reads as a text field somebody is meant to fill in, not as "add another". */
.chip-select .cs-picker { max-width: 340px; }

/* "Everything, or only these?" — a choice big enough to click at, with the
   consequence written underneath rather than left to be inferred. */
.cs-choice {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    padding: 11px 13px;
    border: 1px solid var(--line, #E5E7EB);
    border-radius: 9px;
    cursor: pointer;
    line-height: 1.45;
}
.cs-choice:hover { border-color: var(--primary, #7C3AED); }
.cs-choice input { margin-top: 3px; flex: none; }
.cs-choice:has(input:checked) {
    border-color: var(--primary, #7C3AED);
    background: color-mix(in srgb, var(--primary, #7C3AED) 5%, transparent);
}

/* ---- Mobile number: country picker + national number ----
   Two controls, one answer. The country box is wide enough for a flag, a name
   and a code without truncating the long ones ("Saint Vincent and the
   Grenadines"), and the number takes the remaining width. They wrap rather than
   squeeze on a narrow screen — a phone field squeezed to four characters is
   worse than a second line. */
.phone-input { display: flex; flex-direction: column; gap: 6px; }
.phone-input .phone-label { margin-left: 2px; }
/* `stretch`, not `flex-start`: both halves are 54px `.form-floating`s, and any future
   difference (a validation message under one) should still leave their boxes
   level rather than hanging one of them off the top. */
.phone-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: stretch; }
.phone-country { flex: 0 1 280px; min-width: 200px; }
.phone-number { flex: 1 1 200px; min-width: 160px; margin: 0; }
/* The chosen country sits on the floating label's line, like any other field's
   value — the trigger is a flex row, so its text needs the same top padding the
   plain inputs get from `padding: 22px 12px 6px`. */
.phone-country > .ts-wrapper .ts-control { align-items: flex-end; }
/* The flag renders from the system emoji font; forcing the stack keeps it a
   flag on Linux/Windows rather than the two letters it decomposes to. Both the
   chosen value and the list, which are different elements. */
.phone-country .ts-control .item,
.phone-country .ts-dropdown .option {
    font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", inherit;
}

/* ---- Sub-tabs: one list, two states (Active / Archived) ----
   Lighter than the .tabs used inside a record — these navigate, they do not
   switch a panel, so they are links and each has its own URL. */
.subtabs { display: flex; gap: 6px; flex-wrap: wrap; }
.subtab {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 8px 14px; border-radius: 9px; font-size: 14px; font-weight: 600;
    color: var(--slate-light); text-decoration: none; border: 1px solid transparent;
}
.subtab:hover { background: var(--bg); color: var(--ink); }
.subtab.active { background: #fff; border-color: var(--border); color: var(--ink); box-shadow: var(--shadow-sm); }
.subtab .tab-count {
    font-size: 12px; font-weight: 700; padding: 1px 7px; border-radius: 999px;
    background: var(--bg); color: var(--slate-light);
}
.subtab.active .tab-count { background: color-mix(in srgb, var(--primary, #7C3AED) 12%, transparent); color: var(--primary, #7C3AED); }

/* A destructive item in a row-actions drop-down reads as destructive. */
.row-actions-menu-item.is-danger, .row-actions-menu form button.is-danger { color: var(--danger-ink); }
.row-actions-menu-item.is-danger:hover, .row-actions-menu form button.is-danger:hover {
    background: color-mix(in srgb, var(--danger, #DC2626) 8%, transparent);
}

/* ---- Collapsible card sections ----
   The header stays; everything else in the card goes. `:not()` rather than a
   wrapper element so the markup is an ordinary card and works with JS off. */
.acc > [data-acc-head] { cursor: pointer; user-select: none; position: relative; padding-right: 30px; }
/* Whatever the header carries besides its title — a status chip, a count — sits
   at the far end, clear of the chevron. A collapsed section has to say enough to
   decide whether to open it. */
.acc > [data-acc-head] > :not(h2):last-child { margin-left: auto; }
.acc > [data-acc-head]::after {
    content: ""; position: absolute; right: 2px; top: 50%; width: var(--chevron-w); height: var(--chevron-h);
    margin-top: calc(var(--chevron-h) / -2);
    background-color: var(--slate-light);
    -webkit-mask: var(--chevron-mask) no-repeat center / contain;
            mask: var(--chevron-mask) no-repeat center / contain;
    transition: transform .15s ease;
}
.acc.acc-closed > [data-acc-head]::after { transform: rotate(-90deg); }
.acc > [data-acc-head]:hover::after { background-color: var(--ink); }
.acc > [data-acc-head]:focus-visible { outline: 2px solid var(--primary, #7C3AED); outline-offset: 2px; border-radius: 8px; }
.acc.acc-closed > *:not([data-acc-head]) { display: none !important; }
/* `!important` because the things being hidden set `display` INLINE — the forms
   and chip rows in these cards are `style="display:flex"`, and an inline
   declaration beats any selector without it. Without this a card collapsed
   halfway: the paragraphs vanished and the form stayed, which reads as a
   rendering fault rather than a collapsed section. Same shape of bug as the one
   `[hidden] { display: none !important }` fixes further up.

   A collapsed card is a header and nothing else, so it needs its own spacing
   rather than the margin it carried while holding a form. */

/* ---- One inset for every accordion header ----
   The two card shapes get there differently: `.panel` brings 18px of padding of
   its own, `.table-responsive` brings none because its table is meant to run edge to
   edge. Left to themselves that put two stacked sections' titles on different
   left edges and gave them different amounts of air — visible the moment two of
   them sit one above the other, which is the normal case.

   So the header's inset is stated once here, per shape, to land in the same
   place: 18px from the card edge either way. `!important` because these margins
   are set INLINE at each call site, and an inline declaration beats any selector
   without it — the alternative is editing a dozen views and hoping the next one
   copies the right number.

   The chevron sits at `right: 2px` inside the header, so both shapes put it 20px
   from the card's edge and the column of chevrons lines up down the page. */
.acc.panel > [data-acc-head]     { margin: 0 0 12px !important; }
.acc.table-responsive > [data-acc-head] { margin: 18px 18px 12px !important; }
.acc.acc-closed.panel > [data-acc-head]     { margin: 0 !important; }
.acc.acc-closed.table-responsive > [data-acc-head] { margin: 18px !important; }

/* The third card shape: an item card stacked inside a panel — one piece of
   creative, and whatever comes next. Its inset is stated here with the other two
   for the reason given above: at the call site, ten of them drift. */
.acc-item { border: 1px solid var(--border); border-radius: 10px; padding: 14px 16px; margin: 0 0 12px; }
.acc.acc-item > [data-acc-head]            { margin: 0 0 10px !important; }
.acc.acc-closed.acc-item > [data-acc-head] { margin: 0 !important; }

/* A row for something the hub does not manage yet — present, readable, and
   visibly not actionable. Greyed rather than hidden: hiding it would leave
   somebody looking for a service they know exists. */
tr.is-muted > td { opacity: .62; }
tr.is-muted:hover > td { opacity: .8; }

/* "Somebody else has changed this" — shown at the top of the region that is out
   of date, rather than in a toast that has already faded by the time anybody
   looks up. It appears only when the page could NOT redraw safely, which means
   the reader is mid-sentence in there: interrupting them is the thing being
   avoided, so it offers rather than acts. */
.stale-bar {
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    margin: 0 0 12px; padding: 9px 12px; border-radius: 10px;
    background: var(--warning-bg, #FFFBEB); border: 1px solid #FDE68A;
    color: var(--warning-ink); font-size: 13px;
}
.stale-bar .btn { margin-left: auto; }

/* ---------- charts ----------
   The dashboard grid and the chart-card chrome. Deliberately small: Chart.js
   draws the marks, and everything here is layout plus the table view that ships
   beside every chart. */
.viz-grid {
    display: grid;
    /* `min(360px, 100%)` and not a bare 360px. A hard minimum in `auto-fit` is
       still a minimum on a screen narrower than it, so on a 298px column the
       track was 360px, the card overflowed it, and the PAGE scrolled sideways —
       the same shape as the guide's grid, and just as invisible, because every
       ancestor measured correctly. */
    grid-template-columns: repeat(auto-fit, minmax(min(360px, 100%), 1fr));
    gap: 16px;
}
.viz-card { display: flex; flex-direction: column; }
/* The canvas needs a sized parent — Chart.js measures its container, and a
   height set on the canvas itself is overwritten on every resize. */
.viz-plot { position: relative; width: 100%; }
/* Wide tables scroll inside their own card rather than pushing the page sideways. */
.viz-table { overflow-x: auto; }
.viz-table .table > :not(caption) > * > * { padding: 6px 8px; }

/* ══ The QR builder ═══════════════════════════════════════════════════════════
 *
 * Ported from ella Platform along with the tool. Controls on the left, a live
 * preview on the right that stays put while you change things — the whole point
 * is comparing one setting against the last, and a preview that scrolls away
 * makes that impossible.
 */
.qr-layout {
  display: grid; gap: 20px;
  grid-template-columns: minmax(0, 1.35fr) minmax(min(320px, 100%), 1fr);
  align-items: start;
}
@media (max-width: 1000px) { .qr-layout { grid-template-columns: 1fr; } }

.qr-controls, .qr-output { margin: 0; }
.qr-output { position: sticky; top: 88px; }
@media (max-width: 1000px) { .qr-output { position: static; } }

.qr-controls h3 {
  font-size: 12px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--slate-light);
  margin: 22px 0 11px; padding-top: 16px;
  border-top: 1px solid var(--border);
}
.qr-controls h3:first-of-type { margin-top: 4px; padding-top: 0; border-top: 0; }

/* A grid so the fields and their hints share a baseline — a flex row leaves
   four hints hanging at four different heights. */
.qr-fields {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr));
  align-items: start;
  margin-bottom: 4px;
}
.qr-fields .form-floating { margin: 0; }
.qr-logo-clear { display: flex; align-items: center; }

.qr-colours { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 16px; }
.qr-colour { display: flex; flex-direction: column; gap: 5px; }

/* `appearance: none` first — without it the height is advisory and the swatch
   renders shorter than the fields beside it. */
.qr-colour input[type="color"] {
  -webkit-appearance: none; appearance: none;
  width: 60px; height: 38px; padding: 3px;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface); cursor: pointer;
}
.qr-colour input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.qr-colour input[type="color"]::-webkit-color-swatch { border: 0; border-radius: 5px; }
.qr-colour input[type="color"]:disabled { opacity: .45; cursor: default; }
.qr-check { display: inline-flex; align-items: center; gap: 7px; font-size: 14px; cursor: pointer; padding-bottom: 9px; }

/* The checkerboard, so "transparent" and "white" are told apart on screen —
   which is the whole reason somebody ticks the box. */
.qr-stage {
  display: block; padding: 14px; border-radius: 12px;
  border: 1px solid var(--border);
  background-color: #fff;
  background-image:
    linear-gradient(45deg, #E9E9F2 25%, transparent 25%, transparent 75%, #E9E9F2 75%),
    linear-gradient(45deg, #E9E9F2 25%, transparent 25%, transparent 75%, #E9E9F2 75%);
  background-size: 16px 16px;
  background-position: 0 0, 8px 8px;
  text-align: center;
}
.qr-stage canvas { display: inline-block; max-width: 100%; height: auto; }

/* Same height as the stage it replaces, so the panel does not jump the moment
   an address is typed. */
.qr-placeholder {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; min-height: 150px; margin: 0; padding: 20px;
  color: var(--slate-light); font-size: 12.5px; text-align: center;
}
.qr-placeholder i { font-size: 26px; opacity: .5; }
.qr-placeholder[hidden] { display: none; }

.qr-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.qr-note { margin-top: 12px; }

/* ── A file input that belongs to this form ──────────────────────────────────
   Chrome draws `Choose File | No file chosen` in the OS font with a square grey
   button, which is the one control on a screen of floating-label fields that
   looks like it came from somewhere else. `::file-selector-button` styles the
   button in place, so the element is still the input — keyboard focus, drop
   target and screen-reader label all unchanged. */
.form-field-file > input[type="file"].filefield {
  padding: 24px 12px 8px; font-size: 13px; color: var(--slate); cursor: pointer;
}
.form-field-file > input[type="file"].filefield::file-selector-button {
  font: inherit; font-size: 12.5px; font-weight: 600; margin-right: 10px;
  padding: 4px 12px; border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong); background: #fff; color: var(--ink);
  cursor: pointer; transition: border-color .12s ease, background .12s ease;
}
.form-field-file > input[type="file"].filefield::file-selector-button:hover {
  border-color: var(--slate-light); background: var(--bg);
}

/* ── The saved codes on a job ────────────────────────────────────────────── */
.qr-gallery {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(min(190px, 100%), 1fr));
}
.qr-card { border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; background: var(--surface); }
/* Checkerboard here too: a saved code with a transparent background otherwise
   reads as a white one, and that is the difference between the pack version and
   the press version. */
.qr-card-img {
  display: block; width: 100%; aspect-ratio: 1; object-fit: contain; padding: 10px;
  background-color: #fff;
  background-image:
    linear-gradient(45deg, #E9E9F2 25%, transparent 25%, transparent 75%, #E9E9F2 75%),
    linear-gradient(45deg, #E9E9F2 25%, transparent 25%, transparent 75%, #E9E9F2 75%);
  background-size: 14px 14px; background-position: 0 0, 7px 7px;
}
.qr-card-body { padding: 10px 12px 12px; border-top: 1px solid var(--border); }
.qr-card-body .qr-card-name { font-weight: 700; font-size: 13.5px; }
.qr-card-actions { display: flex; gap: 6px; margin-top: 9px; flex-wrap: wrap; }

/* ── The QR builder's destination tabs ──────────────────────────────────────
   A tab each rather than one address box with a format dropdown: the three
   destinations want different fields and carry different warnings, and a
   dropdown above a single box makes somebody read it to find out what the box
   expects. */
.qr-tabs { display: flex; flex-wrap: wrap; gap: 4px; margin: 4px 0 14px; border-bottom: 1px solid var(--border); }
.qr-tab {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  font: inherit; font-size: 13.5px; font-weight: 600; color: var(--slate);
  padding: 9px 13px; border-radius: 10px 10px 0 0;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  display: inline-flex; align-items: center; gap: 7px;
}
.qr-tab:hover { color: var(--ink); background: var(--bg); }
.qr-tab.is-on { color: var(--primary); border-bottom-color: var(--primary); }
.qr-tab i { font-size: 12px; }
.qr-pane[hidden] { display: none; }

/* ── A DISCLOSURE THAT LOOKS PRESSABLE ─────────────────────────────────────
   Several tabs hid their primary action inside a `<details>` whose `<summary>`
   was muted 13px text with the browser's default triangle — so on a job with
   nothing yet, the one thing that had to happen was drawn as the least
   important thing on the screen. QR codes, Short links and Access groups all
   had it, and Access groups had it worst: the summary was styled as a card
   HEADER with no triangle at all, so the tab looked like a static row and the
   only "New team" button was inside it.

   A disclosure is still the right element — a job with eight short links does
   not want a form above the list — so it keeps the element and takes a
   button's clothes. Pair it with `open` when the list behind it is empty:
   folding away the only action on an empty tab is the whole defect.

   `list-style: none` plus the WebKit pseudo-element removes the triangle in
   both engines. A leading `+` does NOT turn: rotating it 45deg makes an `×`,
   and an `×` beside "Make a new short link" reads as a control that removes
   something. Whether the section is open is already obvious — the form is
   either there or it is not. */
.disclose {
  display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  font: inherit; font-size: 13.5px; font-weight: 600; color: var(--ink);
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius); padding: 8px 13px;
  list-style: none; user-select: none;
}
.disclose::-webkit-details-marker { display: none; }
.disclose:hover { background: var(--bg); border-color: var(--primary); color: var(--primary); }
.disclose:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.disclose i { font-size: 12px; }

/* A panel header that is also a disclosure turns its chevron, because there is
   no `+` there to be misread as an `×` and a header gives no other clue that it
   opens. Scoped to the chevron so a leading `+` elsewhere stays put. */
details[open] > .disclose .fa-chevron-right { transform: rotate(90deg); }

/* ── The same, quietly, for a disclosure that hides HELP rather than an action
   Not everything folded away is a control somebody has to find. "How to test",
   "The same hours as a table", "What the brief actually states" — these are
   detail for the reader who wants it, and giving them a button's clothes would
   say they matter as much as the thing they sit beside.

   What they should NOT be is the browser's default: a raw grey triangle and
   whatever `font-weight:600` somebody typed at the call site, which is what
   nine of them were. That reads as unfinished next to a page of drawn
   components, and it is the same fault as the invisible primary action one
   register down — a control nobody decided the look of.

   A link with a chevron that turns. It is obviously pressable, obviously
   secondary, and it is a decision rather than a default. */
.disclose-quiet {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  font: inherit; font-size: 13px; font-weight: 600; color: var(--primary);
  list-style: none; user-select: none; padding: 2px 0;
}
.disclose-quiet::-webkit-details-marker { display: none; }
.disclose-quiet:hover { text-decoration: underline; }
.disclose-quiet:focus-visible { outline: 2px solid var(--primary); outline-offset: 3px; border-radius: 4px; }
.disclose-quiet::before {
  content: "\f054"; font: 900 10px/1 "Font Awesome 6 Free";
  transition: transform .15s ease; display: inline-block;
}
details[open] > .disclose-quiet::before { transform: rotate(90deg); }

.qr-pane .form-text code { font-size: 12px; }

/* What a saved code carries, on its card. A fixed address and a text message
   cannot be repointed, and that is worth saying on the card rather than only in
   the builder — it is the question somebody has when reprinting. */
.qr-kind { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; font-weight: 700;
           letter-spacing: .04em; text-transform: uppercase; }
.qr-kind.is-tracked { color: var(--success-ink); }
.qr-kind.is-fixed   { color: var(--slate-light); }

/* ---------- capacity calendar ----------
   A month grid where the shade is committed hours, so a crunch is visible
   before it is explained. Scaled against the busiest day in the range rather
   than an absolute number of hours — see the view for why. */
.cap-cal { width:100%; border-collapse:separate; border-spacing:4px; table-layout:fixed; }
.cap-cal th { font-size: 12px; font-weight:600; color:var(--muted,#6b7280); text-align:center; padding:0 0 4px; }
.cap-cal td { height:62px; vertical-align:top; border-radius:8px; padding:6px 7px; border:1px solid var(--border,#e5e7eb); background:#fff; }
.cap-cal td.is-pad { border:0; background:none; }
/* Weekends and holidays are drawn as unavailable, not as quiet. A blank
   Saturday next to a blank Wednesday would otherwise read as spare capacity. */
.cap-cal td.is-off { background:repeating-linear-gradient(45deg,#fafafa,#fafafa 4px,#f2f2f4 4px,#f2f2f4 8px); }
.cap-cal .d { display:block; font-size: 12px; color:var(--muted,#6b7280); }
.cap-cal .h { display:block; font-size:14px; font-weight:700; margin-top:3px; }
.cap-cal .t { display:block; font-size: 12px; color:var(--muted,#6b7280); }
/* Four steps of one hue, light to dark — a sequential scale for a magnitude.
   Never a rainbow here: the quantity is one-directional. */
.cap-cal td.l1 { background:#eef2ff; border-color:#dfe5fd; }
.cap-cal td.l2 { background:#d5dcfb; border-color:#c3cdf8; }
.cap-cal td.l3 { background:#b3bff7; border-color:#9dadf3; }
.cap-cal td.l4 { background:#8b9cf2; border-color:#7287ee; }
.cap-cal td.l4 .h, .cap-cal td.l4 .d, .cap-cal td.l4 .t { color:#111827; }
@media (max-width:720px) { .cap-cal td { height:52px; } .cap-cal .h { font-size:12px; } }

/* ---------- segmented control ----------
   For a small set of mutually exclusive views that live in the URL. Not the
   .tabs component: that one queries its buttons and panels by descendant, so it
   cannot be nested inside itself. */
.seg { display:inline-flex; border:1px solid var(--border,#e5e7eb); border-radius:9px; overflow:hidden; background:#fff; }
.seg-btn { display:inline-flex; align-items:center; gap:7px; padding:7px 13px; font-size:13px; font-weight:600;
           color:var(--muted,#6b7280); text-decoration:none; border-right:1px solid var(--border,#e5e7eb); }
.seg-btn:last-child { border-right:0; }
.seg-btn:hover { background:#f7f7fa; color:var(--text,#111827); }
.seg-btn.is-on { background:var(--primary,#7B68EE); color:#fff; }
.seg-btn.is-on:hover { background:var(--primary,#7B68EE); color:#fff; }

/* ---------- version strip ----------
   A job's pricing versions, as independent pills rather than a joined .seg —
   there can be four options at once (with games, with cash prizes, …) and
   each needs its OWN border and radius, which is why this is not .seg-btn
   forced into that shape with inline overrides. It was, and the inline
   `background:var(--primary-light,#eef2ff)` on the active one was reaching
   for a pale wash that does not exist: brand.css aliases `--primary-light`
   straight to `--accent`, full strength, so the fallback never ran and the
   "active" tab was solid indigo with a white label — which is what "the
   active tab should not have a blue background" was about.

   The inactive ones had the opposite problem: `border:1px solid var(--border)`
   on `--surface` (white) is the same white the page sits on, so a pill with no
   accent read as almost nothing next to the ones that had it — "blends into
   the background, hard to navigate."

   Fixed the ordinary way an unselected-vs-selected control is told apart
   without a solid fill: the inactive pill gets real BODY colour so it reads as
   a control at rest, and the active one is white with the accent as an
   outline and a 3px underline — present, not shouting. */
.version-strip { display:flex; gap:6px; flex-wrap:wrap; }
.version-tab {
    display:inline-flex; align-items:center; gap:7px;
    padding:7px 13px; border-radius:9px;
    border:1px solid var(--border-strong,#D6D6E4);
    background:var(--bg,#F7F7F7);
    color:var(--slate,#4A5168);
    font-size:13px; font-weight:600; text-decoration:none;
}
.version-tab:hover { border-color:var(--primary,#5B52C8); color:var(--ink,#0B0B12); }
.version-tab.active {
    background:var(--surface,#fff);
    border-color:var(--primary,#5B52C8);
    border-bottom:3px solid var(--primary,#5B52C8);
    color:var(--primary-ink,#5B4BC4);
}
/* Declined / superseded: present in the record, not competing for attention. */
.version-tab.is-settled { opacity:.6; }

/* Stage gates — the ways of working, shown as a state rather than a form.
   Three states and they must not be read as two: a failing gate is amber or
   red, a satisfied one green, and an INAPPLICABLE one deliberately grey and
   muted. "Not a promotion, so no results to declare" is not a pass, and
   colouring it green would quietly claim we checked something we did not. */
.gate-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 7px; }
.gate { display: flex; gap: 9px; align-items: flex-start; font-size: 13.5px; line-height: 1.45; }
.gate > i { margin-top: 2px; font-size: 13px; flex: none; }
.gate-t { color: var(--ink); }
.gate-d { display: block; color: var(--slate); font-size: 12.5px; }
.gate.is-pass > i { color: var(--success-ink); }
.gate.is-fail > i { color: #B45309; }
.gate.is-na   > i { color: var(--slate-light); }
.gate.is-na .gate-t { color: var(--slate); }

/* The three form measures moved to `estate.css`, beside `.form-grid` — they
   are part of the same layout system and every console needs them. */

/* The row of buttons under a form. It was an anonymous `<div>` with whatever
   margin the screen happened to give it, so no two forms in the console put
   their Save button the same distance from the last field. */

/* A form that is one field and the things that go with it — a date and a Save,
   a picker and a chip. The grid does the widths; this only aligns what sits in
   the wide half against the control in the narrow one. */
.form-inline-row { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 20px 16px; align-items: start; }
.form-inline-row > * { grid-column: 1 / -1; min-width: 0; }
/* The same twelve spans `.form-grid` has. Written out rather than aliased
   because `.fg-*` in estate.css is scoped to `.form-grid`, and a span class
   that silently does nothing in one of two grids is worse than not having it. */
@media (min-width: 760px) {
  .form-inline-row > .fg-1  { grid-column: span 1; }
  .form-inline-row > .fg-2  { grid-column: span 2; }
  .form-inline-row > .fg-3  { grid-column: span 3; }
  .form-inline-row > .fg-4  { grid-column: span 4; }
  .form-inline-row > .fg-5  { grid-column: span 5; }
  .form-inline-row > .fg-6  { grid-column: span 6; }
  .form-inline-row > .fg-7  { grid-column: span 7; }
  .form-inline-row > .fg-8  { grid-column: span 8; }
  .form-inline-row > .fg-9  { grid-column: span 9; }
  .form-inline-row > .fg-10 { grid-column: span 10; }
  .form-inline-row > .fg-11 { grid-column: span 11; }
  .form-inline-row > .fg-12 { grid-column: 1 / -1; }
}
.form-inline-tail { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; min-height: 54px; }
.form-inline-tail .form-text { margin: 0; }

/* The figure at the top of the Estimate tab. It was three inline styles and a
   30px number with no space around it. */
.es-figure { display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap; margin: 4px 0 18px; }
.es-amount { font-size: 34px; font-weight: 800; letter-spacing: -.02em; line-height: 1; color: var(--ink); }
.es-facts  { display: flex; flex-direction: column; gap: 3px; }
.es-fact   { font-size: 13px; color: var(--slate); }

/* A heading's own actions, pushed to the far end of it.

   Both levels wrap, because neither can know how wide the phone is. The heading
   wraps so the actions drop under the title rather than beside it; the actions
   wrap so two buttons that are wider together than the screen stack instead of
   pushing the whole PAGE sideways — at 390px, "Give somebody access" and "See the
   whole account" on a client were 64px past the edge. `row-gap` only, so a
   heading that fits on one line is spaced exactly as it was. */
.sec-head { flex-wrap: wrap; row-gap: 8px; }
.sec-head-actions { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }

/* ══ Build with AI ═══════════════════════════════════════════════════════════
 *
 * The readiness list on a job's Tasks tab, and the conversation in its modal.
 * Here rather than in `estate.css` by the usual line: remove Bootstrap and
 * these are still needed, because they are this product's screens rather than
 * a reconciliation with anybody's framework.
 */

/* One step: an icon in its own column, text that wraps under itself, and a way
   in on the right. A grid rather than flex so the icon column is the same width
   on every row — with flex the rows step in and out as the icons change. */
.build-step {
  display: grid;
  grid-template-columns: 20px 1fr auto;
  gap: 4px 10px;
  align-items: start;
  padding: 9px 0;
  border-top: 1px solid var(--border);
}
.build-step:first-child { border-top: 0; padding-top: 2px; }

.build-step-icon { font-size: 14px; line-height: 1.5; }

/* The three states. Colour is never the only signal — the icon differs too
   (tick, dot, exclamation) and the blocking rows carry a chip that says so in
   words, so none of this depends on telling green from amber. */
.build-step-ok     { color: var(--success-ink); }
.build-step-warn   { color: var(--warning-ink); }
.build-step-danger { color: var(--danger-ink); }

.build-step-text { min-width: 0; }
.build-step-text strong { font-size: 13.5px; }

/* The Open button sits on the first line of the row rather than centred against
   a two-line detail, which is where the eye expects it when scanning down. */
.build-step-go { align-self: start; white-space: nowrap; }

/* The thread. A fixed height with its own scroll: the modal already scrolls,
   and a growing thread that pushes the answer box off the bottom means somebody
   has to scroll back to type. */
.build-thread {
  max-height: 320px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 12px;
}

.build-msg { margin-bottom: 12px; font-size: 13.5px; }
.build-msg:last-child { margin-bottom: 0; }
.build-msg-who { margin-bottom: 2px; }

/* The assistant's turn is set off by a rule rather than a fill. A tinted bubble
   would need its own ink to stay readable, and this estate has already paid for
   text on a fill twice — see `AmberContrastTest` and `ReadableTextTest`. */
.build-msg-agent {
  border-left: 3px solid var(--primary);
  padding-left: 10px;
}
.build-msg-you { padding-left: 13px; }


/* The account switcher in the topbar.
 *
 * Capped rather than sized to its content: an account called "Northwind
 * Promotions & Partners" would otherwise push the help, hub and bell icons
 * along, and those three are at a fixed position in all six consoles. A
 * control that moves the furniture is worse than one that truncates.
 *
 * Not in estate.css: this is a hub screen's control, and the estate sheet is
 * only for what exists because Bootstrap is underneath. */
.topbar-account { margin: 0; display: flex; align-items: center; }
.topbar-account .form-select { max-width: 190px; min-width: 130px; }
@media (max-width: 820px) { .topbar-account { display: none; } }


/* The rights warranty on an upload form.
 *
 * Full width so it drops onto its own line under the file picker rather than
 * competing with the buttons — a tick box squeezed between "Choose files" and
 * "Attach" reads as a filter, not a confirmation. */
.fl-rights { display: flex; gap: 8px; align-items: flex-start; flex: 1 1 100%; margin-top: 10px; font-size: 13px; color: var(--slate); }
.fl-rights input { margin-top: 2px; width: 16px; height: 16px; flex: 0 0 auto; }
.fl-rights-terms { flex: 1 1 100%; margin-top: 8px; padding: 12px 14px; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); font-size: 13px; color: var(--slate); }


/* The client-approval panel. Printed from the page — there is no build step. */
.approvals-panel .panel-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.approvals-terms { white-space: normal; color: var(--slate); font-size: 13px; padding: 10px 0 4px; max-width: 70ch; }
.approvals-terms-row td { border-top: 0; padding-top: 0; }
@media print {
  /* What a client is shown is the approvals and nothing else — the sidebar,
     the topbar and the Print button are the console, not the evidence. */
  body * { visibility: hidden; }
  .approvals-panel, .approvals-panel * { visibility: visible; }
  .approvals-panel { position: absolute; inset: 0 auto auto 0; width: 100%; border: 0; }
  .approvals-panel .btn { display: none; }
  .approvals-panel details { display: block; }
  .approvals-panel details > summary { display: none; }
}

/* The build panel's one creating action.
   Out of the header row and given its own band, because a `btn-sm` sitting third
   beside two buttons that only open a modal read as the least important control
   on the panel rather than as its point. The band is what makes it obvious
   without making it shout: no colour beyond the button's own, and the sentence
   beside it says what pressing it does. */
.build-action {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin: 0 0 14px; padding: 14px 16px;
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius);
}
.build-action .btn { flex: 0 0 auto; }
.build-action p { flex: 1 1 260px; min-width: 0; }

/* Barred because of who is asking, rather than because of the job.
   Amber as a RULE and a tint, never as the text — `--warning` is 1.46:1 on its
   own background, so the words stay `--warning-ink`. The band is still a band:
   the control is there, it is simply not theirs to press. */
.build-action.is-barred {
  border-color: var(--warning);
  border-left: 3px solid var(--warning);
  background: var(--warning-bg, var(--bg));
}
.build-action.is-barred p { color: var(--warning-ink); }

/* ─────────────────────────────────────────────────────────────────────────────
   ASK ELLA — the first line of support, on every screen
   ───────────────────────────────────────────────────────────────────────────── */

/* Bottom-right, out of the way of everything and in the place people already
   look for one of these. Above the content but below any modal: a question is
   not more important than the confirmation somebody is reading. */
.askella-launch {
    position: fixed; right: 22px; bottom: 22px; z-index: 900;
    display: inline-flex; align-items: center; gap: 8px;
    height: 44px; padding: 0 18px; border-radius: 999px;
    border: none; cursor: pointer;
    background: var(--brand, #3b5bdb); color: #fff;
    box-shadow: 0 4px 14px rgba(0, 0, 0, .18);
    font-size: 15px; font-weight: 600; line-height: 1;
}

/* Room under the page for the launcher to sit in. It floats over whatever is at
   the bottom of the viewport, and scrolled to the end that is the LAST thing on
   the page — on a job's Channel tab, the message box and its send button, which
   it covered at 1440x900. The padding lets the end of the page scroll clear of
   it: 22px from the edge, 44px tall, and room to breathe. On `:has()` so a page
   without the launcher keeps its own spacing. */
body:has(.askella-launch) .content-wrapper { padding-bottom: 88px; }

/* The problem banner on a job: the message, and "Ask ella about this" beside it.
   `.alert.job-problem`, not `.job-problem`: estate.css loads after this file and
   sets `.alert { display: block }`, which beats a single class. */
.alert.job-problem { display: flex; align-items: center; justify-content: space-between; gap: 14px; }
.alert.job-problem .btn { flex: 0 0 auto; }
.askella-launch:hover { filter: brightness(1.08); }

.askella {
    position: fixed; right: 22px; bottom: 86px; z-index: 900;
    width: 380px; max-width: calc(100vw - 32px);
    max-height: min(560px, calc(100vh - 140px));
    display: flex; flex-direction: column;
    background: #fff; border: 1px solid var(--line, #e3e6ec); border-radius: 12px;
    box-shadow: 0 10px 30px rgba(0, 0, 0, .16);
    overflow: hidden;
}

.askella-head {
    padding: 12px 14px; border-bottom: 1px solid var(--line, #e3e6ec);
    display: flex; flex-direction: column; gap: 2px; position: relative;
}
.askella-close {
    position: absolute; top: 8px; right: 10px;
    border: none; background: none; font-size: 22px; line-height: 1;
    cursor: pointer; color: var(--muted, #737a88);
}

/* The log scrolls; the form does not. A composer that walks off the bottom of
   the panel as the conversation grows is the commonest fault in this shape. */
.askella-log { padding: 14px; overflow-y: auto; flex: 1; }
.askella-msg { margin-bottom: 12px; font-size: 14px; line-height: 1.5; }
.askella-msg--user   { background: #f2f4f8; border-radius: 8px; padding: 9px 11px; }
.askella-msg--ella p { margin: 0 0 8px; }
.askella-msg--ella ol { margin: 0 0 8px; padding-left: 20px; }
.askella-msg--ella li { margin-bottom: 4px; }
.askella-msg--error  { color: var(--danger-ink); }

.askella-form { display: flex; gap: 8px; padding: 12px 14px; border-top: 1px solid var(--line, #e3e6ec); }
.askella-form input {
    flex: 1; padding: 8px 10px; border: 1px solid var(--line, #e3e6ec); border-radius: 6px; font-size: 14px;
}

/* The sentence above the button is the point of this block, so it is not
   small-print grey: the customer is told what is about to be shared BEFORE it
   is, never after. */
.askella-escalate {
    padding: 12px 14px; border-top: 1px solid var(--line, #e3e6ec); background: #fffdf5;
}
.askella-escalate p { margin: 0 0 10px; }

@media (max-width: 520px) {
    .askella { right: 8px; left: 8px; width: auto; bottom: 78px; }
    .askella-launch { right: 14px; bottom: 14px; }
}

/* Users & access → Service access: the level picker beside Grant. One row, and
   the picker sizes to its words rather than to the cell. */
.grant-form { display: inline-flex; gap: 6px; align-items: center; margin: 0; }
.grant-form .grant-level, .grant-form .ts-wrapper { width: auto; min-width: 7.5rem; }

/* A submit that is on its way (busy-submit.js): the spinner sits inside the
   button, which is disabled until the next page — or the failure — arrives. */
.btn.is-busy { cursor: progress; }
.btn .busy-spinner { width: .85em; height: .85em; border-width: .15em; margin-right: .4em; vertical-align: -.1em; }

/* My profile: the links to the security screens it does not copy. */
.profile-links { list-style: none; margin: 0; padding: 0; }
.profile-links li { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; padding: 8px 0; border-top: 1px solid var(--border); }
.profile-links li:first-child { border-top: 0; }
.profile-links a i { width: 1.25em; opacity: .7; }

/* Page templates: the preview — one frame, the landing page or the thank-you
   page, at desktop width or a phone's. */
.page-preview-bar { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; }
.page-preview-widths { display: flex; gap: 6px; padding-bottom: 6px; }
.page-preview-widths .btn.active { background: var(--accent); border-color: var(--accent); color: #fff; }
.page-preview-stage { background: var(--bg, #f4f5f7); border-radius: 8px; padding: 12px; display: flex; justify-content: center; overflow: hidden; }
.page-preview-frame { width: 100%; min-height: 480px; border: 1px solid var(--border); border-radius: 6px; background: #fff; transition: width .2s ease; }
.page-preview-frame.is-phone { width: 390px; max-width: 100%; }

/* The New offer window's choosers: who is arriving, and which template to
   start from — cards, so each can be previewed where it is chosen. */
.pick-group { border: 0; margin: 0; padding: 0; min-width: 0; }
.pick-legend { font-size: 14px; font-weight: 600; margin-bottom: 8px; float: none; }
.pick-traffic { display: flex; flex-wrap: wrap; gap: 6px; }
.pick-traffic .btn-check:checked + .btn { background: var(--accent); border-color: var(--accent); color: #fff; }
.tpl-pick { display: grid; gap: 8px; }
.tpl-card { display: flex; align-items: center; gap: 12px; padding: 12px 14px; margin: 0; border: 1px solid var(--border); border-radius: 10px; background: #fff; cursor: pointer; }
.tpl-card:hover { border-color: var(--border-strong); }
.tpl-card:has(input:checked) { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.tpl-card .form-check-input { margin: 0; flex: none; }
.tpl-card-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.tpl-card-side { display: flex; align-items: center; gap: 8px; flex: none; }
@media (max-width: 575.98px) { .tpl-card { flex-wrap: wrap; } .tpl-card-side { width: 100%; justify-content: flex-end; } }
