/* IT Helpdesk — design system
   ============================================================
   One token layer, consumed by every screen. Nothing below this block may
   hardcode a colour, a size or a radius: if a value is not a token, it is a
   bug. Two audiences share these tokens and diverge only in density -
   requesters get space and plain words, agents get rows and shortcuts.
   ============================================================ */

:root {
  /* Colour. Each one carries a meaning; none is decoration. */
  --plum:    #7d5168;   /* primary action, active nav, active tab - only  */
  /* The status fills, taken down the same way the tiles were and for the
     same reason. These were the loudest things in the app - a pure #dc2626
     on a badge is a colour from a warning triangle, not from a desk people
     sit in front of all day. Hue kept, chroma off: rose 81.8 -> 49.0,
     amber 65.0 -> 47.1, moss 52.5 -> 33.5. Each one still clears 4.5:1
     with white on it, and each is now DARKER than it was, which is what
     buys that. Their -ink variants below are measured separately: a hue
     you can fill with is not a hue you can read. */
  --steel:   #375873;   /* informational, "Open"                          */
  --rose:    #A63A42;   /* error, overdue, required marker                */
  --moss:    #2C6B45;   /* resolved, success                              */
  --amber:   #9A5B22;   /* in progress, approaching SLA                   */
  --grey:    #7C8082;   /* closed, disabled                               */
  --ink:     #241a20;   /* primary text                                   */
  --ink-2:   #6d5c65;   /* secondary text                                 */
  --ink-3:   #8B848A;   /* tertiary text, placeholders                    */
  --line:    #d9ccd3;   /* borders                                        */
  --canvas:  #f5f1f3;   /* page background                                */
  --surface: #FFFFFF;   /* cards, panels, table rows                      */
  --surface-2: #f0eaed; /* table heads, inset panels                      */
  --surface-3: #e5dbe1; /* a step deeper again                            */

  /* Derived, not new hues: a pressed step of plum and washes of each status
     for badge grounds. Mixing keeps them provably the same colours. */
  --plum-pressed: color-mix(in srgb, var(--plum) 86%, #000);
  --plum-wash:    color-mix(in srgb, var(--plum) 8%, var(--surface));
  --steel-wash:   color-mix(in srgb, var(--steel) 10%, var(--surface));
  --rose-wash:    color-mix(in srgb, var(--rose) 10%, var(--surface));
  --moss-wash:    color-mix(in srgb, var(--moss) 10%, var(--surface));
  --amber-wash:   color-mix(in srgb, var(--amber) 12%, var(--surface));
  --grey-wash:    color-mix(in srgb, var(--grey) 12%, var(--surface));
  --line-soft:    color-mix(in srgb, var(--line) 55%, var(--surface));
  --hover:        color-mix(in srgb, var(--plum) 4%, var(--surface));

  /* The two colours that must NOT follow the theme, because they are not on
     a screen. A QR sticker is printed and then scanned by a phone camera,
     and a scanner reads contrast: dark-theme ink on a dark-theme ground is
     a label that does not work. --surface and --ink both flip, so neither
     can be used here. Declared once in the token block like everything
     else, and deliberately not redefined under the dark selectors. */
  --paper:     #ffffff;
  --paper-ink: #000000;

  /* Type */
  --font: Inter, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* THE WEIGHT LADDER. Three steps, because the Service Dashboard tile uses
     exactly three and the instruction was to set the rest of the desk the
     same way: a 400 note under the figure, a 600 heading on the panel, and
     800 for the caption and the number - the name of a value, and the value.
     Nothing on the desk may set a weight that is not one of these. */
  --w-body:  400;   /* running text, and an incidental fact beside it */
  --w-head:  600;   /* a heading, a panel head, a control, an active item */
  --w-value: 800;   /* the name of a value, and the value itself */

  /* --mono is gone. The desk is set in one face; what used to reach for a
     monospace now asks for tabular figures instead, which is the only thing
     the monospace was actually doing - keeping a column of numbers from
     shuffling sideways as the digits change. */
  --t-11: 11px; --t-12: 12px; --t-13: 13px; --t-14: 14px;
  --t-16: 16px; --t-19: 19px; --t-24: 24px;

  /* Spacing - the 8px scale, and nothing between its steps. */
  --s-4: 4px;  --s-8: 8px;  --s-12: 12px; --s-16: 16px;
  --s-20: 20px; --s-24: 24px; --s-32: 32px; --s-40: 40px;

  /* Shape. One radius per element class, never mixed.
     These are the CMMS's own: 14px on a card, 10px on a control, a full
     round on a pill. Taken deliberately, overriding the 6/8/10 this file
     used to carry, so the two apps read as one system. */
  --r-control: 10px;
  --r-card:    14px;
  --r-shell:   14px;
  --r-pill:    999px;

  /* Shadows exist for overlays, dropdowns and toasts. Cards and rows get a
     border instead, which is why there is only one. */
  --lift: 0 4px 20px rgba(36, 26, 32, .10);

  /* Text that sits on a saturated brand fill - a primary button, the active
     nav item, an avatar. Near-white in both themes, because the plum stays
     dark in both. Named rather than literal so the dark block can move it. */
  --on-accent: #FFFFFF;
  --scrim:     rgba(36, 26, 32, .42);

  /* Plum when it has to be SEEN against a surface, rather than filled with.
     --plum is the fill: a button, the active nav item, a selected panel head,
     with --on-accent white on top, and it stays #664356 in both themes for
     the reason --on-accent gives. Against a dark ground it is dark plum on
     dark plum - 1.81:1 as text on its own wash, about 1.6:1 as a hover edge
     on a panel, both under their floors. This is the same hue lifted to the
     lowest step that clears 4.5:1 on the wash, the surface AND the canvas,
     which is what --steel, --rose and --moss already do for this theme. Not a
     new colour: the existing one, made legible. Used for plum text and for
     plum edges that have to register; fills keep --plum. */
  --plum-ink: #7d5168;

  /* The status hues when they have to be READ rather than filled with.
     The CMMS's own accents are fill colours - it pairs them with a 10-12%
     dim of themselves, which works. Where this app puts one straight onto a
     surface as text, the bright ones fall just under 4.5:1. These are the
     same hue, one shade further from the ground, computed rather than
     picked: the nearest step that clears the floor on the surface, the
     canvas AND the hue's own wash. The CMMS does exactly this for plum,
     which it calls --blue-on. */
  --moss-ink:  #116932;
  --rose-ink:  #b11c1c;
  --amber-ink: #974507;
  --steel-ink: var(--steel);   /* 6.12:1 already */

  /* The Q brand palette used to be here, four fills from
     "1. Q Brand Colours.xlsx" - one solid colour per tile. It went on
     instruction: asked for something more professional, twice, and the
     second answer was to stop filling with colour at all rather than to
     keep lowering the saturation. There was not much room left anyway -
     the measurements had Delayed and Open one step from reading as the
     same colour.

     The brand has not left the screen: --plum is still the primary action,
     the active nav item and the selected panel head, and it is Q Purple.
     What went is four hues used as backgrounds behind white text, which is
     the thing that made the dashboard look like a warning panel.

     What replaced them is directly below, on .cat-tile: the panel's own
     second surface, a structural left edge, and one accent on the single
     tile that means somebody has to act. */

  --topbar-h: 60px;
  --rail-w:   224px;

  /* ── Compatibility aliases ──────────────────────────────
     The screens were written against the old names. Pointing them at the
     tokens retints the whole desk at once and keeps one source of truth;
     new code should use the token names above. */
  --bg: var(--canvas);
  --panel: var(--surface);
  --ink-soft: var(--ink-2);
  --accent: var(--plum);
  /* The same split as --plum / --plum-ink: one to fill with, one to read. */
  --accent-ink: var(--plum-ink);
  --accent-in: var(--surface);
  --brand: var(--plum);
  --brand-2: var(--steel);
  --brand-2-ink: var(--steel-ink);
  --brand-err: var(--rose);
  --brand-err-ink: var(--rose-ink);
  --brand-mid: var(--grey);
  --brand-ink: var(--plum-pressed);
  --ok: var(--moss);
  --ok-ink: var(--moss-ink);
  --warn: var(--amber);
  --warn-ink: var(--amber-ink);
  --bad: var(--rose);
  --bad-ink: var(--rose-ink);
  /* The one place --bad is a solid FILL with white on it, rather than a wash
     or a border: the danger button's hover. In the dark theme --rose is a
     colour lifted so it can be READ on a dark ground, and filling with it
     put white text at 3.27:1 - the same fill-vs-ink mistake this sheet has
     made before, caught by the live sweep rather than by a token. So the
     fill is fixed in both themes, like --plum and --on-accent are, and for
     the same reason: white  6.35:1, and 2.50:1 against the dark panel, so it
     still reads as a button. */
  --bad-fill: #A63A42;
  --p-urgent: var(--rose);
  --p-high: var(--amber);
  --p-normal: var(--steel);
  --p-low: var(--grey);
  --radius: var(--r-shell);
  --shadow: var(--lift);
}

/* Dark mode is NOT in the supplied token list, and these steps are the one
   addition to it. Flagged deliberately: the desk already shipped with a dark
   theme and dropping it would be a regression, but a light-only palette
   cannot describe a dark surface. Every step below is the same hue as its
   light counterpart, lifted for contrast on a dark ground - no new hues.
   Say the word and this block comes out. */
/* Two themes, and the rule for choosing between them.

   No data-theme attribute means "follow the device", which is the first
   visit. Choosing Light or Dark in the sidebar stamps the attribute on
   <html> and is remembered - and it is applied by a few lines inline in the
   <head>, BEFORE this stylesheet paints. Set from a script at the end of the
   body and the page flashes the wrong theme first, which is worse than not
   having the choice at all. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
  /* ── The furniture, copied from Q CMMS ──────────────────
     Its dark theme to the value, so the two apps look like one system on a
     dark screen: the same warm near-black ground, the same plum-tinted
     panels and borders.

     What is NOT copied is the type and the shape. A typeface and a corner
     radius are not colour-scheme properties - they would have to change when
     somebody flips the theme, which is not a thing an interface should do.
     Poppins and the 6/8/10px radii hold in both. */
  --canvas:  #1e1419;
  --surface: #2d1e25;
  --line:    #4e3340;
  --ink:     #EFECED;
  /* The CMMS lightened this from #957b87, which came to 4.1:1 on a panel -
     under the 4.5:1 secondary text needs, and this is read for twelve hours
     under factory lighting. Same reasoning, same value. */
  --ink-2:   #b8a2aa;
  --ink-3:   #8f7b84;

  /* The accents stay this desk's own hues, lifted for a dark ground. Taking
     the CMMS's bright green and red would mean the helpdesk changed accent
     colours when you flipped the theme, which is the same mistake as
     changing the typeface. */
  --plum:    #664356;
  --steel:   #5d7594;
  /* Taken down with the light theme's, and for the same reason - these were
     the brightest things in the app. Lightness is held where it was, because
     on a dark ground these are read rather than filled with, and only the
     chroma comes off: rose 75.6 -> 49.7, moss 73.7 -> 45.1,
     amber 78.8 -> 49.2. Every one still clears 4.5:1 on the surface, on the
     canvas and on its own wash. */
  --rose:    #DE6B6B;
  --moss:    #5DB77E;
  --amber:   #DDA457;
  --grey:    #9B9A9C;

  --on-accent: #FFFFFF;
  --plum-ink:  #c98aaa;   /* the CMMS's --blue-on: plum light enough to read */
  --moss-ink:  var(--moss);    /* 5.95:1 already */
  --rose-ink:  #f37474;
  --amber-ink: var(--amber);   /* 5.92:1 already */
  --steel-ink: #889bb4;
  --scrim:     rgba(12, 8, 10, .72);
  --surface-2: #3d2831;
  --surface-3: #4a3040;
  --lift:      0 4px 24px rgba(0, 0, 0, .4);
  }
}

:root[data-theme="dark"] {
  /* ── The furniture, copied from Q CMMS ──────────────────
     Its dark theme to the value, so the two apps look like one system on a
     dark screen: the same warm near-black ground, the same plum-tinted
     panels and borders.

     What is NOT copied is the type and the shape. A typeface and a corner
     radius are not colour-scheme properties - they would have to change when
     somebody flips the theme, which is not a thing an interface should do.
     Poppins and the 6/8/10px radii hold in both. */
  --canvas:  #1e1419;
  --surface: #2d1e25;
  --line:    #4e3340;
  --ink:     #EFECED;
  /* The CMMS lightened this from #957b87, which came to 4.1:1 on a panel -
     under the 4.5:1 secondary text needs, and this is read for twelve hours
     under factory lighting. Same reasoning, same value. */
  --ink-2:   #b8a2aa;
  --ink-3:   #8f7b84;

  /* The accents stay this desk's own hues, lifted for a dark ground. Taking
     the CMMS's bright green and red would mean the helpdesk changed accent
     colours when you flipped the theme, which is the same mistake as
     changing the typeface. */
  --plum:    #664356;
  --steel:   #5d7594;
  /* Taken down with the light theme's, and for the same reason - these were
     the brightest things in the app. Lightness is held where it was, because
     on a dark ground these are read rather than filled with, and only the
     chroma comes off: rose 75.6 -> 49.7, moss 73.7 -> 45.1,
     amber 78.8 -> 49.2. Every one still clears 4.5:1 on the surface, on the
     canvas and on its own wash. */
  --rose:    #DE6B6B;
  --moss:    #5DB77E;
  --amber:   #DDA457;
  --grey:    #9B9A9C;

  --on-accent: #FFFFFF;
  --plum-ink:  #c98aaa;   /* the CMMS's --blue-on: plum light enough to read */
  --moss-ink:  var(--moss);    /* 5.95:1 already */
  --rose-ink:  #f37474;
  --amber-ink: var(--amber);   /* 5.92:1 already */
  --steel-ink: #889bb4;
  --scrim:     rgba(12, 8, 10, .72);
  --surface-2: #3d2831;
  --surface-3: #4a3040;
  --lift:      0 4px 24px rgba(0, 0, 0, .4);
}

* { box-sizing: border-box; }

/* The overlays below set an explicit display, which outranks the browser's own
   [hidden] { display: none } and leaves a "hidden" access screen or modal sitting
   invisibly over the page, swallowing every click. This puts hidden back in
   charge, so toggling .hidden in JS is enough. */
[hidden] { display: none !important; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font: var(--w-body) var(--t-14)/1.5 var(--font);
  -webkit-font-smoothing: antialiased;
}

/* A heading is a heading, not a value. It stays at --w-head while the
   caption over a figure and the figure itself go to --w-value, which is the
   same relationship the Service Dashboard tile has between its panel head
   and its numbers - and the reason the rest of the desk was set to match. */
h1, h2, h3, h4 { margin: 0; font-weight: var(--w-head); line-height: 1.3; }
h1 { font-size: var(--t-24); letter-spacing: -.015em; }
h2 { font-size: var(--t-19); letter-spacing: -.015em; }
h3 { font-size: var(--t-16); }
h4 { font-size: var(--t-14); }

.muted { color: var(--ink-2); }

/* Anything the eye compares down a column is monospaced so the digits line
   up: references, counts, dates, durations, sizes. */
/* `time` is deliberately NOT here: a <time> element holds a relative phrase
   as often as a date ("just now", "3d ago"), and monospacing prose makes it
   look like a serial number. Tabular stamps ask for .mono or .num. */
.mono, .tno, .ticket-ref, .num, .tab-n, .stat-value, .tile-n {
  font-variant-numeric: tabular-nums;
}

button, input, select, textarea { font: inherit; color: inherit; }

/* ── Every text control, painted ──────────────────────────
   `color: inherit` above is not enough on its own: a control with no
   BACKGROUND of its own still gets the browser's, and on this canvas
   that is a pale box the inherited near-white ink then disappears into.
   The Settings form hit it first and was fixed there; the Create an
   account form, the filter dropdowns and the search boxes hit it next,
   which is the point at which it stops being three bugs and starts
   being a missing base rule.

   Anything with a class of its own still wins - a class beats an
   element - so the controls that were already painted are untouched. */
input[type="text"], input[type="email"], input[type="password"],
input[type="search"], input[type="number"], input[type="tel"],
input[type="url"], input[type="date"], input[type="time"],
input[type="datetime-local"], input:not([type]),
select, textarea {
  padding: var(--s-8) var(--s-10);
  border: 1px solid var(--line); border-radius: var(--r-control);
  background: var(--surface); color: var(--ink);
}
/* An open dropdown is drawn by the operating system, not by this sheet.
   Chrome and Edge do take these two, which is the difference between a
   readable list and three dark lines on a dark panel. */
select option { background: var(--surface); color: var(--ink); }
/* A placeholder has to be legible AND plainly not a value. ink-3 is the
   token for exactly that; the browser default is neither. */
::placeholder { color: var(--ink-3); opacity: 1; }
/* The one line that tells the browser which way round this page is, so
   the parts it draws itself - a date picker, a scrollbar, the inside of
   a select - come out dark with the rest rather than white. */
:root { color-scheme: light dark; }
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }

/* A link with no colour of its own gets the browser's, which is #0000EE
   unvisited and #551A8B visited. Against this app's dark canvas those
   measure about 2.3:1 and 1.5:1 - the Technician Dashboard's name column
   was a maroon smudge on plum before this line. The browser default was
   never a decision this sheet made; the accent ink is, and it is stepped for
   both themes. Anything that is already a button, tab, rail item or ticket
   reference keeps its own colour - a class beats a bare element. */
a { color: var(--accent-ink); }
/* :not([class]) matters. `a:hover` on its own is specificity 0,1,1 and so
   beats a single class - it was repainting the text of any link that carries
   its own colour on a coloured fill, and a hovered Reopened tile measured
   3.58:1 because dark ink had replaced the white on its orange. The rule was
   only ever for bare links, so it says bare links. */
a:not([class]):hover { color: var(--ink); }

/* ── Top bar ──────────────────────────────────────────── */
.topbar {
  display: flex; align-items: center; gap: 20px;
  padding: 10px 16px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 20;
}

.brand { display: flex; align-items: center; gap: 8px; font-size: 16px; }
.brand .dot {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--accent);
}

.stats { display: flex; gap: 8px; margin-left: auto; }
.stat {
  display: flex; align-items: baseline; gap: 6px;
  padding: 4px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--bg);
}
.stat-n { font-weight: var(--w-value); font-variant-numeric: tabular-nums; }
.stat-l { color: var(--ink-soft); font-size: 12px; }
.stat.warn .stat-n { color: var(--warn-ink); }
.stat.bad  .stat-n { color: var(--bad-ink); }

.topbar-right { display: flex; align-items: center; gap: 10px; }

.who {
  display: flex; align-items: center; gap: 8px;
  background: none; border: 1px solid var(--line);
  border-radius: var(--r-pill); padding: 3px 12px 3px 3px;
  cursor: pointer;
}
.who:hover { border-color: var(--accent); }

.avatar {
  display: grid; place-items: center;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--accent); color: var(--accent-in);
  font-size: 11px; font-weight: var(--w-head);
  flex: none;
}
.avatar.sm { width: 20px; height: 20px; font-size: 11px; }

/* ── Buttons ──────────────────────────────────────────── */
/* One page, one primary button. .btn is the secondary - the default - and
   anything quieter than that is a text link. Controls take the 6px radius. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-8);
  min-height: 36px;
  padding: var(--s-8) 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-control);
  background: var(--surface); color: var(--ink);
  cursor: pointer;
  font: var(--w-head) var(--t-13)/1 var(--font);
  white-space: nowrap;
  text-decoration: none;
}
.btn:hover:not(:disabled) { border-color: var(--plum-ink); color: var(--plum-ink); }
.btn:disabled { color: var(--grey); border-color: var(--line); cursor: default; }

.btn-primary {
  background: var(--plum);
  border-color: var(--plum);
  color: var(--on-accent);
}
.btn-primary:hover:not(:disabled) {
  background: var(--plum-pressed); border-color: var(--plum-pressed); color: var(--on-accent);
}
.btn-primary:active:not(:disabled) { background: var(--plum-pressed); }
.btn-primary:disabled {
  background: var(--surface-3);
  border-color: var(--line);
  color: var(--ink-2);
}

/* Quiet: an action inside a panel that must not compete with the page's one
   primary button - "Add files" beside "Send request". */
.btn-quiet {
  background: var(--surface); color: var(--ink-2);
  border-color: var(--line);
  font-weight: var(--w-body);
}
.btn-quiet:hover:not(:disabled) { color: var(--plum-ink); border-color: var(--plum-ink); }

/* A text link is the third tier: Cancel, Clear filters, Back. */
.link {
  background: none; border: 0; padding: 0;
  color: var(--steel-ink); font: var(--w-body) var(--t-13)/1 var(--font);
  cursor: pointer; text-decoration: none;
}
.link:hover { text-decoration: underline; }

.btn-take { padding: 4px 10px; font-size: 12px; }

.icon {
  background: none; border: 0; cursor: pointer;
  font-size: 24px; line-height: 1; color: var(--ink-soft);
  padding: 0 4px;
}
.icon:hover { color: var(--ink); }

.link {
  background: none; border: 0; padding: 0;
  color: var(--accent-ink); cursor: pointer; font-size: 13px;
}

/* ── Layout ───────────────────────────────────────────── */
.shell {
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr);
  gap: 16px;
  padding: 16px;
  max-width: 1500px;
  margin: 0 auto;
  align-items: start;
}

/* ── Sidebar ──────────────────────────────────────────── */
.side {
  position: sticky; top: 73px;
  display: flex; flex-direction: column; gap: 18px;
  padding: 14px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
}

.search input {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  background: var(--bg);
}
.search input:focus { outline: 2px solid var(--accent-ink); outline-offset: -1px; }

.views { display: flex; flex-direction: column; gap: 2px; }
.view {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 7px 10px;
  border: 0; border-radius: var(--r-card);
  background: none; cursor: pointer;
  text-align: left; color: var(--ink-soft);
}
.view:hover { background: var(--bg); color: var(--ink); }
.view.is-on { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent-ink); font-weight: var(--w-head); }
.view .n {
  font-variant-numeric: tabular-nums; font-size: 12px;
  background: var(--bg); border-radius: var(--r-pill); padding: 1px 7px;
}
.view.is-on .n { background: var(--panel); }

.facet h3 {
  font-size: 11px;
  color: var(--ink-soft); margin-bottom: 8px;
}
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  padding: 3px 9px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: none; cursor: pointer;
  font-size: 12px; color: var(--ink-soft);
}
.chip:hover { border-color: var(--accent); }
.chip.is-on { background: var(--ink); border-color: var(--ink); color: var(--bg); }
.chip.p-urgent.is-on { background: var(--p-urgent); border-color: var(--p-urgent); color: var(--on-accent); }
.chip.p-high.is-on   { background: var(--p-high);   border-color: var(--p-high);   color: var(--on-accent); }
.chip.p-normal.is-on { background: var(--p-normal); border-color: var(--p-normal); color: var(--on-accent); }

.side-foot { border-top: 1px solid var(--line-soft); padding-top: 12px; }

/* ── Queue ────────────────────────────────────────────── */
.queue { min-width: 0; }

.queue-head {
  display: flex; align-items: baseline; gap: 10px;
  margin: 2px 2px 12px;
}
.queue-head h2 { font-size: 19px; }

.rows { display: flex; flex-direction: column; gap: 6px; }

.row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 14px;
  padding: 10px 14px 10px 12px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 3px solid var(--p-normal);
  border-radius: var(--r-card);
  cursor: pointer;
}
.row:hover  { border-color: var(--accent); border-left-color: inherit; }
.row:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
.row.is-open { outline: 1px solid var(--plum-ink); outline-offset: -1px; }

.row.p-urgent { border-left-color: var(--p-urgent); }
.row.p-high   { border-left-color: var(--p-high); }
.row.p-normal { border-left-color: var(--p-normal); }
.row.p-low    { border-left-color: var(--p-low); }

.row-main { min-width: 0; }
.row-top  { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.row-sub  { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-top: 4px; font-size: 12px; }

.tno {
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft); font-size: 12px; flex: none;
}
.subject {
  font-weight: var(--w-head);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.pill {
  padding: 1px 8px; border-radius: var(--r-pill);
  font-size: 11px; font-weight: var(--w-value);
  border: 1px solid var(--line);
}
.pill.s-new      { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent-ink); border-color: transparent; }
.pill.s-open     { background: color-mix(in srgb, var(--warn) 16%, transparent);   color: var(--warn-ink);   border-color: transparent; }
.pill.s-pending  { background: var(--bg); color: var(--ink-soft); }
.pill.s-resolved { background: color-mix(in srgb, var(--ok) 16%, transparent);     color: var(--ok-ink); border-color: transparent; }
.pill.s-closed   { background: var(--bg); color: var(--ink-soft); }

.tag { color: var(--ink-soft); }

.row-right { display: flex; align-items: center; gap: 12px; flex: none; }

.age {
  font-variant-numeric: tabular-nums;
  font-size: 12px; font-weight: var(--w-value);
  color: var(--ink-soft);
  min-width: 34px; text-align: right;
}
/* Scoped to the age itself, in the row and in the drawer. Left unscoped these
   tint every descendant of the row, turning the whole line red and drowning
   out the priority stripe that is meant to carry that signal. */
.age.heat-due,      dd.heat-due      { color: var(--warn-ink); }
.age.heat-breached, dd.heat-breached { color: var(--bad-ink); font-weight: var(--w-head); }

.assignee {
  display: flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--ink-soft);
  max-width: 150px;
}
.assignee.is-me { color: var(--ink); font-weight: var(--w-head); }
.assignee span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.empty {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  padding: 56px 20px;
  background: var(--panel);
  border: 1px dashed var(--line);
  border-radius: var(--r-card);
  text-align: center;
  color: var(--ink-soft);
}
.empty p { margin: 0; max-width: 46ch; }

/* ── Drawer ───────────────────────────────────────────── */
.scrim {
  position: fixed; inset: 0; z-index: 30;
  background: rgba(15, 23, 42, .35);
}

.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 31;
  width: min(560px, 100%);
  display: flex; flex-direction: column;
  background: var(--panel);
  border-left: 1px solid var(--line);
  box-shadow: var(--shadow);
}

.drawer-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
  padding: 16px 16px 12px;
  border-bottom: 1px solid var(--line);
}
.drawer-head h2 { font-size: 16px; margin-top: 2px; }

.drawer-controls {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
  background: var(--bg);
}
.drawer-controls label,
.modal label {
  display: flex; flex-direction: column; gap: 4px;
  font-size: 11px;
  color: var(--ink-soft);
}
.drawer-controls select,
.modal input, .modal select, .modal textarea,
.drawer-foot textarea {
  padding: 7px 9px;
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  background: var(--panel);
  text-transform: none; letter-spacing: normal;
  font-size: 14px; color: var(--ink);
}
.drawer-controls select:focus,
.modal input:focus, .modal select:focus, .modal textarea:focus,
.drawer-foot textarea:focus { outline: 2px solid var(--accent-ink); outline-offset: -1px; }

.drawer-body { flex: 1; overflow-y: auto; padding: 16px; }

.meta {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: 4px 14px; margin: 0 0 16px;
  font-size: 13px;
}
.meta dt { color: var(--ink-soft); }
.meta dd { margin: 0; }

.ticket-body {
  white-space: pre-wrap;
  padding: 12px;
  background: var(--bg);
  border-radius: var(--r-card);
  margin-bottom: 20px;
}

.tl-head {
  font-size: 11px;
  color: var(--ink-soft); margin-bottom: 10px;
}

.timeline { list-style: none; margin: 0; padding: 0 0 0 14px; border-left: 2px solid var(--line-soft); }
.tl { position: relative; padding: 0 0 16px 14px; font-size: 13px; }
.tl::before {
  content: ""; position: absolute; left: -21px; top: 6px;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--line); border: 2px solid var(--panel);
}
.tl-comment::before  { background: var(--accent); }
.tl-status::before   { background: var(--warn); }
.tl-assigned::before { background: var(--ok); }

.tl-line { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.tl-when { margin-left: auto; color: var(--ink-soft); font-size: 11px; }
.tl-text {
  margin: 6px 0 0;
  white-space: pre-wrap;
  padding: 8px 10px;
  background: var(--bg);
  border-radius: var(--r-card);
}

.drawer-foot {
  display: flex; gap: 8px; align-items: flex-end;
  padding: 12px 16px;
  border-top: 1px solid var(--line);
}
.drawer-foot textarea { flex: 1; resize: vertical; }

/* ── Modal ───────────────────────────────────────────── */
.modal {
  position: fixed; inset: 0; z-index: 40;
  display: grid; place-items: center;
  padding: 20px;
  background: rgba(15, 23, 42, .45);
  overflow-y: auto;
}

.modal-card {
  width: min(640px, 100%);
  display: flex; flex-direction: column; gap: 12px;
  padding: 22px;
  background: var(--panel);
  border-radius: var(--r-shell);
  box-shadow: var(--shadow);
}
.modal-wide { width: min(760px, 100%); }

.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.grid3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }

.modal-foot { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }

/* ── Access screen ───────────────────────────────────── */
/* Opaque rather than a scrim: there is nothing behind it to see through, and
   a half-lit queue under a login form reads as a bug. */
.auth {
  position: fixed; inset: 0; z-index: 70;
  display: grid; place-items: center;
  padding: 20px;
  background: var(--bg);
  overflow-y: auto;
}
.auth-card {
  width: min(400px, 100%);
  display: flex; flex-direction: column; gap: 18px;
  padding: 26px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-shell);
}
.auth-brand {
  display: flex; align-items: center; gap: 8px;
  font-size: 16px;
}
.auth-panel { display: flex; flex-direction: column; gap: 12px; }
.auth-panel h1 { margin: 0; font-size: 19px; }
.auth-panel p  { margin: 0; font-size: 13px; }
.auth-panel label { display: flex; flex-direction: column; gap: 5px; font-size: 13px; }
.auth-panel input {
  padding: 9px 11px;
  border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--bg); color: var(--ink);
  font: inherit;
}
.auth-panel input:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }

.btn-wide { width: 100%; justify-content: center; }

.auth-err {
  padding: 8px 10px;
  border-radius: var(--r-card);
  background: color-mix(in srgb, var(--bad) 12%, transparent);
  color: var(--bad-ink);
  font-size: 13px;
}
.auth-foot { font-size: 12px !important; text-align: center; }
/* Under the password box, where somebody looks when it is not
   working - not in the footer with the sign-up line. */
.auth-aside { margin: -4px 0 0 !important; font-size: 12px !important;
              text-align: right; }
.auth-aside a { color: var(--ink-soft); }
.auth-aside a:hover { color: var(--ink); }

/* ── Show the password ───────────────────────────────── */
/* The wrapper is made by password-eye.js, so none of this applies
   until the script has run - with scripting off there is no button to
   leave room for and the field is full width, as it always was. */
.pw-wrap { position: relative; display: block; }
.pw-wrap input { width: 100%; padding-right: 42px; }
.pw-eye {
  position: absolute; top: 50%; right: 6px; transform: translateY(-50%);
  width: 30px; height: 30px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--r-card);
  background: transparent; color: var(--ink-soft); cursor: pointer;
}
.pw-eye:hover { color: var(--ink); background: var(--bg); }
.pw-eye:focus-visible {
  outline: 2px solid var(--accent-ink); outline-offset: 1px;
}
/* The eye itself, drawn rather than fetched: one more image request on
   the sign-in page to say "show" is not worth it. The slash appears
   when the password is visible, which is the state worth flagging. */
.pw-eye::before {
  content: ""; width: 17px; height: 11px;
  border: 1.6px solid currentColor;
  /* A plain ellipse. The design system allows five radii and 50% is
     one of them; inventing a sixth for an eyelid is not worth it. */
  border-radius: 50%;
}
.pw-eye::after {
  content: ""; position: absolute;
  width: 7px; height: 7px; border: 1.6px solid currentColor;
  border-radius: 50%;
}
.pw-eye.is-shown::after {
  width: 20px; height: 0; border-width: 0 0 1.6px 0; border-radius: 0;
  transform: rotate(-45deg);
}
.auth-foot a { color: var(--accent-ink); }

/* ── Creating your own account ───────────────────────── */
/* Nine fields. In the 400px column the sign-in card uses, that is a
   page of scrolling before the button - so this card widens and the
   fields pair up, and both go back to one column on a phone. */
.auth-card.is-wide { width: min(720px, 100%); }
.auth-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px 14px;
}
.auth-field { min-width: 0; }
.auth-field.is-wide { grid-column: 1 / -1; }
.auth-panel select {
  padding: 9px 11px;
  border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--bg); color: var(--ink);
  font: inherit;
}
.auth-panel select:focus-visible {
  outline: 2px solid var(--accent-ink); outline-offset: 1px;
}
.auth-panel [aria-invalid="true"] { border-color: var(--bad); }
.auth-opt { color: var(--ink-soft); font-weight: var(--w-body); }
.auth-hint { font-size: 11px; color: var(--ink-soft); }
/* A field's own complaint sits under that field, so it is not a block
   of red at the top that makes you hunt for which box it means. */
.auth-field .auth-err { padding: 4px 8px; font-size: 12px; }

@media (max-width: 620px) {
  .auth-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ── Account menu ────────────────────────────────────── */
.me { position: relative; }
.me-menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 45;
  min-width: 200px;
  display: flex; flex-direction: column;
  padding: 6px;
  background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--r-shell);
  box-shadow: var(--shadow);
}
.me-email {
  padding: 6px 8px;
  font-size: 12px; color: var(--ink-soft);
  border-bottom: 1px solid var(--line-soft);
  overflow-wrap: anywhere;
}
.me-item {
  padding: 8px;
  border: 0; border-radius: var(--r-control);
  background: none; color: var(--ink);
  font: inherit; text-align: left; cursor: pointer;
}
.me-item:hover { background: var(--bg); }

/* ── Agents panel ────────────────────────────────────── */
.admin-add {
  display: flex; flex-direction: column; gap: 12px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line-soft);
}
/* Inside its own panel the card's edge is already the divider, and two
   lines a few pixels apart read as a mistake. */
.panel-card > .admin-add { padding-bottom: 18px; border-bottom: 0; }
.admin-add label { display: flex; flex-direction: column; gap: 5px; font-size: 13px; }

.admin-list { display: flex; flex-direction: column; gap: 6px; }
.admin-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center; gap: 10px;
  padding: 9px 10px;
  border: 1px solid var(--line); border-radius: var(--r-shell);
}
.admin-row.is-off { opacity: .55; }

.admin-who { display: flex; align-items: center; gap: 9px; min-width: 0; }
.admin-who > div { display: flex; flex-direction: column; min-width: 0; }
.admin-email { font-size: 12px; color: var(--ink-soft); overflow-wrap: anywhere; }

.admin-tags { display: flex; flex-wrap: wrap; gap: 4px; }
/* One tag per role. Agent had none - it fell through to the bare .tag - and
   User had none because the role could not be given out. All three are named
   now, so a row says what the account is rather than what it is not. */
.tag-admin { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent-ink); }
.tag-agent { background: color-mix(in srgb, var(--steel) 12%, transparent);  color: var(--steel-ink); }
.tag-user  { background: color-mix(in srgb, var(--grey) 14%, transparent);   color: var(--ink-2); }
.tag-off   { background: color-mix(in srgb, var(--bad) 12%, transparent);    color: var(--bad-ink); }
.tag-warn  { background: color-mix(in srgb, var(--warn) 16%, transparent);   color: var(--warn-ink); }

.admin-acts { display: flex; flex-wrap: wrap; gap: 10px; justify-content: flex-end; }

/* The role picker sits among text links, so it has to stay small enough not
   to set the row's height on its own. */
.role-pick select {
  padding: 2px 6px;
  border: 1px solid var(--line); border-radius: var(--r-control);
  background: var(--surface); color: var(--ink);
  font: inherit; font-size: var(--t-12);
}

/* ── Toast ────────────────────────────────────────────── */
.toast {
  position: fixed; top: calc(var(--topbar-h) + var(--s-12)); right: var(--s-16);
  z-index: 60; max-width: min(360px, calc(100vw - var(--s-32)));
  display: flex; align-items: flex-start; gap: var(--s-8);
  padding: var(--s-12) var(--s-16);
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-left: 3px solid var(--moss);
  border-radius: var(--r-card);
  box-shadow: var(--lift);
  font-size: var(--t-13);
}
.toast.is-error { border-left-color: var(--rose); }

/* ── Narrow screens ───────────────────────────────────── */
@media (max-width: 900px) {
  .shell { grid-template-columns: minmax(0, 1fr); }
  .side  { position: static; }
  .views { flex-direction: row; flex-wrap: wrap; }
  .view  { flex: 1 1 auto; }
  .stats { display: none; }
  .drawer-controls { grid-template-columns: 1fr; }
  .grid2, .grid3 { grid-template-columns: 1fr; }
  .row { grid-template-columns: minmax(0, 1fr); }
  .row-right { justify-content: flex-start; margin-top: 6px; }
  .admin-row { grid-template-columns: minmax(0, 1fr); }
  .admin-acts { justify-content: flex-start; }
}


/* ── Server-rendered adaptations ──────────────────────────
   The queue is Jinja now, so the things that used to be scripted buttons are
   ordinary links and forms. These rules make that invisible: same design, no
   JavaScript required to reach any of it. */

/* Views and filter chips are anchors. */
.view, .chip { text-decoration: none; }
.view { display: flex; }

/* The clickable part of a row is an anchor wrapping the text, so the Take it
   button beside it stays separately clickable — no overlay to fight with. */
a.row-main {
  display: block;
  color: inherit;
  text-decoration: none;
}
a.row-main:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
.row-top, .row-sub { display: flex; }

/* The account menu is a <details>, which works with scripting off. */
.me > summary {
  list-style: none;
  cursor: pointer;
}
.me > summary::-webkit-details-marker { display: none; }
.me > summary::marker { content: ""; }
.me[open] > summary { border-color: var(--accent); }
.me > .me-menu { display: flex; }

.me-item {
  width: 100%;
  display: block;
  text-decoration: none;
}

/* The scrim and the drawer close control are links now. */
a.scrim { display: block; }
a.icon  { text-decoration: none; display: inline-flex; align-items: center; }

/* Header counters. */
.stat { display: flex; gap: 4px; align-items: baseline; }
.stat b { font-weight: var(--w-value); font-variant-numeric: tabular-nums; }
.stat.is-bad b { color: var(--bad-ink); }

/* Each drawer control is its own form. queue.js submits them on change; the
   Set button is what makes them work when it is not running. */
.drawer-controls form.auto {
  display: flex; align-items: flex-end; gap: 6px; min-width: 0;
}
.drawer-controls form.auto label { flex: 1; min-width: 0; }
.btn-set { padding: 7px 10px; font-size: 12px; flex: none; }
.js .btn-set { display: none; }

/* Standalone pages (new ticket, agents) reuse the modal card on a plain
   background rather than over a scrim. */
.page {
  display: grid; place-items: start center;
  padding: 24px 16px 60px;
}
.page .modal-card { box-shadow: none; border: 1px solid var(--line); }

/* Action forms in the agents list sit inline so the row still reads as a row. */
.admin-acts form { display: inline; }
.admin-acts .link { padding: 0; }

/* The toast is server-rendered, so it has no timer of its own until queue.js
   picks it up. Give it one in CSS so it still goes away on its own. */
.toast { animation: toast-out .3s ease-in 6s forwards; }
@keyframes toast-out { to { opacity: 0; visibility: hidden; } }

@media (prefers-reduced-motion: reduce) {
  .toast { animation-duration: .01ms; animation-delay: 6s; }
}

/* Buttons that are links. The header actions and the drawer's close control
   navigate, so they are anchors — they should still not look like prose. */
a.btn, a.btn:hover { text-decoration: none; }

/* ── Dashboard ────────────────────────────────────────────
   Four figures and what sits behind them. The tiles are links, because a
   number you cannot click is a number you have to go and reproduce by hand. */
.dash-page { padding-block: 28px 60px; }
.dash { width: min(920px, 100%); }

.dash-title { margin: 0; font-size: 19px; }
.dash-sub   { margin: 4px 0 20px; font-size: 13px; }

.tiles {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
}

.tile {
  display: flex; flex-direction: column; gap: 2px;
  padding: 16px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 3px solid var(--line);
  border-radius: var(--r-card);
  color: inherit; text-decoration: none;
}
.tile:hover { border-color: var(--accent); border-left-color: var(--accent); }
.tile:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }

.tile-label {
  font-size: 11px; font-weight: var(--w-value); color: var(--ink-soft);
}
.tile-n {
  font-size: 24px; font-weight: var(--w-value); line-height: 1.15;
  font-variant-numeric: tabular-nums;
}
.tile-foot { font-size: 12px; }

/* Response is a duration, not a count of things, so it is marked out. */
.tile-response { border-left-color: var(--accent); }

.dash-notes {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin: 14px 0 26px;
}
.note {
  padding: 6px 11px;
  border-radius: var(--r-pill);
  background: var(--line-soft);
  color: var(--ink-soft);
  font-size: 12px;
  text-decoration: none;
}
a.note:hover { color: var(--ink); }
.note strong { font-variant-numeric: tabular-nums; }
.note-warn { background: color-mix(in srgb, var(--warn) 16%, transparent); color: var(--warn-ink); }
.note-bad  { background: color-mix(in srgb, var(--bad)  14%, transparent); color: var(--bad-ink); }

.dash-head  { margin: 0 0 10px; font-size: 13px; color: var(--ink-soft); }
.dash-empty { margin-top: 24px; }

@media (max-width: 900px) {
  .tiles { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 480px) {
  .tiles { grid-template-columns: 1fr; }
}

/* ── Master data ──────────────────────────────────────────
   Upload on the left, download on the right, and the gaps underneath -
   because an unrouted ticket head is the thing worth acting on. */
.master-actions { margin-bottom: 22px; }
.master-two {
  display: grid; grid-template-columns: 1fr 1fr; gap: 26px;
}
.master-two h2 { margin: 0 0 6px; font-size: 16px; }
.master-two p  { margin: 0 0 12px; font-size: 13px; }
.master-two label { display: flex; flex-direction: column; gap: 6px; font-size: 13px; }
.master-two input[type="file"] {
  padding: 9px; border: 1px dashed var(--line); border-radius: var(--r-card);
  background: var(--bg); font: inherit; width: 100%;
}
.master-two .btn-wide { margin-top: 12px; }

.cols {
  display: block; margin-top: 4px;
  font-size: 11px; color: var(--ink-soft);
}

.tile-warn { border-left-color: var(--warn); }

.report { margin-bottom: 22px; }
.report-line { font-size: 14px; margin: 0 0 8px; }
.skipped {
  margin: 6px 0 0; padding-left: 18px;
  font-size: 12px; color: var(--ink-soft);
}

@media (max-width: 720px) {
  .master-two { grid-template-columns: 1fr; }
}

/* ── The bars menu ────────────────────────────────────────
   Every page this person may open, behind one control at top left. A
   <details>, so it opens with scripting off; queue.js only adds Escape and
   click-away. */
.nav { position: relative; flex: none; }

.nav > summary {
  list-style: none;
  display: grid; place-items: center;
  width: 34px; height: 34px;
  border: 1px solid var(--line); border-radius: var(--r-card);
  cursor: pointer;
}
.nav > summary::-webkit-details-marker { display: none; }
.nav > summary::marker { content: ""; }
.nav > summary:hover { border-color: var(--accent); }
.nav[open] > summary { border-color: var(--accent); background: var(--bg); }

/* Three lines, drawn rather than typed, so they sit true at any font size. */
.bars-icon,
.bars-icon::before,
.bars-icon::after {
  display: block; width: 16px; height: 2px;
  background: var(--ink); border-radius: 2px;
}
.bars-icon { position: relative; }
.bars-icon::before,
.bars-icon::after { content: ""; position: absolute; left: 0; }
.bars-icon::before { top: -5px; }
.bars-icon::after  { top: 5px; }

.nav-panel {
  position: absolute; left: 0; top: calc(100% + 8px); z-index: 55;
  min-width: 250px;
  display: flex; flex-direction: column; gap: 1px;
  padding: 8px;
  background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--r-shell);
  box-shadow: var(--shadow);
}

.nav-heading {
  padding: 8px 10px 4px;
  font-size: 11px; font-weight: var(--w-value); color: var(--ink-soft);
}

.nav-item {
  display: flex; flex-direction: column; gap: 1px;
  width: 100%;
  padding: 8px 10px;
  border: 0; border-radius: var(--r-card);
  background: none; color: var(--ink);
  font: inherit; text-align: left; text-decoration: none;
  cursor: pointer;
}
.nav-item:hover { background: var(--bg); }
.nav-item.is-on {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent-ink); font-weight: var(--w-head);
}
.nav-note { font-size: 11px; color: var(--ink-soft); }
.nav-item.is-on .nav-note { color: inherit; opacity: .8; }
.nav-out { color: var(--bad-ink); }

/* The counters moved out of the header when it became shared. */
.stats-bar {
  display: flex; gap: 14px; flex-wrap: wrap;
  padding: 10px 20px;
  border-bottom: 1px solid var(--line);
  background: var(--panel);
  font-size: 13px;
}

@media (max-width: 720px) {
  .who-name { display: none; }
  .nav-panel { min-width: 220px; }
}

/* ── Browsing and removing master data ────────────────────
   Rows are labels wrapping a checkbox, so the whole row is the hit target
   rather than a 13-pixel square. */
.browse-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin-bottom: 12px;
}
.browse-bar select,
.browse-bar input[type="search"] {
  padding: 7px 10px;
  border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--panel); color: var(--ink); font: inherit;
}
.browse-bar input[type="search"] { min-width: 200px; }

.browse-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 8px;
}
.pick-all { display: flex; align-items: center; gap: 8px; font-size: 13px; }

.admin-row.pick {
  grid-template-columns: auto minmax(0, 1fr) auto;
  cursor: pointer;
}
.admin-row.pick:hover { border-color: var(--accent); }
.admin-row.pick input[type="checkbox"] { width: 15px; height: 15px; }
.admin-row.pick:has(:checked) {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 6%, transparent);
}

.btn-danger { border-color: var(--bad-ink); color: var(--bad-ink); }
.btn-danger:hover:not(:disabled) { background: var(--bad-fill); color: var(--on-accent); border-color: var(--bad-fill); }

.danger { margin-top: 28px; }
.danger > summary {
  cursor: pointer; font-size: 13px; color: var(--bad-ink);
  padding: 8px 0;
}
.danger > form {
  display: flex; flex-direction: column; gap: 10px; align-items: flex-start;
  max-width: 520px;
  padding: 14px;
  border: 1px solid color-mix(in srgb, var(--bad) 40%, var(--line));
  border-radius: var(--r-card);
}
.danger label { display: flex; flex-direction: column; gap: 5px; font-size: 13px; }
.danger input[type="text"] {
  padding: 8px 10px; border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--bg); color: var(--ink); font: inherit;
}

/* The label was a flex column, which broke "Type DELETE to confirm" into
   three stacked lines. The sentence is one line; only the input goes below. */
.danger label.confirm { gap: 6px; }
.danger label.confirm > span { display: block; }
.danger-count { font-weight: var(--w-body); color: var(--ink-soft); }
.danger > summary { list-style: revert; }

/* ── Data table ───────────────────────────────────────────
   For master data, where the columns carry as much meaning as the name:
   who a ticket would go to, how long it may take, how much depends on it. */
.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  background: var(--panel);
}

.grid {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

/* Every table on the desk: 46px rows, 12px cells, a header that is structure
   rather than a colour band, and hover instead of stripes. */
.grid th {
  position: sticky; top: 0; z-index: 1;
  padding: var(--s-12);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  text-align: left; white-space: nowrap;
  font-size: var(--t-12); font-weight: var(--w-head); color: var(--ink-2);
}

.grid td {
  height: 46px;
  padding: var(--s-12);
  border-bottom: 1px solid var(--line-soft);
  vertical-align: middle;
}
.grid tbody tr:last-child td { border-bottom: 0; }
.grid tbody tr:hover { background: var(--hover); }
.grid tbody tr:has(:checked) {
  background: color-mix(in srgb, var(--accent) 7%, transparent);
}
.grid tr.is-off td { opacity: .55; }

.grid .num { text-align: right; font-variant-numeric: tabular-nums; }
.grid .pick-col { width: 34px; text-align: center; }
.grid .pick-col input { width: 15px; height: 15px; }

.nobody { color: var(--warn-ink); }
.tag-ok { background: color-mix(in srgb, var(--ok) 14%, transparent); color: var(--ok-ink); }

/* ── KPI dashboard ────────────────────────────────────────
   Chart colours are the validated pair: blue on time, red late. Green/red was
   rejected - at ΔE 4.1 for deuteranopia it is invisible to about one man in
   twelve. Both are direct-labelled anyway, so no number depends on hue. */
/* THE ONE DELIBERATE EXCEPTION to "no colour outside the tokens".
   Chart series are read by comparison, so the two must stay apart for a
   colour-blind reader. Measured with the palette validator:

     steel + rose    ΔE 7.3 protan   FAIL  (floor is 8)
     steel + amber   ΔE 18.6 protan  FAIL  (steel chroma 0.074 - reads grey)
     these two       ΔE 23.8 protan, 31.6 normal, chroma OK   PASS

   No pair drawn from the brand tokens clears it, and a chart nobody can read
   is worse than a chart that is off-brand by two hues. They appear only as
   chart marks, never as chrome, and every series is labelled as well as
   coloured. Swap them and the dashboard stops being accessible. */
:root {
  --ontime: #4A7FB5;
  --late:   #B84C4C;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --ontime: #4A8CCB; --late: #B84C4C; }
}
:root[data-theme="dark"] { --ontime: #4A8CCB; --late: #B84C4C; }

.kpi-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--line); margin-bottom: 18px; }
.kpi-tab {
  padding: 9px 16px; margin-bottom: -1px;
  border: 1px solid transparent; border-bottom: 0;
  border-radius: var(--r-card) var(--r-card) 0 0;
  color: var(--ink-soft); text-decoration: none; font-size: 14px;
}
.kpi-tab:hover { color: var(--ink); }
.kpi-tab.is-on {
  background: var(--panel); border-color: var(--line);
  color: var(--accent-ink); font-weight: var(--w-head);
}

.kpi-filters {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 14px;
  padding: 14px 16px; margin-bottom: 18px;
  background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--r-card);
}
.kpi-filters label {
  display: flex; flex-direction: column; gap: 5px;
  font-size: 12px; color: var(--ink-soft);
}
.kpi-filters input, .kpi-filters select {
  padding: 7px 10px;
  border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--bg); color: var(--ink); font: inherit;
}

/* The four headline tiles, coloured by what they mean. */
.kpi-tiles .tile { border-left-width: 3px; }
.t-total   { border-left-color: var(--ink-soft); }
.t-delayed { border-left-color: var(--late); }
.t-ontime  { border-left-color: var(--ontime); }
.t-open    { border-left-color: var(--warn); }
.t-delayed .tile-n { color: var(--late); }
.t-ontime  .tile-n { color: var(--ontime); }

.kpi-identity { margin: 10px 0 22px; font-size: 12px; }

.panels { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 26px; }
.panel {
  background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--r-card);
}
.panel > summary {
  display: flex; flex-direction: column; gap: 2px;
  padding: 12px 16px; cursor: pointer;
  border-bottom: 1px solid var(--line-soft);
  font-size: 14px; font-weight: var(--w-head);
}
.panel:not([open]) > summary { border-bottom: 0; }
.panel-note { font-size: 11px; font-weight: var(--w-body); }
.panel-body { padding: 16px; overflow-x: auto; }

.chart { display: block; max-width: 100%; }
.chart .mark { transition: opacity .12s ease; }
.chart .mark:hover { opacity: .82; }
.chart-empty { padding: 20px 0; font-size: 13px; }

.chart-legend { display: flex; flex-wrap: wrap; gap: 14px; margin-bottom: 10px; font-size: 12px; }
.legend-item { display: inline-flex; align-items: center; gap: 6px; color: var(--ink-soft); }
.swatch { width: 10px; height: 10px; border-radius: 3px; }

.kpi-table-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 8px;
}
.kpi-table-head .dash-head { margin: 0; }
.grid tfoot td {
  border-top: 2px solid var(--line);
  background: var(--bg); font-weight: var(--w-value);
}

@media (max-width: 900px) {
  .panels { grid-template-columns: 1fr; }
}

/* Small multiples: one ring per team, each its own whole. */
.donuts { display: flex; flex-wrap: wrap; gap: 20px; justify-content: flex-start; }
.donut { margin: 0; text-align: center; }
.donut svg { display: block; }
.donut figcaption {
  margin-top: 4px; max-width: 140px;
  font-size: 11px; color: var(--ink-soft); line-height: 1.35;
}

/* == The ticket form ======================================
   Two layouts, one set of fields. .is-roomy is the requester's: one column,
   680px, room to think. .is-dense is the agent's: the same fields two to a
   row so nothing scrolls while somebody is on the phone. */

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  overflow: hidden;
}
.ticket-form.is-roomy { width: 100%; }
.ticket-form.is-dense { width: min(1020px, 100%); }

.card-title {
  padding: var(--s-16) var(--s-24);
  border-bottom: 1px solid var(--line);
}
.card-title h1 { font-size: var(--t-19); color: var(--ink); }
.card-sub { margin: var(--s-4) 0 0; color: var(--ink-2); font-size: var(--t-13); }
.card-body { padding: var(--s-20) var(--s-24); }
.card-error, .card-body > .inline-error { margin: var(--s-16) var(--s-24) 0; }

/* Labels above, controls full width - in both layouts. The only difference
   between them is how many fields sit side by side. */
.form-grid { display: grid; gap: var(--s-20); align-items: start; }
.is-dense .form-grid { grid-template-columns: 1fr 1fr; gap: 14px var(--s-24); }

.fld { display: grid; gap: var(--s-4); align-content: start; }
.fld-label { font-size: var(--t-13); font-weight: var(--w-value); color: var(--ink); }
.fld-opt {
  margin-left: var(--s-8);
  /* "optional" is the only thing telling you this field can be skipped, so
     it has to be readable: ink-2, not the 3.6:1 tertiary. */
  color: var(--ink-2); font-size: var(--t-11); font-weight: var(--w-body);
}
.fld-hint { color: var(--ink-2); font-size: var(--t-12); }

.fld-input select,
.fld-input textarea,
.fld-input input[type="text"] {
  width: 100%;
  padding: var(--s-8) var(--s-12);
  border: 1px solid var(--line);
  border-radius: var(--r-control);
  background: var(--surface); color: var(--ink);
  font: var(--w-body) var(--t-14)/1.5 var(--font);
}
.fld-input select { height: 40px; }
.fld-input textarea { resize: vertical; min-height: 96px; }
.fld-input ::placeholder { color: var(--ink-3); }
.fld-input select:disabled {
  /* --grey is the token's own answer for "disabled". Disabled controls are
     outside the contrast floor by design (WCAG 1.4.3 exempts them) - looking
     unavailable is the whole point. */
  background: var(--canvas); color: var(--grey); cursor: not-allowed;
}
.fld-input select.is-loading { color: var(--ink-2); }

.is-roomy .fld-input textarea { min-height: 120px; }
.is-dense .fld-input textarea { height: 132px; min-height: 132px; }

/* An error says what happened and what to do, on the field it happened to. */
.has-error select,
.has-error textarea { border-color: var(--rose); }
.fld-error { margin: var(--s-4) 0 0; color: var(--rose-ink); font-size: var(--t-12); }

.counter {
  margin: var(--s-4) 0 0;
  font-variant-numeric: tabular-nums;
  font-size: var(--t-11); color: var(--ink-2);
}
.counter .is-short { color: var(--rose-ink); font-weight: var(--w-head); }

/* Attachments. On the dense layout this is a fixed frame beside the
   description, and the list inside it scrolls - five files must never push
   the Send button off the bottom of the screen. */
.uploads {
  display: flex; flex-direction: column;
  padding: var(--s-12);
  border: 1px dashed var(--line);
  border-radius: var(--r-card);
  background: var(--canvas);
}
.is-dense .uploads { height: 132px; }
.is-roomy .uploads { min-height: 120px; }
.uploads-head { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s-8); }
.uploads .hint { margin: var(--s-8) 0 0; color: var(--ink-2); font-size: var(--t-11); }
.uploads-head .hint { margin: 0; }

.file-count {
  padding: 2px var(--s-8);
  background: var(--plum-wash); color: var(--plum-ink);
  border-radius: var(--r-pill);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-11); font-weight: var(--w-value);
}

.file-list {
  list-style: none; margin: var(--s-8) 0 0; padding: 0;
  display: grid; gap: var(--s-8); align-content: start;
  flex: 1; min-height: 0; overflow-y: auto;
}
.file-row {
  display: grid; grid-template-columns: 28px minmax(0, 1fr) auto;
  align-items: center; gap: var(--s-12);
  padding: var(--s-4) var(--s-8);
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-control);
  font-size: var(--t-12);
}
.file-thumb {
  width: 28px; height: 28px;
  border-radius: var(--r-control);
  object-fit: cover; background: var(--line-soft);
}
.file-ext {
  display: grid; place-items: center;
  font-size: var(--t-11); font-weight: var(--w-value);
  color: var(--ink-2);
}
.file-meta { display: flex; flex-direction: column; min-width: 0; }
.file-meta strong { font-weight: var(--w-head); overflow-wrap: anywhere; }
.file-meta span { font-variant-numeric: tabular-nums; font-size: var(--t-11); color: var(--ink-2); }
.file-drop {
  width: 26px; height: 26px;
  border: 1px solid var(--line); border-radius: var(--r-control);
  background: none; color: var(--rose-ink);
  font-size: var(--t-14); line-height: 1; cursor: pointer;
}
.file-drop:hover { border-color: var(--rose); background: var(--rose-wash); }

/* One primary button. Everything beside it is a text link. */
.card-foot {
  display: flex; align-items: center; gap: var(--s-16);
  padding: var(--s-16) var(--s-24);
  border-top: 1px solid var(--line);
  background: var(--surface);
}

/* Below this the two columns are narrower than a legible dropdown, so the
   dense layout becomes the roomy one and is allowed to scroll. Six fields do
   not fit a phone screen, and pretending otherwise only shrinks the text. */
@media (max-width: 860px) {
  .is-dense .form-grid { grid-template-columns: 1fr; }
  .is-dense .uploads { height: auto; min-height: 120px; }
  .is-dense .fld-input textarea { height: auto; min-height: 96px; }
  .ticket-form.is-dense { width: 100%; }
}

/* A genuinely short window: give back the height the description and the
   attachment box are only using to look comfortable. Measured - the card is
   498px and clears a 640px viewport, so nothing shrinks above ~620px. */
@media (min-width: 861px) and (max-height: 620px) {
  .card-title { padding: var(--s-12) var(--s-24); }
  .is-dense .card-body { padding: 14px var(--s-24); }
  .is-dense .form-grid { gap: var(--s-12) var(--s-24); }
  .is-dense .fld-input textarea,
  .is-dense .uploads { height: 104px; min-height: 104px; }
  .card-foot { padding: var(--s-12) var(--s-24); }
}

/* Ticket references are numeric identifiers, so they take tabular figures
   and line up down a column. They used to take a monospace face as well;
   the desk is one face now, and tabular figures were the only part of that
   which was doing any work. */
.tno, .ticket-ref {
  font-variant-numeric: tabular-nums;
}

/* Less bottom padding than the list pages: nothing follows the card, and
   60px of it was the difference between fitting and not. */
.page-form { padding: 20px 16px 24px; }

/* Below this the two columns are narrower than a legible dropdown, so the
   form goes back to one column and is allowed to scroll. A phone cannot fit
   six fields on a screen and pretending otherwise only shrinks the text. */
@media (max-width: 860px) {
  .form-grid { grid-template-columns: 1fr; }
  .uploads { height: auto; min-height: 116px; }
  .fld-input textarea { height: 104px; min-height: 104px; }
}

/* Only a genuinely short window needs the compact sizes. Measured: the card
   is 498px, and it still clears a 640px-tall viewport, so shrinking anything
   above ~620px just leaves dead space under the Save button. */
@media (min-width: 861px) and (max-height: 620px) {
  .card-title { padding: 11px 24px; font-size: 16px; }
  .form-grid { gap: 11px 22px; padding: 14px 24px; }
  .fld-input textarea,
  .uploads { height: 104px; min-height: 104px; }
  .card-foot { padding: 10px 24px; }
}

/* House style now lives entirely in the token block at the top of this file,
   which is the only place a colour, a size or a radius is written down. */

/* ── Master data layout ───────────────────────────────────
   The old page put a half-empty two-column card next to a ragged right edge.
   One column of full-width cards, each with its own header, reads as a page
   rather than as leftovers. */
.page.wide { padding-block: 26px 60px; }
.sheet { width: min(1180px, 100%); display: grid; gap: 18px; }

.sheet-head {
  display: flex; flex-wrap: wrap; align-items: flex-end;
  justify-content: space-between; gap: 16px;
}
.sheet-title { margin: 0; font-size: 24px; letter-spacing: -.01em; }
.sheet-sub { margin: 4px 0 0; font-size: 13px; color: var(--ink-soft); }
.sheet-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }

/* The sheet upload, now that it lives in the header beside Download
   rather than in a panel of its own. A bare file input next to a button
   is a different height and a different font, so it is given the same
   shell as the inputs on the master-data upload - dashed, to read as
   "put a file here" rather than as a third button. */
.sheet-upload { display: flex; align-items: center; gap: 8px; }
.sheet-upload input[type="file"] {
  padding: 7px 9px; border: 1px dashed var(--line);
  border-radius: var(--r-card); background: var(--bg);
  font: inherit; font-size: 12px; color: var(--ink-soft);
  max-width: 230px;
}
/* The columns the sheet needs, under the header. */
.sheet-note { margin: 0; font-size: 12px; line-height: 1.6; }

.stat-strip {
  /* minmax(0, 1fr), not 1fr. A grid track's default minimum is its
     content, so one long note ("every one lands unassigned") pushed the
     strip wider than the page and put a horizontal scrollbar under the
     whole window. */
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px;
}
.stat-card {
  display: flex; flex-direction: column; gap: 2px;
  padding: 14px 16px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 3px solid var(--line);
  border-radius: var(--r-shell);
}
.stat-card.is-flagged { border-left-color: var(--warn); }
.stat-label {
  font-size: 11px; font-weight: var(--w-value); color: var(--ink-soft);
}
.stat-value { font-size: 24px; font-weight: var(--w-value); line-height: 1.15; }
.stat-note { font-size: 11px; color: var(--ink-soft); }
.stat-card.is-flagged .stat-note { color: var(--warn-ink); }

.panel-card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-shell);
  overflow: hidden;
}
.panel-card-head {
  padding: 14px 18px;
  border-bottom: 1px solid var(--line-soft);
}
.panel-card-head h2 { margin: 0; font-size: 16px; }
.panel-card-head p  { margin: 3px 0 0; font-size: 12px; color: var(--ink-soft); }
.panel-card-head.with-search {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 12px;
}
.panel-card-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 12px 18px;
  border-top: 1px solid var(--line-soft);
  background: var(--bg);
  font-size: 12px;
}
.panel-empty { padding: 22px 18px; font-size: 13px; }

.search-inline { display: flex; align-items: center; gap: 8px; }
.search-inline input {
  padding: 7px 11px; min-width: 220px;
  border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--bg); color: var(--ink); font: inherit; font-size: 13px;
}

.upload-grid {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
  gap: 22px; padding: 18px;
}
.drop {
  display: flex; flex-direction: column; gap: 12px; align-items: flex-start;
  padding: 18px;
  border: 1px dashed var(--line); border-radius: var(--r-shell);
  background: var(--bg);
}
.drop input[type="file"] { width: 100%; font-size: 13px; }

.upload-facts { margin: 0; display: grid; grid-template-columns: 84px 1fr; gap: 6px 14px; }
.upload-facts dt {
  font-size: 11px; font-weight: var(--w-value); color: var(--ink-soft); padding-top: 2px;
}
.upload-facts dd { margin: 0; font-size: 12px; line-height: 1.5; }
.upload-facts .cols {
  font-size: 11px; color: var(--ink-soft);
}

.notice {
  padding: 14px 18px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--brand-2);
  border-radius: var(--r-shell);
  background: var(--panel);
}
.notice.is-good    { border-left-color: var(--ok); }
.notice.is-flagged { border-left-color: var(--warn); }
.notice-title { margin: 0; font-size: 14px; }
.notice-line  { margin: 4px 0 0; font-size: 13px; }
.notice-body  { margin: 10px 0 6px; font-size: 12px; color: var(--ink-soft); }
.notice-list  { margin: 4px 0 0; padding-left: 18px; font-size: 12px; color: var(--ink-soft); }

.two-up { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }

.plain-list { list-style: none; margin: 0; padding: 0; }
.plain-list li {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 18px;
  border-bottom: 1px solid var(--line-soft);
  font-size: 13px;
}
.plain-list li:last-child { border-bottom: 0; }
.plain-list .sub { display: block; font-size: 11px; color: var(--ink-soft); }
.plain-list .counts { font-size: 11px; color: var(--ink-soft); white-space: nowrap; }
.plain-list.scroll { max-height: 320px; overflow-y: auto; }

/* The table sits flush inside its card. */
.table-wrap.flush { border: 0; border-radius: 0; }

@media (max-width: 980px) {
  .stat-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .upload-grid, .two-up { grid-template-columns: 1fr; }
}

/* Values must not break mid-way. "Md. Sajjadur Rahman no / account" and
   "120 / min" both read as damage rather than as wrapping. */
.tag { white-space: nowrap; }
.grid td.num, .grid th.num { white-space: nowrap; }
.grid td { vertical-align: top; }
.grid .nobody { white-space: nowrap; }


/* ── All Tickets ──────────────────────────────────────────
   A dense grid that stays readable at three thousand rows: thin borders, one
   line per row, and the tracking column pinned so scrolling sideways never
   loses which ticket you are looking at. */
.create-new {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 13px; font-weight: var(--w-head); text-decoration: none;
  color: var(--brand-2-ink);
}
.create-new:hover { text-decoration: underline; }
.create-new span { font-size: 16px; }

.list-tools { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }
.global-search {
  min-width: 260px; padding: 8px 12px;
  border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--panel); color: var(--ink); font: inherit; font-size: 13px;
}
.export-btn.is-busy { opacity: .7; pointer-events: none; }
.x-icon { color: var(--ok-ink); }

.tabs { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.tab {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 14px;
  border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--panel); color: var(--ink);
  font-size: 13px; text-decoration: none;
}
.tab:hover { border-color: var(--brand); }
.tab.is-on {
  background: var(--brand); border-color: var(--brand); color: var(--on-accent); font-weight: var(--w-head);
}
.tab-n {
  font-variant-numeric: tabular-nums;
  font-size: 11px; font-weight: var(--w-value);
}
.tab.is-on .tab-n { color: var(--on-accent); }
.clear-filters { margin-left: 8px; font-size: 12px; }

.table-wrap.listing { max-height: 68vh; overflow: auto; }
.listing-grid { min-width: 1180px; font-size: 12px; }

/* No coloured band. A header is structure, not status: it earns its weight
   from a rule beneath it and slightly stronger ink, and it leaves the plum
   for the one primary action on the screen. */
.listing-grid thead tr.band th {
  background: var(--surface);
  color: var(--ink-2);
  border-bottom: 1px solid var(--line);
  font-size: var(--t-12); font-weight: var(--w-head);
}
.listing-grid thead tr.band th a {
  color: var(--ink-2); text-decoration: none;
  display: inline-flex; align-items: center; gap: var(--s-4);
}
.listing-grid thead tr.band th a:hover { color: var(--ink); }
/* The sorted column is marked by its arrow and by darker ink, so the cue
   survives greyscale and colour blindness both. */
.listing-grid thead th.is-sorted a { color: var(--ink); font-weight: var(--w-head); }
.arrow { font-size: var(--t-11); }

.listing-grid tr.filters td {
  position: sticky; top: 34px; z-index: 2;
  padding: 5px 6px;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}
.col-filter {
  width: 100%; min-width: 74px; padding: 5px 7px;
  border: 1px solid var(--line); border-radius: var(--r-control);
  background: var(--panel); color: var(--ink);
  font: inherit; font-size: 11px;
}
tr.filters td.range { display: flex; gap: 4px; }

/* Alternating hover, not alternating stripes: the row you are pointing at is
   information, the row's parity is not. */
.listing-grid tbody tr:hover { background: var(--hover); }
.listing-grid td { height: 46px; padding: var(--s-12); }

/* The tracking column stays put when the grid scrolls sideways. */
.listing-grid th:first-child,
.listing-grid td.tracking {
  position: sticky; left: 0; z-index: 1;
  background: var(--panel);
}
.listing-grid thead th:first-child { z-index: 4; background: var(--surface); }
.listing-grid tr.filters td:first-child { z-index: 3; background: var(--canvas); }
.listing-grid tbody tr:hover td.tracking { background: var(--hover); }
td.tracking a { color: var(--brand-2-ink); text-decoration: none; font-size: 12px; }
td.tracking a:hover { text-decoration: underline; }

/* One line, ellipsis, the whole thing in the title attribute. */
.clip {
  max-width: 280px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
.empty-row { padding: 28px 12px; text-align: center; }

.badge {
  display: inline-block; padding: 3px 9px;
  border-radius: var(--r-pill); font-size: 11px; font-weight: var(--w-value); white-space: nowrap;
}
.badge-lg { padding: 6px 14px; font-size: 13px; }
/* The per-status colours live in one place, further down under "Status",
   with .badge { color: var(--ink) }. There used to be a second set here as
   well: every background it set was overridden down there, but its `color`
   was NOT, because .badge.s-open beats .badge on specificity whatever the
   order. So the documented decision - "the wash carries the hue, the text
   goes to ink" - was quietly losing to a rule six hundred lines above the
   comment that described it, and In Progress measured 4.46:1 in the dark
   theme. Two definitions of one badge is the defect; this is the deletion. */

.pager {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 12px;
  padding: 12px 16px;
  background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--r-shell);
  font-size: 12px;
}
.pager-count strong { font-variant-numeric: tabular-nums; }
.page-size { display: flex; align-items: center; gap: 8px; }
.page-size select {
  padding: 5px 8px; border: 1px solid var(--line); border-radius: var(--r-control);
  background: var(--bg); color: var(--ink); font: inherit; font-size: 12px;
}
.pager-nav { display: flex; align-items: center; gap: 6px; }
.pager-nav .btn { padding: 5px 10px; font-size: 12px; }
.pager-at { padding: 0 6px; color: var(--ink-soft); }

/* ── Ticket detail ──────────────────────────────────────── */
.detail-no { font-size: 12px; color: var(--ink-soft); }
.detail-person { font-size: 16px; }

.detail-list { margin: 0; display: grid; grid-template-columns: 130px 1fr; }
.detail-list dt, .detail-list dd {
  padding: 9px 18px; margin: 0;
  border-bottom: 1px solid var(--line-soft); font-size: 13px;
}
.detail-list dt { color: var(--ink-soft); font-size: 12px; }
.detail-list dt:last-of-type, .detail-list dd:last-of-type { border-bottom: 0; }

.detail-body {
  padding: 16px 18px; font-size: 13px; line-height: 1.6;
  white-space: pre-wrap; overflow-wrap: anywhere;
}

.attach-list { list-style: none; margin: 0; padding: 0 18px 16px; display: grid; gap: 10px; }
.attach-list li { display: flex; align-items: center; gap: 12px; }
.attach-thumb {
  width: 44px; height: 44px; border-radius: var(--r-control);
  object-fit: cover; background: var(--line-soft);
}
.attach-ext {
  display: grid; place-items: center;
  font-size: 11px; font-weight: var(--w-value); color: var(--ink-soft);
}
.attach-meta { display: flex; flex-direction: column; font-size: 13px; min-width: 0; }
.attach-meta a { color: var(--brand-2-ink); overflow-wrap: anywhere; }
.attach-meta .muted { font-size: 11px; }

.thread { list-style: none; margin: 0; padding: 0; }
.thread-item {
  display: grid; grid-template-columns: 150px 1fr auto; gap: 12px;
  padding: 10px 18px;
  border-bottom: 1px solid var(--line-soft);
  font-size: 13px;
}
.thread-item:last-child { border-bottom: 0; }
.thread-who { font-weight: var(--w-head); }
.thread-when { font-size: 11px; color: var(--ink-soft); white-space: nowrap; }

@media (max-width: 900px) {
  .list-tools { align-items: stretch; width: 100%; }
  .global-search { min-width: 0; width: 100%; }
  .detail-list { grid-template-columns: 1fr; }
  .detail-list dt { padding-bottom: 0; border-bottom: 0; }
  .thread-item { grid-template-columns: 1fr; gap: 2px; }
}


/* ==========================================================
   DESIGN SYSTEM - shell, states, accessibility floor
   Everything above this line predates the system and was retinted by the
   token block at the top. Everything below is the system itself.
   ========================================================== */

/* -- The shell --------------------------------------------
   60px top bar / optional 224px rail / content. The rail lists named
   destinations - places an agent goes - and never repeats a column filter,
   because the filter already lives on the column. Requesters never see it:
   they have one destination and it is the form. */
.topbar {
  height: var(--topbar-h);
  padding: 0 var(--s-16);
  gap: var(--s-16);
}

.app {
  display: grid;
  grid-template-columns: var(--rail-w) minmax(0, 1fr);
  align-items: start;
  min-height: calc(100vh - var(--topbar-h));
}
.app.no-rail { grid-template-columns: minmax(0, 1fr); }

.rail {
  position: sticky; top: var(--topbar-h);
  height: calc(100vh - var(--topbar-h));
  overflow-y: auto;
  padding: var(--s-16) var(--s-12);
  border-right: 1px solid var(--line);
  background: var(--surface);
}
.rail-group + .rail-group { margin-top: var(--s-24); }
.rail-title {
  padding: 0 var(--s-12) var(--s-8);
  font-size: var(--t-12); font-weight: var(--w-value); color: var(--ink-2);
}
.rail-link {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-8);
  padding: var(--s-8) var(--s-12);
  border-radius: var(--r-control);
  color: var(--ink); text-decoration: none;
  font-size: var(--t-13);
}
.rail-link:hover { background: var(--hover); }
.rail-link.is-on { background: var(--plum); color: var(--on-accent); font-weight: var(--w-head); }
/* A page that belongs to the one above it. Indented rather than given a
   heading of its own: the rail has headings already, and a fifth for a
   single link would weigh more than the link does. */
.rail-n {
  font-variant-numeric: tabular-nums;
  font-size: var(--t-11); font-weight: var(--w-value); color: var(--ink-2);
}
.rail-link.is-on .rail-n { color: var(--on-accent); }
/* An urgent count is red AND labelled - never colour alone. */
.rail-n.is-urgent { color: var(--rose-ink); font-weight: var(--w-head); }
.rail-link.is-on .rail-n.is-urgent { color: var(--on-accent); }

/* -- Content widths ---------------------------------------
   Forms are 680px: a line longer than that is hard to track back to. Tables
   and card grids take the room they need. */
.content { padding: var(--s-24); }
.content.narrow { max-width: 680px; margin-inline: auto; }

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(268px, 1fr));
  gap: 14px;
}

/* -- The five states --------------------------------------
   Every list builds all of them. A blank table is not a state, it is an
   unfinished one. */

/* Loading: the shape of the real thing, so nothing jumps when it arrives. */
.skeleton {
  background: linear-gradient(90deg,
    var(--line-soft) 25%, var(--line) 37%, var(--line-soft) 63%);
  background-size: 400% 100%;
  border-radius: var(--r-control);
  animation: shimmer 1.4s ease-in-out infinite;
}
@keyframes shimmer { to { background-position: -135% 0; } }
.skeleton-line { height: 12px; }
.skeleton-row td { height: 46px; padding: var(--s-12); }

/* Empty: a sentence and the action that fixes it. */
.state {
  display: grid; justify-items: center; gap: var(--s-12);
  padding: var(--s-40) var(--s-24);
  text-align: center;
}
.state-title { font-size: var(--t-16); font-weight: var(--w-head); }
.state-note { max-width: 46ch; color: var(--ink-2); font-size: var(--t-13); }

/* Partial: which filter is excluding things, and a way to drop it. */
.state-filter {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: center; gap: var(--s-8);
  font-size: var(--t-13);
}
.state-chip {
  display: inline-flex; align-items: center; gap: var(--s-4);
  padding: 2px var(--s-8);
  background: var(--plum-wash); color: var(--plum-ink);
  border-radius: var(--r-pill);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-11);
}

/* Error: what happened, what to do, and a retry. */
.inline-error {
  display: flex; align-items: flex-start; gap: var(--s-8);
  padding: var(--s-12) var(--s-16);
  background: var(--rose-wash);
  border: 1px solid var(--rose); border-left-width: 3px;
  border-radius: var(--r-card);
  color: var(--ink); font-size: var(--t-13);
}

/* -- Status -----------------------------------------------
   Colour plus label, always. Colour alone fails a colour-blind reader and
   fails again in print. */
/* Measured, not eyeballed: the status hue on its own pale wash comes in
   under 4.5:1 for amber (3.0), moss (4.3) and rose (4.5 exactly) - text
   nobody with slightly tired eyes reads comfortably. The wash keeps the hue
   and carries the meaning; the text goes to ink, which is 12:1 or better on
   every one of them. The label is still there, so status is never colour
   alone either way. */
.badge { color: var(--ink); }
.badge.s-new      { background: var(--steel-wash); }
.badge.s-open     { background: var(--amber-wash); }
.badge.s-pending  { background: var(--amber-wash); }
.badge.s-reopened { background: var(--rose-wash); }
.badge.s-resolved { background: var(--moss-wash); }
.badge.s-closed   { background: var(--grey-wash); }
/* The asset register's four. Same idea as the ticket statuses above: the
   wash carries the hue and the text stays ink, so none of them needs its
   own -ink variant and none can fall under 4.5:1. */
.badge.s-in_use   { background: var(--steel-wash); }
.badge.s-spare    { background: var(--moss-wash); }
.badge.s-repair   { background: var(--amber-wash); }
.badge.s-retired  { background: var(--grey-wash); }
/* A coloured left edge on the badge keeps the hue legible to someone who
   cannot see the wash at all - on a monochrome screen, or in print. */
.badge.s-new  { box-shadow: inset 2px 0 var(--steel); }
.badge.s-open { box-shadow: inset 2px 0 var(--amber); }
.badge.s-pending  { box-shadow: inset 2px 0 var(--amber); }
.badge.s-reopened { box-shadow: inset 2px 0 var(--rose); }
.badge.s-resolved { box-shadow: inset 2px 0 var(--moss); }
.badge.s-closed   { box-shadow: inset 2px 0 var(--grey); }

/* -- Accessibility floor ---------------------------------- */

/* Every interactive element, one visible ring, never removed.

   --plum-ink, not --plum: a focus ring has to clear 3:1 against what is
   behind it, and dark plum on the dark theme's surface is about 1.6:1 - a
   ring that is technically present and practically invisible, which is worse
   than none because it looks like it was considered. The lifted step reaches
   4.6:1 there and is unchanged in the light theme. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--plum-ink);
  outline-offset: 2px;
}

/* Keyboard users get the skip link; nobody else sees it. */
.skip {
  position: absolute; left: var(--s-8); top: -60px; z-index: 100;
  padding: var(--s-8) var(--s-16);
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--plum); border-radius: var(--r-control);
}
.skip:focus { top: var(--s-8); }

/* Motion answers an action. It never introduces one. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* -- Down to 375px ----------------------------------------
   The rail moves above the content rather than disappearing: a destination
   you cannot reach is worse than one you have to scroll past. */
@media (max-width: 860px) {
  .app { grid-template-columns: minmax(0, 1fr); }
  .rail {
    position: static; height: auto;
    border-right: 0; border-bottom: 1px solid var(--line);
    padding: var(--s-12);
  }
  .rail-group { display: flex; flex-wrap: wrap; gap: var(--s-8); }
  .rail-group + .rail-group { margin-top: var(--s-12); }
  .rail-title { width: 100%; padding: 0 0 var(--s-4); }
  .rail-link { border: 1px solid var(--line); }
  .content { padding: var(--s-16); }
}
@media (max-width: 420px) {
  .content { padding: var(--s-12); }
  .toast { left: var(--s-12); right: var(--s-12); max-width: none; }
}


/* -- Fitting the shell to the pages -----------------------
   The pages predate the rail and each supplied its own padding through
   .page. Inside the shell .content owns that, so .page becomes a plain
   block and stops centring things against a rail that is already there. */
.app .page {
  display: block;
  padding: 0;
  max-width: none;
}
.app .page.wide,
.app .page-form { padding: 0; }
.app .sheet,
.app .dash { width: 100%; max-width: none; }
.app .modal-card { width: 100%; max-width: 980px; }

/* -- Top bar pieces ---------------------------------------*/
.topbar { border-bottom: 1px solid var(--line); background: var(--surface); }
.brand {
  display: flex; align-items: center; gap: var(--s-8);
  color: var(--ink); text-decoration: none;
  font-size: var(--t-16);
}
.brand strong { font-weight: var(--w-head); }
.brand .dot {
  width: var(--s-8); height: var(--s-8);
  border-radius: 50%;
  background: var(--plum);
}

.topbar-search { flex: 1; max-width: 420px; }
.topbar-search input {
  width: 100%;
  height: 36px;
  padding: 0 var(--s-12);
  border: 1px solid var(--line); border-radius: var(--r-control);
  background: var(--canvas); color: var(--ink);
  font: var(--w-body) var(--t-13)/1 var(--font);
}
.topbar-search input::placeholder { color: var(--ink-3); }

.topbar-right { margin-left: auto; display: flex; align-items: center; gap: var(--s-12); }

.who {
  display: flex; align-items: center; gap: var(--s-8);
  padding: 3px var(--s-12) 3px 3px;
  background: none; border: 1px solid var(--line);
  border-radius: var(--r-pill);
  cursor: pointer; font-size: var(--t-13);
}
.who:hover { border-color: var(--plum); }
.avatar {
  display: grid; place-items: center;
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--plum); color: var(--on-accent);
  font-size: var(--t-11); font-weight: var(--w-head);
}

/* Available to a screen reader, invisible to everyone else. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

@media (max-width: 860px) {
  .topbar { gap: var(--s-8); }
  .topbar-search { display: none; }
  .who-name { display: none; }
}


/* -- All tickets, under the design system -----------------
   Rows are one line each. A table people scan down a column loses that job
   the moment a cell wraps to three lines, so every cell truncates and the
   whole value lives in the tooltip. */
.sheet-title { font-size: var(--t-24); }
.sheet-sub { margin: var(--s-4) 0 0; color: var(--ink-2); font-size: var(--t-13); }
.list-tools { display: flex; align-items: center; gap: var(--s-8); }
.global-search {
  width: 260px; height: 36px;
  padding: 0 var(--s-12);
  border: 1px solid var(--line); border-radius: var(--r-control);
  background: var(--surface); color: var(--ink);
  font: var(--w-body) var(--t-13)/1 var(--font);
}

.listing-grid { min-width: 1180px; font-size: var(--t-13); }
.listing-grid td {
  height: 46px;
  padding: var(--s-12);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: middle;
}
.listing-grid td.clip { max-width: 150px; }
.listing-grid td.wide-clip { max-width: 280px; }
.listing-grid td.tracking { white-space: nowrap; }
td.tracking a { font-size: var(--t-12); color: var(--steel-ink); text-decoration: none; }
td.tracking a:hover { text-decoration: underline; }

/* The filter row: every control the same height, and the date pair sized so
   it stops pushing the last column past the right edge. */
.listing-grid tr.filters td { padding: var(--s-8); height: auto; overflow: visible; }
.col-filter {
  width: 100%; height: 30px;
  padding: 0 var(--s-8);
  border: 1px solid var(--line); border-radius: var(--r-control);
  background: var(--surface); color: var(--ink);
  font: var(--w-body) var(--t-12)/1 var(--font);
}
tr.filters td.range { display: flex; gap: var(--s-4); min-width: 210px; }
tr.filters td.range .col-filter { width: 100px; padding: 0 var(--s-4); }

/* The tab row. Plum marks the active one - that is the one place on this
   page it is allowed. */
.tabs { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-8); }
.tab {
  display: inline-flex; align-items: center; gap: var(--s-8);
  height: 34px; padding: 0 14px;
  border: 1px solid var(--line); border-radius: var(--r-control);
  background: var(--surface); color: var(--ink);
  font-size: var(--t-13); text-decoration: none;
}
.tab:hover { border-color: var(--plum); }
.tab.is-on {
  background: var(--plum); border-color: var(--plum);
  color: var(--on-accent); font-weight: var(--w-head);
}
.tab-n {
  font-variant-numeric: tabular-nums;
  font-size: var(--t-11); font-weight: var(--w-value); color: var(--ink-2);
}
.tab.is-on .tab-n { color: var(--on-accent); }
.clear-filters { margin-left: var(--s-8); }

.empty-row { padding: var(--s-24); text-align: center; }
.badge {
  display: inline-block;
  padding: 3px var(--s-8);
  border-radius: var(--r-pill);
  font-size: var(--t-11); font-weight: var(--w-head); white-space: nowrap;
}
.badge-lg { padding: var(--s-4) 14px; font-size: var(--t-13); }

.pager {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: var(--s-12);
  padding: var(--s-12) var(--s-16);
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-card);
  font-size: var(--t-12);
}
.pager-count strong { font-variant-numeric: tabular-nums; }
.page-size { display: flex; align-items: center; gap: var(--s-8); }
.page-size select {
  height: 30px; padding: 0 var(--s-8);
  border: 1px solid var(--line); border-radius: var(--r-control);
  background: var(--surface); color: var(--ink);
  font: var(--w-body) var(--t-12)/1 var(--font);
}
.pager-nav { display: flex; align-items: center; gap: var(--s-8); }
.pager-nav .btn { min-height: 30px; padding: 0 var(--s-12); font-size: var(--t-12); }
.pager-at { padding: 0 var(--s-8); color: var(--ink-2); }
/* The pager's own form: rows-per-page, the page jump, and the Go button that
   carries both when no script is running. Wraps rather than overflows - it is
   the widest thing on a narrow master-data page. */
.pager-form {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s-12); margin-left: auto;
}

/* The second way out of a row that will not delete: move its work to another
   site first. Inside the danger panel, under a rule, because it is the same
   decision arrived at differently - not a separate feature. */
.merge-form { display: grid; gap: var(--s-12); justify-items: start; }
.rule { width: 100%; height: 1px; margin: var(--s-16) 0;
        border: 0; background: var(--line); }
.has-js .pager-go { display: none; }

/* LOADING. Filtering reloads the page, and a table that blanks for 200ms
   reads as broken. These take the place of the rows while the next page is
   on its way, in the shape the rows will arrive in. */
.is-loading-rows tbody tr { visibility: hidden; }
.skeleton-rows tr td { height: 46px; padding: var(--s-12); }
.skeleton-rows .skeleton-line { width: 100%; }

@media (max-width: 860px) {
  .sheet-head { flex-direction: column; align-items: stretch; }
  .list-tools { width: 100%; }
  .global-search { width: 100%; }
}


/* -- The queue, as a table --------------------------------
   Agents compare age and priority down a column, and only a column lets
   them. The rounded cards this replaced could not be scanned that way. */
.shell {
  display: grid;
  grid-template-columns: 236px minmax(0, 1fr);
  gap: var(--s-20);
  align-items: start;
}
.side {
  position: sticky; top: calc(var(--topbar-h) + var(--s-20));
  display: grid; gap: var(--s-20);
  padding: var(--s-16);
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-card);
}
.side .search input {
  width: 100%; height: 36px;
  padding: 0 var(--s-12);
  border: 1px solid var(--line); border-radius: var(--r-control);
  background: var(--canvas); color: var(--ink);
  font: var(--w-body) var(--t-13)/1 var(--font);
}
.facet h3 { margin: 0 0 var(--s-8); font-size: var(--t-12); color: var(--ink-2); font-weight: var(--w-value); }
.chips { display: flex; flex-wrap: wrap; gap: var(--s-8); }
.chip {
  padding: var(--s-4) var(--s-12);
  border: 1px solid var(--line); border-radius: var(--r-pill);
  color: var(--ink); text-decoration: none;
  font-size: var(--t-12);
}
.chip:hover { border-color: var(--plum); }
.chip.is-on { background: var(--plum); border-color: var(--plum); color: var(--on-accent); }
.side-foot { border-top: 1px solid var(--line); padding-top: var(--s-12); }

.queue-head {
  display: flex; align-items: baseline; gap: var(--s-12);
  margin-bottom: var(--s-12);
}
.queue-head h2 { font-size: var(--t-19); }

.queue-grid { width: 100%; }
.queue-grid td {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.queue-grid td.clip { max-width: 160px; }
/* The subject carries the weight: it is what an agent recognises, and the
   reference beside it is metadata. */
.queue-grid .subject-cell { max-width: 420px; }
.queue-grid .subject-cell a {
  color: var(--ink); text-decoration: none; font-weight: var(--w-head);
}
.queue-grid .subject-cell a:hover { text-decoration: underline; }
.queue-grid tr.is-open { outline: 1px solid var(--plum-ink); outline-offset: -1px; }

/* Priority: a dot and the word. The dot alone would say nothing to a
   colour-blind agent, or in print. */
.prio { display: inline-flex; align-items: center; gap: var(--s-8); font-size: var(--t-12); }
.prio-dot { width: var(--s-8); height: var(--s-8); border-radius: 50%; background: var(--grey); }
.prio.p-urgent .prio-dot { background: var(--rose); }
.prio.p-high   .prio-dot { background: var(--amber); }
.prio.p-normal .prio-dot { background: var(--steel); }
.prio.p-low    .prio-dot { background: var(--grey); }

.age {
  font-variant-numeric: tabular-nums;
  font-size: var(--t-12); color: var(--ink-2);
}
.age.heat-breached { color: var(--rose-ink); font-weight: var(--w-head); }
.age-flag {
  margin-left: var(--s-4);
  padding: 1px var(--s-8);
  background: var(--rose-wash); color: var(--ink);
  border-radius: var(--r-pill);
  font-size: var(--t-11); font-weight: var(--w-head);
}

.assign-cell { white-space: nowrap; }
.assignee { display: inline-flex; align-items: center; gap: var(--s-8); font-size: var(--t-12); }
.assignee .clip { max-width: 110px; overflow: hidden; text-overflow: ellipsis; }
.avatar.sm { width: 22px; height: 22px; font-size: var(--t-11); }
.btn-take { min-height: 28px; padding: 0 var(--s-12); font-size: var(--t-12); }

@media (max-width: 860px) {
  .shell { grid-template-columns: minmax(0, 1fr); }
  .side { position: static; }
  .table-wrap { overflow-x: auto; }
  /* The reference stays put while the rest scrolls sideways. */
  .queue-grid td.tracking, .queue-grid th:first-child {
    position: sticky; left: 0; background: var(--surface); z-index: 1;
  }
}


/* -- Last pass: things only visible once rendered ---------- */

/* The search box and Export sit on one line. An older rule stacked them in a
   column, and setting display:flex without a direction left that in charge. */
.list-tools { flex-direction: row; align-items: center; }
@media (max-width: 860px) {
  .list-tools { flex-direction: column; align-items: stretch; }
  .list-tools .btn { width: 100%; }
}

/* ── The filter row, to one measure ───────────────────────
   A select with no width of its own is as wide as its longest option, so
   a row of them came out ragged: "All roles" at 90px beside "All
   departments" at 450px, because one department is called Marketing &
   Merchandising. Nothing about the widths said anything - they were an
   accident of the data - and the row read as broken rather than as four
   controls that do the same kind of thing.

   One measure for all of them, and the long names truncate. The list
   that opens is drawn at its own width by the browser, so nothing is
   lost by narrowing the closed control. */
.filter-form .list-tools > select {
  flex: 0 1 180px; min-width: 0; max-width: 180px;
  height: 36px; padding: 0 var(--s-10);
  text-overflow: ellipsis;
}
.filter-form .list-tools { flex-wrap: wrap; row-gap: var(--s-8); }
/* And it lines up with the heading above it. .list-head had no rule of
   its own at all, so the filter row started hard against the panel's
   edge while "Who has access" sat 18px in - near enough to look like a
   mistake, far enough to see. 18px is what .panel-card-head uses. */
.panel-card > .list-head { padding: var(--s-12) 18px; }
.filter-form .check { white-space: nowrap; font-size: var(--t-12); }

/* The subject is what an agent recognises, so it takes the slack in the row
   rather than truncating at 420px beside empty space. max-width:0 with a
   percentage width is what makes a cell in an auto-layout table truncate. */
.queue-grid .subject-cell { width: 55%; max-width: 0; }
.queue-grid td.clip { max-width: 150px; }

/* A stat card's number is the thing being read; the label above it is the
   caption, not an eyebrow. */
.stat-label { font-size: var(--t-12); font-weight: var(--w-value); color: var(--ink-2); }
.stat-value { font-size: var(--t-24); font-weight: var(--w-value); line-height: 1.3; }
.tile-label { font-size: var(--t-12); font-weight: var(--w-value); color: var(--ink-2); }
.tile-n { font-size: var(--t-24); font-weight: var(--w-value); line-height: 1.3; }


/* -- Contrast floor ---------------------------------------
   --ink-3 (#8B848A) is 3.64:1 on white, under the 4.5:1 the quality floor
   asks for. It is a supplied token, so it stays defined - but it is used
   ONLY where the text is a hint that repeats something already labelled:
   placeholders, which every field duplicates in a real <label>. Anything a
   person has to read to use the page takes --ink-2 (7.6:1). */
.rail-title,
.facet h3,
.fld-hint,
.hint,
.state-note,
.me-email,
.tile-foot,
.stat-note { color: var(--ink-2); }


/* -- What the browser does when we say nothing -------------
   Two rules that only showed up when the pages were measured in a browser
   rather than read as CSS. */

/* The UA stylesheet makes <strong> and <b> 700, which is above the scale's
   ceiling. One page had 601 elements over the limit and every one of them
   came from here, not from a declaration in this file. */
strong, b { font-weight: var(--w-head); }
/* A column header names the values under it, so it takes the value
   weight - the same one the dashboard tile gives its caption. */
th { font-weight: var(--w-value); }

/* "46px row height, 12px cell padding" - 12px of *vertical* padding on top of
   a 22px badge or a 28px button makes a 53px row. The height is the promise;
   the padding is horizontal, and the cell centres its content in it. */
.grid td,
.listing-grid td,
.queue-grid td {
  height: 46px;
  padding: 0 var(--s-12);
  vertical-align: middle;
}
.grid th, .listing-grid thead tr.band th {
  height: 46px;
  padding: 0 var(--s-12);
}
/* The filter row is controls, not data, and sizes to them. */
.listing-grid tr.filters td { height: auto; padding: var(--s-8); }
/* Two-line cells in the master table opt out by name rather than by accident. */
.grid td.stacked { height: auto; padding: var(--s-8) var(--s-12); }


/* -- Ticket routing ---------------------------------------
   An admin screen, so density over calm: this is a grid somebody works down
   looking for gaps. The gap count at the top carries the weight. */
.routing-alert {
  align-items: center;
  font-size: var(--t-13);
}
.routing-alert strong {
  font-variant-numeric: tabular-nums;
  font-size: var(--t-19); font-weight: var(--w-value);
  color: var(--rose-ink);
}

.routing-filters { display: flex; flex-wrap: wrap; gap: var(--s-8); align-items: center; }
.routing-filters .col-filter { width: auto; min-width: 150px; }
.gaps-toggle {
  display: inline-flex; align-items: center; gap: var(--s-8);
  font-size: var(--t-13); white-space: nowrap;
}

.routing-grid { min-width: 1180px; font-size: var(--t-13); }
.routing-grid td { height: 46px; padding: var(--s-4) var(--s-8); }
.routing-grid td.clip { max-width: 170px; overflow: hidden;
                        text-overflow: ellipsis; white-space: nowrap; }
.routing-grid .col-filter { min-width: 130px; }
.routing-grid .target-input { width: 84px; text-align: right; font-variant-numeric: tabular-nums; }
/* A row that will produce unassigned tickets is marked by a rule and by the
   badge in it, not by a red row background - status colour lives in badges. */
.routing-grid tr.is-gap td:first-child { box-shadow: inset 3px 0 var(--rose); }

.confirm-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-12);
  margin-top: var(--s-16);
}
.confirm-row .col-filter { flex: 1; min-width: 220px; }

.bulk-form { display: grid; gap: var(--s-12); }
.bulk-form label { display: grid; gap: var(--s-4); font-size: var(--t-13); font-weight: var(--w-value); }
.bulk-form .btn { justify-self: start; }

.calendar-form { display: grid; gap: var(--s-8); }
.cal-day {
  display: grid; grid-template-columns: 1fr 96px 96px;
  align-items: center; gap: var(--s-8);
}
.cal-day .col-filter { font-variant-numeric: tabular-nums; text-align: right; }
.calendar-form .btn { justify-self: start; margin-top: var(--s-8); }

.holidays { margin-top: var(--s-24); }
.holidays h3 { margin: 0 0 var(--s-8); font-size: var(--t-13); }
.holidays .plain-list li {
  display: flex; align-items: center; gap: var(--s-12);
  padding: var(--s-4) 0; font-size: var(--t-13);
}
.holidays form { display: inline; }
.holiday-form {
  display: flex; flex-wrap: wrap; gap: var(--s-8); margin-top: var(--s-12);
}
.holiday-form .col-filter { width: auto; min-width: 150px; }

/* -- Assignment, on the ticket ---------------------------- */
.reassign-form {
  display: grid; grid-template-columns: auto 1fr auto 2fr auto;
  align-items: center; gap: var(--s-8);
  padding-bottom: var(--s-16);
  border-bottom: 1px solid var(--line-soft);
}
.reassign-form .fld-label { white-space: nowrap; }
.assign-history { padding-top: var(--s-12); }
.assign-history .thread-item { padding-inline: 0; }

@media (max-width: 860px) {
  .reassign-form { grid-template-columns: 1fr; }
  .cal-day { grid-template-columns: 1fr 1fr; }
  .routing-filters .col-filter { min-width: 0; width: 100%; }
}

/* ── A long list that fits the page it is on ──────────────
   The Settings listings were a 68vh scroll box inside a page that also
   scrolled: two scrollbars on top of each other, a short window onto a
   long table, and rows tall enough that five of them filled it. Master
   data has never had the inner box - it uses .table-wrap.flush - and
   these now match it. One scrollbar, the page's own.

   .grid.tight is the row height. The default 46px row with 12px cells is
   right for a queue of twenty tickets; for eight hundred accounts it is
   half a screen of padding. These cells are 10px on a 32px row, which
   fits about twice as many people in the same window. */
.grid.tight th { padding: var(--s-8) var(--s-10); }
.grid.tight td {
  height: 32px; padding: var(--s-4) var(--s-10);
  font-size: var(--t-12); vertical-align: middle;
}
/* Every cell of a tight grid clips rather than wrapping. A single long
   department name used to make its row three lines deep and every other
   row on the page keep its company. */
.grid.tight td.clip { max-width: 220px; }
/* The serial column. width:1% shrinks it to the digits, which left "3"
   touching "A.A.S Nuwan Chamara" with nothing between them - two columns
   reading as one. A floor and a gutter on the right instead: the number
   still takes almost no width, and it is plainly its own column. */
.grid.tight th.sl-col,
.grid.tight td.sl-col {
  width: 1%; min-width: 34px;
  padding-right: var(--s-16); text-align: right;
}
.grid.tight td.sl-col { color: var(--ink-2); }
/* Same gutter after the tickbox, for the same reason. */
.grid.tight th.pick-col,
.grid.tight td.pick-col { padding-right: 0; }

/* The row's controls, on ONE line. They were a wrapping flex box in a
   narrow cell, which is what actually made the rows 120px tall - not the
   text in them. Now they stay in a row and the cell is as wide as it
   needs to be. */
.grid.tight .admin-acts {
  flex-wrap: nowrap; gap: var(--s-8); white-space: nowrap;
  justify-content: flex-start; align-items: center;
}
.grid.tight .admin-acts .link { font-size: var(--t-12); }
.grid.tight .role-pick { display: inline-flex; gap: var(--s-4); align-items: center; }
.grid.tight .role-pick select { padding: 1px 4px; }
/* Tags sit beside each other on one line rather than stacking. */
.grid.tight .tag { white-space: nowrap; }
.grid.tight td .tag + .tag { margin-left: var(--s-4); }

/* A cell whose contents CANNOT shrink needs a floor, or the table hands
   it less width than it needs and the contents spill over the column to
   its right. That is what put "Active / Temp password" on top of "Edit",
   and ran the headings "Role" and "Status" into one word: the tags and
   the action links are all nowrap, so the only columns the browser could
   take width from were the ones that had none to give.
   
   With a floor the table widens instead and the card scrolls sideways,
   which is what Master data does and is the readable failure. */
.grid.tight th.tag-col, .grid.tight td.tag-col {
  white-space: nowrap; min-width: 150px;
}
.grid.tight th.role-col, .grid.tight td.role-col {
  white-space: nowrap; min-width: 64px;
}
.grid.tight td.row-tool { white-space: nowrap; }

/* The per-column search row, in a tight grid. .listing-grid had one of
   these; this is the same row in the grid the Settings lists use. The
   sticky offset the other one carries is deliberately NOT here - these
   tables have no scroll box of their own to stick inside. */
.grid.tight tr.filters td {
  padding: var(--s-4) var(--s-4);
  background: var(--canvas);
  border-bottom: 1px solid var(--line);
}
.grid.tight tr.filters .col-filter {
  width: 100%; min-width: 0; height: 28px;
  font-size: var(--t-11);
}
/* A filter over a column must not be what decides the column's width -
   the heading and the data should. 74px is the .col-filter floor and it
   was widening the narrow columns. */
.grid.tight tr.filters select.col-filter { padding: 0 var(--s-4); }
/* And the text columns give that width up, because they can: each one
   already clips with an ellipsis. */
.grid.tight td.clip { max-width: 180px; }

/* The Delete button under a list, with the sentence that says what it
   is for. The sentence is not decoration: on these two pages the right
   answer is usually Deactivate or Mark as left, and a bare red button
   with no words beside it is an invitation to find that out afterwards. */
/* The three things you can DO to one account, on its own screen. One a
   line: each is a button with a sentence saying what it means, and the
   sentences are the point - "deactivate" and "mark out of office" are
   two words apart and weeks apart in what they do. */
.account-acts { display: grid; gap: var(--s-12); padding: var(--s-16) 18px 18px; }
.account-acts form {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-12);
}
.account-acts .btn { flex: 0 0 auto; min-width: 180px; }
.account-acts .muted { flex: 1 1 280px; font-size: var(--t-12); }
.account-acts .btn:disabled { opacity: .5; cursor: not-allowed; }

.bulk-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-12);
  padding: var(--s-12) var(--s-16);
  font-size: var(--t-12);
}
.bulk-bar .muted { flex: 1 1 320px; }

.routing-legend { padding: 0 var(--s-16) var(--s-12); font-size: var(--t-12); }
/* Open tickets that can sit on no row of the grid. A warning, not a note -
   it is the one thing the narrowed page cannot show. */
.routing-legend.is-flagged { color: var(--bad-ink); }
/* The ticket references against a row. Several fit, wrapping rather than
   widening the column: a head with four open tickets must not push the
   Save button off the edge. */
.routing-open { display: flex; flex-wrap: wrap; gap: var(--s-4) var(--s-8); }
.inherit-note { margin-left: var(--s-4); color: var(--ink-2); font-size: var(--t-11); }


/* == Ticket detail ========================================
   Two columns: the conversation on the left, the properties on the right.
   The rail is FIRST in the document and placed into column 2 here, so a
   phone gets status and assignee at the top with no duplicated markup. */

.crumbs {
  display: flex; align-items: center; gap: var(--s-8);
  margin-bottom: var(--s-12);
  font-size: var(--t-12); color: var(--ink-2);
}
.crumbs a { color: var(--steel-ink); text-decoration: none; }
.crumbs a:hover { text-decoration: underline; }

.ticket-head {
  display: flex; flex-wrap: wrap; align-items: flex-start;
  justify-content: space-between; gap: var(--s-16);
  margin-bottom: var(--s-20);
}
.ticket-ident { display: flex; align-items: center; gap: var(--s-12); }
.ticket-ident .tno { font-size: var(--t-13); color: var(--ink-2); }
.ticket-title { margin: var(--s-4) 0 0; font-size: var(--t-24); }
.ticket-sub { margin: var(--s-4) 0 0; font-size: var(--t-13); }
.ticket-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-8); }
.ticket-actions form { display: inline; }

.resolve-box {
  margin-bottom: var(--s-20);
  padding: var(--s-16);
  background: var(--surface);
  /* The plum lives on the button inside, not on the panel: the token is for
     primary actions, and a panel is not one. */
  border: 1px solid var(--line); border-left: 3px solid var(--line);
  border-radius: var(--r-card);
}
.resolve-box textarea {
  width: 100%; margin-top: var(--s-8);
  padding: var(--s-8) var(--s-12);
  border: 1px solid var(--line); border-radius: var(--r-control);
  background: var(--surface); color: var(--ink);
  font: var(--w-body) var(--t-14)/1.5 var(--font);
  resize: vertical;
}
.resolve-foot {
  display: flex; align-items: center; gap: var(--s-16);
  margin-top: var(--s-12);
}

.detail-grid { display: grid; gap: var(--s-20); align-items: start; }
.detail-main { display: grid; gap: var(--s-20); min-width: 0; }

@media (min-width: 981px) {
  .detail-grid { grid-template-columns: minmax(0, 1fr) 320px; }
  .detail-main { grid-column: 1; grid-row: 1; }
  .props {
    grid-column: 2; grid-row: 1;
    position: sticky; top: calc(var(--topbar-h) + var(--s-20));
    max-height: calc(100vh - var(--topbar-h) - var(--s-40));
    overflow-y: auto;
  }
}

.props {
  padding: var(--s-16);
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-card);
}
.prop-group { display: grid; gap: 2px; margin-bottom: var(--s-16); }
.prop-group:last-child { margin-bottom: 0; }
/* .props was written for the ticket page's narrow right-hand rail, where a
   single column is right. On a full-width panel that same list runs to a
   screen and a half, so this wraps it into as many columns as fit. */
.props-wide {
  display: grid; gap: var(--s-16) var(--s-24);
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  /* And UNDO the ticket page's rail behaviour. .props is sticky above 980px
     with a viewport max-height and its own scrollbar, which is right for a
     320px column beside a long thread and wrong for a full-width panel: the
     box slid down over the paragraph underneath it as soon as the page was
     scrolled, so the line about the QR sticker was there in the HTML,
     measurable in the DOM, and invisible on the screen. Reset here rather
     than narrowing the rule above, because it is .props-wide that means
     "this one is not a rail". */
  position: static;
  max-height: none;
  overflow: visible;
}
.props-wide .prop-group { margin-bottom: 0; }

/* The design system asks for --ink-3 here. It is 3.64:1 on white, under the
   4.5:1 the same design system sets as its floor, so these take --ink-2 at
   the stated 12px instead. Flagged rather than silently ignored. */
.prop-label { font-size: var(--t-12); color: var(--ink-2); }
.prop-value { font-size: var(--t-13); font-weight: var(--w-value); color: var(--ink); }
.prop-sub { font-size: var(--t-12); color: var(--ink-2); }
.prop-link { font-size: var(--t-12); color: var(--steel-ink); overflow-wrap: anywhere; }
.prop-note { margin: var(--s-4) 0 0; font-size: var(--t-11); color: var(--ink-2); }
.prop-rule { margin: var(--s-20) 0; border: 0; border-top: 1px solid var(--line); }

/* Overdue is the one thing on this rail allowed a colour. */
.is-late { color: var(--rose-ink); }

.prop-input {
  width: 100%; height: 34px;
  padding: 0 var(--s-8);
  border: 1px solid var(--line); border-radius: var(--r-control);
  background: var(--surface); color: var(--ink);
  font: var(--w-body) var(--t-13)/1 var(--font);
}
.inline-save { display: grid; gap: var(--s-4); }
.inline-save .btn { justify-self: start; }

/* The inline success indicator: no page-level Save button in the rail. */
.saved {
  font-size: var(--t-11); color: var(--moss-ink);
  opacity: 0; transition: opacity .2s;
}
.saved.is-shown { opacity: 1; }
.saved.is-bad { color: var(--rose-ink); }

.edited-flag {
  font-size: var(--t-11); color: var(--ink-2);
  border-bottom: 1px dotted var(--line); cursor: help;
}
.edit-body { padding: 0 var(--s-16) var(--s-16); }
.edit-body textarea {
  width: 100%; margin: var(--s-8) 0;
  padding: var(--s-8) var(--s-12);
  border: 1px solid var(--line); border-radius: var(--r-control);
  background: var(--surface); color: var(--ink);
  font: var(--w-body) var(--t-13)/1.5 var(--font);
  resize: vertical;
}

/* -- Attachments ------------------------------------------ */
.attach-row {
  display: grid; grid-template-columns: 44px minmax(0, 1fr) auto;
  align-items: center; gap: var(--s-12);
  padding: var(--s-8) 0;
}
.attach-row form { display: inline; }
.attach-thumb-link { display: block; line-height: 0; }
.attach-row .attach-thumb { width: 44px; height: 44px; }
.attach-row.is-removed { opacity: .6; }
.attach-list.removed { border-top: 1px dashed var(--line); margin-top: var(--s-8); }
.attach-add { padding: var(--s-16); border-top: 1px solid var(--line-soft); }
.attach-add-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-8); }
.attach-add .file-list { margin-top: var(--s-8); }

/* -- The conversation ------------------------------------- */
.convo { list-style: none; margin: 0; padding: 0 var(--s-16); }
.convo-item {
  display: grid; grid-template-columns: 28px minmax(0, 1fr);
  gap: var(--s-12);
  padding: var(--s-12);
  margin-bottom: var(--s-12);
  border-radius: var(--r-card);
}
/* Requester and agent are told apart by their ground, not by tinted text -
   coloured body text is harder to read and says nothing in greyscale. */
.convo-item.from-requester { background: var(--canvas); }
.convo-item.from-desk { background: var(--surface); border: 1px solid var(--line); }
.convo-item.is-internal {
  background: var(--amber-wash);
  border: 1px solid var(--amber);
  border-left-width: 3px;
}
.convo-item.is-new { animation: settle .4s ease-out; }
@keyframes settle { from { opacity: 0; transform: translateY(4px); } }

.convo-who {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s-8);
  margin: 0 0 var(--s-4); font-size: var(--t-13);
}
.role-tag {
  padding: 1px var(--s-8);
  background: var(--grey-wash); color: var(--ink-2);
  border-radius: var(--r-pill);
  font-size: var(--t-11);
}
.role-tag.is-note { background: var(--amber); color: var(--on-accent); }
.convo-when { margin-left: auto; font-size: var(--t-11); color: var(--ink-2); }
.convo-text { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; font-size: var(--t-13); }

.comment-box { padding: var(--s-16); border-top: 1px solid var(--line-soft); }
.comment-box textarea {
  width: 100%;
  padding: var(--s-8) var(--s-12);
  border: 1px solid var(--line); border-radius: var(--r-control);
  background: var(--surface); color: var(--ink);
  font: var(--w-body) var(--t-14)/1.5 var(--font);
  resize: vertical;
}
.comment-box.is-internal textarea {
  background: var(--amber-wash); border-color: var(--amber);
}
.comment-foot {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: var(--s-12);
  margin-top: var(--s-12);
}
.note-toggle {
  display: inline-flex; align-items: center; gap: var(--s-8);
  font-size: var(--t-12); color: var(--ink-2);
}
.reopen-nudge {
  margin: 0 var(--s-16) var(--s-12);
  align-items: center; justify-content: space-between; gap: var(--s-12);
}
.reopen-nudge form { display: inline; }

/* -- History ---------------------------------------------- */
.history-grid { width: 100%; font-size: var(--t-12); }
.history-grid td { height: 46px; }
.history-grid td.num { font-variant-numeric: tabular-nums; white-space: nowrap; }
.event-label { font-weight: var(--w-head); }
.event-delta {
  display: block;
  font-size: var(--t-11); color: var(--ink-2);
  max-width: 420px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.history-grid tr.is-internal .event-label::after {
  content: " · internal";
  color: var(--amber-ink); font-weight: var(--w-body);
}

.figures {
  display: flex; flex-wrap: wrap; gap: var(--s-24);
  padding: var(--s-16);
  border-top: 1px solid var(--line-soft);
}
.figure { display: grid; gap: 2px; }
.figure-label { font-size: var(--t-12); font-weight: var(--w-value); color: var(--ink-2); }
.figure-value { font-size: var(--t-19); font-weight: var(--w-value); }
.figures-note { width: 100%; margin: 0; font-size: var(--t-11); color: var(--ink-2); }

/* -- Lightbox --------------------------------------------- */
.lightbox {
  position: fixed; inset: 0; z-index: 80;
  display: grid; place-items: center;
  padding: var(--s-24);
  background: rgba(33, 26, 30, .82);
}
.lightbox img { max-width: 100%; max-height: 90vh; border-radius: var(--r-card); }
.lightbox-close {
  position: absolute; top: var(--s-16); right: var(--s-16);
  width: 36px; height: 36px;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: 50%;
  font-size: var(--t-19); line-height: 1; cursor: pointer;
}

@media (max-width: 980px) {
  .ticket-actions { width: 100%; }
  .figures { gap: var(--s-16); }
}

/* -- Master data: serial column and pager ------------------ */
.sl-col { width: 56px; color: var(--ink-2); }
.grid td.sl-col { font-variant-numeric: tabular-nums; font-size: var(--t-12); }
.master-pager { margin-top: var(--s-12); }


/* == The shell, laid out like Q CMMS =======================
   A fixed full-height sidebar with the logo in its own header, the content
   in a column beside it, and a header strip inside that column. Same shape
   as the CMMS; the typography and the palette stay the helpdesk's, which is
   what "copy the layout, do not change the text design" asks for.
   ========================================================== */

:root {
  --sidebar-w: 240px;   /* the CMMS's width, to the pixel */
  --sidebar-c: 56px;    /* and its collapsed strip */
}

.sidebar {
  position: fixed; left: 0; top: 0;
  width: var(--sidebar-w); height: 100vh;
  display: flex; flex-direction: column;
  overflow: hidden;
  z-index: 200;
  /* The CMMS's own sidebar: surface at the top fading to the page ground at
     the bottom, so the rail reads as part of the shell rather than a panel
     stuck to the side. */
  background: linear-gradient(180deg, var(--surface) 0%, var(--canvas) 100%);
  border-right: 1px solid var(--line);
  transition: width .2s ease-out;
}

.sidebar-header {
  display: flex; align-items: center; gap: var(--s-8);
  min-height: 70px;
  padding: var(--s-16) var(--s-12);
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}
.sidebar-brand {
  display: flex; align-items: center; gap: var(--s-8);
  flex: 1; min-width: 0;
  color: var(--ink); text-decoration: none;
}
/* The company's mark, on the brand tile. It was the letter Q set in the
   page's own typeface until the artwork was supplied.

   Applied as a MASK rather than as an <img>: the file carries shape only -
   its colour is thrown away when it is built - so background-color decides
   what colour it comes out, and one file serves the white-on-plum tile here
   and the plum-on-paper wordmark below. An <img> of plum ink would also
   bring the artwork's white box with it, which shows on the dark theme. */
.sidebar-logo {
  display: grid; place-items: center;
  width: 32px; height: 32px; flex-shrink: 0;
  background: var(--plum);
  border-radius: var(--r-control);
}
.sidebar-logo::before {
  content: "";
  width: 12px; height: 20px;
  background-color: var(--on-accent);
  -webkit-mask: url("logo-mark.png") center / contain no-repeat;
          mask: url("logo-mark.png") center / contain no-repeat;
}

/* The full logo - monogram over COLLECTION - on the sign-in, password and
   scan cards, where there is room for it. */
.brand-logo {
  display: block;
  width: 56px; height: 45px; flex-shrink: 0;
  background-color: var(--accent-ink);
  -webkit-mask: url("logo.png") center / contain no-repeat;
          mask: url("logo.png") center / contain no-repeat;
}
.sidebar-names { display: grid; min-width: 0; }
.sidebar-company {
  font-size: var(--t-11); font-weight: var(--w-value); color: var(--ink-2);
  letter-spacing: .04em; white-space: nowrap;
}
.sidebar-app {
  font-size: var(--t-14); font-weight: var(--w-head); color: var(--ink);
  white-space: nowrap;
}


.sidebar-nav { flex: 1; overflow-y: auto; padding: var(--s-16) var(--s-12); }

/* Collapsed: a narrow strip holding the mark and the bars button, and
   nothing else. The button is the important part - it used to hide itself
   along with the labels, which left the sidebar shut with no way to open it
   again. A row of unlabelled pills is no use either, so the links go too and
   one click on the bars brings the whole menu back. */
body.sidebar-collapsed .sidebar { width: var(--sidebar-c); }
body.sidebar-collapsed .sidebar-names,
body.sidebar-collapsed .sidebar-nav { display: none; }
body.sidebar-collapsed .sidebar-header {
  flex-direction: column; gap: var(--s-12);
  padding: var(--s-12) var(--s-8);
}
body.sidebar-collapsed .main-wrapper { margin-left: var(--sidebar-c); }

/* The bars button: three lines when shut, a chevron when open. Always
   visible, because the only control that reopens a closed menu must be. */
.sidebar-toggle {
  width: 32px; height: 32px; flex-shrink: 0;
  display: grid; place-items: center;
  background: none; border: 1px solid var(--line);
  border-radius: var(--r-control);
  color: var(--ink-2); cursor: pointer;
}
.sidebar-toggle:hover { border-color: var(--plum-ink); color: var(--plum-ink); }
.sidebar-toggle .bars-lines {
  position: relative; display: block;
  width: 15px; height: 2px; border-radius: 2px;
  background: currentColor;
}
.sidebar-toggle .bars-lines::before,
.sidebar-toggle .bars-lines::after {
  content: ""; position: absolute; left: 0;
  width: 15px; height: 2px; border-radius: 2px;
  background: currentColor;
}
.sidebar-toggle .bars-lines::before { top: -5px; }
.sidebar-toggle .bars-lines::after  { top: 5px; }

.main-wrapper {
  margin-left: var(--sidebar-w);
  min-height: 100vh;
  transition: margin-left .2s ease-out;
}

/* The header strip sits inside the column, and sticks to the top of it. */
.topbar {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; gap: var(--s-16);
  height: var(--topbar-h);
  padding: 0 var(--s-16);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

/* The menu button only exists where the sidebar is not permanently on show. */
.bars { display: none; }
.me-role { font-weight: var(--w-head); color: var(--ink); }

/* The sidebar slides over on a phone rather than pushing the page sideways. */
.sidebar-backdrop {
  display: none;
  position: fixed; inset: 0; z-index: 199;
  background: var(--scrim, rgba(33, 26, 30, .5));
}
body.sidebar-open .sidebar-backdrop { display: block; }

@media (max-width: 980px) {
  .sidebar { transform: translateX(-100%); transition: transform .2s ease-out; }
  body.sidebar-open .sidebar { transform: translateX(0); }
  .main-wrapper { margin-left: 0; }
  body.sidebar-collapsed .main-wrapper { margin-left: 0; }
  .bars {
    display: grid; place-items: center;
    width: 36px; height: 36px;
    background: none; border: 1px solid var(--line);
    border-radius: var(--r-control);
    cursor: pointer;
  }
  .sidebar-toggle { display: none; }
}

/* The rail links, restyled for a sidebar that owns the whole left edge. */
.rail-link {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-8);
  width: 100%;
  padding: var(--s-8) var(--s-12);
  border: 0; background: none;
  border-radius: var(--r-control);
  color: var(--ink); text-decoration: none;
  font: var(--w-body) var(--t-13)/1.4 var(--font);
  text-align: left; cursor: pointer;
}
.rail-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rail-out { color: var(--ink-2); }

/* The ticket detail page's own rail sticks under the header strip, which is
   60px tall and sticky - not under a bar that no longer spans the page. */
@media (min-width: 981px) {
  .props { top: calc(var(--topbar-h) + var(--s-20)); }
}


/* == Dashboard category panels =============================
   Laid out the way Q CMMS lays out Break Down / Planned Maintenance /
   Condition Based: a titled panel per kind of work, with a grid of solid
   status tiles inside it.

   The tiles are filled rather than tinted, which is the one place on the desk
   that happens - it is what the CMMS does and what was asked for here. The
   COLOURS are still this desk's, assigned by meaning rather than copied:
   "Total" is a count and takes the neutral steel, not the CMMS's red, which
   in this palette means something is wrong.
   ========================================================== */

.panels-row {
  display: grid;
  /* Three across, the way the CMMS sets them out. auto-fit was dropping the
     third onto its own row at this width, which reads as two categories and
     an afterthought rather than three of equal standing. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-16);
  margin-bottom: var(--s-24);
  align-items: start;
}
@media (max-width: 1100px) {
  .panels-row { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
}

.cat-panel {
  display: flex; flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  overflow: hidden;
}

/* A panel header, not a table header - the rule about coloured bands is
   about tables, where the band competes with the data under it. */
.cat-head {
  margin: 0;
  padding: var(--s-12) var(--s-16);
  background: var(--surface-3); color: var(--ink);
  font-size: var(--t-16); font-weight: var(--w-head);
  text-align: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Four rows of one, the CMMS's .sd-cells shape. Its Break Down panel is
   two columns of four; ours has four tiles, so four rows is one column.
   grid-auto-rows: 1fr with flex: 1 is what makes every tile the same height
   and the panels finish level - the CMMS's own note on this says equal row
   counts are the only way boxes on two sides can match while the panels
   still end together. Its gaps are 4px down and 8px of padding; both are
   copied rather than rounded to this sheet's spacing steps, because the
   whole point is that the two screens measure the same. */
.cat-tiles {
  display: grid; grid-template-columns: 1fr;
  grid-auto-rows: 1fr; align-items: stretch;
  gap: 4px;
  padding: 8px;
  flex: 1; min-height: 0;
}
/* Three panels sit side by side, so a tile is about 140px wide. Everything
   below is sized to that: the figure is the thing being read and gets the
   room, the label and the note stay out of its way. */

/* Solid, the way Q CMMS's Service Dashboard has them, on instruction. This
   was a soft tint with dark text before, on the argument that four saturated
   blocks compete with each other and the figure has to fight the fill. That
   argument is not wrong - it is overruled, deliberately, because the two
   systems are read side by side and matching the one people already know is
   worth more here than the tint was. What is NOT copied is the CMMS's exact
   hues: four of them fail the contrast floor this app is held to, so each is
   stepped to the first legible value of the same colour. */
.cat-tile {
  display: flex; flex-direction: column; justify-content: center;
  align-items: center; gap: 3px;
  min-height: 66px;
  padding: 7px 6px;
  border: 1px solid var(--line);
  /* Every box has a 3px edge in its own colour (below), so the geometry is
     the same on all four. */
  border-left: 3px solid var(--grey);
  border-radius: var(--r-card);
  /* The card itself is neutral - the page's own surface, no plum tint.
     Asked for "more professional", and the answer chosen was: a clean card,
     with colour carried only by a thin edge and the figure. The whole box
     is never filled, so four boxes never read as four alarms. */
  background: var(--surface);
  color: var(--ink); text-decoration: none;
  text-align: center;
}

/* One colour per box, meaning where the tickets are:
     Total        grey    everything, no judgement
     Open         amber   waiting for somebody to pick it up
     In Progress  blue    somebody has it
     Closed       green   finished
   The EDGE takes the fill hue; the FIGURE takes the matching -ink token,
   which is the version of that hue measured to clear 4.5:1 on this surface
   in both themes. Painting the figure in the fill hue is the mistake this
   sheet has already made twice. Total's figure stays plain ink: grey text
   reads as disabled, and the total is the one number that is not. */
.cat-tile.t-total    { border-left-color: var(--grey); }
.cat-tile.t-open     { border-left-color: var(--amber); }
.cat-tile.t-progress { border-left-color: var(--steel); }
.cat-tile.t-closed   { border-left-color: var(--moss); }
.cat-tile:hover { border-color: var(--accent-ink); }
/* The CMMS's type on this box: a small heavy caption over a large number,
   both centred, neither allowed to wrap - a wrapped caption changes the
   box's height, and one tile taller than its neighbours is what makes a
   grid look broken. These two are where --w-value came from: the weight
   was put on this tile first, then asked for across the desk, so every
   caption-over-a-figure in the app now reads the way this one does. The
   CMMS's reason is worth repeating: these are read standing up, at arm's
   length, and a lighter caption is a label you have to lean in for. */
.cat-label {
  font-size: var(--t-11); font-weight: var(--w-value); color: var(--ink-2);
  letter-spacing: .1px; line-height: 1.2;
  white-space: nowrap;
}
/* The figure carries the tile. 24px is the top of the scale, so it is made
   dominant by giving everything around it less room rather than by growing
   past the scale. */
.cat-n {
  /* tabular-nums so the number does not shuffle sideways as the count
     changes. This tile is where that pairing started; the whole desk is set
     in one face now and every figure in it asks for the same thing. */
  font-variant-numeric: tabular-nums;
  font-size: var(--t-24); font-weight: var(--w-value); line-height: 1;
  color: var(--ink);
  white-space: nowrap;
}
/* The figure in its box's -ink colour - see the per-box rules above. */
.cat-tile.t-open     .cat-n { color: var(--amber-ink); }
.cat-tile.t-progress .cat-n { color: var(--steel-ink); }
.cat-tile.t-closed   .cat-n { color: var(--moss-ink); }

/* All four tiles are the same tile. There used to be a Delayed box, the one
   thing on the panel somebody had to act on, and it alone wore a rose wash
   and a red edge. The boxes are Total / Open / In Progress / Closed now, and
   none of those is bad news on its own - a ticket waiting to be picked up is
   not late - so none of them gets an alarm colour. Painting Open red because
   it inherited Delayed's slot would say the opposite of what it means. */


.cat-note {
  font-size: var(--t-11); color: var(--ink-2); line-height: 1.3;
  /* One line, always. A note that wraps makes its tile taller than the three
     beside it, and the row stretches to match the worst of them. */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cat-sum {
  margin: 0;
  padding: 0 8px 8px;
  color: var(--ink-2); font-size: var(--t-11); text-align: center;
}
/* The panel with nothing behind it should not be twice the height of the two
   beside it just because its message is longer. */
.cat-missing { padding: var(--s-16) var(--s-12); }
.cat-missing .state-note { max-width: 30ch; font-size: var(--t-12); }

.cat-missing {
  display: grid; justify-items: center; gap: var(--s-8);
  padding: var(--s-24) var(--s-16);
  text-align: center;
}
.cat-missing .state-note { max-width: 32ch; }

/* The filter bar, with the CMMS's paired date inputs. */
.date-pair { display: flex; align-items: center; gap: var(--s-8); }
.date-arrow { color: var(--ink-2); }

@media (max-width: 720px) {
  .date-pair { flex-wrap: wrap; }
}

/* The Delayed / Ontime / Open filter a dashboard tile set, shown so nobody
   wonders why the list is short - and removable in one click. */
.outcome-chip { display: inline-flex; align-items: center; gap: var(--s-8); }
.outcome-chip .link { color: inherit; font-size: var(--t-13); }

/* -- The theme switch ------------------------------------- */
.theme-pick {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-8);
  padding: var(--s-8) var(--s-12);
}
.theme-label { font-size: var(--t-13); color: var(--ink); }
.theme-buttons {
  display: inline-flex;
  border: 1px solid var(--line); border-radius: var(--r-control);
  overflow: hidden;
}
.theme-btn {
  padding: var(--s-4) var(--s-8);
  background: var(--surface); color: var(--ink-2);
  border: 0; cursor: pointer;
  font: var(--w-body) var(--t-11)/1.4 var(--font);
}
.theme-btn + .theme-btn { border-left: 1px solid var(--line); }
.theme-btn:hover { color: var(--plum-ink); }
.theme-btn.is-on {
  background: var(--plum); color: var(--on-accent); font-weight: var(--w-head);
}
body.sidebar-collapsed .theme-pick { display: none; }


/* == Ticket detail, as the reference screen ================
   Plain bold headings rather than cards: the sections are not separate
   objects, they are one ticket read top to bottom, and four card borders
   around it were four boxes competing with the content.
   ========================================================== */

.plain-section { margin-bottom: var(--s-32); }

.plain-head {
  display: flex; align-items: center; gap: var(--s-8);
  margin: 0 0 var(--s-12);
  font-size: var(--t-16); font-weight: var(--w-head); color: var(--ink);
}

.plain-body {
  font-size: var(--t-14); line-height: 1.6;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.plain-empty { color: var(--ink-2); font-size: var(--t-13); }

/* -- Attachments ------------------------------------------
   Icon, then the filename as a link, and nothing else in the row. The size,
   who uploaded it and when are in the title attribute - useful on the rare
   occasion somebody wants them, and out of the way of the name, which is
   what people actually scan for. */
.attach-plus {
  width: 22px; height: 22px;
  display: grid; place-items: center;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r-control);
  font-size: var(--t-14); line-height: 1; cursor: pointer;
}
.attach-plus:hover { border-color: var(--plum-ink); color: var(--plum-ink); }
.attach-hint { font-size: var(--t-13); font-weight: var(--w-head); color: var(--ink); }

.file-table {
  list-style: none; margin: 0; padding: 0;
  border: 1px solid var(--line); border-radius: var(--r-card);
  overflow: hidden;
}
.file-line {
  display: grid; grid-template-columns: 34px minmax(0, 1fr) auto;
  align-items: center; gap: var(--s-8);
  padding: var(--s-8) var(--s-12);
  border-bottom: 1px solid var(--line-soft);
}
.file-line:last-child { border-bottom: 0; }
.file-line form { display: inline; }
.file-icon { text-align: center; text-decoration: none; font-size: var(--t-16); }
.file-name {
  color: var(--steel-ink); font-size: var(--t-13);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* -- The conversation -------------------------------------
   One panel holding the thread and the box you reply in, because they are
   one thing: the conversation, and the next line of it. */
.convo-panel {
  background: var(--canvas);
  border: 1px solid var(--line); border-radius: var(--r-card);
  padding: var(--s-16);
}
.convo-panel .convo { list-style: none; margin: 0; padding: 0; }
.convo-panel .convo-item {
  display: block;
  padding: 0 0 var(--s-12);
  margin-bottom: var(--s-12);
  background: none; border: 0;
  border-bottom: 1px solid var(--line);
  border-radius: 0;
}
.convo-panel .convo-item.is-internal {
  background: var(--amber-wash);
  border: 1px solid var(--amber); border-left-width: 3px;
  border-radius: var(--r-card);
  padding: var(--s-12);
}
.convo-panel .convo-who {
  display: flex; align-items: baseline; gap: var(--s-8);
  margin: 0 0 var(--s-4);
  font-size: var(--t-14);
}
.convo-panel .convo-when {
  margin-left: auto;
  font-size: var(--t-12); color: var(--ink-2); white-space: nowrap;
}
.convo-panel .convo-text {
  margin: 0; color: var(--ink-2); font-size: var(--t-14);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.convo-panel .comment-box { padding: 0; border-top: 0; }
.convo-panel .comment-box textarea { background: var(--surface); }

/* -- History ----------------------------------------------
   Comments / User / Date / Duration, with the duration right-aligned
   because it is the only column read by comparison. */
.history-table {
  width: 100%; border-collapse: collapse;
  font-size: var(--t-13);
}
.history-table th {
  padding: var(--s-8) var(--s-12);
  border-bottom: 1px solid var(--line);
  background: var(--surface);
  text-align: left; font-weight: var(--w-value); color: var(--ink);
  white-space: nowrap;
}
.history-table td {
  padding: var(--s-8) var(--s-12);
  border-bottom: 1px solid var(--line-soft);
  vertical-align: top;
}
.history-table tbody tr:last-child td { border-bottom: 0; }
.history-table .num {
  text-align: right; white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.history-table tr.is-internal td:first-child::after {
  content: " · internal";
  color: var(--amber-ink);
}

/* The rail's reporter block runs several lines under one label. */
.props .prop-group .prop-value + .prop-value { font-weight: var(--w-body); }
.props .prop-value a { color: var(--steel-ink); overflow-wrap: anywhere; }

/* The upload form is invisible until the + is pressed, so it must take no
   room either - it was leaving a padded empty block between the file list
   and Comments. */
.attach-add {
  padding: 0; border-top: 0;
}
.attach-add:not(:has(.file-row)) { margin: 0; }
.attach-add .file-list:empty { margin: 0; }
.attach-add .file-list { margin-top: var(--s-8); }
.plain-section .attach-add .hint { margin: var(--s-8) 0 0; }


/* == The two dashboard charts =============================
   Panel header as a band, as the reference has it, then the legend, the
   chart, and a note saying what the marks mean. */
.chart-panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  overflow: hidden;
}
.chart-head {
  margin: 0;
  padding: var(--s-12) var(--s-16);
  background: var(--steel-wash); color: var(--ink);
  border-bottom: 1px solid var(--line);
  font-size: var(--t-16); font-weight: var(--w-head);
}
.chart-body { padding: var(--s-16); }

.chart-legend {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: var(--s-16);
  margin-bottom: var(--s-12);
  font-size: var(--t-12); color: var(--ink-2);
}
.chart-legend .key { display: inline-flex; align-items: center; gap: var(--s-8); }
.chart-legend .key-dot {
  width: 14px; height: 10px; border-radius: 2px; display: inline-block;
}

.chart { display: block; width: 100%; height: auto; }
.chart-donut { max-width: 280px; margin: 0 auto; }

/* Series fills, so the dark theme takes its own validated steps rather than
   an automatic flip of the light ones. */
.chart .s-ontime  { fill: #3E8F4A; }
.chart .s-total   { fill: #4A7FB5; }
.chart .s-delayed { fill: #B84C4C; }
.chart .s-total-line { stroke: #4A7FB5; }
:root[data-theme="dark"] .chart .s-total { fill: #4A8CCB; }
:root[data-theme="dark"] .chart .s-total-line { stroke: #4A8CCB; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .chart .s-total { fill: #4A8CCB; }
  :root:not([data-theme="light"]) .chart .s-total-line { stroke: #4A8CCB; }
}

.chart-note {
  margin: var(--s-12) 0 0;
  font-size: var(--t-11); color: var(--ink-2); line-height: 1.5;
}

/* The reference gives the bar chart roughly half again the donut's width,
   and it needs it: three bars per group across four groups. */
.panels {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
  gap: var(--s-16);
  align-items: start;
}
@media (max-width: 1100px) {
  .panels { grid-template-columns: minmax(0, 1fr); }
}

/* -- The category panel heading, used as a filter ----------- */
/* Clicking a heading narrows the success rate, the status analysis and the
   table to that category. The whole band is the target rather than the words
   in it, so it is a comfortable click and an obvious one. */
.cat-head.is-link { padding: 0; }

.cat-head-link {
  display: block;
  padding: var(--s-12) var(--s-16);
  color: inherit;
  text-decoration: none;
}
/* At rest the heading is exactly the heading it was - the layout copied from
   the CMMS is not changed to advertise a control. The affordance lives in the
   hover instead: the whole band lights, which is what tells somebody dragging
   a mouse across the three panels that they are things you can pick. */
.cat-head-link:hover {
  background: var(--plum);
  /* The band's own colour is --surface, which is near-white in the light
     theme and near-black in the dark one. On plum the dark one measured
     1.89:1, so the hover states what its text is rather than inheriting a
     colour chosen for a different background. */
  color: var(--on-accent);
  text-decoration: underline;
}
.cat-panel.is-on .cat-head-link:hover {
  background: var(--plum-pressed);
}

/* The chosen panel, wearing the same mark as every other chosen thing in this
   interface: the active rail item, the active tab, a chip that is on. A wash
   was enough to see in the light theme and nearly invisible in the dark one,
   where surface and plum-wash are two steps apart. Solid reads in both. */
.cat-panel.is-on {
  border-color: var(--plum);
  box-shadow: inset 0 0 0 1px var(--plum);
}
.cat-panel.is-on .cat-head {
  background: var(--plum);
  color: var(--on-accent);
}

/* The banner that says the screen is filtered, and the way back out of it. */
.dash-scope {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--s-8);
  margin: 0 0 var(--s-16);
  padding: var(--s-8) var(--s-12);
  background: var(--plum-wash);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  font-size: var(--t-13); color: var(--ink);
}
.dash-scope .link { margin-left: auto; }

@media (max-width: 720px) {
  .dash-scope .link { margin-left: 0; }
}

/* -- Team Performance -------------------------------------- */
/* A team heading row, then its people indented under it. The heading takes
   the same wash a panel header takes, not a plum band: it is a grouping row
   inside a table, and a saturated band there competes with the figures it is
   summarising. */
.team-grid .team-row > th,
.team-grid .team-row > td {
  background: var(--steel-wash);
  font-weight: var(--w-value);
  color: var(--ink);
}
.team-grid .team-row > th {
  text-align: left;
}
.team-grid .member-name {
  /* Indented, so the eye reads "inside that team" without a second column. */
  padding-left: var(--s-24);
}
.team-grid tbody + tbody .team-row > th,
.team-grid tbody + tbody .team-row > td {
  border-top: 1px solid var(--line);
}

/* The hit-rate bar. The number beside it is the value; the bar is a second
   encoding of the same number, which is why it may be a colour. */
.rate-cell {
  display: flex; align-items: center; gap: var(--s-8);
  min-width: 220px;
}
.rate-bar {
  flex: 1 1 auto;
  position: relative;
  height: 16px;
  min-width: 80px;
  background: var(--line-soft);
  border-radius: 2px;
  overflow: hidden;
}
.rate-fill {
  display: block; height: 100%;
  /* Hatched rather than flat: the texture survives print, forced colours and
     a reader who cannot separate this green from the track behind it. 45
     degrees, the one direction this system uses. */
  background-color: var(--moss);
  background-image: repeating-linear-gradient(
    45deg,
    rgba(255, 255, 255, .28) 0, rgba(255, 255, 255, .28) 3px,
    transparent 3px, transparent 7px);
  /* A zero-width fill would leave the row looking unrendered rather than
     scored; a hairline says "measured, and it is nothing". */
  min-width: 2px;
}
.rate-num {
  flex: 0 0 auto;
  min-width: 4ch; text-align: right;
  font-variant-numeric: tabular-nums;
  font-size: var(--t-12);
  color: var(--ink);
}

@media (max-width: 720px) {
  .rate-cell { min-width: 120px; }
  .rate-bar { display: none; }
}

/* -- Presence, on the user history listing ------------------ */
/* Three states said in words. The tint is a second encoding of what the word
   already says, never the only one. */
.live { font-weight: var(--w-head); }
.live-active      { color: var(--moss-ink); }
.live-inactive    { color: var(--ink-2); font-weight: var(--w-body); }
/* --ink-2, not --ink-3: --ink-3 measures 3.64:1 and is restricted to
   placeholder and disabled text. "Deactivated" is a state somebody has to be
   able to read, and it is told apart from "Inactive" by the word rather than
   by a shade - which is the rule anyway. */
.live-deactivated { color: var(--ink-2); font-weight: var(--w-body); font-style: italic; }

/* The select column: narrow, and the tick is the whole target. */
.pick-col { width: 36px; text-align: center; }
.pick-col input[type="checkbox"] { margin: 0; }

.listing-foot {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: var(--s-12);
  padding: var(--s-12) var(--s-16);
  border-top: 1px solid var(--line);
}
.listing-foot p { margin: 0; font-size: var(--t-13); }

/* A PM task that has come due. The word says it; the tint repeats it. */
.live-due { color: var(--rose-ink); }

/* -- PM Task Library --------------------------------------- */
.tpl-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: var(--s-16);
  margin-bottom: var(--s-24);
}

.tpl-card {
  display: flex; flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: var(--s-16);
  gap: var(--s-12);
}

.tpl-head { display: flex; align-items: flex-start; gap: var(--s-8); }
.tpl-name {
  margin: 0; font-size: var(--t-16); font-weight: var(--w-head); color: var(--ink);
  /* Two lines, then ellipsis: a long machine name must not make one card
     twice the height of the one beside it. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.tpl-category { margin: var(--s-4) 0 0; font-size: var(--t-13); color: var(--ink-2); }

.tpl-freq {
  margin-left: auto; flex: 0 0 auto;
  padding: 2px var(--s-8);
  border-radius: var(--r-pill);
  font-size: var(--t-11); font-weight: var(--w-value);
  background: var(--steel-wash); color: var(--ink);
  white-space: nowrap;
}
/* The band carries a tint, and the word carries the meaning. */
.tpl-freq.f-daily       { background: var(--amber-wash); }
.tpl-freq.f-weekly      { background: var(--moss-wash); }
.tpl-freq.f-monthly,
.tpl-freq.f-quarterly   { background: var(--steel-wash); }
.tpl-freq.f-semi_annual,
.tpl-freq.f-yearly      { background: var(--plum-wash); }

.tpl-facts { margin: 0; font-size: var(--t-12); color: var(--ink-2); }

.tpl-items {
  margin: 0; padding: var(--s-12);
  background: var(--canvas);
  border-radius: var(--r-control);
  list-style: none;
  font-size: var(--t-13); color: var(--ink);
  /* Five items plus the "and N more" line, so every card is one height. */
  min-height: 120px;
}
.tpl-items li { padding: 2px 0; }
.tpl-items li::before { content: "› "; color: var(--ink-2); }

.tpl-acts {
  display: flex; flex-wrap: wrap; gap: var(--s-8);
  margin-top: auto;
}
.tpl-acts form { display: inline; }

@media (max-width: 720px) {
  .tpl-grid { grid-template-columns: minmax(0, 1fr); }
}

/* Five counts, not four: the shared strip is a four-column grid, which left
   Yearly stranded on a row of its own. */
.stat-strip.five { grid-template-columns: repeat(5, minmax(0, 1fr)); }

/* The Agent Dashboard's strip, narrowed on request: label on the left, the
   figure on the right, one line each. The caption-over-number layout left
   every card mostly empty and 90px tall, all of them stretched to match the
   one with a note. The note ("of 3") now sits beside its figure instead of
   under it, so no card is taller than another. */
.stat-strip.is-compact { gap: var(--s-8); }
.stat-strip.is-compact .stat-card {
  flex-direction: row; align-items: baseline; gap: var(--s-8);
  padding: var(--s-8) var(--s-12);
}
.stat-strip.is-compact .stat-label { white-space: nowrap; }
.stat-strip.is-compact .stat-n {
  margin-left: auto;
  font-size: var(--t-16); line-height: 1.3;
}
.stat-strip.is-compact .stat-note { white-space: nowrap; }
@media (max-width: 1100px) {
  .stat-strip.five { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
}

/* The checklist is the substance of a template, so give it room. .col-filter
   is sized for one-line filters and squashed an eight-row textarea to two. */
textarea.checklist {
  width: 100%;
  min-height: 190px;
  padding: var(--s-8) var(--s-12);
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r-control);
  font: var(--w-body) var(--t-13)/1.6 var(--font);
  resize: vertical;
}

/* ── "The helpdesk has been updated" ──────────────────────
   A strip at the foot of the window, over whatever is there. It is an
   overlay, which is the one place this sheet allows a shadow.

   Bottom rather than top: the top of every page is the filter bar and the
   primary action, and a banner that pushes those down moves the thing
   somebody was reaching for at the moment they reach for it. */
.update-bar {
  position: fixed; z-index: 70;
  left: 50%; transform: translateX(-50%);
  bottom: var(--s-16);
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--s-12);
  width: min(560px, calc(100vw - var(--s-32)));
  padding: var(--s-12) var(--s-16);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  /* No coloured left edge. The sheet's own rule says plum is for primary
     actions and active nav, "not for edges", and an overlay that has lifted
     off the page does not need a stripe to be noticed as well. */
  box-shadow: 0 10px 30px var(--scrim);
  color: var(--ink); font-size: var(--t-13); line-height: 1.5;
}
.update-bar span { flex: 1; min-width: 0; }
@media (max-width: 560px) {
  .update-bar { left: var(--s-16); right: var(--s-16); transform: none;
                width: auto; }
}

/* ── The desk pages copied from the CMMS ──────────────────
   My Tasks, Notifications, Technician Dashboard, Reports and Settings. Every
   one of them is built from pieces already in this sheet - sheet, panel-card,
   stat-strip, tabs, grid - so what follows is only what those five pages add,
   and nothing here restyles anything that existed before. */

/* A figure that is bad news takes the same left edge .is-flagged takes for a
   warning, in the status hue rather than the brand one. */
.stat-card.is-bad { border-left-color: var(--bad); }
.stat-card.is-bad .stat-n { color: var(--bad-ink); }

/* Text that sits straight inside a panel rather than in its body. */
.panel-card > .muted, .panel-card > .chart-note { padding: 0 18px 14px; margin: 0; }
/* A legend sits straight inside the panel too, above its chart. */
.panel-card > .chart-legend { padding: var(--s-16) 18px 0; margin: 0; }
.panel-card > .panel-card-head + .muted { padding-top: 14px; }
.panel-card > .chart { padding: var(--s-12) 18px 18px; box-sizing: border-box; }
.panel-card > .admin-add,
.panel-card > .admin-list { padding: var(--s-16) 18px 18px; }

/* Two charts side by side, one above the other on a narrow screen. */
.chart-row {
  display: grid; grid-template-columns: 1fr 1fr; gap: 18px;
}

/* A smaller control for a row of them inside a list item. */
.btn-small { min-height: 30px; padding: var(--s-4) var(--s-12); font-size: var(--t-12); }

/* ── Notifications ── */
.notif-list { list-style: none; margin: 0; padding: 0; }
.notif {
  display: grid; gap: var(--s-8);
  padding: 14px 18px;
  border-bottom: 1px solid var(--line-soft);
}
.notif:last-child { border-bottom: 0; }
/* Unread carries an edge as well as the dot, so it survives greyscale and
   print - the same reasoning as the status badges. The ink step, not the
   fill: 3px of plum on the dark surface is plum on plum. */
.notif.is-unread { box-shadow: inset 3px 0 var(--accent-ink); }
.notif-head { display: flex; align-items: center; gap: var(--s-8); }
.notif-when { color: var(--ink-2); font-size: var(--t-12); }
.notif-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--accent-ink);
}
.notif-subject { margin: 0; font-size: var(--t-14); font-weight: var(--w-head); }
.notif-body {
  margin: 0; color: var(--ink-2); font-size: var(--t-13);
  white-space: pre-wrap; line-height: 1.55;
}
.notif-foot { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-8); }

/* ── Technician Dashboard ── */
.td-grid {
  display: grid; gap: var(--s-12);
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  padding: var(--s-16) 18px 18px;
}
.td-card {
  display: grid; gap: var(--s-4); align-content: start;
  padding: var(--s-12) 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 3px solid var(--line);
  border-radius: var(--r-card);
}
/* Open work with nobody on duty behind it. The word under it says so too. */
.td-card.is-bare { border-left-color: var(--bad); }
.td-name { margin: 0; font-size: var(--t-14); font-weight: var(--w-head); }
.td-figures { margin: 0; font-size: var(--t-12); color: var(--ink-2); }
.td-n { font-variant-numeric: tabular-nums; font-weight: var(--w-value); color: var(--ink); }
/* The CMMS's two chips: how many of this site's people are working and how
   many can take the next thing. Its own card runs the number at 1.35rem in
   700 - this sheet's scale stops at 24px and its weights at 600, so the
   figures take 19px/600, which is the same visual job inside the rules the
   rest of the app is held to. Amber for busy and green for free are the
   CMMS's own pairing, as washes with ink text on top. */
.td-alloc { margin: var(--s-8) 0 var(--s-4); font-size: var(--t-12); }
.td-split { display: flex; gap: var(--s-8); margin-bottom: var(--s-8); }
.td-chip {
  flex: 1;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: var(--s-8) var(--s-4);
  border-radius: var(--r-control);
  text-decoration: none;
}
.td-chip span    { font-size: var(--t-11); font-weight: var(--w-value); }
.td-chip strong  { font-size: var(--t-19); font-weight: var(--w-value);
                   font-variant-numeric: tabular-nums; }
.td-busy { background: var(--amber-wash); color: var(--warn-ink); }
.td-free { background: var(--moss-wash);  color: var(--ok-ink); }
a.td-chip:hover { color: var(--ink); }

.td-who { list-style: none; margin: var(--s-4) 0 0; padding: 0; font-size: var(--t-12); }
.td-who li { padding: 2px 0; }
.td-none { margin: var(--s-4) 0 0; font-size: var(--t-12); }
.td-card.is-bare .td-none { color: var(--bad-ink); }

/* Busy is a state, not a fault - steel rather than a status hue, so the page
   keeps red for the site nobody is covering. */
.live-busy { color: var(--steel-ink); font-weight: var(--w-head); }

/* Holding a ticket they have not opened. Amber, not red: it is a state
   that wants a word, not a fault - the person may have had it for forty
   seconds. It turns red only once the ticket is past the Action time its
   own ticket head sets, which is the point at which a delay stops being
   a delay and becomes a machine nobody is going to. */
.live-waiting { color: var(--warn-ink); font-weight: var(--w-head); }
.live-waiting.is-late { color: var(--bad-ink); }
/* How long, beside the state. Quieter than the state itself: the state
   is what you read, the clock is what you check. */
.live-for { margin-left: var(--s-4); color: var(--ink-2); font-size: var(--t-11); }
/* Of the free, the ones holding something they have not opened. On the
   chip itself because the whole number is read at a glance and this is
   the part of it that is not actually available. */
.td-pending {
  display: block; font-style: normal;
  color: var(--warn-ink); font-size: var(--t-11);
}
/* A row whose ticket is past its action time. A tint, not a shout: the
   state in the cell already says so in words. */
.is-late-row td { background: color-mix(in srgb, var(--bad) 8%, transparent); }
/* A panel about work that is already late. The border carries it; the
   heading says the number and the rule in words. */
.panel-card.is-flagged { border-color: color-mix(in srgb, var(--bad) 45%, var(--line)); }
/* The denominator in "0/231". Quieter than the figure beside it: the
   first number is the one being read, the second is what it is out of. */
.stat-of { color: var(--ink-2); font-weight: var(--w-body); }

/* ── Settings ── */
.settings-form {
  display: grid; gap: var(--s-16); justify-items: start;
  padding: var(--s-16) 18px 18px;
}
.settings-form .fld { width: min(520px, 100%); }
.settings-form input, .settings-form textarea { width: 100%; }

/* The fields had no colours of their own at all - no background, no border,
   no ink - so they fell through to whatever the browser draws for a text
   box. On this canvas Edge renders that as near-black text in a dark box,
   which is how a form full of correct values came to look empty. Every
   other field in the app is already painted; these are now painted the
   same way. */
/* Direct children only. The account screen is the one form here whose
   labels WRAP their field; settings, assets and master data all put the
   label beside the field inside a .fld, and stacking those would be
   restyling three pages that were not asked about. */
.settings-form > label {
  display: grid; gap: var(--s-4); width: min(520px, 100%);
  font-size: var(--t-13); font-weight: var(--w-value);
}
.settings-form input[type="text"],
.settings-form input[type="email"],
.settings-form input[type="password"],
.settings-form input[type="number"],
.settings-form input[type="date"],
.settings-form input[type="search"],
.settings-form select,
.settings-form textarea {
  padding: var(--s-8) var(--s-12);
  border: 1px solid var(--line); border-radius: var(--r-control);
  background: var(--surface); color: var(--ink);
  font: var(--w-body) var(--t-14)/1.5 var(--font);
}
.settings-form select { height: 40px; width: 100%; }
.settings-form textarea { resize: vertical; min-height: 96px; }
/* No ::placeholder here any more - the base rule near the top covers
   every control on the page, and two copies of one declaration is how
   they come to disagree. */
/* A checkbox row reads across, not down. */
.settings-form > label.check {
  display: flex; align-items: center; gap: var(--s-8); width: auto;
}
.field-hint { color: var(--ink-2); font-size: var(--t-12); font-weight: var(--w-body); }
.pw-state { color: var(--ink-2); }
.pw-state strong { color: var(--ink); }

/* The issued password, when the desk can still say what it is. Given its
   own line and a box rather than being set in a sentence: it is going to
   be read aloud or copied, and a string of random characters inside
   running text is where typos come from. */
.pw-issued {
  display: block; margin-top: 6px; padding: 7px 10px;
  border: 1px dashed var(--line); border-radius: var(--r-card);
  background: var(--bg); color: var(--ink-2);
}
.pw-issued strong {
  color: var(--ink); font-size: 14px; letter-spacing: .02em;
  margin: 0 6px; user-select: all;
}
/* Except a tickbox. 100% of a 520px field is a 520px-wide checkbox, which
   reads as a box on the left and its words stranded at the far right of the
   panel with nothing between them. Found by looking at the page, not by a
   test: every value was correct and every label was present. */
.settings-form input[type="checkbox"],
.settings-form input[type="radio"] { width: auto; }
.setting-index {
  margin: 0; padding: var(--s-16) 18px 18px var(--s-32);
  font-size: var(--t-13); line-height: 1.7;
}

@media (max-width: 900px) {
  .chart-row { grid-template-columns: 1fr; }
}


/* An address, a serial, a reference - anything whose digits should not
   shuffle sideways as they change. The replacement for the monospace face
   that used to do this job. */
.tabular { font-variant-numeric: tabular-nums; }

/* ── The QR sticker, and the page it opens ──────────────────
   Two screens that mostly do not share a reader. The sticker page is the
   desk printing a label; the scan page is somebody on a phone in front of a
   broken machine, who will see this app once. */

/* .no-print has no screen rule on purpose - it is a marker, and the only
   thing it does is come off the paper. */
@media print {
  .no-print, .sidebar, .topbar { display: none !important; }
  .main-wrapper, .content, .sheet { margin: 0; padding: 0; }
  /* The app's ground is a warm off-white. Printed, that is a page of ink
     nobody asked for on every label. */
  html, body { background: var(--paper); }
  /* A label printed in colour costs a colour label. The code has to be
     black on white to scan reliably anyway - a QR relies on contrast, and a
     plum tint behind it is contrast spent for nothing. */
  .sticker { border-color: var(--paper-ink); background: var(--paper);
             color: var(--paper-ink); }
}

/* A page of them, for cutting up. auto-fill rather than auto-fit so a sheet
   with three labels on it still lays them out at label width instead of
   stretching three stickers across the screen. */
.sticker-sheet {
  display: grid; gap: var(--s-16);
  grid-template-columns: repeat(auto-fill, minmax(360px, max-content));
  align-items: start;
}
.sticker-sheet .sticker { margin: 0; }

/* Said plainly, because opening the sheet changes the database: it mints a
   public address for every asset on it. */
.sheet-warn {
  margin: 0 0 var(--s-16);
  padding: var(--s-12) var(--s-16);
  max-width: 68ch;
  border: 1px solid var(--line);
  border-left: 3px solid var(--warn-ink);
  border-radius: var(--r-card);
  background: var(--amber-wash);
  color: var(--ink);
  font-size: var(--t-13);
}

.sticker {
  display: flex; align-items: center; gap: var(--s-20);
  width: max-content; max-width: 100%;
  margin: var(--s-16) 0;
  padding: var(--s-16);
  /* Dashed, because it is a cut line. */
  border: 2px dashed var(--line);
  border-radius: var(--r-card);
  background: var(--paper); color: var(--paper-ink);
}
.sticker-qr { flex-shrink: 0; line-height: 0; }
/* NO width or height here, and that is load-bearing rather than an
   omission. segno draws the symbol as strokes on an integer grid, and the
   <svg> carries its natural size - modules times scale, exactly. Forcing
   150px scaled a 41-module symbol to 3.66 pixels per module, the browser
   antialiased the edges, and the result rendered as a perfectly convincing
   QR code that no scanner could read. It was invisible to every check
   except pointing a decoder at it. Let it render at its own size. */
.sticker-qr svg { display: block; }
.sticker-text { display: grid; gap: 2px; }
.sticker-tag {
  margin: 0;
  font-size: var(--t-19); font-weight: var(--w-value);
  letter-spacing: .02em;
}
.sticker-name { margin: 0; font-size: var(--t-13); }
.sticker-cta {
  margin: var(--s-8) 0 0;
  font-size: var(--t-12); font-weight: var(--w-head); line-height: 1.4;
}
.sticker-foot { margin: var(--s-4) 0 0; font-size: var(--t-11); }

/* The URL, for reading off a screen and typing somewhere. Tabular figures
   rather than a monospace face: the desk is set in one face, and the only
   job the monospace was doing here was lining the digits up. */
.sticker-url {
  margin: 0 0 var(--s-12);
  padding: var(--s-8) var(--s-12);
  border: 1px solid var(--line-soft); border-radius: var(--r-control);
  background: var(--surface-2);
  font-size: var(--t-12); font-variant-numeric: tabular-nums;
  word-break: break-all;
}

/* ── The scan page ── */
.scan-card { gap: var(--s-16); }

/* What you scanned, confirmed before anything is asked. */
.scan-thing {
  padding: var(--s-12) var(--s-16);
  border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--surface-2);
}
.scan-thing.is-quiet { background: transparent; }
.scan-tag {
  margin: 0;
  font-size: var(--t-19); font-weight: var(--w-value); letter-spacing: .02em;
}
.scan-name { margin: 2px 0 0; font-size: var(--t-13); color: var(--ink-2); }

.scan-facts {
  display: grid; grid-template-columns: auto 1fr; gap: var(--s-4) var(--s-12);
  margin: var(--s-12) 0 0; font-size: var(--t-12);
}
.scan-facts dt { color: var(--ink-2); }
.scan-facts dd { margin: 0; font-weight: var(--w-head); }
/* What the holder does, under their name rather than beside it. Inline, the
   two ran together as "Sabina Yeasmin Line Supervisor · Sewing" — one long
   name on a phone and the sentence has no seam in it. */
.scan-role {
  display: block; font-weight: var(--w-body); color: var(--ink-2);
}
.scan-ip { font-variant-numeric: tabular-nums; }

/* The form is the New ticket form now, so it has more than one question in
   it. A heading, and room between the fields on a phone. */
.scan-head {
  margin: var(--s-20) 0 0; font-size: var(--t-16); font-weight: var(--w-head);
}
.scan-form select {
  padding: 9px 11px;
  border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--bg); color: var(--ink); font: inherit; width: 100%;
}
.scan-form input[type="file"] { font: inherit; color: var(--ink-2); }

.scan-card textarea {
  padding: 9px 11px;
  border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--bg); color: var(--ink);
  font: inherit; resize: vertical;
}
.scan-card textarea:focus-visible {
  outline: 2px solid var(--accent-ink); outline-offset: 1px;
}

.scan-done h1 { margin: 0 0 var(--s-8); font-size: var(--t-19); }
.scan-done p { margin: 0 0 var(--s-8); font-size: var(--t-13); }
/* The one thing this person takes away, so it is the biggest thing here. */
.scan-ref {
  font-size: var(--t-24) !important; font-weight: var(--w-value);
  font-variant-numeric: tabular-nums;
  color: var(--accent-ink);
}

/* A line of prose at the foot of a panel whose body pads itself - .props and
   the tables both do - so the paragraph after them had margin 0 on every
   side and sat welded to the panel's border. */
.panel-note {
  margin: 0;
  padding: var(--s-12) 18px 18px;
  font-size: var(--t-13);
}

/* An aside inside a <label>. The label is a column flexbox, so display
   alone cannot keep it on the line - it has to share a wrapper with the
   text it belongs to. This only makes it quieter. */
.soft-inline { font-weight: var(--w-body); color: var(--ink-2); }

/* Running text wants a measure. At 1180px this paragraph was 190 characters
   a line, which is about three times readable. */
.sticker-notes p { max-width: 68ch; }

/* ── Suggested steps ──────────────────────────────────────
   Quiet on purpose. It is a suggestion beside the field it is about, not a
   second subject on the page - and it must never look like the desk's own
   answer, which is what the AI tag is for. */
.advice { margin-top: var(--s-12); }
.advice .hint { margin-left: var(--s-8); }

.advice-out {
  margin-top: var(--s-12);
  padding: var(--s-12) var(--s-16);
  border: 1px solid var(--line);
  border-left: 3px solid var(--steel);
  border-radius: var(--r-card);
  background: var(--surface-2);
}
.advice-head {
  display: flex; align-items: center; gap: var(--s-8);
  margin: 0 0 var(--s-8);
  font-size: var(--t-13); font-weight: var(--w-head);
}
/* Said on the panel itself, every time. Somebody reading five confident
   steps is entitled to know a machine wrote them. */
.advice-tag {
  padding: 1px 6px;
  border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--steel) 14%, transparent);
  color: var(--steel-ink);
  font-size: var(--t-11); font-weight: var(--w-value);
  letter-spacing: .04em;
}
.advice-out ol { margin: 0; padding-left: var(--s-20); }
.advice-out li { margin-bottom: var(--s-4); font-size: var(--t-13); line-height: 1.5; }
.advice-line { margin: 0; font-size: var(--t-13); line-height: 1.5; }
.advice-out.is-raise { border-left-color: var(--amber); }

.advice-ask {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-8);
  margin: var(--s-12) 0 0;
  font-size: var(--t-13);
}

/* The remote address is read aloud down a phone and typed into another
   program, so it is monospaced and selectable rather than part of a
   sentence. user-select:all makes one click take the whole thing - a
   half-copied AnyDesk number connects to somebody else's machine. */
.remote-address { user-select: all; word-break: break-all; }

/* Name, address and designation are three facts, not a sentence. Inline
   they run together into "Imran Hossen imran@qcollection.com Assistant
   Manager", which reads as one long name. */
.grid td.who .muted { display: block; }

/* A checkbox belongs BESIDE its label, not above it. `.danger label` is
   column-flow because the confirm box wants its caption on top, and the
   tick boxes added later inherited that and floated above their own text. */
.danger label.check {
  flex-direction: row; align-items: flex-start; gap: 8px;
  line-height: 1.45;
}
.danger label.check input[type="checkbox"] {
  width: auto; flex: 0 0 auto; margin-top: 2px;
}
