/* qahub.css — QA Hub's skin, loaded AFTER shared.css on every page.
   ───────────────────────────────────────────────────────────────────────────

   shared.css is the platform console's stylesheet, copied here unchanged so
   the two can still be diffed. This file is the whole of the difference.

   It works in two layers, and the split is deliberate:

     1. TOKENS. shared.css and qa.css between them use var(--...) 104 times
        and a literal colour three times, so redefining :root moves almost
        every surface, border and label at once. Most of this file's reach
        comes from twenty lines at the top.

     2. STRUCTURE. The things tokens cannot express: a 64px icon rail where a
        220px labelled one used to be, 2px Fluent corners softened to 10px,
        and the overview's tiles and detail panel, which have no counterpart
        in the platform console.

   ⚠️ Do not restyle a page by editing qa.css or feedback.css. Those two are
   shared with nothing, but they are also the files that would have to be
   re-merged if the platform console's versions ever change. Put the override
   here, where it is obvious that it is an override. */


/* ── 1. TOKENS ──────────────────────────────────────────────────────────── */

:root {
  /* Warm neutrals rather than Fluent's cool greys. The page is off-white, the
     panels are pure white, and the tiles are a shade darker than the page so
     they read as raised without needing a border or a shadow. */
  --bg:#FAF9F7; --s1:#FFFFFF; --s2:#F5F3EF; --s3:#F0EEE9;
  --border:#E8E5DF;
  --text:#1A1917; --dim:#6B6862; --faint:#9A968E;

  /* The primary is near-black, not blue. Every status colour on this site
     carries meaning - passed, failed, awaiting approval - so an accent that
     competes with them turns the page into bunting. A black button is the one
     thing on screen that is unambiguously the action. */
  --grass:#1C1B18; --grass-d:#000000;
  --link:#1F5FA8;

  /* Status families. Muted fills, legible text, a border one step darker so a
     pill still has an edge against both the white panel and the tile. */
  --green:#1E7A43; --green-d:#176034; --green-bg:#E6F2EA; --green-bdr:#BCDCC8;
  --red:#A8301F;   --red-bg:#FBEAE7;   --red-bdr:#EFC4BC;
  --amber:#8A5A14; --amber-bg:#FBF0DB; --amber-bdr:#EEDAAE;
  --blue:#1F5FA8;  --blue-bg:#EAF1FA;  --blue-bdr:#C6D9EF;
  --purple:#5C2E91;--purple-bg:#EFE9F7;--purple-bdr:#CFC0E4;
  --teal:#0F6B60;  --teal-bg:#E3F1EF;  --teal-bdr:#B5D9D4;

  --rail:#FFFFFF; --rail-2:#F5F3EF;
  --panel-head:#F5F3EF; --row-alt:#FAF9F7;
  --olive:#1A1917; --olive-d:#000000;

  /* --sidebar is read by shared.css's own
     .sidebar rule and by two media queries, so setting it here keeps those
     in agreement with the rule below instead of fighting it. */
  --sidebar:216px; --rail-w:216px;

  /* Corner radii in one place. Fluent's 2px is the single strongest signal
     that this is a Windows admin tool; 10px is the single strongest signal
     that it is not. */
  --r:10px; --r-sm:8px; --r-lg:14px; --r-pill:999px;

  --shadow-sm:0 1px 2px rgba(26,25,23,.04);
  --shadow:0 2px 10px rgba(26,25,23,.06);
  --shadow-lg:0 12px 40px rgba(26,25,23,.14);
}

html[data-theme="dark"] {
  --bg:#141311; --s1:#1D1C19; --s2:#232220; --s3:#2A2825;
  --border:#33312D; --text:#EDEBE7; --dim:#A8A49C; --faint:#78746C;
  --grass:#EDEBE7; --grass-d:#FFFFFF;
  --link:#8FB8E8;
  --green:#6FC68E; --green-d:#5AB47B; --green-bg:#1A2A20; --green-bdr:#2E4A38;
  --red:#E89A8C;   --red-bg:#2C1C19;   --red-bdr:#4C312B;
  --amber:#E0B86A; --amber-bg:#2A2317; --amber-bdr:#4A3C22;
  --blue:#8FB8E8;  --blue-bg:#1A2330;  --blue-bdr:#2E3E55;
  --rail:#1D1C19; --rail-2:#232220; --panel-head:#232220; --row-alt:#1A1916;
  --olive:#141311; --olive-d:#000000;
  --shadow-sm:0 1px 2px rgba(0,0,0,.3);
  --shadow:0 2px 10px rgba(0,0,0,.35);
  --shadow-lg:0 12px 40px rgba(0,0,0,.55);
}

/* The black primary inverts to white-on-black in dark mode, so its label has
   to invert too. shared.css hardcodes #FFFFFF on .btn-primary. */
html[data-theme="dark"] .btn-primary,
html[data-theme="dark"] .fluent-btn-primary,
html[data-theme="dark"] .btn-confirm { color:#141311; }

body {
  /* Segoe first was right for a console pretending to be Entra. This one is
     not pretending to be anything, so it asks for the platform's own UI face
     and falls back through the same stack. */
  font-family:-apple-system,BlinkMacSystemFont,'Inter','Segoe UI',system-ui,sans-serif;
  font-size:13.5px;
  -webkit-font-smoothing:antialiased;
  letter-spacing:-0.004em;
}


/* ── 2. THE RAIL ────────────────────────────────────────────────────────── */

/* 216px: a logo, a column of named destinations, and identity at the foot.

   It used to be 64px of icons with a control at the bottom that widened it,
   and the width was remembered per browser. Two states meant every rule in
   here had a :not(.expanded) twin - a square item and a row item, a mark and
   a lockup, a label and a tooltip saying the same word - and the thing the
   control bought back was 150px on a page whose content is a list. One
   state now, and the names are always on. */
.sidebar {
  width:var(--rail-w); min-width:var(--rail-w);
  background:var(--rail); border-right:1px solid var(--border);
  display:flex; flex-direction:column; align-items:stretch;
  /* 100%, not 100vh. As 100vh the rail overhung #app by exactly the 52px
     that shared.css's old chrome rule took off it, and the overhang scrolled
     the whole page on an otherwise empty screen. Matching the parent cannot
     drift the same way if #app's height is ever changed again. */
  height:100%;
  padding:12px 8px 10px; gap:4px; z-index:50;
}

.rail-logo { display:flex; align-items:center; height:34px; padding:0 4px; margin-bottom:12px; flex:0 0 auto; }
.rail-logo img { display:block; }
/* Two files, because the wordmark is set in solid black and solid white
   respectively and neither survives the other's background. */
.rail-logo .rl-full { height:26px; width:auto; }
.rail-logo .rl-dark { display:none; }
html[data-theme="dark"] .rail-logo .rl-light { display:none; }
html[data-theme="dark"] .rail-logo .rl-dark  { display:block; }

.rail-nav { flex:1; display:flex; flex-direction:column; gap:3px; width:100%; padding:0; overflow-y:auto; overflow-x:hidden; }
.rail-foot { display:flex; flex-direction:column; gap:3px; width:100%; padding-top:10px; border-top:1px solid var(--border); flex:0 0 auto; }

/* One shape: a full-width row of icon, gap, label. The square-item rules that
   used to sit beside this existed only for the 64px state. */
.rail-item {
  position:relative;
  display:flex; align-items:center; gap:11px;
  width:100%; height:40px; flex:0 0 40px;
  padding:0 10px; border:none; background:transparent; cursor:pointer;
  border-radius:var(--r-sm); color:var(--faint);
  font:inherit; font-size:13px; text-align:left; text-decoration:none;
  transition:background .12s, color .12s;
}
.rail-item svg { width:19px; height:19px; flex:0 0 19px; }
.rail-item:hover { background:var(--s2); color:var(--text); }
.rail-item:focus-visible { outline:2px solid var(--link); outline-offset:1px; }
.rail-item.active { background:var(--grass); color:var(--s1); }
html[data-theme="dark"] .rail-item.active { color:#141311; }
.rail-item.active:hover { background:var(--grass-d); }

.rail-label { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0; }

/* The outstanding count, at the far end of the row.

   A circle while it holds one digit and a stadium once it holds two, which is
   what min-width plus a pill radius gives for free - a fixed circle would
   either clip "12" or be a saucer around "7".

   Red on purpose and nowhere else in the rail: it is the only thing in this
   column that is about work waiting on you rather than about where you can
   go. setRailBadge hides it at zero, so it is never a red nought. */
.rail-badge {
  margin-left:auto; flex:0 0 auto;
  min-width:18px; height:18px; padding:0 5px;
  display:inline-flex; align-items:center; justify-content:center;
  border-radius:var(--r-pill);
  background:var(--red); color:#FFF;
  font-size:10.5px; font-weight:700; line-height:1;
  font-variant-numeric:tabular-nums; letter-spacing:0;
}
.rail-badge[hidden] { display:none; }
/* ⚠️ Dark ink in dark mode. --red is #A8301F on light, where white sits at
   about 7.8:1, but #E89A8C in dark, where white is roughly 2:1 - a number
   you can see is there and cannot read. The same flip the chart segments
   make, for the same reason. */
html[data-theme="dark"] .rail-badge { color:#141311; }
/* On the active entry the row is already filled with --grass, so the disc
   gets a ring in that fill to keep its edge rather than melting into it. */
.rail-item.active .rail-badge { box-shadow:0 0 0 1.5px var(--grass); }
.rail-label b { display:block; font-weight:600; font-size:11.5px; color:var(--text); overflow:hidden; text-overflow:ellipsis; }

/* Identity. Same geometry as a rail item so the column does not step in at
   the bottom, with the avatar standing in for the icon.

   Not a control: no pointer cursor, no hover fill and no tabindex. It does
   nothing when clicked, and dressing it like the buttons above it is what
   made people try. The address is simply printed beside the avatar now,
   which is what the tooltip was for. */
.rail-user {
  position:relative; display:flex; align-items:center; gap:11px;
  width:100%; height:40px; flex:0 0 40px; padding:0 6px;
  border:none; background:transparent; border-radius:var(--r-sm);
  font:inherit; font-size:13px; color:var(--faint); text-align:left;
  margin-top:2px;
}
.rail-avatar {
  width:28px; height:28px; flex:0 0 28px;
  border-radius:var(--r-pill); background:var(--grass); color:var(--s1);
  font-size:12px; font-weight:600;
  display:flex; align-items:center; justify-content:center;
  /* The Entra photo arrives as a background-image on this element, so it has
     to clip to the circle and never letterbox. */
  background-size:cover; background-position:center; overflow:hidden;
}
html[data-theme="dark"] .rail-avatar { color:#141311; }
.rail-user .rail-label { font-size:12px; }

/* ── 3. SHELL ───────────────────────────────────────────────────────────── */

.topbar {
  padding:18px 28px 16px; background:var(--bg);
  border-bottom:1px solid var(--border);
  min-height:0; gap:12px;
}
.topbar h1 { font-size:20px; font-weight:650; letter-spacing:-0.02em; }
.content { padding:24px 28px 40px; }

.hamburger-btn { border-radius:var(--r-sm); }

.refresh-btn {
  border-radius:var(--r-pill); border:1px solid var(--border);
  background:var(--s1); padding:6px 13px; font-size:11.5px; font-weight:500;
}
.refresh-btn:hover { background:var(--s2); border-color:var(--border); }

/* The command bar above a list. Fluent drew it as a bordered strip; here it
   is just the sentence that explains the page, with the actions pushed right. */
.fluent-cmdbar {
  background:transparent; border:none; padding:0 0 4px;
  display:flex; align-items:center; gap:12px;
}

/* ── 4. SOFTENING ───────────────────────────────────────────────────────── */

/* Everything shared.css and qa.css draw at 2-4px corners. Listed by class
   rather than applied with a universal selector, because a blanket
   `border-radius` would also round the things that must stay square - the
   progress track's fill, the avatar, the status dots. */
.qa-card, .qa-req, .qa-person, .qa-form, .qa-note, .qa-opinion, .qa-question,
.qa-grid > *, .qa-stat, .qa-attach, .qa-shots img, .qa-clip,
.fb-row, .fb-msg, .sv-card, .sv-form, .sv-q,
.fluent-card, .fluent-panel, .card, .panel {
  border-radius:var(--r);
}
.qa-btn, .qa-input, .btn-primary, .btn-secondary, .input, .modal select,
.fluent-btn, .fluent-btn-primary, .btn-confirm, .btn-cancel, .fb-filter {
  border-radius:var(--r-sm);
}
.qa-outcome, .qa-ver, .sv-legend span {
  border-radius:var(--r-pill);
}

/* Status is a SQUARE, not a lozenge.
   A pill shape says "tag" - something you attached to the thing. A square
   block says "state" - a reading taken off it. Passed, Failed and Assigned to
   you are readings, and the capsule was dressing eleven verdicts up as
   labels. Sharp corners, deliberately: against 10px cards a 2px corner reads
   as a rounded square that missed, which is the shape this replaces. */
.ov-pill, .qa-status, .qa-tag, .qa-verdict, .qa-chip, .fb-tag, .badge, .pill {
  border-radius:0;
}

/* Buttons. Same geometry everywhere, a touch more padding than Fluent's, and
   the primary is the only filled thing on the page. */
.btn-primary, .fluent-btn-primary, .btn-confirm {
  padding:8px 16px; font-size:12.5px; font-weight:600;
  border-color:var(--grass); box-shadow:none;
}
.btn-secondary, .fluent-btn, .btn-cancel, .qa-btn {
  padding:8px 15px; font-size:12.5px; font-weight:550;
  background:var(--s1); border:1px solid var(--border); color:var(--text);
}
.btn-secondary:hover:not(:disabled), .fluent-btn:hover:not(:disabled),
.btn-cancel:hover, .qa-btn:hover:not(:disabled) { background:var(--s2); border-color:var(--border); }

.qa-input, .input, .modal select, .modal input, .modal textarea {
  border:1px solid var(--border); background:var(--s1); padding:9px 11px;
}
.qa-input:focus, .input:focus, .modal select:focus { border-color:var(--text); box-shadow:none; }

/* The spinner inside a locked confirm button.
   shared.css's .spinner is a grey ring with a #0078D4 cap, built for a white
   page; on the near-black primary it is a dark ring on a dark button. This
   one is drawn from currentColor, so it works on whichever button it lands
   in, in either theme. */
.btn-spin {
  display:inline-block; width:13px; height:13px; margin-right:8px;
  vertical-align:-2px; border-radius:50%;
  border:2px solid currentColor; border-top-color:transparent;
  opacity:.9; animation:spin .7s linear infinite;
}
/* A button mid-flight is not a button you can press, and it should not keep
   offering a hover state that says otherwise. */
.btn-confirm:disabled, .btn-cancel:disabled { opacity:.6; cursor:default; }
.btn-confirm:disabled:hover, .btn-cancel:disabled:hover { filter:none; }

.modal { border-radius:var(--r-lg); border:none; box-shadow:var(--shadow-lg); padding:26px; }
#toast { border-radius:var(--r); box-shadow:var(--shadow); padding:11px 16px; }

/* Anything with a coloured left edge gets SQUARE left corners.
   The edge is a 3px border on a 10px-radius box, so it followed the curve at
   the top and bottom and tapered to nothing at both ends - a bracket where a
   line was meant. Squaring the two left corners is the whole fix: the right
   side stays rounded, and the accent is a straight full-height rule. */
.qa-req, .fb-row, .sv-card,
.nt-f-pass .qa-input, .nt-f-fail .qa-input {
  border-top-left-radius:0; border-bottom-left-radius:0;
}

/* Cards sit on the page with a hairline and no shadow. A shadow on every card
   in a list of forty is forty shadows. */
.qa-card, .fb-row, .sv-card { border:1px solid var(--border); box-shadow:none; background:var(--s1); }
.qa-card:hover, .fb-row:hover { border-color:#D9D5CD; }
html[data-theme="dark"] .qa-card:hover, html[data-theme="dark"] .fb-row:hover { border-color:#45423D; }

/* The QA and feedback pages' own status chips carry NO mark.
   
   They had the same generic dot the overview pills had, added here with a
   ::before. It went for the same reason: qa.js and fb.js emit a dozen states
   between them and the dot was identical for every one, so it was shape
   without meaning. Those chips are already tinted per state and sit in dense
   lists where a repeated glyph is pure noise. The overview, which shows one
   status per row with room around it, is where a real mark earns its place. */

.cg-foot {
  border-top:1px solid var(--border); margin-top:40px; padding:18px 0 0;
  display:flex; flex-wrap:wrap; gap:4px 18px;
  font-size:11px; color:var(--faint);
}


/* ── 5. OVERVIEW ────────────────────────────────────────────────────────── */

/* The landing page: a row of figures, the work assigned to you, and a detail
   panel that opens beside it. Nothing here exists in the platform console, so
   these classes are defined rather than overridden. */

/* No .ov-wrap / .ov-left any more: with the detail panel gone the overview is
   a single column, and a flex row with one child is a flex row for nothing. */
.ov-stats { display:grid; grid-template-columns:repeat(auto-fit,minmax(170px,1fr)); gap:14px; margin-bottom:26px; }
.ov-stat { background:var(--s2); border-radius:var(--r-lg); padding:18px 20px 20px; }
.ov-stat-l { font-size:12px; color:var(--dim); font-weight:500; margin-bottom:10px; }
.ov-stat-v { font-size:36px; font-weight:600; letter-spacing:-0.035em; line-height:1; color:var(--text); }
.ov-stat-v.muted { color:var(--faint); }

.ov-sec { font-size:12px; font-weight:600; color:var(--dim); letter-spacing:-0.005em; margin:0 0 2px; }

.ov-tbl { width:100%; }
.ov-hrow { display:flex; align-items:center; gap:16px; padding:8px 12px; font-size:11.5px; color:var(--faint); font-weight:500; }
/* An <a>, not a <button>: these open the item's own page in a new window, and
   a link is the only one of the two the browser will let somebody cmd-click,
   middle-click or drag onto a second monitor. */
.ov-row {
  display:flex; align-items:center; gap:16px; width:100%; text-align:left;
  padding:13px 12px; border:none; background:transparent; font:inherit;
  color:var(--text); text-decoration:none;
  /* Square, so the 1px rule above each row runs the full width of the list.
     At var(--r) the corners pulled the line in at both ends and every row
     read as a separate rounded tile with a floating divider over it. */
  border-radius:0; cursor:pointer; border-top:1px solid var(--border);
  transition:background .1s;
}
.ov-row:hover { background:var(--s2); }
.ov-row:focus-visible { outline:2px solid var(--link); outline-offset:-2px; }

/* The leaves-the-page arrow. Faint until the row is under the pointer, so a
   list of twelve is not a column of twelve arrows. */
.ov-go { flex:0 0 16px; display:flex; color:var(--border); transition:color .1s; }
.ov-go svg { width:14px; height:14px; }
.ov-row:hover .ov-go, .ov-row:focus-visible .ov-go { color:var(--dim); }
/* The reference the manager typed, monospaced because it is an identifier and
   gets read character by character off the row and into a standup. */
.ov-id {
  flex:0 0 118px; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:11.5px; font-weight:600; color:var(--dim);
}
.ov-row .ov-id { color:var(--text); }
.ov-name { flex:1 1 auto; min-width:0; font-size:13.5px; font-weight:500; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.ov-pill-cell { flex:0 0 168px; display:flex; justify-content:flex-start; }

.ov-pill {
  display:inline-flex; align-items:center; gap:7px;
  padding:5px 11px 5px 9px;
  font-size:11.5px; font-weight:550; white-space:nowrap;
  background:var(--s3); color:var(--dim); border:1px solid transparent;
}
.ov-pill svg { width:13px; height:13px; flex:0 0 13px; margin-left:-1px; }
.ov-pill.ok    { background:var(--green-bg); color:var(--green); border-color:var(--green-bdr); }
.ov-pill.warn  { background:var(--amber-bg); color:var(--amber); border-color:var(--amber-bdr); }
.ov-pill.bad   { background:var(--red-bg);   color:var(--red);   border-color:var(--red-bdr); }
.ov-pill.info  { background:var(--blue-bg);  color:var(--blue);  border-color:var(--blue-bdr); }
.ov-pill.quiet { background:transparent; color:var(--faint); border-color:var(--border); }


.ov-empty { padding:40px 12px; text-align:center; color:var(--faint); font-size:13px; }


/* ── 6. SIGN-IN ─────────────────────────────────────────────────────────── */

.qh-login { min-height:100vh; display:flex; align-items:center; justify-content:center; padding:32px 24px; background:var(--bg); }
.qh-login-card { width:100%; max-width:380px; background:var(--s1); border:1px solid var(--border); border-radius:var(--r-lg); padding:36px 36px 30px; box-shadow:var(--shadow); }
/* The wordmark is solid black in one file and solid white in the other, so
   the page shows whichever one its background can carry. Swapped in CSS
   rather than by script: the logo is the first thing painted, and a JS swap
   would flash the wrong one on every load in dark mode. */
.qh-login-card img.lockup { width:176px; height:auto; display:block; margin-bottom:26px; }
.qh-login-card img.lockup-dark { display:none; }
html[data-theme="dark"] .qh-login-card img.lockup-light { display:none; }
html[data-theme="dark"] .qh-login-card img.lockup-dark  { display:block; }
.qh-login-card h1 { font-size:21px; font-weight:650; letter-spacing:-0.025em; margin-bottom:6px; }
.qh-login-card p.sub { font-size:13px; color:var(--dim); line-height:1.6; margin-bottom:24px; }
.qh-ms {
  width:100%; padding:11px; display:flex; align-items:center; justify-content:center; gap:9px;
  background:var(--grass); color:var(--s1); border:1px solid var(--grass);
  border-radius:var(--r-sm); font:inherit; font-size:13px; font-weight:600; cursor:pointer;
}
html[data-theme="dark"] .qh-ms { color:#141311; }
.qh-ms:hover:not(:disabled) { background:var(--grass-d); }
.qh-ms:disabled { opacity:.5; cursor:not-allowed; }
/* ⚠️ TEMPORARY, with the password form in index.html. Both come out once
   this site's domains are in the Firebase Auth authorised-domains list and
   signInWithPopup can succeed. */
.qh-alt { text-align:center; margin-top:16px; }
.qh-alt button { background:none; border:none; color:var(--link); font:inherit; font-size:12px; cursor:pointer; text-decoration:underline; text-underline-offset:2px; }
.qh-pw { display:none; margin-top:16px; border-top:1px solid var(--border); padding-top:16px; }
.qh-pw .field { margin-bottom:12px; }
.qh-err { color:var(--red); font-size:12px; margin-top:12px; min-height:17px; }
.qh-foot { margin-top:26px; padding-top:16px; border-top:1px solid var(--border); font-size:11px; color:var(--faint); line-height:1.7; }

/* buildFooter runs on every page, and on the sign-in page there is no
   .content to put the footer in, so it lands on <body> - below a card that is
   already min-height:100vh, which put the one line of copyright off the
   bottom of the screen behind a scrollbar.
   
   Making the body a column and letting the card take the slack keeps both on
   screen at any height, with no fixed positioning to collide with the card on
   a short window. */
body.login { display:flex; flex-direction:column; min-height:100vh; }
/* ⚠️ min-height:0 is doing real work. .qh-login is min-height:100vh for its
   own sake, and body.login is another 100vh around it - so with the footer
   below, the page was a footer's height taller than the viewport and scrolled
   on an empty screen. Letting the card section shrink to the space actually
   left is what removes the scrollbar. */
body.login .qh-login { flex:1 1 auto; min-height:0; }
body.login .cg-foot { justify-content:center; border-top:none; margin:0; padding:0 24px 26px; }


/* ── 7. QA PAGES ────────────────────────────────────────────────────────── */

/* The section header on My tests. There is one section, so it is not a tab -
   it is the page saying what it holds and how much of it there is. Sized to
   be read from across a desk, because the count is the whole point. */
.qa-sec {
  display:flex; align-items:center; gap:15px;
  padding:16px 20px; background:var(--s2); border-radius:var(--r-lg);
  margin-bottom:20px;
}
.qa-sec-ico {
  flex:0 0 40px; width:40px; height:40px; border-radius:var(--r);
  background:var(--grass); color:var(--s1);
  display:flex; align-items:center; justify-content:center;
}
html[data-theme="dark"] .qa-sec-ico { color:#141311; }
.qa-sec-ico svg { width:20px; height:20px; }
.qa-sec-main { flex:1; min-width:0; }
.qa-sec-t { font-size:17px; font-weight:650; letter-spacing:-0.022em; margin:0; }
.qa-sec-s { font-size:12.5px; color:var(--dim); margin-top:3px; line-height:1.5; }
.qa-sec-n {
  font-size:30px; font-weight:600; letter-spacing:-0.03em; line-height:1;
  font-variant-numeric:tabular-nums; color:var(--text);
}

/* An outstanding test: facts on the left, work on the right. */
.qa-card-split { display:grid; grid-template-columns:186px 1fr; gap:24px; }
.qa-side {
  display:flex; flex-direction:column; gap:12px;
  padding-right:22px; border-right:1px solid var(--border);
}
.qa-side-kv { font-size:11.5px; color:var(--faint); }
/* ⚠️ Direct child only. As `.qa-side-kv span` this also matched the platform
   mark's own wrapper - two classes and an element beats one class - and set
   it to display:block, which stacked the Apple and Android logos vertically
   on any test marked for both. */
.qa-side-kv > span { display:block; margin-bottom:4px; }
.qa-side-kv b { display:block; font-size:12.5px; font-weight:550; color:var(--text); }
.qa-body { min-width:0; }
.qa-body .qa-title { margin-bottom:14px; }

/* The platform, as its marks. */
.qa-plat { display:inline-flex; align-items:center; gap:7px; color:var(--text); }
.qa-plat svg { width:15px; height:15px; display:block; }
/* Apple's mark is optically heavier and sits a touch high on its own box. */
.qa-plat svg:first-child { margin-top:-1px; }
.qa-plat-none { color:var(--faint); }

/* The test id is the identifier people quote, so it gets the monospace and
   the weight rather than being one grey line among four. */
.qa-side-id b.qa-ref {
  font-size:13px; font-weight:650; color:var(--text);
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace; letter-spacing:-0.01em;
}

/* The three answers.
   One row, three equal blocks, each big enough to be a decision rather than a
   toolbar item. They used to be 7px-tall chips in a row of six alongside
   Cancel, Raise Issue and Question, which made answering a test look like the
   same size of act as clearing the form.

   Coloured at rest. They were neutral until hover on the argument that a
   green Passed beside a red Failed reads as a result the test already has.
   In use it reads as three identical grey buttons you have to finish reading
   to tell apart, which is worse: the colour is the fastest way to find the
   one you want, and the confirm dialog is what stops a mis-click becoming an
   answer. The fill is the pale status tint, not the solid one - three solid
   blocks would be the loudest thing on a page of someone's own writing. */
.qa-answers { display:grid; grid-template-columns:repeat(3,1fr); gap:10px; margin-top:4px; }
.qa-answer {
  display:flex; align-items:center; justify-content:center; gap:9px;
  padding:14px 12px; font:inherit; font-size:13.5px; font-weight:600;
  border:1px solid transparent; border-radius:var(--r); cursor:pointer;
  transition:background .12s, border-color .12s, box-shadow .12s;
}
.qa-answer svg { width:18px; height:18px; flex:0 0 18px; }
.qa-answer:disabled { opacity:.45; cursor:default; }
.qa-answer:focus-visible { outline:2px solid var(--link); outline-offset:2px; }

.qa-answer.ok   { background:var(--green-bg); border-color:var(--green-bdr); color:var(--green); }
.qa-answer.bad  { background:var(--red-bg);   border-color:var(--red-bdr);   color:var(--red); }
.qa-answer.warn { background:var(--amber-bg); border-color:var(--amber-bdr); color:var(--amber); }
/* Hover deepens the edge rather than changing the fill, so the button does
   not appear to change meaning under the pointer. */
.qa-answer.ok:hover:not(:disabled)   { border-color:var(--green); }
.qa-answer.bad:hover:not(:disabled)  { border-color:var(--red); }
.qa-answer.warn:hover:not(:disabled) { border-color:var(--amber); }
.qa-answer:hover:not(:disabled) { box-shadow:var(--shadow-sm); }
.qa-answer:active:not(:disabled) { transform:translateY(1px); box-shadow:none; }

/* The attach control. A proper button rather than a blue link with a
   paperclip: it is the only other thing on the card you can press, and it was
   drawn as the quietest element on it. */
.qa-file {
  display:inline-flex; align-items:center; gap:8px;
  padding:9px 14px; border:1px solid var(--border); border-radius:var(--r-sm);
  background:var(--s1); color:var(--text); font-size:12.5px; font-weight:550;
  cursor:pointer; transition:background .12s, border-color .12s;
}
.qa-file:hover { background:var(--s2); border-color:#D9D5CD; }
html[data-theme="dark"] .qa-file:hover { border-color:#45423D; }
.qa-file:focus-within { outline:2px solid var(--link); outline-offset:2px; }
.qa-file svg { width:15px; height:15px; color:var(--dim); }
.qa-shots { display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin:12px 0 0; }
.qa-filenames { font-size:12px; color:var(--dim); }
.qa-filenames.bad { color:var(--red); }
.qa-outcome-l { margin-top:18px; }

@media (max-width:620px) { .qa-answers { grid-template-columns:1fr; } }

/* The deadline on a tester's card: the date, and how long is left under it.
   Two lines, because "7 Novin 2d" is what one line gets you. */
.qa-due em {
  display:block; font-style:normal; font-size:11px; font-weight:500;
  color:var(--faint); margin-top:2px;
}
.qa-due.soon em { color:var(--amber); font-weight:600; }
.qa-due.late em { color:var(--red); font-weight:600; }

/* The deep-link landing flash. A smooth scroll puts the card on screen; this
   says WHICH card, for the second after it stops moving. */
.qa-card-flash { animation:qa-flash 1.6s ease-out; }
@keyframes qa-flash {
  0%, 30% { border-color:var(--grass); box-shadow:0 0 0 3px var(--s3); }
  100%    { border-color:var(--border); box-shadow:none; }
}
.qa-link { color:var(--link); font-size:12.5px; }


/* `.dim` - secondary text inside an error or empty state. It was defined in
   feedback.css, which the QA pages do not load, so every "Could not load your
   tests" on this site rendered its reason in full-weight body text. One
   definition, site-wide, beside the chip that had the same problem. */
.dim { color:var(--faint); }

/* Filter chips. Used by the feedback inbox, the surveys page and the
   completed-tests scope toggle, so defined here rather than in any one of
   their stylesheets. */
.fb-filters { display:flex; gap:7px; margin-bottom:16px; flex-wrap:wrap; }
.fb-filter {
  padding:6px 13px; font:inherit; font-size:12px; font-weight:550;
  border:1px solid var(--border); background:var(--s1); color:var(--dim);
  border-radius:var(--r-pill); cursor:pointer; transition:background .12s, color .12s;
}
.fb-filter:hover { background:var(--s2); color:var(--text); }
.fb-filter:focus-visible { outline:2px solid var(--link); outline-offset:1px; }
.fb-filter.on { background:var(--grass); border-color:var(--grass); color:var(--s1); }
html[data-theme="dark"] .fb-filter.on { color:#141311; }
.fb-filter .c { opacity:.6; font-variant-numeric:tabular-nums; margin-left:4px; }


/* Labels in sentence case, at normal tracking.
   qa.css sets six of these in uppercase at .04-.09em; they are overridden
   here rather than edited there because qa.css is shared with the platform
   console's copy of these pages and this is QA Hub's skin. */
.qa-h, .qa-stat span, .qa-ver-l, .qa-desc-l, .qa-outcome-l, .qa-req-l {
  text-transform:none; letter-spacing:-0.005em; font-weight:600;
}
.qa-h { font-size:12px; color:var(--dim); }
.qa-ver-l, .qa-desc-l, .qa-outcome-l { font-size:11.5px; }
.qa-req-l { font-size:11.5px; }


/* ── 8. COMPLETED TESTS ─────────────────────────────────────────────────── */

/* Charts are built from HTML boxes, not SVG.
   The figures are three bar charts and a split bar, all of which are rectangles
   with a width or a height - which is what a div is. Drawn in SVG they would
   need a viewBox, and a viewBox either scales the type with the drawing or
   needs its text positioned in user units that stop matching the page's at any
   other window width. Here the type is page type and the geometry is the
   browser's. The colours come from the same tokens as everything else, so dark
   mode is not a second palette to keep in step.

   ⚠️ Status colours carry a mark and a label EVERYWHERE they appear. Failed
   and Inconclusive are 1.4 apart in deuteranopic colour space, which is to
   say identical to a red-green colourblind reader, so the fill is never the
   only thing saying which is which: each segment is labelled, the legend
   repeats the names in block order, and Inconclusive is hatched as well as
   amber. */

.cm-stats { display:grid; grid-template-columns:repeat(auto-fit,minmax(158px,1fr)); gap:14px; margin-bottom:22px; }

.cm-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(300px,1fr)); gap:16px; margin-bottom:22px; }
.cm-panel { background:var(--s1); border:1px solid var(--border); border-radius:var(--r-lg); padding:18px 20px 20px; }
.cm-panel.wide { grid-column:1 / -1; }
.cm-h { font-size:13px; font-weight:600; letter-spacing:-0.01em; margin:0 0 3px; }
.cm-sub { font-size:11.5px; color:var(--faint); margin-bottom:16px; line-height:1.5; }

/* The outcome split: one whole, divided. 28px, 4px corners, a 2px gap in the
   page colour between blocks rather than a stroke around each. */
.cm-split { display:flex; gap:2px; height:28px; margin-bottom:14px; }
.cm-seg {
  display:flex; align-items:center; border-radius:4px; overflow:visible;
  padding:0 9px; font-size:11.5px; font-weight:600; color:var(--s1);
  white-space:nowrap; min-width:4px;
}
html[data-theme="dark"] .cm-seg { color:#141311; }
/* Anything the three named verdicts do not cover - the retired statuses, and
   any added server side since. Grey on purpose: it is a residue, not a fourth
   outcome, and giving it a hue of its own would invite reading it as one. */
.cm-seg.quiet { background:var(--s3); color:var(--dim); }
html[data-theme="dark"] .cm-seg.quiet { color:var(--dim); }
.cm-seg.ok  { background:var(--green); }
.cm-seg.bad { background:var(--red); }
/* Hatched, not just amber. This is the secondary encoding that makes the
   amber/red pair readable to a colourblind reader. */
.cm-seg.warn {
  background:var(--amber);
  background-image:repeating-linear-gradient(135deg,
    rgba(255,255,255,.22) 0 3px, transparent 3px 7px);
}
.cm-legend { display:flex; flex-wrap:wrap; gap:16px; font-size:11.5px; color:var(--dim); }
.cm-legend span { display:inline-flex; align-items:center; gap:7px; }
/* The NAME and the count stay in text ink; only the mark and the swatch wear
   the status colour. A label set in #A8301F on white is a legend you squint
   at, and the colour is already carried by the two things beside it.
   ⚠️ The colour goes on the SVG, not on the row. `.cm-lg.ok` is two classes
   to `.cm-lg`'s one, so setting it on the row would win and paint the label
   as well - which is the rule this comment exists to prevent. */
.cm-lg { color:var(--dim); }
.cm-lg b { color:var(--text); font-weight:600; font-variant-numeric:tabular-nums; }
.cm-lg.ok   svg { color:var(--green); }
.cm-lg.bad  svg { color:var(--red); }
.cm-lg.warn svg { color:var(--amber); }
.cm-lg.quiet svg{ color:var(--faint); }
.cm-key { width:10px; height:10px; flex:0 0 10px; border-radius:2px; }
.cm-key.ok { background:var(--green); } .cm-key.bad { background:var(--red); }
.cm-key.quiet { background:var(--s3); border:1px solid var(--border); }
.cm-key.warn { background:var(--amber); background-image:repeating-linear-gradient(135deg,rgba(255,255,255,.3) 0 2px,transparent 2px 5px); }
.cm-legend svg { width:12px; height:12px; }

/* Columns. One series, so one hue and no legend - the panel heading names it.
   Rounded at the cap, square at the baseline, because the baseline is zero
   and a rounded foot would lift the bar off it. */
.cm-cols { display:flex; align-items:flex-end; gap:8px; height:132px; padding-top:18px; border-bottom:1px solid var(--border); }
.cm-col { flex:1; display:flex; flex-direction:column; justify-content:flex-end; align-items:center; height:100%; min-width:0; }
.cm-bar {
  width:100%; max-width:24px; background:var(--grass);
  border-radius:4px 4px 0 0; min-height:0; transition:opacity .1s;
}
.cm-bar.zero { height:2px; background:var(--border); border-radius:0; }

/* A column split by verdict. column-reverse so the first segment written is
   the one at the baseline, and the 4px cap is clipped by the stack rather
   than set on a segment - the top segment is whichever verdict happens to be
   last that week, and putting the radius on a segment would round whichever
   one that was even when it sat in the middle.

   2px of surface between segments, the same spacer the Outcomes bar uses. */
.cm-stack {
  width:100%; max-width:24px; display:flex; flex-direction:column-reverse;
  gap:2px; border-radius:4px 4px 0 0; overflow:hidden; min-height:4px;
  transition:opacity .1s;
}
.cm-col:hover .cm-stack { opacity:.75; }
.cm-bseg { display:block; width:100%; min-height:2px; }
.cm-bseg.ok  { background:var(--green); }
.cm-bseg.bad { background:var(--red); }
/* Hatched as well as amber: beside Failed the two are indistinguishable to a
   red-green colourblind reader on colour alone. */
.cm-bseg.warn {
  background:var(--amber);
  background-image:repeating-linear-gradient(135deg,
    rgba(255,255,255,.22) 0 3px, transparent 3px 7px);
}
.cm-bseg.quiet { background:var(--s3); box-shadow:inset 0 0 0 1px var(--border); }
.cm-col:hover .cm-bar { opacity:.75; }
/* In flow above the bar, not absolutely placed at the top of the column.
   Absolute put it at the top of the SLOT, which for the tallest column - the
   only one that gets a label - is exactly where that bar already is, so the
   one number on the chart was printed in near-black on a near-black bar. */
.cm-cap { margin-bottom:4px; font-size:11px; font-weight:600; color:var(--text); font-variant-numeric:tabular-nums; line-height:1; }
.cm-xlabels { display:flex; gap:8px; margin-top:8px; }
/* nowrap, and small enough that eight of them fit a 300px panel. Wrapping
   put "28 Sep" on two lines, which pushed that one column's label down and
   left the axis reading as a ragged edge rather than a row. */
.cm-xlabels span { flex:1; min-width:0; text-align:center; font-size:10px; color:var(--faint); white-space:nowrap; }

/* Horizontal bars, value at the tip. */
.cm-hrow { display:grid; grid-template-columns:minmax(72px,104px) 1fr 42px; gap:12px; align-items:center; margin-bottom:9px; font-size:12.5px; }
.cm-hrow .nm { color:var(--dim); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.cm-track { display:block; height:16px; background:var(--s3); border-radius:3px; overflow:hidden; }
.cm-fill { display:block; height:100%; background:var(--grass); border-radius:0 4px 4px 0; min-width:2px; }
/* The deadline chart's buckets are states, not series, so they wear the status
   palette. Overdue and Next 2 days are the red/amber pair, which a red-green
   colourblind reader cannot separate on hue, so the amber is hatched and both
   rows carry a mark and a word in the label. */
.cm-fill.bad   { background:var(--red); }
.cm-fill.warn  {
  background:var(--amber);
  background-image:repeating-linear-gradient(135deg,
    rgba(255,255,255,.26) 0 3px, transparent 3px 7px);
}
.cm-fill.ok    { background:var(--green); }
.cm-fill.quiet { background:var(--s3); box-shadow:inset 0 0 0 1px var(--border); }
/* The mark wears the status colour; the WORD stays in text ink. A label set in
   red on cream is a label you squint at, and the colour is already carried by
   the mark beside it and the bar to its right. */
.cm-hrow .nm { display:inline-flex; align-items:center; gap:6px; }
.cm-hrow .nm svg { width:12px; height:12px; flex:0 0 12px; }
.cm-hrow .nm.bad   svg { color:var(--red); }
.cm-hrow .nm.warn  svg { color:var(--amber); }
.cm-hrow .nm.ok    svg { color:var(--green); }
.cm-hrow .nm.quiet svg { color:var(--faint); }
.cm-hrow .v { text-align:right; font-size:12px; color:var(--text); font-weight:550; font-variant-numeric:tabular-nums; }

/* The hover layer. Every mark has one; without it a bar is a shape with no
   number on it unless it happened to be the labelled one. */
.cm-tip {
  position:fixed; z-index:60; pointer-events:none; opacity:0;
  background:var(--olive); color:#FFF; font-size:11.5px; line-height:1.5;
  padding:7px 10px; border-radius:var(--r-sm); box-shadow:var(--shadow);
  transition:opacity .1s; max-width:220px;
}
html[data-theme="dark"] .cm-tip { background:#3A3835; }
.cm-tip.on { opacity:1; }
.cm-tip b { font-weight:600; }

/* The records table. It is also the table view the charts owe the reader:
   every figure above is in here as a row. */
.cm-tbl { width:100%; }
.cm-thead, .cm-trow { display:grid; grid-template-columns:130px 1fr 96px 86px 110px 132px; gap:14px; align-items:center; }
.cm-thead { padding:8px 12px; font-size:11px; color:var(--faint); font-weight:500; }
.cm-trow { padding:11px 12px; border-top:1px solid var(--border); font-size:13px; }
.cm-trow:hover { background:var(--s2); }
.cm-id {
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:11.5px;
  background:none; border:none; padding:0; text-align:left; cursor:pointer;
  color:var(--link); font-weight:600; text-decoration:underline;
  text-decoration-thickness:1px; text-underline-offset:2px;
}
.cm-id:hover { text-decoration-thickness:2px; }
.cm-tname { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.cm-tdim { font-size:12px; color:var(--dim); }

/* The detail view. A view, not a drawer: it owns the whole content area and a
   URL of its own, so it can be linked to from the overview and the back
   button does the obvious thing. */
.cm-back { display:inline-flex; align-items:center; gap:7px; background:none; border:none; padding:0; margin-bottom:18px; font:inherit; font-size:12.5px; color:var(--dim); cursor:pointer; }
.cm-back:hover { color:var(--text); }
.cm-back svg { width:14px; height:14px; }
.cm-d-head { display:flex; align-items:flex-start; gap:16px; margin-bottom:20px; }
.cm-d-head .g { flex:1; min-width:0; }
.cm-d-ref { font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:11.5px; color:var(--faint); margin-bottom:5px; }
.cm-d-t { font-size:21px; font-weight:650; letter-spacing:-0.025em; margin:0; line-height:1.3; }
.cm-kv { display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:1px; background:var(--border); border:1px solid var(--border); border-radius:var(--r); overflow:hidden; margin-bottom:22px; }
.cm-kv > div { background:var(--s1); padding:12px 14px; }
.cm-kv dt { font-size:11px; color:var(--faint); margin-bottom:5px; }
.cm-kv dd { margin:0; font-size:13px; font-weight:550; color:var(--text); }
.cm-d-l { font-size:11.5px; font-weight:600; color:var(--dim); margin:0 0 9px; }
.cm-d-sec { margin-bottom:22px; }

/* The trail. What happened to this test, in the order it happened.
   The connector is drawn per ITEM, running from under its own dot into the
   next one, and omitted on the last. Drawn once behind the whole list
   instead, it has to guess where the final dot is from the bottom of the
   list, and the guess is wrong by however tall the last timestamp is - so
   the line hung below the last event with nothing on the end of it. */
.tr { list-style:none; margin:0; padding:0; }
.tr-i { position:relative; display:flex; gap:16px; padding:0 0 16px; }
.tr-i::before {
  content:""; position:absolute; left:5px; top:16px; bottom:-2px;
  width:1px; background:var(--border);
}
.tr-i:last-child::before { display:none; }
.tr-i:last-child { padding-bottom:0; }
.tr-dot {
  position:relative; z-index:1; flex:0 0 11px; width:11px; height:11px;
  margin-top:4px; border-radius:var(--r-pill);
  background:var(--s1); border:2px solid var(--border); box-sizing:border-box;
}
/* The verdict is the one event on the trail that carries a colour, because it
   is the one that is a result rather than a step. */
.tr-i.ok   .tr-dot { border-color:var(--green); background:var(--green); }
.tr-i.bad  .tr-dot { border-color:var(--red);   background:var(--red); }
.tr-i.warn .tr-dot { border-color:var(--amber); background:var(--amber); }
.tr-b { min-width:0; font-size:13px; line-height:1.5; color:var(--text); }
.tr-b b { font-weight:600; }
.tr-b code {
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:12px;
  background:var(--s2); border:1px solid var(--border); padding:1px 6px;
}
.tr-at { display:block; font-size:11.5px; color:var(--faint); margin-top:3px; }

/* The tester's response. The loudest thing on the record, deliberately.
   It is the one part written by a person rather than recorded by the system,
   and it was a grey paragraph under a grey heading, quieter than the table of
   facts above it. Icon, label, and a rule running to the right edge, then the
   text a size up with room around it. */
.cm-resp { margin-bottom:24px; }
.cm-resp-h {
  display:flex; align-items:center; gap:9px; margin-bottom:12px;
  font-size:11px; font-weight:700; letter-spacing:.09em; color:var(--text);
}
.cm-resp-h svg { width:16px; height:16px; flex:0 0 16px; }
/* The rule fills whatever the label leaves. */
.cm-resp-h i { flex:1; height:1px; background:var(--border); }
.cm-resp-t {
  font-size:15px; line-height:1.7; white-space:pre-wrap; color:var(--text);
  border-left:3px solid var(--text); padding:2px 0 2px 16px;
}

/* Met or missed, beside the date. */
.cm-due { margin-left:9px; font-size:10.5px; font-weight:700; letter-spacing:.04em; padding:2px 7px; }
.cm-due.met  { background:var(--green-bg); color:var(--green); }
.cm-due.late { background:var(--red-bg);   color:var(--red); }

@media (max-width:860px) {
  .qa-card-split { grid-template-columns:1fr; gap:16px; }
  .qa-side { flex-direction:row; flex-wrap:wrap; gap:18px; padding-right:0; padding-bottom:14px; border-right:none; border-bottom:1px solid var(--border); }
  .cm-thead { display:none; }
  .cm-trow { grid-template-columns:1fr auto; gap:6px 12px; }
}


/* ── 9. QA MANAGER, AND WRITING A TEST ──────────────────────────────────── */

.mg-bar { display:flex; align-items:center; gap:14px; margin:0 0 16px; flex-wrap:wrap; }
.mg-bar .fb-filters { flex:1; }
.mg-new { display:inline-flex; align-items:center; gap:7px; margin-left:auto;
          text-decoration:none; white-space:nowrap; }
.mg-new svg { width:14px; height:14px; }

/* The manager's list is the same table as the completed-tests one, with
   Answers in place of Answered. Defined as an override rather than a second
   table so the two consoles cannot drift apart a column at a time. */
.cm-tbl.mg .cm-thead, .cm-tbl.mg .cm-trow {
  grid-template-columns:130px 1fr 80px 80px 132px 112px;
}
.mg-prog { display:flex; align-items:center; gap:9px; }
.mg-track { flex:1; height:6px; background:var(--s3); border-radius:var(--r-pill); overflow:hidden; }
.mg-fill { display:block; height:100%; background:var(--grass); border-radius:var(--r-pill); }
/* One failure fails the batch, and the bar says so without being read. */
.mg-fill.bad { background:var(--red); }
.mg-prog-l { font-size:11.5px; color:var(--dim); font-variant-numeric:tabular-nums; }

@media (max-width:980px) {
  .cm-tbl.mg .cm-thead { display:none; }
  .cm-tbl.mg .cm-trow { grid-template-columns:1fr auto; gap:6px 12px; }
}


/* Writing a test. A form on a page, with room to think. */

.nt-wrap { max-width:780px; }
.nt-form { display:block; }
.nt-sec {
  background:var(--s1); border:1px solid var(--border); border-radius:var(--r-lg);
  padding:20px 22px 22px; margin-bottom:16px;
}
.nt-h { font-size:13px; font-weight:600; letter-spacing:-0.01em; margin:0 0 16px;
        padding-bottom:12px; border-bottom:1px solid var(--border); }

.nt-f { display:block; margin-bottom:16px; }
.nt-f:last-child { margin-bottom:0; }
.nt-f > span { display:block; font-size:12px; font-weight:600; color:var(--text); margin-bottom:6px; }
/* "required" rides the label rather than being a star the reader has to
   decode, and it is the only thing on the line that is not in text ink. */
.nt-f > span i { font-style:normal; font-weight:500; color:var(--faint); margin-left:6px; }
.nt-f em { display:block; font-style:normal; font-size:11.5px; color:var(--faint);
           margin-top:5px; line-height:1.5; }
.nt-f .qa-input { width:100%; }
.nt-grid2 { display:grid; grid-template-columns:1fr 1fr; gap:14px; }

/* The two outcome boxes are a pair, and they are the pair the tester is
   choosing between. Tinted to match the boxes they will read on the card. */
.nt-f-pass .qa-input { border-left:3px solid var(--green); }
.nt-f-fail .qa-input { border-left:3px solid var(--red); }

/* Platform as three cards carrying the marks, not a <select>. The tester's
   first question is which device to pick up; a menu hides the answer behind
   a click on the page where it is decided. */
.nt-plats { display:grid; grid-template-columns:repeat(3,1fr); gap:10px; }
.nt-plat input { position:absolute; opacity:0; pointer-events:none; }
.nt-plat-box {
  display:flex; flex-direction:column; align-items:center; gap:9px;
  padding:14px 10px; border:1px solid var(--border); border-radius:var(--r);
  background:var(--s1); cursor:pointer; transition:border-color .12s, background .12s;
}
.nt-plat-box b { font-size:12px; font-weight:550; }
.nt-plat-box .qa-plat svg { width:19px; height:19px; }
.nt-plat-box:hover { background:var(--s2); }
.nt-plat input:checked + .nt-plat-box { border-color:var(--grass); background:var(--s2); }
.nt-plat input:focus-visible + .nt-plat-box { outline:2px solid var(--link); outline-offset:2px; }

.nt-check { display:flex; align-items:center; gap:9px; font-size:13px; cursor:pointer; }

/* Who it goes to. Faces and addresses, because two people in a department of
   this size can share a first name and the uid is no help to anybody. */
.nt-who-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(210px,1fr)); gap:9px; }
.nt-who input { position:absolute; opacity:0; pointer-events:none; }
.nt-who-box {
  display:flex; align-items:center; gap:10px; padding:9px 11px;
  border:1px solid var(--border); border-radius:var(--r); background:var(--s1);
  cursor:pointer; transition:border-color .12s, background .12s;
}
.nt-who-box:hover { background:var(--s2); }
.nt-who input:checked + .nt-who-box { border-color:var(--grass); background:var(--s2); }
.nt-who input:focus-visible + .nt-who-box { outline:2px solid var(--link); outline-offset:2px; }
.nt-who-n { min-width:0; font-size:12.5px; font-weight:550; line-height:1.3; }
.nt-who-n em { display:block; font-style:normal; font-size:11px; font-weight:400;
               color:var(--faint); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.nt-count { font-size:11.5px; color:var(--faint); margin-top:10px; }

.nt-foot { display:flex; align-items:center; gap:10px; padding-bottom:20px; }
.nt-note { font-size:12.5px; color:var(--dim); }
.nt-note.bad { color:var(--red); }

.nt-done { max-width:460px; padding:40px 0; }
.nt-tick {
  display:flex; align-items:center; justify-content:center;
  width:46px; height:46px; border-radius:var(--r-pill);
  background:var(--green-bg); color:var(--green); margin-bottom:18px;
}
.nt-tick svg { width:24px; height:24px; }
.nt-done h2 { font-size:19px; font-weight:650; letter-spacing:-0.02em; margin:0 0 7px; }
.nt-done p { font-size:13px; color:var(--dim); line-height:1.6; margin:0 0 22px; }
.nt-done-act { display:flex; gap:10px; }

@media (max-width:620px) {
  .nt-grid2, .nt-plats { grid-template-columns:1fr; }
}


/* ── 9b. ANNOUNCEMENT ───────────────────────────────────────────────────── */

/* One notice for the department: shown on the overview, written in QA Manager.

   A straight 3px rule down the left edge, and the corners on that edge are
   square so the rule is a line rather than a tab with two bends in it. The
   same treatment as .qa-req, .fb-row and the rest - "straight line, not a
   corner border" applies here too. */
.ov-ann {
  position:relative; margin:0 0 26px; padding:16px 20px 15px 21px;
  background:var(--s2); border:1px solid var(--border);
  border-left:3px solid var(--dim);
  border-radius:var(--r-lg); border-top-left-radius:0; border-bottom-left-radius:0;
}
.ov-ann.info { background:var(--blue-bg);  border-color:var(--blue-bdr);  border-left-color:var(--blue); }
.ov-ann.warn { background:var(--amber-bg); border-color:var(--amber-bdr); border-left-color:var(--amber); }
.ov-ann.bad  { background:var(--red-bg);   border-color:var(--red-bdr);   border-left-color:var(--red); }

/* The word ANNOUNCEMENT, set small and tracked out. It is a sign over the
   block, not a heading competing with the text under it. */
.ov-ann-h {
  display:flex; align-items:center; gap:8px; margin-bottom:9px;
  font-size:10.5px; font-weight:700; letter-spacing:0.09em; color:var(--dim);
}
.ov-ann-h svg { width:14px; height:14px; flex:0 0 14px; }
.ov-ann.info .ov-ann-h svg { color:var(--blue); }
.ov-ann.warn .ov-ann-h svg { color:var(--amber); }
.ov-ann.bad  .ov-ann-h svg { color:var(--red); }

/* How serious it is, as a WORD. The tint behind the block says it too, but a
   reader who cannot tell amber from red gets it from here. Square, like every
   other status block on this site. */
.ov-ann-lv {
  margin-left:auto; padding:3px 8px; border-radius:0;
  font-size:10px; font-weight:700; letter-spacing:0.06em;
  background:var(--s1); border:1px solid var(--border); color:var(--dim);
}
.ov-ann.info .ov-ann-lv { color:var(--blue);  border-color:var(--blue-bdr); }
.ov-ann.warn .ov-ann-lv { color:var(--amber); border-color:var(--amber-bdr); }
.ov-ann.bad  .ov-ann-lv { color:var(--red);   border-color:var(--red-bdr); }

/* The notice itself, in text ink rather than the level's colour. Six lines of
   amber prose is six lines nobody finishes. */
.ov-ann-t {
  margin:0; font-size:13.5px; line-height:1.6; color:var(--text);
  white-space:pre-wrap; overflow-wrap:anywhere;
}
.ov-ann-by { margin-top:10px; font-size:11px; color:var(--faint); }

/* ── The editor, in QA Manager ──────────────────────────────────────────── */

.mg-ann { margin:0 0 22px; padding:17px 20px 18px; background:var(--s1);
  border:1px solid var(--border); border-radius:var(--r-lg); }
.mg-ann-h { display:flex; align-items:center; gap:10px; margin-bottom:4px; }
.mg-ann-h h2 { font-size:13px; font-weight:600; letter-spacing:-0.01em; margin:0; }
.mg-ann-live {
  padding:2px 7px; border-radius:0; font-size:10px; font-weight:700;
  letter-spacing:0.06em; background:var(--green-bg); color:var(--green);
  border:1px solid var(--green-bdr);
}
.mg-ann-live.off { background:transparent; color:var(--faint); border-color:var(--border); }
.mg-ann-sub { font-size:11.5px; color:var(--faint); margin-bottom:14px; line-height:1.5; }
.mg-ann textarea {
  width:100%; min-height:78px; resize:vertical; font:inherit; font-size:13px;
  line-height:1.6; color:var(--text);
}
.mg-ann-row {
  display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-top:12px;
}
/* The three levels, as a segmented control. Each carries its own mark, so the
   one that is selected is never selected by colour alone. */
.mg-lv {
  display:inline-flex; align-items:center; gap:6px; padding:7px 12px;
  font:inherit; font-size:12px; font-weight:550; cursor:pointer;
  background:var(--s1); border:1px solid var(--border); color:var(--dim);
  border-radius:var(--r-sm);
}
.mg-lv svg { width:13px; height:13px; flex:0 0 13px; }
.mg-lv:hover { background:var(--s2); }
.mg-lv.on { color:var(--text); border-color:var(--text); background:var(--s2); }
.mg-lv.on.info svg { color:var(--blue); }
.mg-lv.on.warn svg { color:var(--amber); }
.mg-lv.on.bad  svg { color:var(--red); }
.mg-ann-act { margin-left:auto; display:flex; gap:8px; }
.mg-ann-by { margin-top:11px; font-size:11px; color:var(--faint); }

/* ── 11. SERVICE DESK ───────────────────────────────────────────────────── */

/* Everything above is QA Hub's skin, forked as it stood. This section is the
   desk's own: the rail's section headings, the overview cards, the queue, the
   ticket thread and the compose form. */

/* Two series colours for "opened" and "closed". Run through the dataviz
   validator against the panel surface in each theme rather than picked by
   eye: lightness band, chroma floor, CVD separation (ΔE 22+), normal-vision
   separation (ΔE 27+) and 3:1 contrast all pass. Not status colours - these
   are two series - and the orange sits well clear of --amber so a week of
   closures does not read as a week of warnings. */
:root { --s-open:#1F5FA8; --s-closed:#C0652A; }
html[data-theme="dark"] { --s-open:#4A88D6; --s-closed:#D9763A; }

/* Rail: the mark with the name set in text, and a heading per desk. */
.desk-logo { gap:10px; text-decoration:none; }
.desk-logo img { width:26px; height:26px; flex:0 0 26px; }
.desk-logo span { font-size:16px; font-weight:650; letter-spacing:-0.02em; color:var(--text); white-space:nowrap; }
.rail-sec {
  margin:14px 10px 5px; font-size:10.5px; font-weight:700; letter-spacing:0.08em;
  text-transform:uppercase; color:var(--faint);
}
.rail-nav > .rail-sec:first-child { margin-top:4px; }

/* Page header: what this page is, which desk, and its one primary action. */
.dk-head { display:flex; align-items:center; gap:16px; margin-bottom:20px; flex-wrap:wrap; }
/* Icons in a header action are sized explicitly. Unsized, an inline SVG
   takes the width it is given, which turned "New email" into a 40px plus sign
   with the words wrapped underneath it. */
.dk-head .btn-primary, .dk-card-a a { white-space:nowrap; }
.dk-head .btn-primary svg { width:14px; height:14px; flex:0 0 14px; }
.dk-new { display:inline-flex; align-items:center; gap:7px; text-decoration:none; }
/* ⚠️ A permission-gated control is hidden with the `hidden` attribute, and
   any author `display` beats the browser's own [hidden] rule. This puts the
   attribute back on top for everything the gates touch. */
[data-needs][hidden] { display:none !important; }
.dk-head .g { flex:1; min-width:0; }
.dk-head h2 { font-size:20px; font-weight:650; letter-spacing:-0.025em; margin:0 0 3px; }
.dk-head p { margin:0; font-size:12.5px; color:var(--dim); }
.dk-head p code { font-size:11.5px; background:var(--s3); border:1px solid var(--border); padding:1px 6px; }

/* ── Overview ── */
.dk-cards { display:grid; grid-template-columns:repeat(auto-fit,minmax(320px,1fr)); gap:16px; margin-bottom:26px; }
.dk-card {
  background:var(--s1); border:1px solid var(--border); border-radius:var(--r-lg);
  padding:20px 22px; display:flex; flex-direction:column; gap:14px;
}
.dk-card-h { display:flex; align-items:baseline; gap:10px; }
.dk-card-h h3 { margin:0; font-size:15px; font-weight:650; letter-spacing:-0.015em; }
.dk-card-h span { font-size:11.5px; color:var(--faint); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.dk-big { display:flex; align-items:baseline; gap:10px; }
.dk-big b { font-size:40px; font-weight:650; letter-spacing:-0.04em; line-height:1; font-variant-numeric:tabular-nums; }
.dk-big b.zero { color:var(--faint); }
.dk-big span { font-size:12.5px; color:var(--dim); }
.dk-meta { display:grid; grid-template-columns:repeat(3,1fr); gap:1px; background:var(--border);
  border:1px solid var(--border); border-radius:var(--r); overflow:hidden; }
.dk-meta > div { background:var(--s1); padding:10px 12px; }
.dk-meta dt { font-size:10.5px; color:var(--faint); margin-bottom:4px; }
.dk-meta dd { margin:0; font-size:13px; font-weight:600; font-variant-numeric:tabular-nums; }
.dk-card-a { display:flex; gap:8px; flex-wrap:wrap; }
.dk-card-a a { text-decoration:none; display:inline-flex; align-items:center; gap:7px; }
.dk-card-a svg { width:14px; height:14px; }

/* ── Queue ── */
.dk-bar { display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:12px; }
.dk-bar .fb-filters { margin:0; }
.dk-search {
  flex:1 1 220px; max-width:340px; display:flex; align-items:center; gap:8px;
  background:var(--s1); border:1px solid var(--border); border-radius:var(--r-sm); padding:0 10px;
}
.dk-search svg { width:14px; height:14px; color:var(--faint); flex:0 0 14px; }
.dk-search input { flex:1; min-width:0; border:0; background:transparent; font:inherit; font-size:13px;
  color:var(--text); padding:8px 0; outline:none; }
.dk-select {
  font:inherit; font-size:12.5px; color:var(--text); background:var(--s1);
  border:1px solid var(--border); border-radius:var(--r-sm); padding:7px 10px;
}

.dk-tbl { width:100%; }
.dk-th, .dk-tr {
  display:grid; grid-template-columns:96px minmax(0,1fr) 150px 160px 168px 92px;
  gap:14px; align-items:center;
}
.dk-th { padding:8px 12px; font-size:11px; color:var(--faint); font-weight:500; }
/* An <a>: a ticket is a page with a URL, so it can be opened in a second
   tab, linked in a chat, and reached with the back button. Square, so the
   rule between rows runs the full width. */
.dk-tr {
  padding:12px; border-top:1px solid var(--border); color:var(--text); text-decoration:none;
  border-radius:0; transition:background .1s;
}
.dk-tr:hover { background:var(--s2); }
.dk-tr:focus-visible { outline:2px solid var(--link); outline-offset:-2px; }
.dk-ref { font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:11.5px; font-weight:600; }
.dk-subj { min-width:0; }
.dk-subj b { display:block; font-size:13.5px; font-weight:550; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.dk-subj span { display:block; font-size:11.5px; color:var(--faint); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; margin-top:2px; }
.dk-dim { font-size:12px; color:var(--dim); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.dk-age { font-size:12px; color:var(--dim); text-align:right; font-variant-numeric:tabular-nums; }
.dk-age.late { color:var(--red); font-weight:600; }
.dk-empty { padding:44px 12px; text-align:center; color:var(--faint); font-size:13px; border-top:1px solid var(--border); }

/* ── Ticket ── */
.dk-ticket { display:grid; grid-template-columns:minmax(0,1fr) 300px; gap:22px; align-items:start; }
.dk-thread { display:flex; flex-direction:column; gap:12px; min-width:0; }

/* One message. A straight 3px rule on the left says whose it is - the
   requester's in their tone, ours in the desk's - and the corners on that
   edge are square so the rule is a line, not a tab. */
.dk-msg {
  background:var(--s1); border:1px solid var(--border); border-left:3px solid var(--border);
  border-radius:var(--r); border-top-left-radius:0; border-bottom-left-radius:0;
  padding:14px 16px 15px;
}
.dk-msg.in  { border-left-color:var(--s-open); }
.dk-msg.out { border-left-color:var(--grass); background:var(--s2); }
.dk-msg-h { display:flex; align-items:center; gap:8px; margin-bottom:9px; font-size:12px; color:var(--dim); flex-wrap:wrap; }
.dk-msg-h b { color:var(--text); font-weight:600; }
.dk-msg-h .who-tag {
  font-size:10px; font-weight:700; letter-spacing:0.06em; padding:2px 6px;
  border:1px solid var(--border); color:var(--dim); border-radius:0;
}
.dk-msg-h time { margin-left:auto; color:var(--faint); font-variant-numeric:tabular-nums; }
.dk-msg-b { font-size:13.5px; line-height:1.6; white-space:pre-wrap; overflow-wrap:anywhere; color:var(--text); }

.dk-reply { background:var(--s1); border:1px solid var(--border); border-radius:var(--r-lg); padding:14px 16px 16px; }
.dk-reply h3 { margin:0 0 4px; font-size:13px; font-weight:600; }
.dk-reply .hint { font-size:11.5px; color:var(--faint); margin:0 0 10px; line-height:1.5; }
.dk-reply textarea, .dk-form textarea {
  width:100%; min-height:150px; resize:vertical; font:inherit; font-size:13.5px; line-height:1.6;
}
.dk-reply-a { display:flex; align-items:center; gap:10px; margin-top:10px; }
.dk-reply-a .count { margin-left:auto; font-size:11px; color:var(--faint); font-variant-numeric:tabular-nums; }

.dk-side { position:sticky; top:0; display:flex; flex-direction:column; gap:14px; }
.dk-facts { background:var(--s1); border:1px solid var(--border); border-radius:var(--r-lg); overflow:hidden; }
.dk-facts > div { padding:11px 14px; border-top:1px solid var(--border); }
.dk-facts > div:first-child { border-top:0; }
.dk-facts dt { font-size:10.5px; color:var(--faint); margin-bottom:4px; }
.dk-facts dd { margin:0; font-size:13px; font-weight:550; overflow-wrap:anywhere; }
.dk-facts dd a { color:var(--link); }
.dk-facts select { width:100%; }

/* ── Compose ── */
.dk-form { max-width:720px; background:var(--s1); border:1px solid var(--border); border-radius:var(--r-lg); padding:20px 22px 22px; }
.dk-f { margin-bottom:14px; }
.dk-f label { display:block; font-size:12px; font-weight:600; margin-bottom:6px; }
.dk-f .hint { font-size:11.5px; color:var(--faint); margin-top:6px; line-height:1.5; }
.dk-f input, .dk-f select { width:100%; }
.dk-row2 { display:grid; grid-template-columns:1fr 1fr; gap:14px; }

/* ── Trends: grouped columns (opened beside closed, per week) ── */
.dk-gcol { flex:1; display:flex; flex-direction:column; justify-content:flex-end; align-items:center; height:100%; min-width:0; }
/* ⚠️ flex-shrink:0. The busiest week carries a number above its bars, in
   flow, and a shrinkable pair gave up exactly that much height to make room -
   so the one column labelled as the tallest was drawn shorter than its
   neighbours. Fixed at the full column, the label overflows into the
   container's top padding instead, which is what that padding is for. */
.dk-gpair { display:flex; align-items:flex-end; justify-content:center; gap:2px; width:100%; height:100%; flex-shrink:0; }
.dk-gbar { width:100%; max-width:12px; border-radius:3px 3px 0 0; transition:opacity .1s; }
.dk-gbar.o { background:var(--s-open); }
.dk-gbar.c { background:var(--s-closed); }
.dk-gbar.zero { background:var(--border); border-radius:0; }
.dk-gcol:hover .dk-gbar { opacity:.75; }
.cm-key.o { background:var(--s-open); } .cm-key.c { background:var(--s-closed); }
/* Category names here are longer than QA Hub's platform names, and at 104px
   "Account and sign-in" was clipped to "Account and sign-". */
.cm-hrow { grid-template-columns:minmax(96px,148px) 1fr 42px; }

/* ── 10. NARROW ─────────────────────────────────────────────────────────── */

@media (max-width:768px) {
  /* The rail slides over the page rather than squeezing it: a phone has about
     360px to spend and 216 of them on navigation is most of the screen. */
  .sidebar {
    position:fixed; inset:0 auto 0 0; transform:translateX(-100%);
    transition:transform .2s ease; box-shadow:var(--shadow-lg);
  }
  .sidebar.mobile-open { transform:translateX(0); }
  .sidebar-overlay { position:fixed; inset:0; background:rgba(26,25,23,.35); opacity:0; pointer-events:none; transition:opacity .2s; z-index:-1; }
  .sidebar-overlay.visible { opacity:1; pointer-events:auto; }
  /* It is the same 216px rail with the same labels, just sliding over the
     page instead of standing beside it. Nothing about it changes here. */

  .topbar { padding:14px 16px; }
  .content { padding:18px 16px 32px; }
  /* All three columns survive the narrow breakpoint - the test ID is the
     thing a tester came to the row for. They only get tighter. */
  .ov-id { flex:0 0 82px; }
  .ov-name { flex:1 1 auto; }
  .ov-pill-cell { flex:0 0 auto; }
  .ov-ann { padding:14px 16px; }
  /* Queue: the reference and subject carry the row on a phone. Status,
     category and the next-move pill fold away; the age stays because it is
     the one thing that decides what to open first. */
  .dk-th, .dk-tr { grid-template-columns:76px minmax(0,1fr) 64px; }
  .dk-th > :nth-child(3), .dk-th > :nth-child(4), .dk-th > :nth-child(5),
  .dk-tr > :nth-child(3), .dk-tr > :nth-child(4), .dk-tr > :nth-child(5) { display:none; }
  .dk-ticket { grid-template-columns:1fr; }
  .dk-side { position:static; order:-1; }
  .dk-row2 { grid-template-columns:1fr; }
  .dk-meta { grid-template-columns:1fr 1fr 1fr; }
}
