/* =====================================================================
   Keyanna Release Management - design system
   Extends the NivFlow Monitor token set (localhost:8088/app.css) with the
   components a tracker needs: data grids, filter rows, chips, timelines,
   progress meters and avatars.
   Themes: keyanna (default, navy/orange), nivtrix (purple/pink),
           keyanna-light (the light variant proposed in the analysis).
   ===================================================================== */

/* ---- semantic colours, shared by every theme ---- */
:root {
  --ok:#3fb950; --err:#f85149; --warn:#f0b429; --net:#4c8dff; --viol:#a371f7;
  --p1:#f85149; --p2:#f0883e; --p3:#f0b429; --p4:#9aa3b4;
}

/* ---- Keyanna (default): navy surfaces + orange/magenta brand ---- */
:root, :root[data-theme="keyanna"] {
  color-scheme:dark;
  --bg:#0e1119; --panel:#141824; --panel2:#1c2130; --panelDeep:#0f131d;
  --line:#2b3242; --lineSoft:#222839; --ink:#eef1f6; --inkDim:#c8cedb;
  --muted:#9aa3b4; --accent:#f95d1a; --brand2:#cc3366; --bgGlow:#2a1a12;
  --rowHover:rgba(249,93,26,.07); --shadow:0 18px 44px -22px rgba(0,0,0,.85);
}

/* ---- Nivtrix: purple surfaces + pink/violet brand ---- */
:root[data-theme="nivtrix"] {
  color-scheme:dark;
  --bg:#0e0b18; --panel:#160f22; --panel2:#1e162e; --panelDeep:#140c22;
  --line:#2c2440; --lineSoft:#241d36; --ink:#f4f0fa; --inkDim:#d8d0e4;
  --muted:#b0a8c0; --accent:#ff0088; --brand2:#9b51e0; --bgGlow:#241536;
  --rowHover:rgba(255,0,136,.07); --shadow:0 18px 44px -22px rgba(0,0,0,.85);
}

/* ---- Keyanna light: echoes the grey of the system it replaces ---- */
:root[data-theme="keyanna-light"] {
  color-scheme:light;
  --bg:#eef0f3; --panel:#ffffff; --panel2:#f4f6f9; --panelDeep:#e7eaef;
  --line:#d5dbe4; --lineSoft:#e4e8ee; --ink:#171b23; --inkDim:#333a47;
  --muted:#5c6673; --accent:#d2490b; --brand2:#ac2853; --bgGlow:#f6ece6;
  --rowHover:rgba(210,73,11,.06); --shadow:0 14px 34px -20px rgba(23,27,35,.3);
  --ok:#1a7f37; --err:#cf222e; --warn:#8f6100; --net:#0969da; --viol:#8250df;
  --p1:#cf222e; --p2:#bc4c00; --p3:#8f6100; --p4:#5c6673;
}

* { box-sizing:border-box; }

html, body {
  margin:0; height:100%;
  font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  background:var(--bg); color:var(--ink);
  font-size:14px;
}
body { display:flex; flex-direction:column; }
a { color:var(--accent); text-decoration:none; }
a:hover { text-decoration:underline; }
:focus-visible { outline:2px solid var(--accent); outline-offset:2px; border-radius:4px; }

/* =============================== login ============================= */

/* A class, not an id: this rule makes an element a full-screen overlay, and
   an id selector will happily apply that to any element that shares the name. */
.loginGate {
  position:fixed; inset:0; z-index:1000;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:16px;
  background:radial-gradient(1200px 600px at 50% -10%, var(--bgGlow), var(--bg));
}
.loginCard {
  width:360px; max-width:90vw; background:var(--panel); border:1px solid var(--line);
  border-radius:16px; padding:28px 26px; display:flex; flex-direction:column;
  box-shadow:0 20px 60px rgba(0,0,0,.45);
}
.loginLogo { height:36px; width:auto; align-self:center; margin-bottom:14px; }
.loginTitle { text-align:center; font-size:21px; font-weight:700; letter-spacing:.3px; }
.loginTitle b {
  background:linear-gradient(90deg,var(--accent),var(--brand2));
  -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent;
}
.loginSub { text-align:center; font-size:12px; color:var(--muted); margin:4px 0 18px; }
.llabel { font-size:10px; text-transform:uppercase; letter-spacing:.6px; color:var(--muted); margin-bottom:5px; }
.loginCard input[type=text], .loginCard input[type=password] {
  background:var(--panel2); border:1px solid var(--line); color:var(--ink);
  border-radius:8px; padding:10px 12px; font-size:14px; outline:none; margin-bottom:14px; width:100%;
}
.loginCard input:focus { border-color:var(--accent); }
.loginBtn {
  margin-top:4px; border:none; border-radius:9px; padding:11px; font-size:14px;
  font-weight:650; color:#fff; cursor:pointer;
  background:linear-gradient(90deg,var(--accent),var(--brand2));
}
.loginBtn:hover { filter:brightness(1.08); }
.loginErr { min-height:16px; color:var(--err); font-size:12px; margin-bottom:8px; }
.loginOk { min-height:16px; color:var(--ok); font-size:12px; margin-bottom:8px; }
.loginFoot { font-size:11px; color:var(--muted); }
.loginHint {
  font-size:11px; color:var(--muted); text-align:center; margin-top:14px;
  padding-top:12px; border-top:1px solid var(--line); line-height:1.7;
}
.loginHint code {
  background:var(--panel2); border:1px solid var(--line); border-radius:4px;
  padding:1px 5px; color:var(--inkDim); font-size:11px;
}
.rememberRow { display:flex; align-items:center; gap:8px; font-size:12.5px; color:var(--muted); margin-bottom:14px; }

/* =============================== shell ============================= */

#appShell { flex:1; display:flex; min-height:0; }

#sidebar {
  width:216px; flex-shrink:0;
  background:linear-gradient(180deg,var(--panel),var(--panelDeep));
  border-right:1px solid var(--line);
  display:flex; flex-direction:column; transition:width .15s ease; overflow:hidden;
}
#appShell.collapsed #sidebar { width:58px; }
.sbTop {
  display:flex; align-items:flex-start; justify-content:space-between; gap:8px;
  padding:16px 14px 14px; border-bottom:1px solid var(--line); min-height:74px;
}
.sbBrand { display:flex; flex-direction:column; gap:6px; overflow:hidden; }
.sbLogoFull { height:26px; width:auto; display:block; }
.sbLogoMini { height:28px; width:28px; display:none; }
.sbProd {
  font-size:15px; font-weight:800; letter-spacing:.4px; line-height:1; white-space:nowrap;
  background:linear-gradient(90deg,var(--accent),var(--brand2));
  -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent;
}
#appShell.collapsed .sbLogoFull, #appShell.collapsed .sbProd { display:none; }
#appShell.collapsed .sbLogoMini { display:block; }
#appShell.collapsed .sbTop { flex-direction:column; align-items:center; }
.sbToggle { background:none; border:none; color:var(--muted); cursor:pointer; padding:4px; border-radius:7px; display:flex; }
.sbToggle svg { width:20px; height:20px; }
.sbToggle:hover { color:var(--ink); background:var(--panel2); }
.sbNav { display:flex; flex-direction:column; padding:10px 8px; gap:3px; flex:1; overflow-y:auto; }
.sbBottom { padding:10px 8px; border-top:1px solid var(--line); display:flex; flex-direction:column; gap:3px; }
.sbSection {
  font-size:9.5px; text-transform:uppercase; letter-spacing:.11em; color:var(--muted);
  padding:12px 12px 5px; font-weight:600; white-space:nowrap;
  /* It is a button now. Everything below resets what that brings with it. */
  display:flex; align-items:center; justify-content:space-between; gap:8px;
  width:100%; background:none; border:none; cursor:pointer; text-align:left;
  font-family:inherit;
}
.sbSection:hover { color:var(--ink); }
.sbSecTog {
  font-size:13px; line-height:1; width:14px; text-align:center; opacity:.65;
  font-weight:400;
}
.sbSection:hover .sbSecTog { opacity:1; }
.sbGroup { display:flex; flex-direction:column; gap:3px; }
.sbGroup.collapsed { display:none; }

/* With the whole sidebar collapsed to icons there are no headings to press,
   so a group closed in the wide view must not take its icons with it -
   otherwise the way to get them back has just been hidden too. */
#appShell.collapsed .sbSection { display:none; }
#appShell.collapsed .sbGroup.collapsed { display:flex; }
.sbItem {
  display:flex; align-items:center; gap:11px; width:100%; background:none; border:none;
  color:var(--ink); padding:9px 12px; border-radius:9px; cursor:pointer; font-size:13px;
  text-align:left; white-space:nowrap; text-decoration:none;
}
.sbItem:hover { background:var(--panel2); text-decoration:none; }
.sbItem.active {
  background:linear-gradient(90deg,
    color-mix(in srgb, var(--accent) 20%, transparent),
    color-mix(in srgb, var(--brand2) 6%, transparent));
  font-weight:600;
}
.sbIco { width:18px; height:18px; flex-shrink:0; stroke-width:1.8; }
.sbItem.active .sbIco { color:var(--accent); }
#appShell.collapsed .sbLbl { display:none; }
#appShell.collapsed .sbItem { justify-content:center; padding:9px 0; }

#mainCol { flex:1; display:flex; flex-direction:column; min-height:0; }

/* =============================== topbar ============================ */

header.topbar {
  padding:0 20px; min-height:56px; flex-shrink:0;
  background:linear-gradient(90deg,var(--panel),var(--panelDeep));
  border-bottom:1px solid var(--line);
  display:flex; gap:16px; align-items:center;
}
.crumbs { display:flex; align-items:center; gap:8px; font-size:13px; color:var(--muted); min-width:0; }
.crumbs a { color:var(--inkDim); }
.crumbs .sep { color:var(--line); }
.crumbs .cur { color:var(--ink); font-weight:600; }
.topSpacer { flex:1; }
.topSearch {
  background:var(--panel2); border:1px solid var(--line); color:var(--ink);
  border-radius:8px; padding:7px 11px; font-size:13px; min-width:220px; outline:none;
}
.topSearch:focus { border-color:var(--accent); }
.userChip { display:flex; align-items:center; gap:9px; font-size:13px; color:var(--inkDim); }

/* =============================== content =========================== */

main.content { flex:1; min-height:0; overflow:auto; padding:20px 22px 44px; }
.pageHead {
  display:flex; align-items:center; gap:14px; flex-wrap:wrap; margin-bottom:18px;
}
.pageHead h1 { margin:0; font-size:21px; font-weight:700; letter-spacing:-.2px; }
.pageHead .spacer { flex:1; }
.pageSub { color:var(--muted); font-size:13px; margin:-10px 0 18px; }

.card {
  background:var(--panel); border:1px solid var(--line); border-radius:10px;
  overflow:hidden; margin-bottom:18px;
}
.cardHead {
  padding:12px 16px; border-bottom:1px solid var(--line); background:var(--panel2);
  display:flex; align-items:center; gap:12px;
}
.cardHead h2 { margin:0; font-size:14px; font-weight:650; }
.cardHead .spacer { flex:1; }
.cardBody { padding:16px; }
.grid2 { display:grid; grid-template-columns:repeat(auto-fit,minmax(300px,1fr)); gap:18px; }
.grid3 { display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr)); gap:14px; }
/* Full-width panels, one under the next. */
.stack { display:grid; grid-template-columns:1fr; gap:18px; }

/* =============================== buttons =========================== */

.btn {
  border:1px solid var(--line); background:var(--panel2); color:var(--ink);
  border-radius:8px; padding:8px 14px; font-size:13px; cursor:pointer;
  display:inline-flex; align-items:center; gap:7px; text-decoration:none; font-weight:500;
}
.btn:hover { border-color:var(--accent); text-decoration:none; }
.btn svg { width:15px; height:15px; }
.btn.primary {
  background:linear-gradient(90deg,var(--accent),var(--brand2));
  border-color:transparent; color:#fff; font-weight:600;
}
.btn.primary:hover { filter:brightness(1.09); }
.btn.danger:hover { border-color:var(--err); color:var(--err); }
.btn.sm { padding:5px 10px; font-size:12px; border-radius:7px; }
.btn:disabled { opacity:.45; cursor:not-allowed; }
.btnRow { display:flex; gap:9px; flex-wrap:wrap; align-items:center; }

/* =============================== forms ============================= */

.field { display:flex; flex-direction:column; gap:5px; margin-bottom:14px; }
.field > label, .flabel {
  font-size:10px; text-transform:uppercase; letter-spacing:.6px; color:var(--muted); font-weight:600;
}
.field .req { color:var(--err); margin-left:2px; }
input[type=text], input[type=password], input[type=date], input[type=number],
input[type=email], select, textarea {
  background:var(--panel2); border:1px solid var(--line); color:var(--ink);
  border-radius:8px; padding:8px 11px; font-size:13.5px; outline:none;
  font-family:inherit; width:100%;
}
input:focus, select:focus, textarea:focus { border-color:var(--accent); }
textarea { min-height:130px; resize:vertical; line-height:1.6; }
select { cursor:pointer; }
/* The open dropdown is drawn by the operating system, not by this stylesheet,
   so styling the select itself does nothing for the list of options - they
   came out dark text on a dark popup and were unreadable. color-scheme is how
   a page tells the platform which palette to draw its native controls in, and
   it has to be declared per theme rather than once. */
option { background:var(--panel2); color:var(--ink); }
input[type=checkbox] { accent-color:var(--accent); width:15px; height:15px; }
.formGrid { display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:0 18px; }
/* Three across, said rather than fitted. auto-fit counts what will go in the
   width, so a row of three becomes two-and-one on a narrow desktop and reads as
   a mistake; where the three belong together, name the number. */
.formGrid.trio { grid-template-columns:repeat(3,minmax(0,1fr)); }
@media (max-width:900px) { .formGrid.trio { grid-template-columns:1fr; } }
.checkLine { display:flex; align-items:center; gap:8px; font-size:13px; margin-bottom:12px; }
.hint { font-size:11.5px; color:var(--muted); }
.fieldError { color:var(--err); font-size:11.5px; }

/* =============================== tables ============================ */

.tableWrap { overflow-x:auto; }
table.data { border-collapse:collapse; width:100%; font-size:13px; }
table.data thead th {
  text-align:left; padding:9px 12px; color:var(--muted); font-weight:600;
  border-bottom:1px solid var(--line); background:var(--panel2);
  font-size:10.5px; text-transform:uppercase; letter-spacing:.07em; white-space:nowrap;
  position:sticky; top:0; z-index:2;
}
table.data thead th a { color:var(--muted); }
table.data thead th a:hover { color:var(--ink); }
table.data thead th.sorted a { color:var(--accent); }
table.data tbody td { padding:9px 12px; border-bottom:1px solid var(--lineSoft); vertical-align:middle; }
table.data tbody tr:hover td { background:var(--rowHover); }
table.data tbody tr.closed td .subj { text-decoration:line-through; opacity:.65; }
table.data td.num { text-align:right; font-variant-numeric:tabular-nums; white-space:nowrap; }
table.data td.id { font-variant-numeric:tabular-nums; white-space:nowrap; }
table.data tr.groupRow td {
  background:var(--panel2); font-weight:650; font-size:12.5px; padding:8px 12px;
  border-bottom:1px solid var(--line); border-top:1px solid var(--line);
}
table.data tr.totalRow td {
  background:var(--panel2); font-weight:650; border-top:2px solid var(--line);
  position:sticky; bottom:0;
}
.subj { color:var(--ink); }
.subj:hover { color:var(--accent); }
.tblEmpty { padding:44px; text-align:center; color:var(--muted); font-size:13.5px; }

/* ========================== test run list ========================== */

/* A card each rather than a table row each. A run carries a name, two dates,
   five numbers and a progress bar; as a row that is nine columns of small
   type, and the bar - the one thing worth seeing from across the desk - ends
   up the narrowest thing on it. */
.runList { display:flex; flex-direction:column; gap:12px; }
.runCard .cardBody { display:flex; flex-direction:column; gap:10px; }
.runCard:hover { border-color:color-mix(in srgb,var(--accent) 45%,var(--line)); }

.runTop { display:flex; align-items:center; gap:9px; flex-wrap:wrap; }
.runName { display:flex; align-items:baseline; gap:9px; font-size:14.5px; font-weight:600; }
.runRef {
  font-size:11.5px; font-weight:700; letter-spacing:.04em; color:var(--muted);
  font-variant-numeric:tabular-nums;
}

.runMeta { font-size:12px; color:var(--muted); display:flex; flex-wrap:wrap; gap:6px; }
.runMeta .sep { opacity:.5; }

/* The bar takes the width it can get; the percentage stays put beside it. */
.runMeter .meter { flex:1 1 auto; height:10px; }

.runCounts {
  display:flex; flex-wrap:wrap; gap:4px 18px; font-size:12px; color:var(--muted);
}
.runCount b { font-variant-numeric:tabular-nums; font-size:13.5px; color:var(--ink); }
.runCount.ok b  { color:var(--ok); }
.runCount.err b { color:var(--err); }

/* ============================== portal ============================= */

/* The customer's side. It shares the tokens, the cards and the chips with the
   staff application - one stylesheet, so a colour fixed in one place is fixed
   in both - but none of its navigation. There is no engineering link here to
   render by accident because there is no engineering navigation here at all. */
.portalBody { background: var(--bg); min-height: 100vh; display: flex; flex-direction: column; }

.portalBar {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 12px 22px; background: var(--panel); border-bottom: 1px solid var(--line);
}
.portalBrand { display: flex; align-items: center; gap: 9px; font-weight: 700; font-size: 15px; }
.portalBrand img { width: 24px; height: 24px; }
.portalCustomer {
  font-size: 12px; color: var(--muted); padding-left: 14px;
  border-left: 1px solid var(--line);
}
.portalMain { flex: 1 1 auto; padding: 24px 22px 48px; max-width: 1000px; width: 100%; margin: 0 auto; }
.portalFoot {
  padding: 16px 22px; border-top: 1px solid var(--line); background: var(--panel);
  font-size: 12px; color: var(--muted); display: flex; gap: 8px; flex-wrap: wrap;
}
.portalFoot .sep { opacity: .5; }

/* ------------------------- one run, in detail ---------------------- */

/* The header block. It was five stacked tiles and four labelled fields, nine
   hundred pixels of caption above the thing the page is for. Everything it
   said is still here; it says it in three lines. */
.runHead .cardBody { display:flex; flex-direction:column; gap:9px; }
.runHead .runTop { align-items:baseline; }
.runTitle {
  font-size:19px; font-weight:700; margin:0; display:flex; gap:9px;
  align-items:baseline; flex-wrap:wrap;
}

/* Counts and filter as one control: a finished run is read for what failed,
   and this is both the summary and the way to reach it. */
.runTabs { display:flex; flex-wrap:wrap; gap:6px; }
.runTabs a {
  font-size:11.5px; font-weight:600; padding:4px 10px; border-radius:20px;
  border:1px solid var(--line); color:var(--muted); white-space:nowrap;
  font-variant-numeric:tabular-nums;
}
.runTabs a:hover { border-color:var(--accent); color:var(--ink); }
.runTabs a.on { background:var(--accent); border-color:var(--accent); color:#fff; }
.runTabs a.r-fail:not(.on) { color:var(--err); border-color:color-mix(in srgb,var(--err) 35%,var(--line)); }

/* What actually happened, under the case it happened to. Clamped, because a
   long note should not push the next row off the screen - the run page opens
   it in full. */
.runActual {
  margin-top:4px; font-size:12px; color:var(--muted); white-space:pre-wrap;
  max-height:3.6em; overflow:hidden; border-left:2px solid var(--line);
  padding-left:8px;
}

/* Folded away by default; opened on a run with nothing in it yet, because
   then it is the only thing to do. */
.pickCard > summary {
  cursor:pointer; list-style:none; align-items:center;
}
.pickCard > summary::-webkit-details-marker { display:none; }
.pickCard > summary::after {
  content:'+'; font-size:15px; font-weight:700; color:var(--muted); margin-left:10px;
}
.pickCard[open] > summary::after { content:'2'; }
.pickTable { max-height:420px; overflow-y:auto; }

.nowrap { white-space:nowrap; }

/* ---------------------- the description editor -------------------- */

/* The textarea is still the field; the editor is drawn over it. Hidden with
   a class rather than removed, so it keeps posting and keeps working when
   the script never runs. */
.editorSource { display:none; }

.editorHost { border:1px solid var(--line); border-radius:8px; overflow:hidden; }
.editorHost:focus-within { border-color:var(--accent); }

.editorBar {
  display:flex; flex-wrap:wrap; gap:2px; padding:5px 6px;
  background:var(--panel2); border-bottom:1px solid var(--line);
}
.editorBtn {
  min-width:28px; height:26px; padding:0 7px; border-radius:5px;
  border:1px solid transparent; background:transparent; color:var(--muted);
  font-size:12.5px; line-height:1; cursor:pointer;
}
.editorBtn:hover { background:var(--panel); border-color:var(--line); color:var(--ink); }

.editorCanvas {
  min-height:190px; max-height:520px; overflow-y:auto; padding:12px 14px;
  background:var(--panel); color:var(--ink); font-size:13.5px; line-height:1.65;
}
.editorCanvas:focus { outline:none; }
.editorCanvas > *:first-child { margin-top:0; }
.editorCanvas > *:last-child { margin-bottom:0; }
.editorCanvas h3 { font-size:15px; margin:14px 0 6px; }
.editorCanvas p { margin:0 0 10px; }
.editorCanvas ul, .editorCanvas ol { margin:0 0 10px; padding-left:22px; }
.editorCanvas blockquote {
  margin:0 0 10px; padding-left:12px; border-left:3px solid var(--line); color:var(--muted);
}
.editorCanvas pre {
  margin:0 0 10px; padding:10px 12px; border-radius:6px;
  background:var(--panel2); overflow-x:auto; font-size:12.5px;
}
/* A screenshot is usually wider than the box. Never wider than the box. */
.editorCanvas img { max-width:100%; height:auto; border-radius:6px; }

/* The product filter in the My Page header. Sits with the buttons rather than
   above them, because it belongs to the page the buttons act on. */
.myProductPick { display:flex; align-items:center; gap:8px; margin:0; }
.myProductPick label {
  font-size:10.5px; text-transform:uppercase; letter-spacing:.08em;
  color:var(--muted); font-weight:600;
}
.myProductPick select { min-width:150px; }
/* With script the select submits itself, so the button is the no-script way
   through and nothing more. */
.js .myProductPick button { display:none; }

/* =============================== chips ============================= */

.chip {
  display:inline-block; font-size:10.5px; font-weight:700; padding:3px 9px;
  border-radius:20px; white-space:nowrap; letter-spacing:.03em;
}
.chip.sq { border-radius:5px; }
/*
  A readable chip for any status name nobody has written a rule for.

  Wrapped in :where() so it contributes no specificity at all: every rule below
  is a plain class and beats it on source order alone. Written as
  .chip[class*="st-"] instead - the obvious way - it would carry an attribute
  selector and outrank every one of them, and the whole application would draw
  grey chips.

  It exists because an unstyled chip here is not a wrong colour, it is no
  colour: transparent text on a transparent ground, indistinguishable from the
  label having gone missing.
*/
:where(.chip[class*="st-"]) {
  background:color-mix(in srgb,var(--muted) 15%,transparent); color:var(--inkDim);
}

/*
  One class per status name, and the names have to match.

  The class is built in IssuePresenter as "st-" + the status name lowercased
  with spaces stripped, so IN PROGRESS asks for .st-inprogress and DONE for
  .st-done. Neither existed: the rules here were written against names the
  database does not have (.st-progress, .st-devdone), so of the seven statuses
  in use only OPEN and CLOSED were ever coloured and the other five drew as a
  bare chip with no background at all.

  That is why it looked as though only OPEN "showed nicely" - the others were
  not styled wrongly, they were not styled.

  The old names are kept below rather than renamed. An installation that has
  added its own statuses may be using them, and a rule nobody matches costs a
  line of CSS where a rule somebody was matching costs them their colour.
*/
.st-open       { background:color-mix(in srgb,var(--net) 16%,transparent);  color:var(--net); }
.st-inprogress { background:color-mix(in srgb,var(--warn) 18%,transparent); color:var(--warn); }
.st-done       { background:color-mix(in srgb,var(--ok) 16%,transparent);   color:var(--ok); }
.st-closed     { background:color-mix(in srgb,var(--ok) 16%,transparent);   color:var(--ok); }
.st-onhold     { background:color-mix(in srgb,var(--muted) 17%,transparent);color:var(--muted); }
.st-rejected   { background:color-mix(in srgb,var(--err) 16%,transparent);  color:var(--err); }
.st-re-open    { background:color-mix(in srgb,var(--viol) 17%,transparent); color:var(--viol); }
.st-reopen     { background:color-mix(in srgb,var(--viol) 17%,transparent); color:var(--viol); }

/*
  Ticket statuses, which name a colour rather than a state.

  TicketStatus.getChipClass() returns "st-" + the colour column, so Open asks
  for .st-net and Resolved for .st-ok. None of these existed either, so the
  whole ticket module drew unstyled chips - the same fault as the KeyPoint ones
  and from the opposite direction: those name a status and these name a colour,
  and neither list had been written.
*/
.st-net    { background:color-mix(in srgb,var(--net) 16%,transparent);   color:var(--net); }
.st-warn   { background:color-mix(in srgb,var(--warn) 18%,transparent);  color:var(--warn); }
.st-ok     { background:color-mix(in srgb,var(--ok) 16%,transparent);    color:var(--ok); }
.st-muted  { background:color-mix(in srgb,var(--muted) 17%,transparent); color:var(--muted); }
.st-err    { background:color-mix(in srgb,var(--err) 16%,transparent);   color:var(--err); }
.st-viol   { background:color-mix(in srgb,var(--viol) 17%,transparent);  color:var(--viol); }

/* Names from other installations, and from the system this replaced. */
.st-progress { background:color-mix(in srgb,var(--p2) 17%,transparent);   color:var(--p2); }
.st-devdone  { background:color-mix(in srgb,var(--viol) 17%,transparent); color:var(--viol); }
.st-qa       { background:color-mix(in srgb,var(--warn) 18%,transparent); color:var(--warn); }
.st-pending  { background:color-mix(in srgb,var(--muted) 17%,transparent);color:var(--muted); }
.st-qapassed { background:color-mix(in srgb,var(--ok) 16%,transparent);   color:var(--ok); }
.st-new      { background:color-mix(in srgb,var(--net) 16%,transparent);  color:var(--net); }


/* A failed test. The status chips above are KeyPoint statuses and none of them
   means "this is broken" - CLOSED is green, and a red one was missing. */
.st-failed   { background:color-mix(in srgb,var(--err) 16%,transparent);  color:var(--err); }
.pr-P1 { background:color-mix(in srgb,var(--p1) 16%,transparent); color:var(--p1); }
.pr-P2 { background:color-mix(in srgb,var(--p2) 16%,transparent); color:var(--p2); }
.pr-P3 { background:color-mix(in srgb,var(--p3) 17%,transparent); color:var(--p3); }
.pr-P4 { background:color-mix(in srgb,var(--p4) 17%,transparent); color:var(--p4); }
.trk {
  display:inline-block; font-size:11px; font-weight:600; padding:2px 8px;
  border-radius:5px; border:1px solid var(--line); background:var(--panel2); color:var(--inkDim);
}
.badge-count {
  display:inline-block; min-width:20px; text-align:center; font-size:11px; font-weight:700;
  padding:1px 7px; border-radius:20px; background:var(--panel2); border:1px solid var(--line); color:var(--muted);
}

/* =============================== avatars =========================== */

.av {
  width:26px; height:26px; border-radius:50%; flex-shrink:0;
  display:inline-flex; align-items:center; justify-content:center;
  font-size:10.5px; font-weight:700; color:#fff; letter-spacing:.02em;
}
.av.sm { width:20px; height:20px; font-size:9px; }
.av.lg { width:34px; height:34px; font-size:13px; }
.av-0 { background:linear-gradient(135deg,#f95d1a,#cc3366); }
.av-1 { background:linear-gradient(135deg,#4c8dff,#8957e5); }
.av-2 { background:linear-gradient(135deg,#3fb950,#1f6feb); }
.av-3 { background:linear-gradient(135deg,#a371f7,#cc3366); }
.av-4 { background:linear-gradient(135deg,#f0883e,#f85149); }
.av-5 { background:linear-gradient(135deg,#f0b429,#f95d1a); }
.av-6 { background:linear-gradient(135deg,#1f6feb,#3fb950); }
.av-7 { background:linear-gradient(135deg,#cc3366,#8957e5); }
.who { display:inline-flex; align-items:center; gap:7px; white-space:nowrap; }

/* =============================== filters =========================== */

.filterBar {
  background:var(--panel); border:1px solid var(--line); border-radius:10px;
  padding:14px 16px; margin-bottom:16px;
}
.filterRow { display:flex; gap:9px; align-items:center; margin-bottom:9px; flex-wrap:wrap; }
.filterRow select, .filterRow input { width:auto; }
.filterRow .fname { min-width:170px; }
.filterRow .fop { min-width:140px; }
.filterRow .fval { min-width:190px; }
.filterRow .rm {
  background:none; border:none; color:var(--muted); cursor:pointer; font-size:16px;
  line-height:1; padding:4px 7px; border-radius:6px;
}
.filterRow .rm:hover { color:var(--err); background:var(--panel2); }
.filterTools {
  display:flex; gap:9px; align-items:center; flex-wrap:wrap;
  padding-top:12px; border-top:1px solid var(--lineSoft); margin-top:4px;
}
.optToggle {
  font-size:12.5px; color:var(--muted); cursor:pointer; user-select:none;
  display:inline-flex; align-items:center; gap:6px;
}
.optToggle:hover { color:var(--ink); }
.optPanel {
  display:none; gap:20px; padding:14px 0 4px; border-top:1px solid var(--lineSoft);
  margin-top:10px; flex-wrap:wrap;
}
.optPanel.open { display:flex; }
.optCol { display:flex; flex-direction:column; gap:6px; min-width:180px; }
.colPicker { display:flex; gap:10px; align-items:center; }
.colPicker select[multiple] { height:160px; min-width:190px; }
.colPickerBtns { display:flex; flex-direction:column; gap:6px; }

.queryPills { display:flex; gap:7px; flex-wrap:wrap; margin-bottom:14px; }
.qp {
  border:1px solid var(--line); background:var(--panel2); color:var(--muted);
  border-radius:20px; padding:5px 13px; font-size:12.5px; cursor:pointer; text-decoration:none;
}
.qp:hover { color:var(--ink); border-color:var(--accent); text-decoration:none; }
.qp.active { background:var(--accent); border-color:var(--accent); color:#fff; font-weight:600; }

/* =============================== progress ========================== */

.meter {
  height:9px; border-radius:5px; background:var(--panel2); border:1px solid var(--line);
  overflow:hidden; min-width:90px; display:flex;
}
.meter .done { background:var(--ok); height:100%; }
.meter .part { background:var(--net); height:100%; }
/* A failed share of the bar. The two segments sit side by side and what is
   left of the track is the part nobody has run yet - three states in one
   nine-pixel strip, which is the point of drawing it at all. */
.meter .fail { background:var(--err); height:100%; }
.meterWrap { display:flex; align-items:center; gap:9px; font-size:12px; color:var(--muted); }
.meterWrap .pct { font-variant-numeric:tabular-nums; min-width:34px; text-align:right; }

/* =============================== issue page ======================== */

.issueHead {
  display:flex; align-items:flex-start; gap:14px; margin-bottom:6px; flex-wrap:wrap;
}
.issueHead h1 { margin:0; font-size:19px; font-weight:650; line-height:1.35; flex:1; min-width:260px; }
.issueMeta { font-size:12.5px; color:var(--muted); margin-bottom:18px; }
.attrGrid {
  display:grid; grid-template-columns:repeat(auto-fit,minmax(250px,1fr));
  gap:1px; background:var(--panel); border:1px solid var(--line);
  border-radius:10px; overflow:hidden;
}
/* display:grid is an author rule and [hidden] is only a browser default, so
   the author rule wins and a hidden grid stays on screen. The fold-away panel
   of empty fields was fully visible because of it. */
.attrGrid[hidden], .keyFacts[hidden] { display:none; }
/* The separators are drawn on the cells rather than by letting a line-coloured
   background show through the gaps. A grid rarely divides evenly into its
   columns, and the leftover space at the end of the last row was showing up as
   a stray coloured band with nothing in it. */
.attr {
  background:var(--panel); padding:10px 14px; display:flex; gap:10px;
  align-items:baseline; box-shadow:0 0 0 1px var(--line);
}
.attr .k { color:var(--muted); font-size:11.5px; min-width:112px; flex-shrink:0; }
.attr .v { font-size:13px; color:var(--ink); word-break:break-word; }
.attr .v.blank { color:var(--muted); }

/* ------------------------------------------------ issue approval */
/* A band across the top of an issue that is waiting on somebody, coloured by
   what it is waiting for. Above the facts because "this needs a decision"
   outranks every attribute underneath it. */
.approvalBar {
  display:flex; align-items:center; gap:12px; flex-wrap:wrap;
  padding:10px 14px; margin:0 0 14px;
  border:1px solid var(--line); border-radius:10px; background:var(--panel);
}
.approvalBar .approvalState { display:flex; flex-direction:column; gap:4px; }
.approvalBar .fk {
  font-size:10.5px; letter-spacing:.06em; text-transform:uppercase; color:var(--muted);
}
.approvalBar .fv { font-size:14px; font-weight:600; }
.approvalBar.awaiting { background:color-mix(in srgb,var(--warn,#f0b429) 10%,var(--panel)); }
.approvalBar.agreed   { background:color-mix(in srgb,#3fb950 10%,var(--panel)); }
.approvalBar.returned { background:color-mix(in srgb,var(--err) 10%,var(--panel)); }
.approvalDecide { display:flex; gap:6px; margin:0; }
.approvalDecide input { min-width:200px; }

/* ------------------------------------------- issue key facts */
/* The handful of attributes an issue is opened to check, given their own
   weight. They used to be five rows inside a grid of fifty-five, forty-five
   of which were empty custom fields reading as a dash - a large box saying
   almost nothing, which is what made the page look broken rather than sparse. */
.keyFacts {
  display:grid; grid-template-columns:repeat(auto-fit,minmax(160px,1fr));
  gap:1px; background:var(--line); border:1px solid var(--line);
  border-radius:10px; overflow:hidden; margin:0 0 18px;
}
.fact {
  background:var(--panel); padding:10px 14px;
  display:flex; flex-direction:column; gap:5px; align-items:flex-start;
}
.fact .fk {
  font-size:10.5px; letter-spacing:.06em; text-transform:uppercase; color:var(--muted);
}
.fact .fv { font-size:14px; font-weight:600; color:var(--ink); }
.fact .fv.blank { font-weight:400; color:var(--muted); }
/* Overdue is the one fact worth colouring: it is the only one that is a problem
   rather than a value. */
.fact.factAlert { background:color-mix(in srgb,var(--err) 9%,var(--panel)); }
.fact.factAlert .fv { color:var(--err); }

/* A heading for the details grid, so it reads as a section rather than as more
   of the thing above it. */
.detailHead { margin:22px 0 8px; padding:0; border:none; background:none; }
.detailHead h2 { font-size:13px; letter-spacing:.04em; text-transform:uppercase; color:var(--muted); }

.descBox {
  background:var(--panel); border:1px solid var(--line); border-radius:10px;
  padding:16px 18px; margin:16px 0; line-height:1.65; font-size:13.5px;
}
.descBox h1, .descBox h2, .descBox h3 { font-size:15px; margin:14px 0 6px; }
.descBox p { margin:0 0 10px; }
.descBox ul, .descBox ol { margin:0 0 10px; padding-left:22px; }
/* A pasted screenshot arrives at whatever size the screen was. Without this it
   is that many pixels wide and takes the page sideways with it. Applied to the
   history entries as well, where the same pictures end up in notes. */
.descBox img, .tlBody img { max-width:100%; height:auto; border-radius:6px; display:block;
  margin:8px 0; border:1px solid var(--line); }
.descBox code {
  background:var(--panel2); border:1px solid var(--line); border-radius:4px;
  padding:1px 5px; font-family:ui-monospace,Menlo,Consolas,monospace; font-size:12px;
}
.descBox pre {
  background:var(--bg); border:1px solid var(--line); border-radius:8px; padding:12px;
  overflow:auto; font-size:12px; line-height:1.5;
}
.descBox pre code { background:none; border:none; padding:0; }
.descBox blockquote {
  margin:0 0 10px; padding-left:12px; border-left:3px solid var(--line); color:var(--muted);
}

/* history timeline */
.tl { display:flex; flex-direction:column; }
.tlItem { display:flex; gap:13px; padding-bottom:16px; position:relative; }
.tlNode { position:relative; flex-shrink:0; }
.tlItem:not(:last-child) .tlNode::after {
  content:""; position:absolute; left:50%; top:32px; bottom:-16px; width:1px;
  background:var(--line); transform:translateX(-50%);
}
.tlBody {
  flex:1; min-width:0; background:var(--panel); border:1px solid var(--line);
  border-radius:10px; padding:11px 15px;
}
.tlHead {
  display:flex; align-items:baseline; gap:9px; flex-wrap:wrap;
  font-size:12.5px; color:var(--muted); margin-bottom:4px;
}
.tlHead b { color:var(--ink); font-weight:600; font-size:13px; }
.tlHead .n { margin-left:auto; font-size:11.5px; color:var(--muted); }
.tlChanges { list-style:none; margin:6px 0 0; padding:0; display:flex; flex-direction:column; gap:3px; }
.tlChanges li { font-size:12.5px; color:var(--inkDim); }
.tlChanges .pk { color:var(--muted); }
.tlChanges .ov { color:var(--err); text-decoration:line-through; opacity:.8; }
.tlChanges .nv { color:var(--ok); }
.tlNote { margin-top:8px; font-size:13.5px; line-height:1.6; }
.tlNote p { margin:0 0 8px; }
.tlNote p:last-child { margin-bottom:0; }

/* =============================== misc ============================== */

.flash {
  padding:11px 16px; border-radius:9px; margin-bottom:16px; font-size:13.5px;
  border:1px solid transparent; display:flex; align-items:center; gap:10px;
}
.flash.ok  { background:color-mix(in srgb,var(--ok) 12%,transparent);  border-color:color-mix(in srgb,var(--ok) 35%,transparent);  color:var(--ok); }
.flash.err { background:color-mix(in srgb,var(--err) 12%,transparent); border-color:color-mix(in srgb,var(--err) 35%,transparent); color:var(--err); }
/* Neither good news nor a failure: something worth knowing before it is
   mistaken for the application being broken. */
.flash.warn { background:color-mix(in srgb,var(--warn) 12%,transparent); border-color:color-mix(in srgb,var(--warn) 35%,transparent); color:var(--warn); }

.pager { display:flex; gap:6px; align-items:center; padding:14px 2px; font-size:12.5px; color:var(--muted); flex-wrap:wrap; }
.pager a, .pager span.cur {
  padding:5px 10px; border-radius:7px; border:1px solid var(--line); background:var(--panel2);
  color:var(--inkDim); text-decoration:none; min-width:32px; text-align:center;
}
.pager a:hover { border-color:var(--accent); color:var(--accent); text-decoration:none; }
.pager span.cur { background:var(--accent); border-color:var(--accent); color:#fff; font-weight:600; }
.pager .info { margin-left:auto; }

/* The full-page empty state: an icon, a line of explanation, and room
   around it. Not to be confused with a blank single value, which uses
   .blank - the two shared a name once, and every blank field on the
   issue page came out 96px tall and centred because of it. */
div.empty { color:var(--muted); font-size:13.5px; padding:48px 20px; text-align:center; line-height:1.7; }
.empty .eico {
  width:52px; height:52px; margin:0 auto 14px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  border:1px solid var(--line); background:var(--panel2); color:var(--muted);
}
.empty .eico svg { width:24px; height:24px; }
.empty .etitle { font-size:16px; font-weight:700; color:var(--ink); margin-bottom:6px; }

.kpiRow { display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:12px; margin-bottom:20px; }
.kpi {
  background:var(--panel); border:1px solid var(--line); border-radius:10px;
  padding:14px 16px; display:flex; flex-direction:column; gap:3px;
}
.kpi .n { font-size:25px; font-weight:700; font-variant-numeric:tabular-nums; line-height:1.15; }
.kpi .l { font-size:10.5px; text-transform:uppercase; letter-spacing:.08em; color:var(--muted); font-weight:600; }
.kpi.accent { border-left:3px solid var(--accent); }

/*
  A KPI that is a link.

  Anchors take var(--accent) from the global rule, so without this the number
  and its label both turn orange and the card reads as one long link rather
  than as a figure. The hover is the only thing telling somebody these are
  pressable at all - they look identical to the ones elsewhere that are not.
*/
a.kpi { color:inherit; text-decoration:none; transition:border-color .12s ease, background .12s ease; }
a.kpi:hover { border-color:var(--accent); background:var(--panel2); }
a.kpi:hover .n { color:var(--accent); }
a.kpi:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }

footer.appFoot {
  flex-shrink:0; background:linear-gradient(90deg,var(--panel),var(--panelDeep));
  border-top:1px solid var(--line); padding:11px 20px;
  display:flex; flex-wrap:wrap; gap:6px 18px; align-items:center; justify-content:space-between;
  font-size:11.5px; color:var(--muted);
}
footer.appFoot .fbrand { display:flex; align-items:center; gap:9px; }
footer.appFoot img { height:16px; width:auto; opacity:.9; }

.sideBox {
  background:var(--panel); border:1px solid var(--line); border-radius:10px;
  padding:13px 15px; margin-bottom:14px;
}
.sideBox h3 {
  margin:0 0 9px; font-size:10.5px; text-transform:uppercase;
  letter-spacing:.08em; color:var(--muted); font-weight:600;
}
.sideBox ul { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:6px; font-size:13px; }
.withSide { display:grid; grid-template-columns:1fr 250px; gap:20px; align-items:start; }

@media (max-width:1000px) {
  .withSide { grid-template-columns:1fr; }
}
@media (max-width:760px) {
  #sidebar { position:fixed; z-index:60; height:100%; box-shadow:var(--shadow); }
  #appShell.collapsed #sidebar { width:0; border:none; }
  main.content { padding:16px 14px 40px; }
  .topSearch { min-width:0; width:130px; }
}
@media (prefers-reduced-motion:reduce) {
  * { transition:none !important; animation:none !important; }
}

/* ---- theme switch in the topbar ---- */
.themeBtn {
  background:var(--panel2); border:1px solid var(--line); color:var(--muted);
  border-radius:8px; padding:7px; cursor:pointer; display:flex; align-items:center;
}
.themeBtn:hover { color:var(--accent); border-color:var(--accent); }
.themeBtn svg { width:17px; height:17px; display:block; }

/* ---- administration ---- */
.adminTile {
  display:block; padding:18px 20px; text-decoration:none; color:inherit;
  transition:border-color .12s;
}
.adminTile:hover { border-color:var(--accent); text-decoration:none; }
.adminTile h2 { margin:0 0 6px; font-size:15px; font-weight:650; color:var(--ink); }
.adminTile p { margin:0; font-size:12.5px; color:var(--muted); line-height:1.6; }
.permGrid { display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr)); gap:10px 18px; }

/* ---- saved query pills ---- */
.qpWrap { display:inline-flex; align-items:center; }
.qp { display:inline-flex; align-items:center; gap:6px; }
.qpShared { color:var(--ok); font-size:8px; }
.qpDel {
  background:none; border:none; color:var(--muted); cursor:pointer;
  font-size:15px; line-height:1; padding:2px 6px; border-radius:6px; margin-left:-4px;
}
.qpDel:hover { color:var(--err); background:var(--panel2); }

/* ---- search hits ---- */
.hitRow { padding:12px 16px; border-bottom:1px solid var(--lineSoft); }
.hitRow:last-child { border-bottom:none; }
.hitRow:hover { background:var(--rowHover); }
.hitHead { display:flex; align-items:center; gap:9px; flex-wrap:wrap; margin-bottom:5px; }
.hitBody { font-size:13px; color:var(--inkDim); line-height:1.6; }
.hitMeta { font-size:11.5px; color:var(--muted); margin-top:5px; }

/* =============================== gantt ============================== */

.ganttWrap {
  display:flex; border:1px solid var(--line); border-radius:10px;
  background:var(--panel); overflow:hidden; margin-bottom:14px;
}
.ganttLabels {
  width:340px; flex-shrink:0; border-right:1px solid var(--line);
  background:var(--panel); overflow:hidden;
}
.ganttChart { flex:1; overflow-x:auto; overflow-y:hidden; position:relative; }

.ganttHead {
  height:34px; border-bottom:1px solid var(--line); background:var(--panel2);
  position:relative; flex-shrink:0;
}
.gTick {
  position:absolute; top:0; height:34px; border-left:1px solid var(--line);
  display:flex; align-items:center; padding-left:7px;
  font-size:11px; color:var(--muted); white-space:nowrap; overflow:hidden;
}
.gTick.major { border-left-color:var(--muted); }
.gTick.major span { color:var(--ink); font-weight:600; }

.ganttRow {
  height:28px; display:flex; align-items:center; gap:8px;
  border-bottom:1px solid var(--lineSoft); padding-right:10px; overflow:hidden;
}
.ganttRow.g-project { background:var(--panel2); }
.ganttRow.g-project .gLabel { font-weight:700; font-size:13px; }
.ganttRow.g-version .gLabel { font-weight:600; color:var(--inkDim); }
.ganttRow.g-closed .gLabel { opacity:.6; text-decoration:line-through; }
.gLabel {
  font-size:12.5px; color:var(--ink); white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis; text-decoration:none; flex:1; min-width:0;
}
a.gLabel:hover { color:var(--accent); }
.gSub { font-size:10.5px; color:var(--muted); white-space:nowrap; flex-shrink:0; }

.ganttBody { position:relative; }
.gGrid { position:absolute; top:0; bottom:0; width:1px; background:var(--lineSoft); }
.gToday { position:absolute; top:0; bottom:0; width:2px; background:var(--err); opacity:.75; z-index:3; }
.gArrows { position:absolute; top:0; left:0; z-index:2; color:var(--muted); opacity:.8; pointer-events:none; }

.gBar {
  position:absolute; height:16px; border-radius:4px; overflow:hidden;
  background:var(--panel2); border:1px solid var(--line); z-index:1;
}
.gFill { display:block; height:100%; background:var(--net); opacity:.85; }
.gb-project { background:color-mix(in srgb,var(--accent) 22%,transparent); border-color:var(--accent); height:12px; }
.gb-project .gFill { background:var(--accent); }
.gb-version { background:color-mix(in srgb,var(--viol) 20%,transparent); border-color:var(--viol); height:14px; }
.gb-version .gFill { background:var(--viol); }
.gb-issue .gFill { background:var(--net); }
.gb-closed .gFill { background:var(--ok); }
.gb-late { border-color:var(--err); }
.gb-late .gFill { background:var(--err); }

.ganttKey {
  display:flex; gap:16px; flex-wrap:wrap; font-size:11.5px; color:var(--muted);
  align-items:center; padding:0 2px 8px;
}
.ganttKey span { display:inline-flex; align-items:center; gap:6px; }
.ganttKey i.k {
  width:20px; height:9px; border-radius:3px; display:inline-block;
  background:var(--panel2); border:1px solid var(--line);
}
.ganttKey i.gb-project { background:var(--accent); border-color:var(--accent); }
.ganttKey i.gb-version { background:var(--viol); border-color:var(--viol); }
.ganttKey i.gb-issue   { background:var(--net); border-color:var(--net); }
.ganttKey i.gb-late    { background:var(--err); border-color:var(--err); }
.ganttKey i.gb-closed  { background:var(--ok); border-color:var(--ok); }
.ganttKey i.kToday     { width:3px; background:var(--err); border-color:var(--err); }

@media print {
  #sidebar, header.topbar, footer.appFoot, .pageHead .btn, .pageHead form { display:none; }
  .ganttChart { overflow:visible; }
  .ganttWrap { border-color:#999; }
}

/* ============================== calendar ============================ */

.cal {
  border:1px solid var(--line); border-radius:10px; overflow:hidden;
  background:var(--panel); margin-bottom:14px;
}
.calHead { display:grid; grid-template-columns:repeat(7,1fr); background:var(--panel2); }
.calDayName {
  padding:9px 10px; font-size:10.5px; font-weight:600; letter-spacing:.08em;
  text-transform:uppercase; color:var(--muted); border-left:1px solid var(--line);
  border-bottom:1px solid var(--line);
}
.calDayName:first-child { border-left:none; }

.calWeek { display:grid; grid-template-columns:repeat(7,1fr); }
.calCell {
  min-height:112px; border-left:1px solid var(--lineSoft);
  border-top:1px solid var(--lineSoft); padding:6px 6px 8px;
  display:flex; flex-direction:column; gap:4px; overflow:hidden;
}
.calCell:first-child { border-left:none; }
.calCell.outside { background:var(--panelDeep); }
.calCell.outside .calDate { opacity:.4; }
.calCell.weekend:not(.outside) { background:color-mix(in srgb,var(--muted) 5%,transparent); }
.calCell.today { background:color-mix(in srgb,var(--accent) 9%,transparent); }

.calDate { font-size:12px; font-weight:600; color:var(--muted); padding:1px 2px; }
.calCell.today .calDate {
  color:#fff; background:var(--accent); border-radius:50%;
  width:21px; height:21px; display:flex; align-items:center; justify-content:center;
  font-size:11.5px;
}

.calItems { display:flex; flex-direction:column; gap:3px; min-width:0; }
.calItem {
  display:flex; align-items:center; gap:5px; min-width:0;
  font-size:11.5px; padding:2px 5px; border-radius:5px;
  background:var(--panel2); border:1px solid var(--lineSoft);
  color:var(--inkDim); text-decoration:none;
}
.calItem:hover { border-color:var(--accent); color:var(--ink); text-decoration:none; }
.ciText { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.ciMark { width:6px; height:6px; border-radius:50%; flex-shrink:0; background:var(--muted); }

.ci-start   .ciMark { background:var(--net); }
.ci-due     .ciMark { background:var(--warn); }
.ci-both    .ciMark { background:var(--viol); }
/* A day in the middle of a run: the work is in flight rather than starting or
   ending, so the mark is quieter than either end of it. */
.ci-span    .ciMark { background:var(--muted); opacity:.55; }
.ci-span    .ciText { opacity:.75; }
.ci-version .ciMark { background:var(--accent); border-radius:2px; width:7px; height:7px; }
.ci-version { font-weight:600; }
.ci-overdue .ciMark { background:var(--err); }
.ci-overdue { border-color:color-mix(in srgb,var(--err) 40%,transparent); }
.ci-closed .ciText { text-decoration:line-through; opacity:.6; }
.ci-closed .ciMark { background:var(--ok); }

.calMore { font-size:10.5px; color:var(--muted); padding:1px 6px; }

.ganttKey i.ck-start   { background:var(--net); border-color:var(--net); }
.ganttKey i.ck-due     { background:var(--warn); border-color:var(--warn); }
.ganttKey i.ck-both    { background:var(--viol); border-color:var(--viol); }
.ganttKey i.ck-version { background:var(--accent); border-color:var(--accent); }
.ganttKey i.ck-overdue { background:var(--err); border-color:var(--err); }
.ganttKey i.ck-closed  { background:var(--ok); border-color:var(--ok); }

@media (max-width:900px) {
  .calCell { min-height:88px; }
  .ciText { font-size:10.5px; }
}
@media print {
  .cal { border-color:#999; }
  .calCell { min-height:96px; }
}

/* ============================= attachments ========================== */

.fileInput {
  background:var(--panel2); border:1px dashed var(--line); color:var(--muted);
  border-radius:8px; padding:9px 11px; font-size:12.5px; width:100%; cursor:pointer;
}
.fileInput:hover { border-color:var(--accent); color:var(--ink); }
.fileInput::file-selector-button {
  background:var(--panel); border:1px solid var(--line); color:var(--ink);
  border-radius:6px; padding:5px 11px; font-size:12px; margin-right:11px;
  cursor:pointer; font-family:inherit;
}
.fileInput::file-selector-button:hover { border-color:var(--accent); color:var(--accent); }

.attList { display:flex; flex-direction:column; }
.att {
  display:flex; align-items:center; gap:13px; padding:11px 16px;
  border-bottom:1px solid var(--lineSoft);
}
.att:last-child { border-bottom:none; }
.att:hover { background:var(--rowHover); }

.attThumb {
  width:52px; height:40px; flex-shrink:0; border-radius:6px; overflow:hidden;
  border:1px solid var(--line); background:var(--panel2); display:block;
}
.attThumb img { width:100%; height:100%; object-fit:cover; display:block; }
.attIcon {
  width:52px; height:40px; flex-shrink:0; border-radius:6px;
  border:1px solid var(--line); background:var(--panel2); color:var(--muted);
  display:flex; align-items:center; justify-content:center;
}
.attIcon svg { width:20px; height:20px; }

.attMeta { flex:1; min-width:0; display:flex; flex-direction:column; gap:2px; }
.attName {
  font-size:13px; font-weight:600; color:var(--ink);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.attName:hover { color:var(--accent); }
.attSub { font-size:11.5px; color:var(--muted); }
.attActions { display:flex; gap:7px; flex-shrink:0; align-items:center; }

@media (max-width:640px) {
  .att { flex-wrap:wrap; }
  .attActions { width:100%; justify-content:flex-end; }
}

/* ============================ notifications ========================= */

.smsPreview {
  background:var(--panel2); border:1px solid var(--line); border-radius:12px;
  padding:13px 16px; font-size:13.5px; line-height:1.55; color:var(--ink);
  max-width:340px; position:relative;
}
.smsCount {
  display:block; margin-top:9px; padding-top:8px; border-top:1px solid var(--line);
  font-size:11px; color:var(--muted);
}

/* ---- workflow grid ---- */
table.wfGrid th { text-align:center; }
table.wfGrid th:first-child { text-align:left; }
table.wfGrid td { padding:7px 10px; }
table.wfGrid input[type=checkbox] { width:16px; height:16px; }

/* ---------------------------------------------------- product tree */
/* The expander sits in the same column as the name, so a leaf reserves the
   same width rather than letting its name slide left of its siblings'. */
.treeToggle {
    width: 20px;
    height: 20px;
    margin-right: 6px;
    padding: 0;
    line-height: 1;
    font-size: 14px;
    font-weight: 700;
    color: var(--muted);
    background: color-mix(in srgb, var(--fg) 6%, transparent);
    border: 1px solid var(--line);
    border-radius: 4px;
    cursor: pointer;
    vertical-align: middle;
}
.treeToggle:hover {
    color: var(--fg);
    border-color: var(--accent);
}
.treeSpacer {
    display: inline-block;
    width: 26px;
}

/* A multi-select is a list, not a one-line control: give it room and keep the
   indentation of the product tree readable inside it. */
select[multiple] {
    width: 100%;
    padding: 6px;
    font-family: inherit;
    white-space: pre;
}

/* A four-digit code is read back a digit at a time, so it is set wide and
   spaced rather than as ordinary prose. */
.otpInput {
  font-size:26px; letter-spacing:.5em; text-align:center;
  font-family:ui-monospace,Menlo,Consolas,monospace; padding:10px 0 10px .5em;
}

/* The role somebody holds now, beside the list of roles they could hold. A
   marked option in a scrolling multi-select is not readable as an answer. */
.roleChip {
    background: color-mix(in srgb, var(--accent) 16%, transparent);
    color: var(--accent);
    margin: 0 4px 4px 0;
}
/* Where the browser honours it, the currently held roles stand out in the list
   as well. Chromium and Firefox do; Safari ignores it, which is why the column
   above exists rather than this being the whole answer. */
select[multiple] option:checked {
    background: color-mix(in srgb, var(--accent) 28%, var(--panel2));
    color: var(--ink);
    font-weight: 600;
}

/* ------------------------------------------------------------------ dashboard
   Bars rather than a chart library. Every value here is a count on one axis, so
   a length is the whole of what a chart would say, and a length made of two
   divs reads correctly in both themes, prints, scales with the font, and
   survives having no JavaScript. */

.dashFilters {
  display:flex; flex-wrap:wrap; align-items:flex-end; gap:14px; padding:14px 16px;
}
.dashFilter { display:flex; flex-direction:column; gap:5px; min-width:190px; flex:1 1 190px; }
.dashFilter label {
  font-size:10.5px; text-transform:uppercase; letter-spacing:.08em;
  color:var(--muted); font-weight:600;
}
.dashFilterActions { display:flex; gap:8px; align-items:center; }

/* Two columns where there is room, one where there is not. The assignee chart
   spans both: it is the longest list and the one whose names wrap worst. */
.dashGrid { display:grid; grid-template-columns:repeat(auto-fit,minmax(340px,1fr)); gap:18px; }
.dashGrid > .card { margin-bottom:0; }
.dashWide { grid-column:1 / -1; }

/* The whole set in one bar, above the breakdown. */
.dashSplit {
  display:flex; height:10px; margin:16px 16px 10px; border-radius:999px;
  overflow:hidden; background:var(--panelDeep); border:1px solid var(--line);
}
.dashSplit .seg { display:block; height:100%; }
.dashSplit .seg.live { background:var(--accent); }
.dashSplit .seg.done { background:var(--ok); }

.dashLegend {
  display:flex; gap:18px; padding:0 16px 14px; font-size:12px; color:var(--muted);
  border-bottom:1px solid var(--lineSoft);
}
.dashLegend b { color:var(--ink); font-variant-numeric:tabular-nums; margin-left:4px; }
.dashLegend .sw {
  display:inline-block; width:9px; height:9px; border-radius:2px; margin-right:6px;
}
.dashLegend .sw.live { background:var(--accent); }
.dashLegend .sw.done { background:var(--ok); }

.dashBars { list-style:none; margin:0; padding:8px 8px 12px; }

/* The whole row is the link, not just the label: a target the width of the
   card is one people can hit, and the bar is the thing they are looking at
   when they decide to click. */
.dashBar {
  display:grid; grid-template-columns:minmax(96px,150px) 1fr 52px;
  align-items:center; gap:12px; padding:6px 8px; border-radius:7px;
  color:var(--ink); text-decoration:none; border:1px solid transparent;
}
.dashBar:hover { background:var(--rowHover); text-decoration:none; }
.dashBar:focus-visible { outline:2px solid var(--accent); outline-offset:1px; }
.dashBar.on { border-color:var(--accent); background:var(--rowHover); }

.dashBar .lbl {
  font-size:12.5px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.dashBar.on .lbl { font-weight:650; }
.dashBar .track {
  display:flex; height:14px; border-radius:4px; overflow:hidden;
  background:var(--panelDeep); box-shadow:inset 0 0 0 1px var(--lineSoft);
}
.dashBar .fill { display:block; height:100%; min-width:0; }
.dashBar .fill.live { background:var(--accent); }
.dashBar .fill.done { background:var(--ok); }
.dashBar .fill.none { background:var(--muted); }
.dashBar .val {
  font-size:12.5px; font-weight:650; text-align:right;
  font-variant-numeric:tabular-nums; color:var(--inkDim);
}

.dashNote { margin-top:14px; }

@media (max-width: 560px) {
  /* The label column stops earning its width before the bar does. */
  .dashBar { grid-template-columns:minmax(72px,110px) 1fr 44px; gap:8px; }
}

/* A nested nav item. Indented and slightly quieter, so Dashboard reads as
   part of My page rather than as a seventh thing at the same level.
   The indent is dropped when the sidebar collapses to icons, where there is
   no room for it and nothing left to indent under. */
.sbItem.sbSub { padding-left:30px; font-size:12.5px; color:var(--inkDim); }
.sbItem.sbSub .sbIco { width:15px; height:15px; }
#appShell.collapsed .sbItem.sbSub { padding-left:0; }

/* Forms inside a side box. The column is 250px wide, so fields stack and the
   controls fill it rather than sitting at their natural width and leaving a
   ragged edge. */
.sideBox form { display:flex; flex-direction:column; gap:9px; }
.sideBox .field { display:flex; flex-direction:column; gap:4px; }
.sideBox .field label {
  font-size:10.5px; text-transform:uppercase; letter-spacing:.06em;
  color:var(--muted); font-weight:600;
}
.sideBox select, .sideBox textarea { width:100%; }
.sideBox textarea { min-height:52px; resize:vertical; }
.sideBox .btn { align-self:flex-start; }

/* Pagers inside a card sit against its edges rather than the page's, and the
   dead arrow at either end stays put instead of the row jumping when it goes.
   Drawn as a disabled-looking button, not removed: a control that vanishes at
   the end of a list moves everything beside it. */
.card .pager { padding:12px 16px; border-top:1px solid var(--lineSoft); }
.card .pager .hint { margin:0 4px; }
.pager [aria-disabled="true"] { opacity:.4; cursor:default; }

/* ---- escalation ladder, under each response target ---- */
/* Indented and quieter than the row above it, because a rung is a detail of
   the target rather than a peer of it - drawn as a sibling the two read as
   unrelated settings that happen to be adjacent. */
.ladder { border-left:2px solid var(--lineSoft); margin:0 0 6px 12px; padding:2px 0 2px 12px; }
.ladder .rung { display:flex; gap:10px; align-items:center; font-size:12.5px;
                padding:4px 0; color:var(--ink); flex-wrap:wrap; }
.ladder .rung b { min-width:110px; }
.ladder .rungAdd { display:flex; gap:10px; align-items:center; flex-wrap:wrap;
                   padding:6px 0 2px; font-size:12px; color:var(--muted); }
.ladder .rungAdd label { display:inline-flex; align-items:center; gap:5px; margin:0; }
.ladder .rungAdd input[type="number"] { padding:4px 6px; }
