/* =====================================================================
   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; justify-content:space-between; gap:8px;
  font-size:12.5px; color:var(--muted); margin-bottom:14px;
}
.rememberMe { display:flex; align-items:center; gap:8px; cursor:pointer; }

/* =============================== 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; }
/* ---- a form that has been sent ---- */
/* The button that was pressed keeps its colour and gains a turning ring, so
   it reads as working rather than as refused - the other disabled buttons on
   the page mean "you cannot", and this one means "wait". */
.btn.waiting { opacity:1; cursor:progress; position:relative; padding-left:32px; }
.btn.waiting::before {
  content:""; position:absolute; left:12px; top:50%; width:12px; height:12px;
  margin-top:-6px; border-radius:50%;
  border:2px solid currentColor; border-top-color:transparent;
  animation:waitSpin .7s linear infinite;
}
[dir="rtl"] .btn.waiting { padding-left:14px; padding-right:32px; }
[dir="rtl"] .btn.waiting::before { left:auto; right:12px; }
@keyframes waitSpin { to { transform:rotate(360deg); } }
/* And the bar across the top of the page, which is the answer to the second
   press: something is happening and it is not finished. Under reduced motion
   the sheen stops and the bar stays, which still says so. */
#waitBar {
  position:fixed; top:0; left:0; right:0; height:3px; z-index:200;
  background:linear-gradient(90deg,var(--accent),var(--brand2),var(--accent));
  background-size:200% 100%;
  animation:waitSlide 1.1s linear infinite;
}
@keyframes waitSlide { to { background-position:-200% 0; } }
/* The release's own download, beside its date on the roadmap. The arrow is
   what says "this fetches a file" before the word does. */
.pdfLink { display:inline-flex; align-items:center; gap:5px; }
.pdfLink svg { width:13px; height:13px; display:block; }
.btnRow { display:flex; gap:9px; flex-wrap:wrap; align-items:center; }

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

.field { display:flex; flex-direction:column; gap:5px; margin-bottom:14px; }
/* Taken off the page entirely rather than greyed: a field with nothing to
   choose from is not a choice somebody nearly has. Stronger than .field's own
   display, which is why this is a class and not the hidden attribute. */
.hidden { display:none !important; }
.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; }
/* A ticket past its resolution time: the whole line red, so it is seen from
   across the room rather than found by reading a column. After the hover rule
   on purpose - same weight, later wins - so pointing at a late row does not
   quietly turn it back to grey. */
table.data tbody tr.late td { background:color-mix(in srgb,var(--err) 15%,transparent); }
table.data tbody tr.late:hover td { background:color-mix(in srgb,var(--err) 22%,transparent); }
table.data tbody tr.late .countdown { color:var(--err); font-weight:600; }
.countdown { font-variant-numeric:tabular-nums; white-space:nowrap; }

/* A checkbox with its sentence beside it, on its own line of a form. */
.checkRow { display:flex; align-items:flex-start; gap:9px; margin:4px 0 12px; font-size:13px;
            color:var(--inkDim); cursor:pointer; }
.checkRow input { margin-top:3px; }
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);
}
/* The same slot, when the login works for more than one customer. */
.portalCustomerPick {
  width: auto; min-width: 150px; margin: 0 0 0 14px;
  font-size: 12px; padding: 4px 8px; border-radius: 8px;
}
[dir="rtl"] .portalCustomerPick { margin: 0 14px 0 0; }
.portalMain { flex: 1 1 auto; padding: 24px 22px 48px; max-width: 1000px; width: 100%; margin: 0 auto; }

/* The one portal page that carries a rail beside it.
   A thousand pixels is a comfortable measure for a column of reading; take 250
   of them for the actions and what is left is a cramped one. Wider only here,
   so the lists and the raise form keep the width that suits them. */
.portalMain.withRail { max-width: 1180px; }

/* Present or not, in the portal bar. Said in words beside the button, because a
   button alone does not say which of the two states you are in. */
.presence { display:inline-flex; align-items:center; gap:7px; font-size:12px; color:var(--muted); }
.presence .dot { width:8px; height:8px; border-radius:50%; background:var(--muted); opacity:.55; }
.presence.on { color:var(--ok); }
.presence.on .dot {
  background:var(--ok); opacity:1;
  box-shadow:0 0 0 3px color-mix(in srgb,var(--ok) 22%,transparent);
}
.presenceBtn { border-color:color-mix(in srgb,var(--ok) 55%,var(--line)); color:var(--ok); }

/* One person's month on the roster page: the plan on top of each date, what
   happened under it. Seven columns always, so every date sits under its
   weekday - the questions asked of a rota are about weekdays. */
.rosterMonth { display:grid; grid-template-columns:repeat(7,minmax(92px,1fr)); gap:6px; min-width:680px; }
.rmHead {
  font-size:10.5px; text-transform:uppercase; letter-spacing:.08em;
  color:var(--muted); padding:0 4px 2px; font-weight:600;
}
.rmDay {
  min-height:92px; border:1px solid var(--line); border-radius:8px; padding:6px 7px;
  background:var(--panel); display:flex; flex-direction:column; gap:3px; font-size:11.5px;
}
.rmDay.blank { border:none; background:transparent; }
.rmNum { font-weight:700; font-size:12px; color:var(--ink); display:flex; gap:4px; align-items:center; }
.rmSrc { color:var(--accent); font-size:14px; line-height:1; }
.rmPlan { color:var(--inkDim); font-variant-numeric:tabular-nums; }
.rmActual { color:var(--ok); font-variant-numeric:tabular-nums; }
.rmFlag { margin-top:auto; font-size:10.5px; font-weight:600; }
.rmDay.rm-absent { border-color:color-mix(in srgb,var(--err) 60%,var(--line)); }
.rmDay.rm-absent .rmFlag { color:var(--err); }
.rmDay.rm-late .rmFlag, .rmFlag.early { color:var(--warn); }
.rmDay.rm-extra .rmFlag { color:var(--net); }
.rmDay.rm-off { background:var(--panel2); }
.rmDay.rm-off .rmPlan, .rmDay.rm-none .rmPlan { color:var(--muted); }
.rmDay.rm-none { background:var(--panel2); border-style:dashed; }
.rmDay.rm-future { opacity:.72; }
.rmDay.today { box-shadow:inset 0 0 0 2px var(--accent); }

/* Figures that line up, and the sign of a delta said in colour as well. */
.tnum { text-align:right; font-variant-numeric:tabular-nums; white-space:nowrap; }
.deltaNeg { color:var(--err); }
.deltaPos { color:var(--ok); }
.kpi.kpiWarn .n { color:var(--err); }
.plainList { margin:0; padding-left:18px; display:flex; flex-direction:column; gap:4px; }
.problemList {
  margin:14px 0 0; padding:10px 14px 10px 30px; border-radius:8px; font-size:12.5px;
  background:color-mix(in srgb,var(--err) 9%,transparent); color:var(--ink);
  border:1px solid color-mix(in srgb,var(--err) 35%,var(--line));
}
.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; }
/* Clicking one picks it up for resizing, so it says so. */
.editorCanvas img { cursor:pointer; }

/* The resize controls, over the canvas rather than in it: they are not part of
   the description and must never end up in what is saved. The host is the
   positioning context, so it has to be relative for either of them to land. */
[data-editor-for] { position:relative; }
.imgTools {
  position:absolute; z-index:5; display:flex; gap:3px; padding:3px;
  background:var(--panel2); border:1px solid var(--line); border-radius:7px;
  box-shadow:0 2px 8px rgba(0,0,0,.18);
}
.imgTools[hidden] { display:none; }
.imgSize {
  font:inherit; font-size:10.5px; font-weight:650; line-height:1;
  padding:4px 6px; border-radius:5px; cursor:pointer;
  background:transparent; border:1px solid transparent; color:var(--muted);
}
.imgSize:hover, .imgSize:focus-visible {
  background:var(--panel); border-color:var(--line); color:var(--ink);
}
.imgHandle {
  position:absolute; z-index:5; width:14px; height:14px; border-radius:4px;
  background:var(--accent); border:2px solid var(--panel);
  cursor:nwse-resize; touch-action:none;
}
.imgHandle[hidden] { display:none; }
/* Drawn over the picture, never on it: nothing here reaches the description. */
.imgOutline {
  position:absolute; z-index:4; pointer-events:none; border-radius:6px;
  outline:2px solid var(--accent); outline-offset:1px;
}
.imgOutline[hidden] { display:none; }

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

/*
  A KPI counting something that has already gone wrong.

  Only worn when the figure is above zero. A permanently red "Past due: 0" is a
  page crying wolf, and after a week nobody sees the colour at all.
*/
.kpi.late { border-left:3px solid var(--err); }
.kpi.late .n { color:var(--err); }

/*
  The line that divides one page into two subjects.

  The customer overview answers two questions that share a customer and nothing
  else - what we are building for them, and what they have asked us to fix. The
  rule above the heading is what stops the second set of numbers reading as a
  continuation of the first.
*/
.secHead {
  display:flex; align-items:baseline; gap:12px; flex-wrap:wrap;
  margin:26px 0 14px; padding-top:20px; border-top:1px solid var(--line);
}
.secHead h2 { margin:0; font-size:16px; font-weight:700; color:var(--ink); }

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

/* ---- language switch: top bar, portal bar and the sign-in page ---- */
/* ---- language picker: the flag and name, opening to all nine ---- */
.langPick { position:relative; }
.langPick > summary {
  list-style:none; display:flex; align-items:center; gap:7px; cursor:pointer; user-select:none;
  background:var(--panel2); border:1px solid var(--line); color:var(--inkDim);
  border-radius:8px; padding:5px 9px; font-size:12.5px; white-space:nowrap;
}
.langPick > summary::-webkit-details-marker { display:none; }
.langPick > summary::after {
  content:""; width:0; height:0; border:4px solid transparent; border-top-color:currentColor;
  margin-top:4px; margin-inline-start:1px;
}
.langPick > summary:hover, .langPick > summary:focus-visible, .langPick[open] > summary {
  border-color:var(--accent); color:var(--ink); outline:none;
}
.langPick .flag {
  width:20px; height:14px; object-fit:cover; border-radius:2px; flex-shrink:0;
  box-shadow:0 0 0 1px rgba(0,0,0,.28);
}
/* inset-inline-end, not right: the list opens under the button's end in both directions. */
.langMenu {
  position:absolute; inset-inline-end:0; top:calc(100% + 6px); z-index:1200; min-width:196px;
  display:flex; flex-direction:column; gap:1px; padding:5px;
  background:var(--panel); border:1px solid var(--line); border-radius:10px;
  box-shadow:0 14px 36px rgba(0,0,0,.38);
  max-height:calc(100vh - 24px); overflow-y:auto; overscroll-behavior:contain;
}
.langPick.up .langMenu { top:auto; bottom:calc(100% + 6px); }
/* i18n.js turns a menu the other way when the side it prefers has too little room */
.langPick.up.flip .langMenu { top:calc(100% + 6px); bottom:auto; }
.langPick.flip:not(.up) .langMenu { top:auto; bottom:calc(100% + 6px); }
.langMenu button {
  display:flex; align-items:center; gap:10px; width:100%; text-align:start;
  background:none; border:none; border-radius:7px; padding:7px 9px;
  color:var(--ink); font:inherit; font-size:13px; cursor:pointer;
}
.langMenu button:hover, .langMenu button:focus-visible { background:var(--panel2); outline:none; }
.langMenu button.on { color:var(--accent); font-weight:650; }
.langMenu button.on::after { content:"\2713"; margin-inline-start:auto; }
@media (max-width:700px) {
  header .langPick .langName { display:none; }
}

/* ---- auto refresh and full screen -------------------------------------
   The two controls on the screens people leave open, and what full screen
   does to the page. Not the browser's full screen: a reload ends that, and
   these are the screens that reload themselves. This gives the content the
   window instead, which survives any number of refreshes. */
/* One control, so it stays on one line: the label, the interval and the
   button sit on the same baseline and never wrap apart from each other. */
.screenTools { display:inline-flex; align-items:center; gap:8px; flex-wrap:nowrap; }
.screenTools .screenLabel {
  margin:0; font-size:11.5px; font-weight:500; color:var(--muted); white-space:nowrap;
}
.screenTools select { width:auto; min-width:104px; margin:0; }
.iconBtn { display:inline-flex; align-items:center; justify-content:center; padding:6px 9px; }
.iconBtn svg { width:15px; height:15px; display:block; }
.screenTools .onExit { display:none; }
html.presenting .screenTools .onEnter { display:none; }
html.presenting .screenTools .onExit { display:block; }

html.presenting #sidebar,
html.presenting .topbar,
html.presenting .appFoot { display:none; }
html.presenting #mainCol { margin-inline-start:0; }
html.presenting .content { max-width:none; padding:14px 18px; }
/* The portal's own chrome, hidden the same way. Its bar holds the controls, so
   the bar cannot simply go: everything in it except the way back is hidden
   instead, and the controls themselves are lifted into the corner by the rule
   below - otherwise full screen would also be the way out of it. */
html.presenting .portalBar > *:not(.screenTools) { display:none; }
html.presenting .portalBar {
  padding:0; min-height:0; border:0; background:transparent; box-shadow:none;
}
html.presenting .portalFoot { display:none; }
html.presenting .portalMain { max-width:none; padding:14px 18px 24px; }
/* The way back, always in the same corner, because the top bar is gone. */
html.presenting .screenTools {
  position:fixed; top:10px; inset-inline-end:14px; z-index:1400;
  padding:6px 9px; border-radius:10px;
  background:var(--panel); border:1px solid var(--line);
  box-shadow:0 10px 30px rgba(0,0,0,.35);
}

/* ---- spent time list --------------------------------------------------
   Every column here is short except the comment, and the comment was the
   one being given no room: the date broke across three lines to make
   space a comment then did not use. So the comment takes whatever is
   left over and the rest stay on one line each. */
.timeTable .cDate, .timeTable .cUser, .timeTable .cProduct,
.timeTable .cKp, .timeTable .cAct, .timeTable .cHours { white-space:nowrap; }
.timeTable .cDate, .timeTable .cHours { font-variant-numeric:tabular-nums; }
.timeTable .cComment { width:100%; min-width:18ch; }
.timeTable td { vertical-align:middle; }

/* ---- remove-from-customer window --------------------------------------
   Which customer to take a staff login away from. A popover, so it is drawn
   above everything: the staff table scrolls sideways, and a menu positioned
   inside it is cut off at the table's edge. */
.rmPop {
  border:1px solid var(--line); border-radius:12px; padding:15px 17px;
  background:var(--panel); color:var(--ink);
  min-width:min(92vw, 280px); max-width:min(92vw, 380px);
  box-shadow:0 24px 60px rgba(0,0,0,.45);
  /* The cell this sits in is nowrap and right-aligned for its row of
     buttons, and both inherit in here: without these the sentence runs off
     the side rather than wrapping, and everything hangs to the right. */
  white-space:normal; text-align:start;
}
.rmPop::backdrop { background:rgba(0,0,0,.5); }
.rmHead {
  margin:0; font-size:11px; font-weight:600; letter-spacing:.05em;
  text-transform:uppercase; color:var(--muted);
}
.rmWho { margin:3px 0 10px; font-weight:600; overflow-wrap:anywhere; }
.rmList { display:flex; flex-direction:column; gap:3px; }
.rmList button {
  display:block; width:100%; text-align:start;
  background:var(--panel2); border:1px solid var(--line); border-radius:8px;
  padding:9px 11px; color:var(--ink); font:inherit; font-size:13.5px; cursor:pointer;
}
.rmList button:hover, .rmList button:focus-visible {
  border-color:var(--err); color:var(--err); outline:none;
}
.rmNote { margin:11px 0 0; font-size:12px; color:var(--muted); }
.rmFoot { display:flex; justify-content:flex-end; margin-top:11px; }
/* Without popover support the window would otherwise sit open inside the row. */
@supports not selector(:popover-open) {
  .rmPop { display:none; }
}

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

/* Where the number is a link of its own: the bar narrows the page, and the
   number beside it opens the queue holding exactly those tickets. Two links
   cannot be nested, so the count comes out of the bar and the row becomes the
   flex line holding both - which is why this is a modifier and not the rule
   for every bar. The other dashboards keep their count inside the bar, as its
   third column, and must not be flattened to two. */
.dashBars.split > li { display:flex; align-items:center; gap:4px; }
.dashBars.split > li > .dashBar {
  flex:1 1 auto; min-width:0; grid-template-columns:minmax(96px,150px) 1fr;
}
.dashCount {
  flex:0 0 52px; text-align:right; padding:6px 6px; border-radius:7px;
  font-size:12.5px; font-weight:650; font-variant-numeric:tabular-nums;
  color:var(--inkDim); text-decoration:none;
}
a.dashCount:hover, a.dashCount:focus-visible {
  color:var(--accent); background:var(--rowHover); text-decoration:none;
}
.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; }

/* ---- right-to-left pages (Arabic) ----
   dir="rtl" on the page turns every flex row, grid and table round by itself.
   These are the few rules that named a side, mirrored. */
[dir="rtl"] #sidebar { border-right:none; border-left:1px solid var(--line); }
[dir="rtl"] .sbSection, [dir="rtl"] .sbItem, [dir="rtl"] table.data thead th,
[dir="rtl"] table.wfGrid th:first-child { text-align:right; }
[dir="rtl"] table.data td.num, [dir="rtl"] .tnum, [dir="rtl"] .meterWrap .pct,
[dir="rtl"] .dashBar .val { text-align:left; }
[dir="rtl"] .field .req { margin-left:0; margin-right:2px; }
[dir="rtl"] .portalCustomer { padding-left:0; border-left:none; padding-right:14px; border-right:1px solid var(--line); }
[dir="rtl"] .plainList { padding-left:0; padding-right:18px; }
[dir="rtl"] .runActual { border-left:none; padding-left:0; border-right:2px solid var(--line); padding-right:8px; }
[dir="rtl"] .pickCard > summary::after { margin-left:0; margin-right:10px; }
[dir="rtl"] .editorCanvas ul, [dir="rtl"] .editorCanvas ol,
[dir="rtl"] .descBox ul, [dir="rtl"] .descBox ol { padding-left:0; padding-right:22px; }
[dir="rtl"] .editorCanvas blockquote, [dir="rtl"] .descBox blockquote {
  padding-left:0; border-left:none; padding-right:12px; border-right:3px solid var(--line);
}
[dir="rtl"] .tlItem:not(:last-child) .tlNode::after { left:auto; right:50%; }
[dir="rtl"] .tlHead .n, [dir="rtl"] .pager .info { margin-left:0; margin-right:auto; }
[dir="rtl"] .kpi.accent { border-left:none; border-right:3px solid var(--accent); }
[dir="rtl"] .kpi.late { border-left:none; border-right:3px solid var(--err); }
[dir="rtl"] .qpDel { margin-left:0; margin-right:-4px; }
[dir="rtl"] .calDayName { border-left:none; border-right:1px solid var(--line); }
[dir="rtl"] .calCell { border-left:none; border-right:1px solid var(--lineSoft); }
[dir="rtl"] .calDayName:first-child, [dir="rtl"] .calCell:first-child { border-right:none; }
[dir="rtl"] .fileInput::file-selector-button { margin-right:0; margin-left:11px; }
[dir="rtl"] .treeToggle { margin-right:0; margin-left:6px; }
[dir="rtl"] .dashLegend b { margin-left:0; margin-right:4px; }
[dir="rtl"] .dashLegend .sw { margin-right:0; margin-left:6px; }
[dir="rtl"] .sbItem.sbSub { padding-left:0; padding-right:30px; }
[dir="rtl"] #appShell.collapsed .sbItem.sbSub { padding-right:0; }
[dir="rtl"] .ladder { border-left:none; border-right:2px solid var(--lineSoft); margin:0 12px 6px 0; padding:2px 12px 2px 0; }
/* A time axis runs left to right in every language. */
[dir="rtl"] .ganttWrap { direction:ltr; }

/* ---- somebody's own words keep their own direction, paragraph by paragraph ----
   An Arabic paragraph in an English article sits right to left, and an
   English one in an Arabic article left to right, as each was written. */
.descBox p, .descBox li, .descBox blockquote, .kbBody p, .kbBody li, .tlNote,
.editorCanvas p, .editorCanvas div, .editorCanvas li { unicode-bidi:plaintext; }

/* ---- improve button ------------------------------------------------
   A star in the top corner of the subject and description, and the
   suggestion it opens underneath them. The button is placed over the
   field rather than in the label so no other label on any other form
   changes shape. */
.field.hasImprove { position:relative; }
.improveBtn {
  position:absolute; top:0; inset-inline-end:0; z-index:2;
  display:inline-flex; align-items:center; gap:5px;
  padding:2px 9px; font:inherit; font-size:11.5px; font-weight:600; line-height:1.7;
  color:var(--muted); background:var(--panel2); cursor:pointer;
  border:1px solid var(--line); border-radius:999px;
}
.improveBtn:hover:not(:disabled) { color:var(--accent); border-color:var(--accent); }
.improveBtn:disabled { opacity:.55; cursor:progress; }
.improvePanel {
  margin-top:7px; padding:9px 11px;
  background:var(--panel2); border:1px solid var(--line); border-left:3px solid var(--accent);
  border-radius:9px;
}
[dir="rtl"] .improvePanel { border-left:1px solid var(--line); border-right:3px solid var(--accent); }
.improveNote { margin:0 0 6px; font-size:12px; color:var(--muted); }
.improveAfter {
  margin:0 0 9px; padding:7px 9px; white-space:pre-wrap; line-height:1.55;
  background:var(--panel); border:1px solid var(--line); border-radius:7px;
}
/* Changed words carry the accent and an underline rather than a highlight
   block: the same treatment reads on the dark themes and the light one. */
.improveAfter mark {
  background:transparent; color:var(--accent); font-weight:650;
  box-shadow:inset 0 -2px 0 var(--accent);
}
.improveRow { display:flex; gap:8px; flex-wrap:wrap; align-items:center; }
