/* ==========================================================================
   Helpdesk — mobile-app design system
   Cool blue-white canvas, floating white cards, one blue accent.
   Mobile first: bottom tab bar + FAB. From 1024px a floating rail takes over.
   ========================================================================== */

/* Colour, gloss and gradient tokens live in themes.css, which is loaded first.
   This file owns layout, shape and behaviour, and never hard-codes a colour. */
:root {
  --r-xl: 28px;
  --r-lg: 22px;
  --r-md: 16px;
  --r-sm: 12px;
  --r-btn: 12px;
  --r-pill: 999px;

  --shadow:    0 2px 10px rgba(16, 44, 40, .05);
  --shadow-md: 0 8px 24px rgba(16, 44, 40, .08);
  --shadow-lg: 0 18px 44px rgba(16, 44, 40, .14);

  --rail: 250px;
  --tabbar: 76px;

  --font: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --safe-b: env(safe-area-inset-bottom, 0px);

  /*
     Spacing, on a 4pt grid.

     Every margin, padding and gap in this file comes from this ladder. Nobody
     ever sees the grid itself; what they see is that nothing is one pixel out
     of line with anything else, and that the distance between two things means
     something because there are eight distances rather than twenty-six.

     --sp-hair is the exception and is deliberately off the grid: a one- or
     two-pixel nudge to sit a line of text optically on a baseline is not
     layout, and rounding it to four would move it visibly wrong. It is the
     only sub-grid value allowed, and scripts/hig-check.js enforces that.
  */
  --sp-hair: 2px;
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --sp-6: 24px;
  --sp-8: 32px;
  --sp-10: 40px;
  --sp-12: 48px;

  /*
     The smallest thing a finger is expected to hit.

     44 is Apple's number and it is about hands, not about Apple, so it applies
     on any touch screen. It is deliberately NOT applied to a mouse: a pointer
     lands within a pixel, and forcing every toolbar button to 44px on a desktop
     buys nothing and costs the density that makes a dense screen readable. The
     switch is on pointer type rather than on width, because a touchscreen
     laptop is a wide screen with thumbs — and it is any-pointer rather than
     pointer for the same reason: a device with both a trackpad and a touch
     screen reports its *primary* pointer as fine while still being poked with
     a finger all day, so the question worth asking is whether a finger is
     available at all, not whether it is the usual one.
  */
  --tap: 44px;
  --tap-min: 0px;
}

@media (any-pointer: coarse) {
  :root { --tap-min: var(--tap); }
}

/* The dark palette and the accent overrides live in themes.css. */


* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
/* `hidden` has to beat a layout class. Every flex or grid rule in this file
   would otherwise un-hide the element it is on — which is how the bulk bar and
   the pager both stayed visible after being told not to be. */
[hidden] { display: none !important; }
html, body { height: 100%; }
html { -webkit-text-size-adjust: 100%; }
/*
   A quiet motif behind the whole app.
 
   It is applied as a mask rather than an image, so the one SVG is painted
   through in a colour taken from the theme — otherwise nine surface palettes
   and two modes would need nine files, and they would drift. Fixed rather than
   scrolling, and pointer-events:none, so it is scenery and never a target.
 
   How strongly it shows is set by an admin for the whole company; the ladder,
   and the ink compensation the strongest steps need, live in themes.css. It
   disappears entirely behind a card either way. Anyone who has asked their
   system for more contrast gets none of it.
*/
body::before {
  content: "";
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background-color: var(--brand);
  opacity: var(--pattern-opacity);
  -webkit-mask-image: url('/img/bg-pattern.svg');
  mask-image: url('/img/bg-pattern.svg');
  -webkit-mask-size: 440px 440px;
  mask-size: 440px 440px;
  -webkit-mask-repeat: repeat;
  mask-repeat: repeat;
}
/* On a dark page the same marks need to be a touch brighter to register. */
[data-theme="dark"] body::before { opacity: var(--pattern-dark); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) body::before { opacity: var(--pattern-dark); }
}
/* Off means gone, not transparent — nothing left to composite or repaint. */
[data-pattern="off"] body::before { display: none; }
@media (prefers-contrast: more) {
  body::before { display: none; }
}
/* Everything the app draws sits above the scenery. */
.app, .auth-page { position: relative; z-index: 1; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.55 var(--font);
  -webkit-font-smoothing: antialiased;
  overscroll-behavior-y: none;
}
a { color: var(--brand-ink); text-decoration: none; }
/* <small> scales to 0.833 of its parent, so inside an already-small hint it
   lands under the 11px floor. It is a semantic tag here, not a size. */
small { font-size: max(11px, .875em); }
h1, h2, h3, h4 { margin: 0; font-weight: 650; letter-spacing: -.015em; }
img { max-width: 100%; }
.boot { display: grid; place-items: center; height: 100vh; color: var(--muted); }

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

.app { min-height: 100vh; display: flex; flex-direction: column; }

.topbar {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; gap: 12px;
  padding: 16px 16px calc(16px + env(safe-area-inset-top, 0px));
  padding-top: calc(16px + env(safe-area-inset-top, 0px));
  background: var(--bg);
}
/* The hamburger is a bare glyph, so there is nothing to make bigger visually —
   the button simply occupies the full tap size and centres its lines in it. */
.topbar .menu-btn {
  width: max(38px, var(--tap-min)); height: max(38px, var(--tap-min));
  border: 0; background: transparent; cursor: pointer;
  display: grid; place-items: center; color: var(--text); flex: none; padding: 0;
  margin-left: -4px;
}
.topbar .title { min-width: 0; flex: 1; }
.topbar h1 { font-size: 20px; line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.topbar .sub {
  font-size: 12px; color: var(--muted); margin-top: 1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.topbar .actions { display: flex; align-items: center; gap: 8px; flex: none; }

/* Two pixels short of the tap minimum, and they clip their own contents, so a
   transparent pad outside them would be clipped away. At this size growing is
   invisible — a 42px circle and a 44px circle are the same control. */
.bell {
  position: relative; width: max(42px, var(--tap-min)); height: max(42px, var(--tap-min));
  border-radius: 50%;
  background-color: var(--surface); background-image: var(--gloss-wash);
  background-repeat: no-repeat; background-size: 100% 100%; border: 0;
  box-shadow: var(--shadow), var(--gloss-rim);
  display: grid; place-items: center; cursor: pointer; color: var(--text); font-size: 17px;
  padding: 0;   /* the UA's own 1px 6px, which nothing else here wants */
  transition: transform .12s ease;
}
.bell:active { transform: scale(.94); }
.bell .dot {
  position: absolute; top: -3px; right: -3px; min-width: 19px; height: 19px;
  /* --brand, not --accent: the accent is the light end of the scale and left
     the unread count at 4.1:1 whichever ink went on it. --brand carries its
     ink at 5:1 or better on every accent, by construction. */
  border-radius: var(--r-pill); background: var(--brand); color: var(--on-brand);
  font-size: 11px; font-weight: 700; display: grid; place-items: center;
  padding: 0 4px; border: 2px solid var(--bg);
}
.top-avatar {
  position: relative;
  width: max(42px, var(--tap-min)); height: max(42px, var(--tap-min));
  border-radius: 50%; overflow: hidden; flex: none;
  border: 0; padding: 0; cursor: pointer; background: var(--brand-soft);
  box-shadow: var(--shadow), var(--gloss-rim); display: grid; place-items: center;
  font-weight: 700; font-size: 13px; color: var(--brand-ink);
  transition: transform .12s ease;
}
.top-avatar::after {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 55%;
  background: var(--gloss-wash); pointer-events: none;
}
.top-avatar:active { transform: scale(.94); }
.top-avatar img { width: 100%; height: 100%; object-fit: cover; }

.content {
  flex: 1; padding: 4px 16px calc(var(--tabbar) + 36px + var(--safe-b));
  width: 100%; max-width: 720px; margin: 0 auto;
}

/* ---------------------------------------------------------- bottom tabs */

.tabbar {
  position: fixed; z-index: 45;
  left: 12px; right: 12px; bottom: calc(10px + var(--safe-b));
  height: var(--tabbar);
  background-color: var(--surface);
  background-image: var(--gloss-wash);
  background-repeat: no-repeat; background-size: 100% 100%;
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-lg), var(--gloss-rim);
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  align-items: center; padding: 0 8px;
  backdrop-filter: saturate(1.4) blur(8px);
}
.tabbar a {
  display: grid; place-items: center; gap: 4px; height: 100%;
  color: var(--faint); font-size: 11px; font-weight: 600; text-decoration: none;
  border-radius: var(--r-lg);
}
.tabbar a .ico {
  width: 46px; height: 32px; display: grid; place-items: center;
  border-radius: var(--r-pill); transition: background .16s, color .16s;
}
.tabbar a .ico svg { width: 21px; height: 21px; }
.rail nav a .ico svg, .sheet-list .ico svg { width: 19px; height: 19px; }
.bell .ico { display: grid; place-items: center; }
.bell .ico svg { width: 19px; height: 19px; }
.tabbar a.active { color: var(--brand-ink); }
.tabbar a.active .ico {
  background-color: var(--brand-soft);
  background-image: var(--gloss-wash);
  box-shadow: var(--gloss-rim), 0 1px 3px rgba(var(--brand-rgb), .14);
}
.tabbar a .tab-dot {
  position: absolute; margin: -22px 0 0 20px; min-width: 16px; height: 16px;
  background: var(--warn); color: var(--on-warn); border-radius: var(--r-pill);
  font-size: 11px; font-weight: 700; display: grid; place-items: center; padding: 0 4px;
}

.fab {
  position: fixed; z-index: 46; overflow: hidden;
  right: 20px; bottom: calc(var(--tabbar) + 22px + var(--safe-b));
  width: 58px; height: 58px; border-radius: 50%; border: 0; padding: 0;
  background-image: var(--grad-brand);
  color: var(--on-brand); font-size: 25px; line-height: 1;
  box-shadow: var(--gloss-btn-rim), var(--lift-fab);
  cursor: pointer; display: grid; place-items: center;
  transition: transform .12s ease, box-shadow .16s ease;
}
.fab::after {
  content: ""; position: absolute; left: 12%; right: 12%; top: 4px; height: 44%;
  border-radius: 999px 999px 60% 60% / 999px 999px 14px 14px; pointer-events: none;
  background: linear-gradient(180deg, rgba(255,255,255,.5), rgba(255,255,255,.06));
  opacity: var(--gloss-highlight, 1);
}
.fab:hover { box-shadow: var(--gloss-btn-rim), var(--lift-fab-hi); }
.fab:active { transform: scale(.94); box-shadow: inset 0 2px 8px rgba(0,0,0,.3); }

/* --------------------------------------------------------- desktop rail */

.rail { display: none; }

@media (min-width: 1024px) {
  /*
     The desktop shell is an application, not a document.

     The window is the frame: the rail and the top bar are fixed furniture and
     the content scrolls inside its own pane, so the navigation never slides
     away and a long ticket list never pushes the company name off the top.
     That is the difference people mean by "app" rather than "web page", and it
     is three declarations rather than a framework.

     A cap on the whole shell rather than on the content column: 1180px of
     content on a 2000px display left five hundred pixels of empty wallpaper
     down the right-hand side, which is the thing that made this look like a
     phone layout someone had stretched.
  */
  .app {
    flex-direction: row; gap: 24px; padding: 24px; align-items: stretch;
    height: 100vh; max-width: 1800px; margin: 0 auto;
  }
  .rail {
    display: flex; flex-direction: column; width: var(--rail); flex: none;
    background: var(--surface); border-radius: var(--r-xl); box-shadow: var(--shadow-md);
    overflow: hidden;
  }
  .rail .brand-row {
    display: flex; align-items: center; gap: 12px; padding: 20px 16px 16px;
  }
  .rail .brand-row .logo {
    width: 40px; height: 40px; border-radius: var(--r-md); background: var(--brand);
    color: var(--on-brand); display: grid; place-items: center; font-size: 19px; flex: none;
  }
  .rail .brand-txt { flex: 1; min-width: 0; }
  .rail .brand-row b { display: block; font-size: 15px; line-height: 1.2; }
  .rail .brand-row span { font-size: 11.5px; color: var(--muted); }
  .rail nav { padding: 4px 12px 12px; overflow-y: auto; flex: 1; }

  /* Rail-specific spacing for the shared accordion group (defined below the
     media block, so the phone's More sheet uses the same control). */
  .rail .nav-group + .nav-group { margin-top: 2px; }
  .rail .group-label { padding: 12px 12px 8px; }
  .rail .group-label:disabled .gl-chev { display: none; }

  .rail nav a {
    display: flex; align-items: center; gap: 12px; padding: 12px 12px;
    border-radius: var(--r-md); color: var(--text); font-size: 14px; font-weight: 550;
    margin-bottom: 2px;
  }
  .rail nav a .ico { width: 22px; flex: none; text-align: center; font-size: 16px; }
  .rail nav a .lbl { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .rail nav a:hover { background: var(--surface-2); }
  .rail nav a.active { background: var(--brand-soft); color: var(--brand-ink); font-weight: 650; }
  .rail nav a .count {
    flex: none; font-size: 11px; font-weight: 700; padding: 1px 8px;
    border-radius: var(--r-pill); background: var(--warn); color: var(--on-warn);
  }
  .rail .rail-foot { border-top: 1px solid var(--border); padding: 12px; }

  /* ------------------------------------------------- narrow / wide rail */

  /* The hamburger that folds the whole rail down to its icons. Three bars
     rather than a chevron: it is the same control as the one in the phone
     header, doing the same job to the same menu. */
  .rail-toggle {
    flex: none; width: 34px; height: 34px; border-radius: var(--r-sm); padding: 0;
    border: 0; background: none; cursor: pointer; color: var(--muted);
    display: grid; place-items: center;
    transition: background-color .14s ease, color .14s ease;
  }
  .rail-toggle:hover { background: var(--surface-2); color: var(--text); }
  .rail-toggle:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
  .rt-bars { position: relative; width: 16px; height: 12px; }
  .rt-bars::before, .rt-bars::after {
    content: ""; position: absolute; left: 0; height: 1.8px; border-radius: 1px;
    background: currentColor; transition: width .2s ease;
  }
  .rt-bars::before { top: 0; width: 16px; box-shadow: 0 5.1px 0 currentColor; }
  .rt-bars::after { bottom: 0; width: 10px; }
  .rail.narrow .rt-bars::after { width: 16px; }

  .rail.narrow { width: 76px; }
  .rail.narrow .brand-row { padding: 20px 0 12px; flex-direction: column; gap: 10px; }
  .rail.narrow .brand-txt { display: none; }
  .rail.narrow nav { padding: 4px 10px 12px; }
  .rail.narrow .group-label { display: none; }
  /* A hairline between groups instead of their names — the grouping is still
     information, and without it the narrow rail is one undifferentiated column
     of fourteen icons. */
  .rail.narrow .nav-group + .nav-group { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border); }
  .rail.narrow nav a { justify-content: center; padding: 12px 0; gap: 0; position: relative; }
  .rail.narrow nav a .lbl { display: none; }
  /* The number itself will not fit, so it becomes a mark saying "something
     here", and the title attribute carries the count. */
  .rail.narrow nav a .count {
    position: absolute; top: 6px; right: 12px; padding: 0;
    width: 8px; height: 8px; border-radius: 50%;
    font-size: 0; color: transparent; overflow: hidden;
  }
  .rail.narrow .rail-foot { padding: 10px 8px; }
  .rail.narrow .rail-foot .inline { justify-content: center; gap: 4px; }
  .rail.narrow .rail-foot .btn { padding: 8px; min-width: 0; }
  .rail.narrow .version-chip { font-size: 10px; padding: 4px 2px; }
  .rail.narrow .version-chip .new-pill { display: none; }

  .main { flex: 1; min-width: 0; display: flex; flex-direction: column; min-height: 0; }
  .topbar {
    position: static; flex: none;
    background: var(--surface); border-radius: var(--r-xl); box-shadow: var(--shadow);
    padding: 16px 20px; margin-bottom: 20px;
  }
  .topbar .menu-btn { display: none; }
  /*
     The content is the scroll pane. The 4px of side padding is so a card's
     shadow is not shaved off against the scroller's edge, and the negative
     margin gives it back, keeping the cards aligned with the bar above them.
  */
  .content {
    flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
    padding: 0 4px 40px; margin: 0 -4px; max-width: none;
  }
  .tabbar { display: none; }
  .fab { right: 34px; bottom: 34px; }
  .grid.k2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid.k3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .grid.k4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

@media (min-width: 700px) and (max-width: 1023px) {
  .content { max-width: 860px; }
  .grid.k2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ================================================================ cards */

.card {
  position: relative; isolation: isolate;
  background-color: var(--surface);
  background-image: var(--gloss-wash);
  background-repeat: no-repeat; background-size: 100% 130px;
  border-radius: var(--r-xl);
  border: var(--card-hairline);
  box-shadow: var(--shadow), var(--gloss-rim); overflow: hidden;
}
/* The sheen sits behind the content and is never interactive. */
.card::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  border-radius: inherit; background: var(--gloss-sheen);
}
.card > header {
  padding: 16px 20px 12px; display: flex; align-items: center; gap: 12px;
}
.card > header h3 { font-size: 15.5px; }
.card > header .spacer { flex: 1; }
.card .body { padding: 8px 20px 20px; }
.card .body.tight { padding: 0; }
.card > header + .body.tight { padding-top: 0; }

.grid { display: grid; gap: 16px; grid-template-columns: 1fr; }
.stack { display: flex; flex-direction: column; gap: 16px; }
.inline { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sep { height: 1px; background: var(--border); margin: 16px 0; }

/* =========================================================== stat tiles */

.tiles { display: grid; gap: 12px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 560px) { .tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.tile {
  position: relative; isolation: isolate;
  background-color: var(--surface);
  background-image: var(--gloss-wash);
  background-repeat: no-repeat; background-size: 100% 70px;
  border-radius: var(--r-lg); padding: 16px 16px;
  border: var(--card-hairline);
  box-shadow: var(--shadow), var(--gloss-rim); min-width: 0; overflow: hidden;
  transition: transform .12s ease, box-shadow .16s ease;
}
.tile:hover { transform: translateY(-1px); box-shadow: var(--shadow-md), var(--gloss-rim); }
.tile::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  border-radius: inherit; background: var(--gloss-sheen);
}
/*
   The corner mark. Deliberately an outline at low opacity and behind the text:
   it is a way of finding the right tile among eight at a glance, not something
   to read, and a solid icon at full strength competes with the number that is
   the entire point of the tile.
*/
.tile-mark {
  position: absolute; top: 14px; right: 14px; z-index: -1;
  color: var(--brand); opacity: .13; pointer-events: none;
  display: grid; place-items: center;
}
[data-theme="dark"] .tile-mark { opacity: .22; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .tile-mark { opacity: .22; }
}
.tile .k {
  font-size: 11px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--muted); line-height: 1.4; padding-right: 32px;
}
.tile .v {
  font-size: 25px; font-weight: 700; letter-spacing: -.025em; line-height: 1.15;
  margin-top: 4px; overflow-wrap: anywhere;
}
.tile .d { font-size: 11.5px; color: var(--muted); margin-top: 4px; overflow-wrap: anywhere; }
.tile.ok .v { color: var(--ok); }
.tile.warn .v { color: var(--warn); }
.tile.bad .v { color: var(--danger); }
.tile.brand .v { color: var(--brand-ink); }

/* Big coloured summary blocks, as in the reference "Task Summary". */
.summary-row { display: grid; gap: 12px; grid-template-columns: repeat(3, 1fr); }
.summary-block {
  border-radius: var(--r-lg); padding: 16px; position: relative; min-height: 96px;
  color: var(--sb-ink, var(--text));
  display: flex; flex-direction: column; justify-content: space-between; overflow: hidden;
  box-shadow: inset 0 0 0 1px var(--sb-rim, transparent);
  transition: transform .12s ease, box-shadow .16s ease;
}
/* Curved specular highlight across the top — the classic "glossy button" arc. */
.summary-block::after {
  content: ""; position: absolute; left: 3px; right: 3px; top: 3px; height: 46%;
  pointer-events: none;
  border-radius: 18px 18px 60% 60% / 18px 18px 26px 26px;
  background: linear-gradient(180deg, var(--sb-sheen), var(--sb-sheen-2));
  opacity: var(--gloss-highlight, 1);
}
.summary-block:hover { transform: translateY(-2px); }
.summary-block > * { position: relative; z-index: 1; }
.summary-block b { font-size: 13px; font-weight: 600; opacity: .85; }
.summary-block .n { font-size: 27px; font-weight: 700; letter-spacing: -.02em; }
.summary-block .corner {
  position: absolute; right: 12px; bottom: 12px; width: 26px; height: 26px;
  border-radius: 50%; background: var(--sb-corner); display: grid; place-items: center;
  font-size: 13px; box-shadow: inset 0 0 0 1px var(--sb-rim, transparent);
}
/* Each tile names its own ink and rim; the fill comes from the gradient level. */
.sb-amber { --sb-ink: var(--ink-amber); --sb-rim: var(--rim-amber);
            background: var(--grad-amber);
            box-shadow: inset 0 0 0 1px var(--sb-rim), var(--grad-shadow); }
.sb-blue  { --sb-ink: var(--ink-blue);  --sb-rim: var(--rim-blue);
            background: var(--grad-blue);
            box-shadow: inset 0 0 0 1px var(--sb-rim), var(--grad-shadow); }
.sb-green { --sb-ink: var(--ink-green); --sb-rim: var(--rim-green);
            background: var(--grad-green);
            box-shadow: inset 0 0 0 1px var(--sb-rim), var(--grad-shadow); }

/* =============================================================== controls */

.btn {
  position: relative; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  /*
     A rounded rectangle rather than a full pill.

     The pill is the shape of a tag; a button that does something is a
     rectangle with its corners taken off, and every one of the reference
     designs draws it that way. Badges stay pills, because a badge *is* a tag.
  */
  padding: 12px 16px; border-radius: var(--r-btn); border: 1px solid var(--border-2);
  background-color: var(--surface);
  background-image: var(--gloss-wash);
  color: var(--text); font: inherit; font-size: 13.5px;
  font-weight: 600; cursor: pointer; white-space: nowrap;
  /* 40 is comfortable under a mouse; a finger gets the full 44 (see --tap). */
  min-height: max(40px, var(--tap-min));
  box-shadow: var(--gloss-rim), 0 1px 2px rgba(16,44,40,.06);
  transition: background-color .14s, border-color .14s, transform .1s, box-shadow .16s;
}
/*
   Specular highlight sitting in the top half, with a gently curved bottom edge
   — that curve is what makes a surface read as glossy rather than merely
   gradient-filled.

   It is tied to the gloss switch, which it should have been from the start.
   With gloss off the fill is a single flat colour, so an arc painted over it
   has nothing to blend into: it reads as a hard-edged lighter rectangle inset
   inside the button — a button drawn inside a button. That has been the shape
   of every primary button since flat became the default in v4.5.0, and no
   audit caught it because the colour it produces still clears contrast; it is
   the *shape* that was wrong.
*/
.btn::after {
  content: ""; position: absolute; left: 3px; right: 3px; top: 2px; height: 46%;
  pointer-events: none;
  border-radius: 999px 999px 70% 70% / 999px 999px 16px 16px;
  background: linear-gradient(180deg, rgba(255,255,255,.62) 0%, rgba(255,255,255,.14) 100%);
  opacity: var(--gloss-highlight, 1);
}
.btn:hover { background-color: var(--surface-2); }
.btn:active { transform: translateY(1px) scale(.985); box-shadow: inset 0 2px 5px rgba(16,44,40,.14); }
.btn:active::after { opacity: .45; }
.btn:disabled { opacity: .5; cursor: not-allowed; box-shadow: none; }
.btn:disabled::after { display: none; }

.btn.primary {
  border-color: transparent; color: var(--on-brand); text-shadow: var(--on-brand-shadow);
  background-image: var(--grad-brand);
  box-shadow: var(--gloss-btn-rim), var(--lift);
}
.btn.primary::after { background: linear-gradient(180deg, rgba(255,255,255,.34) 0%, rgba(255,255,255,.05) 100%); }
.btn.primary:hover {
  background-image: var(--grad-brand);
  box-shadow: var(--gloss-btn-rim), var(--lift-hi);
}
.btn.primary:active { box-shadow: inset 0 2px 6px rgba(0,0,0,.3); }

.btn.danger {
  border-color: transparent; color: #fff; text-shadow: 0 1px 1px rgba(0,0,0,.22);
  /* Every stop holds white at 4.5:1 — the old top stop managed 2.7:1, so the
     word Delete was the hardest thing on the screen to read. */
  background-image: linear-gradient(180deg, #d34439 0%, #c33a2f 46%, #a92e25 100%);
  box-shadow: var(--gloss-btn-rim), var(--lift-danger, 0 5px 14px rgba(224, 72, 60, .32));
}
.btn.danger::after { background: linear-gradient(180deg, rgba(255,255,255,.34) 0%, rgba(255,255,255,.05) 100%); }
.btn.danger:active { box-shadow: inset 0 2px 6px rgba(0,0,0,.3); }

.btn.ghost {
  border-color: transparent; background-color: transparent; background-image: none;
  box-shadow: none;
}
.btn.ghost::after { display: none; }
.btn.ghost:hover { background-color: var(--surface-2); }
/*
   The small button is a density choice, not a smaller target. Under a mouse it
   is 32px and stays out of the way; on a touch screen it keeps its compact
   look and grows its hit box to 44, which is why the padding is vertical
   rather than the height being fixed.
*/
.btn.sm { padding: 8px 12px; font-size: 12.5px; min-height: max(32px, var(--tap-min)); }
/*
   A small button whose whole label is an icon.

   .btn.sm's horizontal padding is sized for words; around a 15px glyph it came
   to 42px, two short of the touch minimum, on every one of them. The height
   rule already grows on a touch screen and the width has to do the same — a
   target is a square, not a height.
*/
.btn.sm.icon-btn { padding: 8px; min-width: max(34px, var(--tap-min)); }
.btn.block { width: 100%; }
.btn.lg { padding: 16px 24px; font-size: 15px; min-height: 52px; }

/* Text-entry controls. Checkboxes and radios are excluded deliberately: this
   rule stretches a control to the full width and strips its native appearance,
   which turned every checkbox in the app into a blank rounded box with no tick
   in it. They get their own rule below. */
input:not([type="checkbox"]):not([type="radio"]), select, textarea {
  /* --field-border, not --border-2: a field has to announce itself as
     something you type into, and the card hairline measured 1.3:1 doing it. */
  width: 100%; padding: 12px 16px; border: 1px solid var(--field-border);
  border-radius: var(--r-md); background: var(--surface-2); color: var(--text);
  font: inherit; font-size: 15px; /* 15px+ stops iOS zooming on focus */
  appearance: none; -webkit-appearance: none;
}

/* The browser's default placeholder grey is lighter than anything in this
   palette and does not follow the preset — it measured 3.2:1 against the
   field. Placeholders here carry a hint people need to read. */
input::placeholder, textarea::placeholder { color: var(--faint); opacity: 1; }

/* Native, tinted to the theme — a real tick a person recognises, and a target
   large enough to hit on a phone. */
input[type="checkbox"], input[type="radio"] {
  width: 17px; height: 17px; flex: none; margin: 0;
  accent-color: var(--brand); cursor: pointer;
}
select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%236f8380' stroke-width='1.8' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 14px center; padding-right: 40px;
}
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--brand-ink); background: var(--surface);
  box-shadow: 0 0 0 3px var(--brand-soft);
}
input[type="checkbox"], input[type="radio"] { width: auto; min-height: 0; padding: 0; }
textarea { resize: vertical; min-height: 92px; line-height: 1.5; }

label.field { display: block; margin-bottom: 16px; }
label.field > span {
  display: block; font-size: 12.5px; font-weight: 650; color: var(--muted); margin-bottom: 8px;
}
label.field small { font-weight: 500; color: var(--faint); }
.row { display: grid; gap: 12px; grid-template-columns: 1fr; }
.row-3 { display: grid; gap: 12px; grid-template-columns: 1fr; }
@media (min-width: 620px) {
  .row { grid-template-columns: 1fr 1fr; }
  .row-3 { grid-template-columns: repeat(3, 1fr); }
}
.check { display: flex; align-items: center; gap: 8px; font-size: 14px; }
/*
   12px of padding put two 44px switches four pixels apart once the switches
   grew their tap area — comfortably inside the row, but too close to each
   other for a thumb. 16 gives the eight the rule asks for and the pair back.
*/
.switch-row {
  display: flex; align-items: center; gap: 12px; padding: 16px 0;
  border-bottom: 1px solid var(--border);
}
.switch-row:last-child { border-bottom: 0; }
/* A switch that does not apply here. Dimmed rather than removed: the setting
   still exists, and its own caption says what would bring it back. */
.switch-row.disabled .txt b { color: var(--muted); }
.switch-row.disabled .toggle { opacity: .45; cursor: not-allowed; }
.switch-row .txt { flex: 1; min-width: 0; }
.switch-row .txt b { display: block; font-size: 14px; font-weight: 600; }
.switch-row .txt span { font-size: 12px; color: var(--muted); }

/* A table cell that is also a button: no chrome until you point at it, so the
   table still reads as data rather than as a row of controls. */
.plan-cell {
  display: block; text-align: left; padding: 8px 8px; margin: -6px -8px;
  /* The hover surface bleeds 8px past the cell on each side, so the box has to
     be that much wider than the column. Under border-box a plain 100% would
     take the padding out of the content instead, squeezing "2/10 users" onto
     two lines and clipping it — which is exactly what it did. */
  width: calc(100% + 16px);
  border: 1px solid transparent; border-radius: var(--r-md);
  background: none; font: inherit; color: inherit; cursor: pointer;
}
.plan-cell:hover { background: var(--surface-2); border-color: var(--border-2); }
.plan-cell:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.plan-cell .badge + .badge { margin-left: 4px; }
/* The seat line is a fact, not a sentence — it should never wrap. */
.plan-cell .plan-usage { margin-top: 4px; white-space: nowrap; }

/* ========================================================= working hours */

.day-pills { display: flex; flex-wrap: wrap; gap: 8px; }
.day-pill {
  flex: 1 1 64px; min-width: 58px; padding: 12px 8px;
  border-radius: var(--r-md); border: 1px solid var(--border-2);
  background-color: var(--surface); background-image: var(--gloss-wash);
  background-repeat: no-repeat; background-size: 100% 60%;
  color: var(--muted); font: inherit; font-size: 13px; font-weight: 600;
  cursor: pointer; text-align: center;
  transition: background-color .14s, border-color .14s, color .14s;
}
.day-pill:hover { border-color: var(--brand-lt); }
.day-pill.on {
  background-color: var(--brand-soft); border-color: var(--brand-ink);
  color: var(--brand-dk); box-shadow: var(--gloss-rim);
}

/* A number and its unit, side by side — "2" + "working days". */
.esc-row { display: grid; grid-template-columns: 122px minmax(0, 1fr); gap: 8px; }

/* ============================================================ validation */

.field-error {
  margin-top: 4px; font-size: 12px; font-weight: 500; color: var(--danger);
  display: flex; align-items: flex-start; gap: 4px;
}
.field-error::before { content: "!"; font-weight: 800; line-height: 1.35; }
input.invalid, select.invalid, textarea.invalid {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px var(--danger-soft);
}

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

.att { margin-top: 2px; }
/* Hidden outright rather than shrunk: a file input keeps an intrinsic width
   that global input styling can restore, and on a 390px screen that was enough
   to push the whole ticket page sideways. The label's `for` still opens it. */
.att-file { display: none; }
.att-label {
  font-size: 12px; font-weight: 600; color: var(--muted); margin-bottom: 8px;
}
.att-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.att-actions .btn { flex: none; }
.att-hint { font-size: 11.5px; color: var(--faint); flex: 1 1 160px; min-width: 0; }
.att.compact { margin-top: 8px; }
.att.compact .att-hint { display: none; }
@media (min-width: 560px) { .att.compact .att-hint { display: block; } }

.att-previews { display: flex; flex-wrap: wrap; gap: 8px; }
.att-previews:not(:empty) { margin-top: 12px; }
.att-thumb {
  position: relative; width: 92px; height: 92px; border-radius: var(--r-md);
  overflow: hidden; background: var(--surface-2); border: 1px solid var(--border-2);
  display: grid; place-items: center; color: var(--muted);
}
.att-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.att-thumb.file { padding: 8px; text-align: center; }
.att-name {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 4px 8px;
  font-size: 11px; line-height: 1.3; background: rgba(12, 22, 20, .62); color: #fff;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.att-thumb.file .att-name { position: static; background: none; color: var(--muted); white-space: normal; }
.att-x {
  position: absolute; top: 4px; right: 4px; width: 22px; height: 22px; padding: 0;
  border: 0; border-radius: 50%; cursor: pointer; display: grid; place-items: center;
  background: rgba(12, 22, 20, .68); color: #fff;
}
.att-x:hover { background: var(--danger); }

/* =========================================================== performance */

/* The score, drawn as a ring with a conic fill — no chart library, and it
   prints as a solid circle rather than vanishing. */
.score-head { display: flex; align-items: center; gap: 16px; padding: 4px 0 2px; }
.score-head.compact { padding: 0 0 8px; }
.score-ring {
  --pct: 0;
  position: relative; flex: none;
  width: 104px; height: 104px; border-radius: 50%;
  display: grid; place-items: center; text-align: center;
  background:
    conic-gradient(var(--ring, var(--ok)) calc(var(--pct) * 1%), var(--surface-3) 0);
}
.score-ring.warn { --ring: var(--warn); }
.score-ring.bad  { --ring: var(--danger); }
.score-ring::before {
  content: ""; position: absolute; inset: 9px; border-radius: 50%;
  background: var(--surface); box-shadow: var(--gloss-rim);
}
.score-ring b, .score-ring span { position: relative; z-index: 1; display: block; }
.score-ring b { font-size: 27px; font-weight: 700; letter-spacing: -.02em; line-height: 1.1; }
.score-ring span { font-size: 11px; color: var(--muted); margin-top: 1px; }
.score-meta { min-width: 0; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.score-meta .meta { flex-basis: 100%; }

.rank-chip {
  width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center;
  font-size: 13px; font-weight: 700; background: var(--surface-3); color: var(--muted);
  box-shadow: var(--gloss-rim);
}
.rank-chip.ok   { background: var(--ok-soft); color: var(--ok); }
.rank-chip.warn { background: var(--warn-soft); color: var(--warn); }
.rank-chip.bad  { background: var(--danger-soft); color: var(--danger); }

/* ================================================================= plans */

/* One card per plan, side by side where there is room so prices can be
   compared at a glance, stacked on a phone. */
.plan-grid {
  display: grid; gap: 12px; grid-template-columns: 1fr;
}
@media (min-width: 720px)  { .plan-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1180px) { .plan-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.plan-card {
  border: 1px solid var(--border-2); border-radius: var(--r-lg);
  padding: 16px; background-color: var(--surface-2);
  background-image: var(--gloss-wash); background-repeat: no-repeat; background-size: 100% 64px;
  display: flex; flex-direction: column; gap: 12px; min-width: 0;
}
.plan-card > header { display: flex; align-items: flex-start; gap: 12px; }
.plan-card > header b { font-size: 15px; font-weight: 700; }
.plan-card > header .meta { margin-top: 2px; }
.plan-card > header .badge { margin-left: auto; flex: none; }
.plan-fields {
  display: grid; gap: 12px; grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: end;   /* labels vary in length; the inputs still line up */
}
.plan-features .k {
  font-size: 11px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 2px;
}
.switch-row.tight { padding: 12px 0; }
.switch-row.tight .txt b { font-size: 13px; font-weight: 500; }

/* Two fields on one line where the pair belongs together — a cap and its
   price, a start and an end date. */
.grid-2 { display: grid; gap: 12px; grid-template-columns: 1fr; }
@media (min-width: 480px) { .grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

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

.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
th {
  text-align: left; font-size: 11px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted); padding: 12px 16px;
  border-bottom: 1px solid var(--border); white-space: nowrap; background: var(--surface-2);
}
td { padding: 12px 16px; border-bottom: 1px solid var(--border); vertical-align: middle; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr.clickable { cursor: pointer; }
tbody tr.clickable:active, tbody tr.clickable:hover { background: var(--surface-2); }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
.sla-over { color: var(--danger-ink); font-weight: 600; }
.sla-soon { color: var(--warn-ink); font-weight: 600; }

.tno { font-family: var(--mono); font-size: 12px; font-weight: 700; color: var(--brand-ink); }

/* Stacked list rows — the mobile-native alternative to a table. */
/*
   A ticket row wears its status.
 
   A coloured edge down the left of each row and a matching tint, so a queue can
   be read by shape before it is read by words — the amber band is the one
   waiting on somebody, the green ones are done. The colours are the same ones
   the status badges already use, so the row and the badge never say different
   things.
*/
.list-row {
  display: flex; align-items: flex-start; gap: 12px; padding: 12px 20px 12px 16px;
  border-bottom: 1px solid var(--border); cursor: pointer;
  border-left: 3px solid transparent;
  transition: background-color .12s, border-left-color .12s;
}
/* The badges sit on their own line but tight under the meta, so a row is two
   lines of text plus a strip rather than four separate blocks. */
.row-tags { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.list-row:hover { background: var(--surface-2); }

.list-row.st-open       { border-left-color: var(--brand); }
.list-row.st-progress   { border-left-color: var(--accent); }
.list-row.st-hold       { border-left-color: var(--warn); background: var(--warn-soft); }
.list-row.st-resolved   { border-left-color: var(--ok); }
.list-row.st-reopened   { border-left-color: var(--violet); background: var(--violet-soft); }
.list-row.st-closed     { border-left-color: var(--border-2); }
/* Cancelled is not a state anybody needs to act on, so it recedes. */
.list-row.st-cancelled  { border-left-color: var(--border-2); opacity: .62; }
.list-row.st-cancelled .mid b { text-decoration: line-through; text-decoration-thickness: 1px; }

/* Hover must not wipe out the tint the status already put there. */
.list-row.st-hold:hover     { background: var(--warn-soft); filter: brightness(.985); }
.list-row.st-reopened:hover { background: var(--violet-soft); filter: brightness(.985); }

/*
   Unread — it has moved since you last opened it, or you never have. A heavier
   edge, a heavier subject and a dot, because on a queue of forty the two that
   changed overnight are the whole question.
*/
/*
   The thicker bar is paid for out of the padding, so the text does not move.

   It was 5 + 12 against an ordinary row's 3 + 16, which put every unread row's
   content two pixels left of every read one — a shiver running down the list
   as you read it. 7 + 12 comes to the same 19px, and both paddings stay on the
   4px grid, which 5 + 14 did not.
*/
.list-row.unread { border-left-width: 7px; padding-left: 12px; }
.list-row.unread .mid > b { font-weight: 750; }
.list-row.unread .mid > b::after {
  content: ""; display: inline-block; vertical-align: middle;
  width: 7px; height: 7px; margin-left: 8px; border-radius: 50%;
  background: var(--brand);
}
.list-row.st-cancelled.unread .mid > b::after { display: none; }
.list-row:last-child { border-bottom: 0; }
.list-row:active { filter: brightness(.97); }
.list-row .lead { flex: none; }
.list-row .mid { flex: 1; min-width: 0; }
.list-row .mid b { display: block; font-size: 14.5px; font-weight: 600; line-height: 1.35; }
.list-row .mid .meta { font-size: 12px; color: var(--muted); margin-top: 2px; }
/* 8px, not 4: two buttons side by side in a tail are two tap targets, and
   anything under 8px apart is a mis-tap waiting to happen. */
.list-row .tail { flex: none; text-align: right; display: grid; gap: 8px; justify-items: end; }

/* ================================================================ badges */

.badge {
  position: relative; overflow: hidden;
  display: inline-flex; align-items: center; gap: 4px; padding: 4px 12px;
  border-radius: var(--r-pill); font-size: 11px; font-weight: 700;
  white-space: nowrap; border: 1px solid transparent; letter-spacing: .01em;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.75);
}
.badge::after {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 55%;
  background: linear-gradient(180deg, rgba(255,255,255,.45), rgba(255,255,255,0));
  pointer-events: none;
  opacity: var(--gloss-highlight, 1);
}
[data-theme="dark"] .badge { box-shadow: inset 0 1px 0 rgba(255,255,255,.1); }
[data-theme="dark"] .badge::after { opacity: .18; }
/* Solid badges keep their strong colour — they are small, and the definitive
   fill for each is set further down with the rest of the badge palette. */
.b-solid-amber, .b-solid-brand { box-shadow: var(--gloss-btn-rim); }
.b-solid-brand { background-image: var(--grad-brand); }
.b-open      { background: var(--brand-soft); color: var(--brand-ink); }
.b-progress  { background: var(--accent-soft); color: var(--ink-accent); }
.b-hold      { background: var(--warn-soft); color: var(--ink-warn); }
.b-resolved  { background: var(--ok-soft); color: var(--ink-ok); }
.b-closed    { background: var(--surface-3); color: var(--muted); }
.b-reopened  { background: var(--violet-soft); color: var(--ink-violet); }
.b-cancelled { background: var(--surface-3); color: var(--faint); }
.b-low       { background: var(--surface-3); color: var(--muted); }
.b-medium    { background: var(--accent-soft); color: var(--ink-accent); }
.b-high      { background: var(--warn-soft); color: var(--ink-warn); }
.b-critical  { background: var(--danger-soft); color: var(--ink-bad); }
.b-esc1      { background: var(--warn-soft); color: var(--ink-warn); }
.b-esc2      { background: var(--danger-soft); color: var(--ink-bad); }
.b-role      { background: var(--surface-3); color: var(--muted); }
.b-ok        { background: var(--ok-soft); color: var(--ink-ok); }
.b-bad       { background: var(--danger-soft); color: var(--ink-bad); }
.b-pending   { background: var(--warn-soft); color: var(--ink-warn); }
.b-approved  { background: var(--ok-soft); color: var(--ink-ok); }
.b-rejected  { background: var(--danger-soft); color: var(--ink-bad); }
.b-solid-amber { background: var(--warn); color: var(--on-warn); }
.b-solid-brand { background: var(--brand); color: var(--on-brand); }

.muted { color: var(--muted); }
.faint { color: var(--faint); }
.small { font-size: 12.5px; }
.mono  { font-family: var(--mono); font-size: 12.5px; }
.right { text-align: right; }
.center { text-align: center; }
.nowrap { white-space: nowrap; }
.trunc { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.avatar {
  width: 40px; height: 40px; border-radius: 50%; background: var(--brand-soft);
  color: var(--brand-ink); display: grid; place-items: center; font-weight: 700;
  font-size: 13px; flex: none; overflow: hidden;
}
.avatar img { width: 100%; height: 100%; object-fit: cover; }
.avatar.sm { width: 30px; height: 30px; font-size: 11px; }
.avatar.lg { width: 84px; height: 84px; font-size: 26px; }
.avatar.a1 { background: #fde3c8; color: #b45f06; }
.avatar.a2 { background: #d9e6ff; color: #2657f0; }
.avatar.a3 { background: #ffe0dd; color: #c33a2e; }
.avatar.a4 { background: #ddf3e6; color: #0f9d76; }
.avatar.a5 { background: #e9e2ff; color: #6b46e5; }

/* ========================================================= ticket detail */

/* minmax(0, 1fr), not 1fr: a grid item's automatic minimum is its content, so
   one unbreakable string — a long attachment filename — would otherwise widen
   the column and push the whole page sideways on a phone. */
.detail { display: grid; gap: 16px; grid-template-columns: minmax(0, 1fr); }
@media (min-width: 1024px) { .detail { grid-template-columns: minmax(0, 1fr) 340px; align-items: start; } }

.thread { display: flex; flex-direction: column; gap: 12px; }
.msg { display: flex; gap: 12px; }
.msg .bubble {
  flex: 1; background: var(--surface-2); border-radius: var(--r-lg);
  border-top-left-radius: 6px; padding: 12px 16px; min-width: 0;
}
.msg.internal .bubble { background: var(--warn-soft); }
.msg .bubble .head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 4px; flex-wrap: wrap; }
.msg .bubble .head b { font-size: 13.5px; }
.msg .bubble .head span { font-size: 11.5px; color: var(--faint); }
.msg .bubble p { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; font-size: 14px; }

.timeline { list-style: none; margin: 0; padding: 0; font-size: 13px; }
.timeline li { display: flex; gap: 12px; padding: 8px 0; border-bottom: 1px dashed var(--border); }
.timeline li:last-child { border-bottom: 0; }
.timeline .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--border-2); margin-top: 8px; flex: none; }
.timeline .dot.b { background: var(--brand); }
.timeline .dot.r { background: var(--danger); }
.timeline .dot.g { background: var(--ok); }

dl.kv { margin: 16px 0 0; }   /* the UA's own 1em, off the grid */
.kv { display: grid; grid-template-columns: 112px 1fr; gap: 8px 12px; font-size: 14px; align-items: baseline; }
.kv dt { color: var(--muted); font-size: 12.5px; }
.kv dd { margin: 0; overflow-wrap: anywhere; }

.sla-bar { height: 7px; border-radius: var(--r-pill); background: var(--surface-3); overflow: hidden; margin-top: 8px; }
.sla-bar > i { display: block; height: 100%; background: var(--ok); border-radius: var(--r-pill); }
.sla-bar.warn > i { background: var(--warn); }
.sla-bar.bad > i { background: var(--danger); }

.stars { display: inline-flex; gap: 2px; font-size: 15px; color: var(--star); letter-spacing: 1px; }
.star-pick { font-size: 32px; cursor: pointer; color: var(--border-2); background: none; border: 0; padding: 0 4px; line-height: 1; }
.star-pick.on { color: var(--star); }

.composer {
  display: flex; align-items: center; gap: 8px; background: var(--surface-2);
  border-radius: var(--r-pill); padding: 8px 8px 8px 16px;
}
.composer input, .composer textarea {
  border: 0; background: transparent; padding: 8px 0; min-height: 0;
}
.composer input:focus, .composer textarea:focus { box-shadow: none; background: transparent; }
.composer .send {
  width: 42px; height: 42px; border-radius: 50%; border: 0; background: var(--accent);
  color: var(--on-accent); font-size: 17px; display: grid; place-items: center; cursor: pointer; flex: none;
}

/* ================================================================ charts */

.chart { width: 100%; overflow: visible; }
.chart text { font-size: 11px; fill: var(--muted); font-family: var(--font); }
.chart .grid-line { stroke: var(--border); stroke-width: 1; }

/*
   The trend line. Rounded joins and a generous weight, because the shape is
   the message — a hairline polyline with mitred corners reads as a diagram,
   and this is meant to read as a movement.
*/
.line-chart .spark {
  fill: none; stroke-width: 2.5;
  stroke-linecap: round; stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.line-chart .spark.s1 { stroke: var(--chart-1); }
.line-chart .spark.s2 { stroke: var(--chart-2); }

/*
   Drawn in on first paint.

   The dash properties live in the keyframes, not on the element. Putting them
   on the element means the *resting* state of the line is "not drawn yet", so
   anywhere the animation does not run — a browser extension that blocks them,
   a print stylesheet, a screenshot taken before it starts — the chart renders
   as an empty box. The resting state has to be the finished line, with the
   animation as the thing that takes it away and gives it back.
*/
.line-chart .spark { animation: chart-draw 1.1s cubic-bezier(.25, 1, .4, 1) both; }
.line-chart .spark.s2 { animation-delay: .12s; }
@keyframes chart-draw {
  from { stroke-dasharray: 1400; stroke-dashoffset: 1400; }
  to   { stroke-dasharray: 1400; stroke-dashoffset: 0; }
}

/* The read-out. Hidden until a pointer is actually on the chart, so the
   default state is the shape and nothing else. */
.chart-wrap { position: relative; touch-action: pan-y; }
.chart-guide, .chart-dot { opacity: 0; transition: opacity .12s; }
.chart-wrap.reading .chart-guide, .chart-wrap.reading .chart-dot { opacity: 1; }
.chart-guide { stroke: var(--border-2); stroke-width: 1; stroke-dasharray: 3 3; }
.chart-dot { stroke: var(--surface); stroke-width: 2.5; }
.chart-dot.d1 { fill: var(--chart-1); }
.chart-dot.d2 { fill: var(--chart-2); }

.chart-tip {
  position: absolute; top: 4px; transform: translateX(-50%);
  display: none; flex-direction: column; gap: 4px;
  padding: 8px 12px; border-radius: var(--r-sm);
  background: var(--tip-bg); color: var(--tip-ink);
  font-size: 12px; line-height: 1.45; white-space: nowrap;
  pointer-events: none; z-index: 2; box-shadow: var(--shadow-md);
}
.chart-wrap.reading .chart-tip { display: flex; }
.chart-tip b { font-size: 12px; font-weight: 700; }
.chart-tip span { display: flex; align-items: center; gap: 8px; }

.legend { display: flex; gap: 16px; font-size: 12px; color: var(--muted); flex-wrap: wrap; }
.legend i { display: inline-block; width: 9px; height: 9px; border-radius: 3px; margin-right: 8px; flex: none; }
.legend i.k1, .chart-tip i.k1 { background: var(--chart-1); }
.legend i.k2, .chart-tip i.k2 { background: var(--chart-2); }
.chart-tip i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; flex: none; }
.bars { display: flex; flex-direction: column; gap: 12px; }
.bars .b-row { display: grid; grid-template-columns: 104px 1fr 52px; gap: 12px; align-items: center; font-size: 13px; }
@media (min-width: 560px) { .bars .b-row { grid-template-columns: 160px 1fr 58px; } }
.bars .track { background: var(--surface-3); border-radius: var(--r-pill); height: 22px; overflow: hidden; }
.bars .fill { height: 100%; border-radius: var(--r-pill); background: var(--accent); }
.donut-wrap { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; justify-content: center; }

/* =========================================================== auth pages */
/*
   The signed-out screens carry their own palette, and it is fixed.
 
   Everywhere else in this app the colours come from the theme engine — the
   preset, the accent and the light/dark mode a company or a person chose. That
   is wrong here for two reasons. Nobody looking at this page has chosen
   anything: they have not signed in, so the theme being applied is whichever
   default the platform happens to hold, which is arbitrary to them. And this is
   the one screen every customer of every company sees, so it is the product's
   face — a face that changes hue depending on which tenant's default loaded
   first is not a face.
 
   The mechanism is simply that these custom properties are declared on
   .auth-page itself. The theme engine writes its values on <html>, so however
   specific those selectors are they never compete with these: a value declared
   on an element wins for that element and everything inside it. Which means the
   whole card below — buttons, inputs, banners, the signup wizard, all of them
   shared components — renders in this palette without any of them knowing.
*/

.auth-page {
  /* Surfaces */
  --bg:        #eef2fb;
  --bg-2:      #e6ecf9;
  --surface:   #ffffff;
  --surface-2: #f5f7fd;
  --surface-3: #eceffa;
  --border:    #e6eaf5;
  --border-2:  #d8dff0;
  --field-border: #7c88a6;

  /* Ink */
  --text:  #101a33;
  --muted: #4e5a76;
  --faint: #626e87;

  /* The one accent, and what goes on top of it */
  --brand:      #1f47c4;
  --brand-dk:   #12328f;
  --brand-hi:   #3d6bf0;
  --brand-lt:   #6f95ff;
  --brand-ink:  #1b3fae;
  --brand-soft: #e7edff;
  --accent:      #3d6bf0;
  --accent-soft: #e3ebff;
  --brand-rgb:  31, 71, 196;
  --on-brand:   #ffffff;
  --on-brand-shadow: 0 1px 1px rgba(0, 0, 0, .22);
  --on-accent:  #ffffff;
  --grad-brand:  linear-gradient(180deg, #3d6bf0 0%, #1f47c4 52%, #12328f 100%);
  --grad-accent: linear-gradient(180deg, #3d6bf0 0%, #1f47c4 100%);

  /* Status ink, so a wrong password reads the same on every install */
  --ink-info: #12328f;  --ink-warn: #8a5806;
  --ink-bad:  #97281f;  --ink-ok:   #0a6b51;
  --danger-ink: #c03428; --warn-ink: #8a5806; --ok-ink: #0a6b51;
  --warn-soft: #fdf0d9; --danger-soft: #fdeae8; --ok-soft: #e0f5ee;
  --star: #b0700a;

  /* Depth */
  --shadow:    0 1px 2px rgba(16, 26, 51, .06), 0 4px 14px rgba(16, 26, 51, .07);
  --shadow-md: 0 8px 26px rgba(16, 26, 51, .12);
  --shadow-lg: 0 18px 44px rgba(16, 26, 51, .16);
  --card-hairline: 0 none;

  min-height: 100vh; display: flex; justify-content: center;
  padding: 28px 20px calc(32px + var(--safe-b));
  color: var(--text);
  background:
    radial-gradient(70% 55% at 12% 8%, rgba(61, 107, 240, .16) 0%, rgba(61, 107, 240, 0) 62%),
    radial-gradient(60% 50% at 92% 96%, rgba(18, 50, 143, .14) 0%, rgba(18, 50, 143, 0) 60%),
    linear-gradient(160deg, #eef2fb 0%, #e4eafa 100%);
}

/* The sheet the two halves sit on. On a phone there is only the card. */
.auth-sheet { width: 100%; max-width: 460px; margin: auto; }

.auth-card {
  width: 100%; max-width: 460px; background: var(--surface);
  border-radius: var(--r-xl); box-shadow: var(--shadow-lg); padding: 32px 24px;
}
.auth-card h2 { font-size: 25px; letter-spacing: -.02em; }
.auth-card p.sub { color: var(--muted); font-size: 14px; margin: 8px 0 24px; }
.auth-foot { text-align: center; margin-top: 20px; font-size: 14px; color: var(--muted); }
/* The one link under the form is a destination, not a word in a sentence, so
   it gets a real target rather than the height of its own text. */
.auth-foot a {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: max(32px, var(--tap-min)); padding: 4px 16px;
}
.demo-hint {
  margin-top: 20px; padding: 16px 16px; background: var(--surface-2);
  border-radius: var(--r-md); font-size: 12.5px; color: var(--muted); line-height: 1.9;
}
.demo-hint b { color: var(--text); }
.demo-hint code { font-family: var(--mono); font-size: 11.5px; }
/*
   The demo logins are links, but they are a row of choices rather than words
   inside a sentence — an inline link in prose is exempt from the tap minimum,
   a list of things to pick is not. So they are drawn as chips, which is what
   they behave like anyway.
*/
.demo-hint a {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: max(28px, var(--tap-min)); padding: 4px 12px; margin: 2px 0;
  border-radius: var(--r-pill); background: var(--surface);
  border: 1px solid var(--border); font-weight: 600; font-size: 12.5px;
}
.demo-hint a:hover { background: var(--brand-soft); border-color: var(--brand-lt); }

/* ------------------------------------------------------ signup wizard */

.steps { display: flex; align-items: center; gap: 8px; margin-bottom: 24px; }
.steps .step { flex: 1; height: 4px; border-radius: var(--r-pill); background: var(--border-2); }
.steps .step.done { background: var(--accent); }
.steps .step.now { background: var(--brand); }
.step-label { font-size: 12px; font-weight: 650; color: var(--brand-ink); letter-spacing: .04em;
  text-transform: uppercase; margin-bottom: 8px; }

.pick-list { display: flex; flex-direction: column; gap: 8px; max-height: 46vh; overflow-y: auto; margin: 0 -4px; padding: 0 4px; }
.pick {
  display: flex; align-items: center; gap: 12px; padding: 12px 16px; width: 100%;
  border: 1px solid var(--border-2); border-radius: var(--r-md); background: var(--surface);
  cursor: pointer; text-align: left; font: inherit;
}
.pick:hover { border-color: var(--brand-ink); background: var(--brand-soft); }
.pick.on { border-color: var(--brand-ink); background: var(--brand-soft); }
.pick .mid { flex: 1; min-width: 0; }
.pick .mid b { display: block; font-size: 14.5px; font-weight: 600; }
.pick .mid span { font-size: 12px; color: var(--muted); }

.selfie-frame {
  width: 190px; height: 190px; border-radius: 50%; margin: 0 auto 16px;
  background: var(--surface-2); border: 3px dashed var(--border-2);
  display: grid; place-items: center; overflow: hidden; position: relative;
}
.selfie-frame.filled { border-style: solid; border-color: var(--brand-ink); }
.selfie-frame img, .selfie-frame video { width: 100%; height: 100%; object-fit: cover; }
.selfie-frame .hint { text-align: center; color: var(--faint); font-size: 12.5px; padding: 0 20px; }

.result-icon {
  width: 76px; height: 76px; border-radius: 50%; margin: 0 auto 20px;
  display: grid; place-items: center; font-size: 34px;
}
.result-icon.ok { background: var(--ok-soft); color: var(--ok); }
.result-icon.pending { background: var(--warn-soft); color: var(--warn); }
.ref-box {
  background: var(--surface-2); border-radius: var(--r-md); padding: 16px;
  text-align: center; margin: 16px 0;
}
.ref-box .ref { font-family: var(--mono); font-size: 19px; font-weight: 700; letter-spacing: .05em; }

/* ================================================================ modal */

.modal-back {
  position: fixed; inset: 0; background: rgba(10, 30, 27, .5);
  backdrop-filter: blur(3px); z-index: 100;
  display: flex; align-items: flex-end; justify-content: center;
}
@media (min-width: 700px) { .modal-back { align-items: center; padding: 24px; } }
.modal {
  background: var(--surface); width: 100%; max-width: 620px;
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  max-height: 92vh; display: flex; flex-direction: column;
  box-shadow: var(--shadow-lg); animation: sheet-up .22s cubic-bezier(.2,.8,.3,1);
  padding-bottom: var(--safe-b);
}
@media (min-width: 700px) {
  .modal { border-radius: var(--r-xl); max-height: 88vh; animation: pop .18s ease-out; padding-bottom: 0; }
  .modal.wide { max-width: 820px; }
}
@keyframes sheet-up { from { transform: translateY(24px); opacity: .6; } }
@keyframes pop { from { transform: scale(.97); opacity: 0; } }
.modal .grabber {
  width: 40px; height: 4px; border-radius: var(--r-pill); background: var(--border-2);
  margin: 12px auto 0;
}
@media (min-width: 700px) { .modal .grabber { display: none; } }
.modal > header { padding: 16px 20px; display: flex; align-items: center; gap: 12px; }
.modal > header h3 { font-size: 16.5px; flex: 1; }
.modal > .body { padding: 4px 20px 20px; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.modal > footer {
  padding: 16px 20px; border-top: 1px solid var(--border);
  display: flex; gap: 12px; justify-content: flex-end;
}
.modal > footer .btn { flex: 1; }
@media (min-width: 700px) { .modal > footer .btn { flex: 0 0 auto; } }

/* Bottom "More" sheet used for secondary navigation on phones. */
.sheet-list { display: flex; flex-direction: column; }
.sheet-list a, .sheet-list button {
  display: flex; align-items: center; gap: 16px; padding: 16px 4px;
  border: 0; border-bottom: 1px solid var(--border); background: none;
  font: inherit; font-size: 15px; color: var(--text); cursor: pointer; text-align: left; width: 100%;
}
.sheet-list a:last-child, .sheet-list button:last-child { border-bottom: 0; }
.sheet-list .ico {
  width: 40px; height: 40px; border-radius: var(--r-md); background: var(--surface-2);
  display: grid; place-items: center; font-size: 17px; flex: none;
}
.sheet-list .count {
  margin-left: auto; background: var(--warn); color: var(--on-warn); font-size: 11px; font-weight: 700;
  border-radius: var(--r-pill); padding: 2px 8px;
}

/* ==================================================== auth: brand layout */

/* ------------------------------------------- the two-panel sign-in sheet */
/*
   One sheet, floating on the page, split down the middle: the product on the
   left, the form on the right. Below 940px the left half is dropped entirely
   rather than stacked — on a phone somebody wants to sign in, not read a
   pitch — and the card carries a small lockup instead.
*/

.auth-brand { display: none; }
.auth-brand-sm { margin-bottom: 24px; }

@media (min-width: 940px) {
  .auth-page.split .auth-sheet {
    max-width: 1120px;
    display: grid; grid-template-columns: 1.02fr .98fr;
    background: var(--surface); border-radius: 26px; overflow: hidden;
    box-shadow: 0 30px 70px rgba(16, 26, 51, .18), 0 2px 6px rgba(16, 26, 51, .06);
    position: relative;
  }
  /*
     Behind the form: rings, drifting message cards and a dot field. Drawn once
     at full size rather than tiled, so nothing repeats behind the fields, and
     it carries its own colour because this whole surface sits outside the
     theme engine on purpose.
  */
  .auth-page.split .auth-sheet::before {
    content: "";
    position: absolute; top: 0; right: 0; bottom: 0; left: 50%;
    background: url('/img/auth-pattern.svg') center / cover no-repeat;
    pointer-events: none;
  }
  .auth-page.split .auth-card { position: relative; z-index: 1; }
  .auth-page.split .auth-card {
    margin: auto; max-width: 410px; box-shadow: none; border-radius: 0;
    padding: 56px 48px;
  }
  .auth-brand { display: block; }
  .auth-brand-sm { display: none; }
}

.auth-brand {
  position: relative; overflow: hidden; padding: 56px 48px 0;
  color: #ffffff; min-height: 620px;
  background: linear-gradient(158deg, #1c47cc 0%, #12328f 55%, #0d2570 100%);
}
/* A wash of light from the top-left, and a soft disc bottom-right, so the
   panel has some depth without an image. */
.auth-brand::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(80% 55% at 12% 0%, rgba(255, 255, 255, .20), rgba(255, 255, 255, 0) 62%),
    radial-gradient(46% 38% at 96% 104%, rgba(109, 150, 255, .40), rgba(109, 150, 255, 0) 70%);
}
.auth-brand-inner {
  position: relative; z-index: 1; height: 100%;
  display: flex; flex-direction: column;
}
.auth-brand h1 {
  font-size: 34px; line-height: 1.18; letter-spacing: -.025em;
  margin: 32px 0 16px; max-width: 15ch;
}
.auth-brand > .auth-brand-inner > p {
  font-size: 15px; line-height: 1.6; color: rgba(255, 255, 255, .78);
  margin: 0; max-width: 42ch;
}
.brand-rule {
  display: block; width: 46px; height: 3px; border-radius: 2px;
  background: rgba(255, 255, 255, .45); margin: 24px 0 0;
}
.brand-art {
  display: block; width: 100%; max-width: 470px;
  margin: auto auto 0; align-self: center;
  /* The desk at the foot of the drawing sits on the panel's bottom edge. */
  margin-bottom: -14px;
}

/* The mark, and the mark with the name beside it. Both carry their own colour
   — see brand.js for why they are outside the theme. */
.lockup { display: flex; align-items: center; gap: 12px; }
.lockup svg { flex: none; border-radius: 12px; }
.lockup-name {
  font-size: 19px; font-weight: 750; letter-spacing: -.02em; color: currentColor;
}
.auth-brand .lockup-name { color: #ffffff; }
.auth-brand-sm .lockup { justify-content: center; }
.auth-brand-sm .lockup-name { color: var(--text); }

/* Password field with a reveal button. */
.input-affix { position: relative; }
.input-affix input { padding-right: 48px; }
.reveal {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  width: 34px; height: 34px; border: 0; background: none; border-radius: 50%;
  color: var(--faint); cursor: pointer; display: grid; place-items: center;
}
/* A 44px circle would fill the field edge to edge, so the eye stays 34 and
   reaches out to the full tap size through a transparent pad, the same way the
   switch does. */
.reveal::before {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: var(--tap-min); height: var(--tap-min); transform: translate(-50%, -50%);
}
.reveal:hover { background: var(--surface-3); color: var(--muted); }
.reveal.on { color: var(--brand-ink); }

.signup-cta {
  display: flex; align-items: center; gap: 12px; margin-top: 20px;
  padding: 16px; border-radius: var(--r-md); background: var(--surface-2);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.7);
}
[data-theme="dark"] .signup-cta { box-shadow: inset 0 1px 0 rgba(255,255,255,.06); }
.signup-cta b { display: block; font-size: 14px; }
.signup-cta span { font-size: 12px; color: var(--muted); line-height: 1.45; }
.signup-icon {
  width: 40px; height: 40px; border-radius: var(--r-md); flex: none;
  background: var(--brand-soft); color: var(--brand-ink); display: grid; place-items: center;
}

/* Account menu header. */
.account-head {
  display: flex; align-items: center; gap: 16px; padding: 4px 0 16px;
  border-bottom: 1px solid var(--border); margin-bottom: 8px;
}
.account-head b { display: block; font-size: 16px; }
.account-head span { display: block; font-size: 12.5px; color: var(--muted); }
.sheet-list .danger-row { color: var(--danger); }
.sheet-list .danger-row .ico { background: var(--danger-soft); color: var(--danger); }

/* ========================================================= theme pickers */

.swatch-grid {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
}
.swatch {
  border: 2px solid var(--border-2); background: var(--surface); border-radius: var(--r-md);
  padding: 8px; cursor: pointer; font: inherit; color: var(--text);
  display: flex; flex-direction: column; gap: 8px; align-items: stretch;
  transition: border-color .14s, transform .1s;
}
.swatch:active { transform: scale(.97); }
.swatch.on { border-color: var(--brand-ink); box-shadow: 0 0 0 3px var(--brand-soft); }
.chip-preview {
  height: 42px; border-radius: var(--r-sm); display: grid; place-items: end center;
  padding-bottom: 8px;
  /* A hairline, not just a highlight — white and off-white swatches would
     otherwise vanish into the card behind them. */
  box-shadow: inset 0 1px 0 rgba(255,255,255,.6), inset 0 0 0 1px rgba(16,24,26,.12);
}
.chip-preview i {
  display: block; width: 62%; height: 14px; border-radius: 4px;
  box-shadow: var(--shadow), 0 0 0 1px rgba(16,24,26,.08);
}
.swatch-name { font-size: 12px; font-weight: 600; text-align: center; }

.accent-grid {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(72px, 1fr));
}
.accent-dot {
  border: 2px solid transparent; background: none; cursor: pointer; font: inherit;
  color: var(--muted); border-radius: var(--r-md); padding: 8px 4px;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
}
.accent-dot .dot {
  width: 34px; height: 34px; border-radius: 50%; background: var(--dot);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.45), var(--shadow);
}
.accent-dot.on { border-color: var(--brand-ink); background: var(--brand-soft); color: var(--brand-ink); }
.accent-dot.on .dot { box-shadow: inset 0 1px 0 rgba(255,255,255,.45), 0 0 0 3px var(--surface), 0 0 0 5px var(--dot); }
.accent-name { font-size: 11px; font-weight: 600; }

.field-label {
  font-size: 12.5px; font-weight: 650; color: var(--muted); margin-bottom: 8px;
}

.theme-preview {
  background: var(--bg); border-radius: var(--r-lg); padding: 16px;
  box-shadow: inset 0 1px 3px rgba(0,0,0,.05);
}

/* Switch control used by appearance and feature settings. */
/*
   The switch stays 50×30 — a 44px-tall pill would read as a fat lozenge — and
   grows its hit area outwards instead, with a transparent pseudo-element that
   reaches the full 44. Same trick iOS uses for a small control inside a row:
   what you see and what you can hit are allowed to be different sizes, and it
   is the second one that has to clear the minimum.
*/
.toggle {
  width: 50px; height: 30px; border-radius: var(--r-pill); border: 0; flex: none;
  background: var(--border-2); cursor: pointer; padding: 4px; position: relative;
  transition: background .18s ease;
}
.toggle::before {
  content: ""; position: absolute; left: 0; right: 0;
  top: 50%; height: var(--tap-min); transform: translateY(-50%);
}
.toggle .knob {
  display: block; width: 24px; height: 24px; border-radius: 50%; background: #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,.25); transition: transform .18s cubic-bezier(.3,1.4,.5,1);
}
.toggle.on { background: var(--brand); }
.toggle.on .knob { transform: translateX(20px); }

/* ============================================================== version */

.version-chip {
  display: block; margin-top: 12px; text-align: center;
  font-size: 11.5px; font-weight: 700; letter-spacing: .04em;
  color: var(--muted); background: var(--surface-2);
  border-radius: var(--r-pill); padding: 8px 12px;
  min-height: var(--tap-min);
  display: flex; align-items: center; justify-content: center;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.7);
}
.version-chip:hover { color: var(--brand-ink); background: var(--brand-soft); text-decoration: none; }
[data-theme="dark"] .version-chip { box-shadow: inset 0 1px 0 rgba(255,255,255,.06); }

.version-line {
  display: block; margin-top: 16px; padding: 12px 16px; border-radius: var(--r-md);
  background: var(--surface-2); text-align: center; color: var(--text);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.7);
}
.version-line:hover { text-decoration: none; background: var(--brand-soft); }
.version-line b { display: block; font-size: 14px; }
.version-line span { font-size: 12px; color: var(--muted); }
[data-theme="dark"] .version-line { box-shadow: inset 0 1px 0 rgba(255,255,255,.06); }

.build-stamp {
  text-align: center; margin-top: 20px; font-size: 11.5px; color: var(--faint);
}
.build-stamp a { color: var(--faint); }

/* Marks release notes this browser has not read yet. */
.new-pill {
  display: inline-block; margin-left: 8px; padding: 1px 8px; border-radius: var(--r-pill);
  background: var(--warn); color: var(--on-warn); font-size: 11px; font-weight: 800; letter-spacing: .06em;
  vertical-align: 1px;
}
.version-line.fresh { background: var(--warn-soft); }
.version-chip .new-pill { margin-left: 4px; }

/* =============================================================== toasts */

.toasts {
  position: fixed; z-index: 200; left: 12px; right: 12px;
  bottom: calc(var(--tabbar) + 22px + var(--safe-b));
  display: flex; flex-direction: column; gap: 8px; align-items: center; pointer-events: none;
}
@media (min-width: 1024px) { .toasts { left: auto; right: 24px; bottom: 24px; align-items: flex-end; } }
.toast {
  background: #12332f; color: #f2fbf9; border-radius: var(--r-md);
  box-shadow: var(--shadow-lg); padding: 12px 16px; font-size: 13.5px;
  max-width: 460px; animation: slide-in .2s ease-out; pointer-events: auto;
}
.toast.error { background: #8e2b22; }
.toast.ok { background: #0c6b52; }
@keyframes slide-in { from { opacity: 0; transform: translateY(10px); } }

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

.empty { padding: 48px 20px; text-align: center; color: var(--muted); }
.empty .big { font-size: 34px; margin-bottom: 12px; opacity: .55; }

.filters {
  display: flex; gap: 8px; overflow-x: auto; -webkit-overflow-scrolling: touch;
  padding: 12px 16px; background: var(--surface-2); scrollbar-width: none;
}
.filters::-webkit-scrollbar { display: none; }
/* Text and select controls become pills. Checkboxes and radios must be
   excluded: the shared rule gave every one of them a 132px minimum width and a
   38px height, so each filter toggle rendered as a large empty box beside its
   own label. */
.filters select,
.filters input:not([type="checkbox"]):not([type="radio"]) {
  width: auto; min-width: 132px; font-size: 13px; padding: 8px 12px;
  min-height: 38px; border-radius: var(--r-pill); background: var(--surface);
}
.filters select { padding-right: 32px; }

/* A checkbox filter is a pill too — the whole label is the target, which is a
   larger and more obvious thing to tap than a 16px box. */
.filters .check {
  white-space: nowrap; font-size: 13px; gap: 8px;
  padding: 8px 16px; min-height: 38px; border-radius: var(--r-pill);
  background: var(--surface); border: 1px solid var(--border-2);
  cursor: pointer; user-select: none;
}
.filters .check:hover { border-color: var(--brand-lt); }
.filters .check:has(input:checked) {
  background: var(--brand-soft); border-color: var(--brand-ink); color: var(--brand-dk);
  font-weight: 600;
}
.filters .check input[type="checkbox"] {
  width: 15px; height: 15px; min-width: 0; min-height: 0; margin: 0;
  accent-color: var(--brand); flex: none;
}
.filters > * { flex: none; }

.tabs {
  display: flex; gap: 8px; overflow-x: auto; scrollbar-width: none;
  padding: 2px 0 16px; -webkit-overflow-scrolling: touch;
}
.tabs::-webkit-scrollbar { display: none; }
/* Rounded rectangles, like every other control since v4.5.0. The pill was the
   last survivor of the glossy look and it made the reports screen the only
   place in the app with a different vocabulary for "pick one of these". */
.tabs button {
  border: 1px solid var(--border-2); background: var(--surface);
  padding: 8px 16px; border-radius: var(--r-btn);
  font: inherit; font-size: 13.5px; font-weight: 600; color: var(--muted);
  cursor: pointer; white-space: nowrap; flex: none;
  min-height: max(38px, var(--tap-min));
  transition: background-color .14s ease, color .14s ease, border-color .14s ease;
}
.tabs button:hover { border-color: var(--brand-lt); color: var(--text); }
.tabs button.on {
  background: var(--brand); border-color: var(--brand); color: var(--on-brand);
}
.tabs button.on:hover { color: var(--on-brand); }
.tabs button:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }

/* The segmented control. Two shapes of one thing: `.seg` fills its container
   and splits the width evenly, `.seg.auto` sits inline and takes only the room
   its labels need. Both are defined together so a picker and a view switch can
   never drift apart. The full styling is in the v4.6.0 section below. */

.notif-list { max-height: 60vh; overflow-y: auto; }
.notif { padding: 12px 20px; border-bottom: 1px solid var(--border); cursor: pointer; }
.notif:last-child { border-bottom: 0; }
.notif:active { background: var(--surface-2); }
.notif.unread { background: var(--brand-soft); }
.notif b { font-size: 14px; display: block; }
.notif span { font-size: 12.5px; color: var(--muted); }

.file-chip {
  display: inline-flex; align-items: center; gap: 8px; padding: 8px 12px; min-width: 0;
  background: var(--surface-2); border-radius: var(--r-pill); font-size: 12.5px;
  margin: 4px 4px 0 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chip-row { display: flex; flex-wrap: wrap; gap: 8px; }
.cat-chip { cursor: pointer; gap: 8px; }
.cat-chip:hover { background: var(--brand-soft); }
.cat-chip.off { opacity: .55; }
.chip {
  display: inline-flex; align-items: center; gap: 8px; padding: 8px 16px;
  border-radius: var(--r-pill); background: var(--surface-2); font-size: 13px;
  font-weight: 550; cursor: pointer; border: 1px solid transparent;
}
.chip.on { background: var(--brand-soft); border-color: var(--brand-ink); color: var(--brand-ink); }

.banner {
  display: flex; align-items: flex-start; gap: 12px; padding: 12px 16px;
  border-radius: var(--r-md); font-size: 13.5px; line-height: 1.5;
}
.banner.info { background: var(--brand-soft); color: var(--ink-info); }
.banner.warn { background: var(--warn-soft); color: var(--ink-warn); }
.banner.bad  { background: var(--danger-soft); color: var(--ink-bad); }
.banner.ok   { background: var(--ok-soft); color: var(--ink-ok); }
.banner .ico { font-size: 16px; line-height: 1.3; }

.spinner {
  width: 22px; height: 22px; border-radius: 50%; border: 2.5px solid var(--border-2);
  border-top-color: var(--brand-ink); animation: spin .7s linear infinite; margin: 0 auto;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* =========================================================== gloss extras */

/* Every remaining raised surface gets the same treatment, so the sheen reads
   as one lighting model rather than a per-component effect. */
.rail, .modal, .auth-card, .toast, .pop {
  background-image: var(--gloss-wash);
  background-repeat: no-repeat;
  background-size: 100% 180px;
}
.rail, .auth-card { box-shadow: var(--shadow-md), var(--gloss-rim); }
.modal { box-shadow: var(--shadow-lg), var(--gloss-rim); }
@media (min-width: 1024px) { .topbar { box-shadow: var(--shadow), var(--gloss-rim); background-image: var(--gloss-wash); background-repeat: no-repeat; background-size: 100% 100px; } }

.tabs button, .chip, .seg button.on {
  background-image: var(--gloss-wash);
  background-repeat: no-repeat; background-size: 100% 100%;
  box-shadow: var(--shadow), var(--gloss-rim);
}
.tabs button.on {
  position: relative; overflow: hidden; color: var(--on-brand); text-shadow: var(--on-brand-shadow);
  background-image: var(--grad-brand);
  box-shadow: var(--gloss-btn-rim), var(--lift-sm);
}
.tabs button.on::after {
  content: ""; position: absolute; left: 3px; right: 3px; top: 2px; height: 46%;
  border-radius: 999px 999px 70% 70% / 999px 999px 14px 14px; pointer-events: none;
  background: linear-gradient(180deg, rgba(255,255,255,.34), rgba(255,255,255,.05));
  opacity: var(--gloss-highlight, 1);
}
.chip.on {
  background-image: linear-gradient(180deg, rgba(255,255,255,.85), rgba(255,255,255,.1));
}

.composer .send {
  background-image: var(--grad-accent);
  box-shadow: var(--gloss-btn-rim), var(--lift-sm);
  position: relative; overflow: hidden;
}
.composer .send::after {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 52%;
  background: linear-gradient(180deg, rgba(255,255,255,.48), rgba(255,255,255,0));
  opacity: var(--gloss-highlight, 1);
}
.composer .send:active { transform: scale(.94); box-shadow: inset 0 2px 6px rgba(0,0,0,.25); }

.avatar {
  position: relative;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.8), 0 1px 2px rgba(16,44,40,.07);
}
.avatar::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background: linear-gradient(180deg, rgba(255,255,255,.42), rgba(255,255,255,0) 58%);
  opacity: var(--gloss-highlight, 1);
}
[data-theme="dark"] .avatar { box-shadow: inset 0 1px 0 rgba(255,255,255,.12); }
[data-theme="dark"] .avatar::after { opacity: .16; }
[data-theme="dark"] .card, [data-theme="dark"] .tile { background-size: 100% 100%; }
[data-theme="dark"] .pick, [data-theme="dark"] .btn { background-size: 100% 100%; }

/* Filled progress runs get a lengthwise highlight, like coated glass — with
   the shine off they are the solid bar the flat designs draw. */
.bars .fill, .sla-bar > i {
  background-image: var(--gloss-fill, none);
  box-shadow: var(--gloss-fill-rim, none);
}
.bars .track, .sla-bar { box-shadow: inset 0 1px 3px rgba(16,44,40,.09); }
[data-theme="dark"] .bars .track, [data-theme="dark"] .sla-bar { box-shadow: inset 0 1px 3px rgba(0,0,0,.5); }

.summary-block, .result-icon, .logo-lg, .rail .brand-row .logo {
  position: relative; overflow: hidden;
}
/* The mark brings its own colour and its own rounded tile, so the holder is
   just a box of the right size now. */
.logo-lg, .rail .brand-row .logo {
  background: none; box-shadow: none;
}
.logo-lg svg, .rail .brand-row .logo svg { display: block; border-radius: 12px; }
.logo-lg::after, .rail .brand-row .logo::after {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 52%;
  background: linear-gradient(180deg, rgba(255,255,255,.42), rgba(255,255,255,0));
  opacity: var(--gloss-highlight, 1);
}
.result-icon::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  background: linear-gradient(180deg, rgba(255,255,255,.5), rgba(255,255,255,0) 60%);
  opacity: var(--gloss-highlight, 1);
}

.ref-box, .banner, .demo-hint, .msg .bubble, .filters, .seg, .file-chip {
  box-shadow: inset 0 1px 0 rgba(255,255,255,.7);
}
[data-theme="dark"] .ref-box, [data-theme="dark"] .banner, [data-theme="dark"] .demo-hint,
[data-theme="dark"] .msg .bubble, [data-theme="dark"] .filters, [data-theme="dark"] .seg,
[data-theme="dark"] .file-chip { box-shadow: inset 0 1px 0 rgba(255,255,255,.06); }

input:not([type="checkbox"]):not([type="radio"]), select, textarea {
  box-shadow: inset 0 1px 2px rgba(16,44,40,.05);
}
[data-theme="dark"] input:not([type="checkbox"]):not([type="radio"]),
[data-theme="dark"] select, [data-theme="dark"] textarea {
  box-shadow: inset 0 1px 2px rgba(0,0,0,.35);
}
input:not([type="checkbox"]):not([type="radio"]):focus, select:focus, textarea:focus {
  box-shadow: 0 0 0 3px var(--brand-soft), inset 0 1px 2px rgba(16,44,40,.05);
}
input[type="checkbox"]:focus-visible, input[type="radio"]:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px;
}

.pick { background-image: var(--gloss-wash); background-repeat: no-repeat; background-size: 100% 100%; }
.pick:hover, .pick.on { box-shadow: var(--shadow), inset 0 1px 0 rgba(255,255,255,.9); }

/* =============================================================== print */

@media print {
  /*
     No animation on paper.

     The entrance animations use `animation-fill-mode: both`, which means that
     before one starts the element holds its *first* keyframe — and that
     keyframe is `opacity: 0`. A print renderer lays the page out fresh and
     does not wait for a 340ms animation to finish, so every card on the page
     was being printed invisible: a sheet of paper with nothing on it but the
     background motif. This was true of the reports screen's own Print button
     long before anything here needed it.
  */
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
    opacity: 1;
  }
  .rail, .topbar, .tabbar, .fab, .filters, .btn, .tabs, .toasts { display: none !important; }
  /* The background motif is for a screen. On paper it is ink nobody asked for,
     and behind a QR code it eats into the quiet zone the scanner needs. */
  body::before { display: none !important; }
  body { background: #fff; }
  .app { display: block; padding: 0; }
  .content { padding: 0; max-width: none; }
  .card { box-shadow: none; border: 1px solid #ccc; break-inside: avoid; background-image: none; }
  .card::before, .tile::before, .badge::after { display: none; }
  .print-head { display: block !important; margin-bottom: 20px; }
  th { background: #f1f1f1 !important; }
}
.print-head { display: none; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ------------------- the sign-in surface stays light, whatever the theme */
/*
   The palette on .auth-page covers everything that reads a custom property,
   which is nearly all of it. These are the leftovers: rules that set a real
   property directly under [data-theme="dark"] to suit a dark panel. On a
   surface that is always light they would put a white highlight where there is
   nothing to highlight, so they are put back here. Kept at the end of the file
   so they win the tie on source order.
*/
.auth-page .ref-box, .auth-page .banner, .auth-page .demo-hint,
.auth-page .msg .bubble, .auth-page .filters, .auth-page .seg,
.auth-page .file-chip, .auth-page .signup-cta {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .7);
}
.auth-page input:not([type="checkbox"]):not([type="radio"]),
.auth-page select, .auth-page textarea {
  box-shadow: inset 0 1px 2px rgba(16, 26, 51, .05);
}
.auth-page input:not([type="checkbox"]):not([type="radio"]):focus,
.auth-page select:focus, .auth-page textarea:focus {
  box-shadow: 0 0 0 3px var(--brand-soft), inset 0 1px 2px rgba(16, 26, 51, .05);
}
.auth-page .avatar, .auth-page .badge { box-shadow: none; }
.auth-page .card, .auth-page .tile, .auth-page .pick, .auth-page .btn {
  background-size: 100% 60%;
}

/* The sign-in form is a quiet, flat surface — the glossed pill button and the
   inset rims that suit the dense application chrome look overdone on it. */
.auth-page .btn.primary {
  background-image: none; background-color: var(--brand);
  text-shadow: none; border-radius: 12px;
  box-shadow: 0 6px 16px rgba(31, 71, 196, .28);
}
.auth-page .btn.primary::after { display: none; }
.auth-page .btn.primary:hover { background-color: var(--brand-dk); box-shadow: 0 8px 20px rgba(31, 71, 196, .34); }
.auth-page .btn.primary:active { box-shadow: 0 3px 10px rgba(31, 71, 196, .3); }
.auth-page .btn.primary.block.lg {
  /* Label centred, arrow parked on the right, as a sign-in button reads. */
  display: flex; align-items: center; justify-content: center; gap: 12px;
  position: relative; padding: 16px 20px; font-size: 15.5px;
}
.auth-page .btn.primary.block.lg .go { position: absolute; right: 18px; display: grid; }
.auth-page .btn.sm { border-radius: 10px; }
.auth-page input:not([type="checkbox"]):not([type="radio"]) { border-radius: 11px; padding: 12px 16px; }
.auth-page .field > span { font-size: 12.5px; font-weight: 650; color: var(--muted); }

/* The invitation to register. The card is narrow, and an icon, two lines of
   copy and a button do not share 320px — the copy ended up in a column three
   words wide. The icon goes; the words get the room. */
.auth-page .signup-cta { gap: 12px; padding: 16px 16px; border-radius: 13px; }
.auth-page .signup-icon { display: none; }
.auth-page .signup-cta span { line-height: 1.5; }
.auth-page .signup-cta .btn { flex: none; align-self: center; }

/* Saved replies above the reply box: a row of titles, one click to insert. */
.canned-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 12px;
}
.canned-bar .faint { margin-right: 2px; }
.canned-bar .btn { font-weight: 600; }

/* The tick that selects a ticket for a bulk action, and the bar that appears
   once anything is selected. */
/* The select-a-ticket checkbox. The box itself is the browser's, and a 13px
   one is far too small to hit, so the label around it carries the target. */
.row-pick {
  flex: none; display: grid; place-items: center; padding: 2px 2px 0 0; cursor: pointer;
  min-width: var(--tap-min); min-height: var(--tap-min);
}
.row-pick input { width: 18px; height: 18px; }
.bulk-bar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 12px 16px; border-bottom: 1px solid var(--border);
  background: var(--brand-soft); color: var(--text);
}
.bulk-bar b { font-size: 13.5px; margin-right: 2px; }

/* ============================================================== licensing */
/*
   The licence bar sits between the topbar and the content on every screen
   while something needs doing. It is a bar rather than a toast on purpose: it
   has to still be there when somebody tries to save and is refused, which is
   the moment it explains.
*/
.licence-bar {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 16px; border-bottom: 1px solid var(--border);
  font-size: 13.5px; line-height: 1.45;
}
.licence-bar .txt { flex: 1; min-width: 0; }
.licence-bar .ico { flex: none; display: grid; place-items: center; }
.licence-bar.warn  { background: var(--warn-soft);   color: var(--ink-warn); }
.licence-bar.error { background: var(--danger-soft); color: var(--ink-bad); }
.licence-bar .btn { flex: none; }
@media (max-width: 560px) {
  .licence-bar { align-items: flex-start; flex-wrap: wrap; }
  .licence-bar .txt { flex-basis: 100%; order: 2; }
}

/* A licence number is quoted in letters and read down a phone line, so it is
   set in the monospace face with the digits spaced enough to dictate. */
.lic-no {
  font-family: var(--mono); font-size: 13px; letter-spacing: .04em;
  background: var(--surface-3); border-radius: 6px; padding: 2px 8px;
  color: var(--text); white-space: nowrap;
}
.lic-no.small { font-size: 11.5px; padding: 1px 4px; }

.lic-list { display: flex; flex-direction: column; gap: 2px; }
.lic-row {
  display: flex; align-items: flex-start; gap: 16px;
  padding: 12px 4px; border-bottom: 1px solid var(--border);
}
.lic-row:last-child { border-bottom: 0; }
.lic-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.lic-company { font-weight: 600; font-size: 14px; }
.lic-actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; flex: none; }
@media (max-width: 640px) {
  .lic-row { flex-direction: column; gap: 12px; }
  .lic-actions { justify-content: flex-start; }
}

/* The key, shown once. Large, spaced and selectable — somebody is going to
   read it aloud or copy it into an email, and both want the same treatment. */
.key-show {
  margin: 16px 0; padding: 16px; border-radius: 12px;
  background: var(--surface-3); border: 1px dashed var(--border-2);
  text-align: center;
}
.key-show code {
  font-family: var(--mono); font-size: 19px; letter-spacing: .12em;
  font-weight: 600; user-select: all; word-break: break-all;
}

/* The ledger. Numbered down the left so an entry can be referred to by number
   in correspondence, with its fingerprint under each one. */
.ledger { display: flex; flex-direction: column; }
.ledger-row {
  display: flex; gap: 12px; padding: 12px 0;
  border-bottom: 1px solid var(--border);
}
.ledger-row:last-child { border-bottom: 0; }
.ledger-seq {
  flex: none; width: 46px; font-family: var(--mono); font-size: 12px;
  color: var(--faint); padding-top: 2px;
}
.ledger-hash {
  font-family: var(--mono); font-size: 11px; color: var(--faint);
  margin-top: 4px; word-break: break-all;
}

/* A plain two-column list of facts, for the summary somebody reads off.
   Block rather than flex on the container: a flex column here inherited a
   cross-axis size from the card and left every rule stopping short of the
   right edge, so the underlines ended mid-card. */
.facts { display: block; }
.kv-row {
  display: flex; gap: 16px; justify-content: space-between;
  padding: 8px 0; border-bottom: 1px solid var(--border); font-size: 14px;
}
.kv-row:last-child { border-bottom: 0; }
.kv-row .k { color: var(--muted); }
.kv-row .v { font-weight: 600; text-align: right; }

/* Revocation is destructive but not frequent: coloured text on a plain button
   in the list, and the full weight saved for the dialog that confirms it. */
.btn.lic-revoke { color: var(--danger); border-color: var(--danger-soft); }
.btn.lic-revoke:hover { background-color: var(--danger-soft); }
/* The customer's copy of the history has no entry numbers, so it needs no
   gutter to line them up in. */
.ledger-row.plain { padding-left: 0; }
/* A licence number is a label, not a field: it should be as wide as the number
   and no wider, even as a flex child that would otherwise stretch. */
.lic-main > .lic-no { align-self: flex-start; }

/* ========================================================== guest tickets */
/*
   The public form. It borrows the sign-in surface — a card floating on a page
   that ignores whatever theme the company chose — because the person filling it
   in is not a customer of the theme, and because a form that looks the same on
   every company's link is one fewer thing to get wrong.
*/
.guest-page .auth-card { max-width: 460px; }
.guest-page textarea { min-height: 108px; }

.guest-done {
  width: 64px; height: 64px; border-radius: 50%; margin: 0 auto 16px;
  display: grid; place-items: center;
  background: var(--ok-soft); color: var(--ok);
}
.guest-done .ico svg { width: 32px; height: 32px; }
/* The link is the ticket. It is shown in full, selectable, and small enough
   not to shout over the reference number above it. */
.guest-url {
  margin-top: 16px; text-align: center; word-break: break-all;
  font-family: var(--mono); user-select: all;
}

.guest-thread { display: flex; flex-direction: column; gap: 12px; margin-top: 20px; }
.guest-msg {
  padding: 12px 16px; border-radius: var(--r-md);
  background: var(--surface-2); font-size: 14px; line-height: 1.5;
}
.guest-msg.mine { background: var(--brand-soft); }
.guest-msg .who { display: flex; justify-content: space-between; gap: 8px; margin-bottom: 4px; }
.guest-msg .who b { font-size: 13px; }
.guest-msg .body { overflow-wrap: anywhere; white-space: pre-wrap; }

/* The admin's side: the address to print, and the code to print it as. */
.guest-link {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 12px; border-radius: var(--r-sm); background: var(--surface-3);
}
.guest-link code {
  font-family: var(--mono); font-size: 12px; flex: 1; min-width: 0;
  overflow-wrap: anywhere; user-select: all;
}
.guest-qr { margin-top: 16px; text-align: center; }
.guest-qr img {
  width: 180px; height: 180px; padding: 12px;
  background: #ffffff; border-radius: var(--r-sm); border: 1px solid var(--border);
}

/* ====================================================== the username field */
/*
   A login id typed into a namespace you cannot see is a guess. This shows the
   answer next to the box: whether what is there now is free, and a few that
   already are. The verdict sits under the input rather than beside it so the
   field keeps its width in a two-column row.
*/
.uname { display: flex; flex-direction: column; gap: 8px; }
.uname-status { font-size: 12px; min-height: 16px; line-height: 16px; color: var(--faint); }
.uname-status.ok  { color: var(--ink-ok); }
.uname-status.bad { color: var(--ink-bad); }
.uname-status.checking { color: var(--faint); }

.uname-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.uname-label {
  font-size: 11px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--faint);
}
.uname-chip {
  border: 1px solid var(--border-2); background: var(--surface); cursor: pointer;
  border-radius: var(--r-pill); padding: 4px 12px;
  font: inherit; font-family: var(--mono); font-size: 12px; color: var(--text);
  min-height: max(28px, var(--tap-min));
}
.uname-chip:hover { background: var(--brand-soft); border-color: var(--brand-lt); }
.uname-chip.on { background: var(--brand); border-color: var(--brand); color: var(--on-brand); }

/* ============================================================== entrances */
/*
   Cards and tiles arrive rather than appear.

   The stagger is doing a job, not decorating: the eye follows movement, so a
   screen that reveals its rows in the order they are laid out gets read in
   that order instead of all at once. It is short — 340ms, finished before
   anybody could call it a wait — and the global prefers-reduced-motion rule
   collapses the duration to nothing, which leaves the final state, which is
   the correct still picture.
*/
@keyframes rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
.content > .stack > *,
.content > .stack > .tiles > *,
.content > * {
  animation: rise .34s cubic-bezier(.22, 1, .36, 1) both;
}
/* Six steps is enough: past that the last card would arrive late enough to
   read as slowness rather than as sequence. */
.content > .stack > *:nth-child(2)  { animation-delay: .04s; }
.content > .stack > *:nth-child(3)  { animation-delay: .08s; }
.content > .stack > *:nth-child(4)  { animation-delay: .12s; }
.content > .stack > *:nth-child(5)  { animation-delay: .16s; }
.content > .stack > *:nth-child(n+6){ animation-delay: .2s; }
.tiles > *:nth-child(2) { animation-delay: .05s; }
.tiles > *:nth-child(3) { animation-delay: .1s; }
.tiles > *:nth-child(4) { animation-delay: .15s; }
.tiles > *:nth-child(n+5) { animation-delay: .2s; }

/* A row lifts under the pointer and presses under a finger. Both are cheap
   compositor-only transforms, so they stay smooth on a mid-range phone. */
.list-row { transition: background-color .12s, border-left-color .12s, transform .12s ease; }
@media (hover: hover) {
  .list-row:hover { transform: translateX(2px); }
}
.list-row:active { transform: scale(.995); }

/* ================================================= header: crumbs + action */
/*
   The breadcrumb is the reference's header treatment: a quiet line saying
   which part of the app this is, above the title that says which screen. It is
   information the desktop rail already carries, so it is hidden on a phone
   where the space is worth more than the reassurance.
*/
.crumbs { display: none; align-items: center; gap: 8px; font-size: 12px; color: var(--faint); margin-bottom: 4px; }
.crumbs .here { color: var(--muted); font-weight: 600; }
.crumbs .sep { opacity: .6; }
@media (min-width: 700px) {
  .crumbs { display: flex; }
  /*
     The bar stops naming the screen where the screen names itself.

     Every content screen opens with its own heading and a sentence saying what
     it is for, so the bar was printing "Tickets" directly above a page that
     said "Tickets" — a stutter, and a line of vertical space spent on it, on
     every screen in the app. The breadcrumb above still says where you are.

     Below 700px this rule does not apply: the breadcrumb is hidden there and
     the bar is the only context a phone gets.
  */
  .topbar .top-title { display: none; }
  .topbar .crumbs { margin-bottom: 2px; font-size: 12.5px; }
  .topbar .crumbs .here { color: var(--text); font-weight: 650; font-size: 15px; }
}

/* The action itself. Hidden on a phone, where the floating button is better
   placed for a thumb and this would crowd the title. */
.top-action { display: none; }
@media (min-width: 700px) {
  .top-action { display: inline-flex; }
  .top-action .ico { margin-right: -2px; }
}
@media (min-width: 1024px) {
  /* With the rail showing there is room for the FAB to go entirely. */
  .fab { display: none; }
}

/* ==========================================================================
   v4.6.0 — the reference language, applied everywhere
   ==========================================================================
   Everything below is shared chrome. It is written here rather than in the
   twenty view modules deliberately: the references are consistent because the
   same card, the same table and the same heading appear on every screen, and
   the only way to get that in a codebase this size is to make the primitives
   carry it. A screen gets the new look by using card() and a <table>, which
   every screen already does.
*/

/* ------------------------------------------------------- the page heading */

.page-head {
  display: flex; align-items: flex-start; gap: 12px;
  margin: 0 0 4px; padding: 2px 2px 0;
}
.ph-mark {
  flex: none; width: 42px; height: 42px; border-radius: var(--r-md);
  display: grid; place-items: center;
  background: var(--brand-soft); color: var(--brand-ink);
}
.ph-txt { flex: 1; min-width: 0; }
.ph-txt h2 {
  font-size: 19px; font-weight: 700; letter-spacing: -.02em; line-height: 1.25;
}
.ph-txt p {
  margin: 4px 0 0; font-size: 13px; line-height: 1.5; color: var(--muted);
  max-width: 68ch;
}
.ph-actions { flex: none; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
@media (max-width: 559px) {
  .page-head { flex-wrap: wrap; }
  .ph-actions { width: 100%; }
  .ph-actions .btn { flex: 1; justify-content: center; }
}

/*
   The mirror image of the rule that hides the bar's title on a desktop.

   On a phone the bar is sticky and carries the screen's name, so the page head
   repeating it is the same stutter costing the same line — and a line is worth
   more on a phone than anywhere else. The sentence survives, because it is the
   part that says what the screen is for, and nothing else on the page says it.
*/
@media (max-width: 699px) {
  /* Only the heading goes. Touching the head's own flex layout here made the
     text column collapse to its min-content and the actions jump above it —
     the sentence is the thing to keep, not a layout to re-derive. */
  .ph-txt h2 { display: none; }
  .ph-txt p { margin-top: 0; }
}

/* ------------------------------------------------------- card header marks */
/*
   A small tinted square carrying the section's icon, left of the title. The
   references use it on every panel and it does more than decorate: on a
   screen of five stacked cards it is the only thing that distinguishes them
   at a glance once you have scrolled past the titles.
*/
.head-mark {
  flex: none; width: 30px; height: 30px; border-radius: var(--r-sm);
  display: grid; place-items: center;
  background: var(--brand-soft); color: var(--brand-ink);
}
.card > header h3 { letter-spacing: -.01em; }

/* -------------------------------------------------------------- the table */
/*
   Tables were the one surface that had not moved: a hairline under every row
   and nothing else. The references give theirs a tinted header band that
   separates the labels from the data, a row that tints under the pointer, and
   no line under the last row — the card's own edge is already there, and two
   borders a pixel apart is the detail that makes a table look unfinished.

   A tall table gets its header pinned as well, but only when it has been given
   a height to scroll inside: .table-wrap scrolls sideways, so on an ordinary
   table there is no vertical scrollport for a sticky header to stick to.
*/
.card .body.tight .table-wrap { border-radius: 0 0 var(--r-xl) var(--r-xl); }
th {
  background: var(--surface-2);
  border-bottom: 1px solid var(--border-2);
  white-space: nowrap;
}
.table-wrap.tall { max-height: 62vh; overflow-y: auto; }
.table-wrap.tall th { position: sticky; top: 0; z-index: 2; }
tbody tr { transition: background-color .12s ease; }
tbody tr:last-child td { border-bottom: 0; }
@media (hover: hover) {
  tbody tr:hover > td { background: var(--surface-2); }
}
tr.clickable { cursor: pointer; }
/* Figures line up under each other; words do not need to. */
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* --------------------------------------------------- the segmented control */

.seg {
  display: flex; padding: 4px; gap: 4px;
  background: var(--surface-3); border-radius: var(--r-btn);
}
/* Inline variant: a view switch beside a heading, sized to its labels. */
.seg.auto { display: inline-flex; flex: none; }
.seg button, .seg-btn {
  flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  border: 0; background: none; cursor: pointer; font: inherit;
  font-size: 13px; font-weight: 600; color: var(--muted);
  padding: 8px 12px; border-radius: calc(var(--r-btn) - 4px);
  /* A full tap target on a touch screen. Shaving six pixels off to make the
     control look tighter is exactly the trade that produces a segmented
     control people miss with a thumb. */
  min-height: max(36px, var(--tap-min));
  transition: background-color .14s ease, color .14s ease;
}
.seg.auto .seg-btn { flex: none; }
.seg .ico svg { width: 16px; height: 16px; }
.seg button:hover, .seg-btn:hover { color: var(--text); }
.seg button.on, .seg-btn.on {
  background: var(--surface); color: var(--brand-ink);
  box-shadow: var(--shadow);
}
.seg button:focus-visible, .seg-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }

/* -------------------------------------------------------------- accordion */

.acc { display: flex; flex-direction: column; }
.acc-row { border-bottom: 1px solid var(--border); }
.acc-row:last-child { border-bottom: 0; }
.acc-q {
  width: 100%; display: flex; align-items: center; gap: 12px; text-align: left;
  border: 0; background: none; cursor: pointer; font: inherit;
  padding: 16px 4px; min-height: var(--tap-min); color: var(--text);
}
.acc-t { flex: 1; min-width: 0; font-size: 14.5px; font-weight: 600; line-height: 1.4; }
.acc-chev {
  flex: none; width: 22px; height: 22px; border-radius: 50%;
  background: var(--surface-3); color: var(--muted);
  display: grid; place-items: center; position: relative;
  transition: transform .2s ease, background-color .2s ease, color .2s ease;
}
.acc-chev::before, .acc-chev::after {
  content: ""; position: absolute; background: currentColor; border-radius: 1px;
}
.acc-chev::before { width: 10px; height: 1.8px; }
.acc-chev::after { width: 1.8px; height: 10px; transition: opacity .2s ease; }
.acc-row.on .acc-chev { background: var(--brand); color: var(--on-brand); transform: rotate(180deg); }
.acc-row.on .acc-chev::after { opacity: 0; }
/* Height is animated through a grid track rather than max-height: a guessed
   max-height either clips a long answer or eases through empty space. */
.acc-a { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .24s ease; }
.acc-row.on .acc-a { grid-template-rows: 1fr; }
.acc-inner { overflow: hidden; }
.acc-inner > * { margin: 0 0 16px; padding: 0 4px; font-size: 14px; line-height: 1.6; color: var(--muted); }
.acc-q:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; border-radius: var(--r-sm); }

/* ------------------------------------------------------------ leaderboard */

.ldr { display: flex; flex-direction: column; }
.ldr-row {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 4px; border-bottom: 1px solid var(--border);
}
.ldr-row:last-child { border-bottom: 0; }
.ldr-rank {
  flex: none; width: 24px; text-align: center;
  font-size: 12px; font-weight: 700; color: var(--faint);
  font-variant-numeric: tabular-nums;
}
.ldr-row.m1 .ldr-rank { color: var(--warn); }
.ldr-row.m2 .ldr-rank { color: var(--muted); }
.ldr-row.m3 .ldr-rank { color: var(--ink-amber); }
.ldr-mid { flex: 1; min-width: 0; }
.ldr-mid b { display: block; font-size: 14px; font-weight: 600; line-height: 1.3; }
.ldr-mid > span { display: block; font-size: 11.5px; color: var(--muted); margin-top: 1px; }
.ldr-track {
  height: 5px; border-radius: var(--r-pill); background: var(--surface-3);
  margin-top: 7px; overflow: hidden;
}
.ldr-fill {
  height: 100%; border-radius: inherit; background: var(--brand);
  animation: ldr-grow .7s cubic-bezier(.25, 1, .4, 1) both;
  transform-origin: left center;
}
@keyframes ldr-grow { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.ldr-row.m1 .ldr-fill { background: var(--warn); }
.ldr-val { flex: none; text-align: right; }
.ldr-val b { display: block; font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; }
.ldr-val span { font-size: 10.5px; color: var(--faint); text-transform: uppercase; letter-spacing: .06em; }

/* ------------------------------------------------------- the choice grid */
/*
   The mobile reference opens with a wall of categories, each with an icon,
   and that is how somebody who does not work in support picks a department:
   by recognising the picture, not by reading fourteen names in a dropdown.
*/
/* auto-fit rather than a fixed count, so a company with one open department
   gets one full-width tile instead of a lonely half-width one next to a gap. */
.pick-grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); }
@media (min-width: 560px) { .pick-grid { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); } }
@media (min-width: 900px) { .pick-grid.wide { grid-template-columns: repeat(auto-fit, minmax(216px, 1fr)); } }

/* A small uppercase label above a block that is not a card. */
.eyebrow {
  font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--faint); margin-bottom: 4px;
}

/* Scoped to the grid: `.pick` on its own is already the row used by the guest
   index and the company search, and those must keep their shape. */
.pick-grid .pick {
  display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
  padding: 16px 12px; text-align: left; cursor: pointer; font: inherit;
  min-height: 96px; color: var(--text);
  background: var(--surface); border: 1px solid var(--border-2);
  border-radius: var(--r-lg);
  transition: border-color .14s ease, background-color .14s ease, transform .12s ease;
}
.pick-grid .pick:hover { border-color: var(--brand-lt); background: var(--surface); transform: translateY(-1px); }
.pick-grid .pick:active { transform: scale(.985); }
.pick-grid .pick:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.pick-ico {
  width: 34px; height: 34px; border-radius: var(--r-sm); margin-bottom: 4px;
  display: grid; place-items: center;
  background: var(--surface-3); color: var(--muted);
  transition: background-color .14s ease, color .14s ease;
}
.pick-l { font-size: 13.5px; font-weight: 600; line-height: 1.3; }
.pick-s { font-size: 11.5px; color: var(--muted); line-height: 1.35; }
.pick-grid .pick.on { border-color: var(--brand); background: var(--brand-soft); }
.pick.on .pick-ico { background: var(--brand); color: var(--on-brand); }
.pick.on .pick-l { color: var(--brand-ink); }

/* ------------------------------------------------------------ the board */
/*
   A column per status, from the Kanban reference. It scrolls sideways as one
   piece rather than making each column its own scroller, because a board that
   scrolls in two directions at once is a board nobody can aim at.
*/
/* The 4px of breathing room (so a hovered column's shadow is not clipped) sits
   on the board rather than on the scroller: padding on a scroll container is
   not counted in scrollWidth on every engine, which left the board reporting a
   6px overflow it did not have and fading its own last column for no reason. */
.board-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; margin: -4px; }
/*
   No min-width: min-content here.

   A flex container sized to its content's minimum resolves a few pixels wider
   than the columns' own declared minimum — enough that a board which fits
   perfectly well reported an overflow and faded its own last column. The
   columns already refuse to shrink past 208px, and overflowing flex items are
   scrollable by the wrapper on their own, so the board is simply as wide as
   the space it is given.
*/
.board {
  display: flex; gap: 12px; align-items: stretch; width: 100%;
  padding: 4px;
}
/*
   The columns share the width where they fit and scroll where they do not.

   Fixed 272px columns needed 1408px for the five working statuses, and the
   content column is 1118px — so on an ordinary desktop the board always ended
   with a column sliced down the middle at the right edge. A half-column reads
   as something broken, not as an invitation to scroll. They flex now: five
   across a desktop with no scrolling at all, and below ~1100px they stop
   shrinking at 208px and the board scrolls as one piece, which is the point at
   which scrolling is obviously what is happening.
*/
.board-col {
  flex: 1 1 0; min-width: 208px;
  background: var(--surface-2); border-radius: var(--r-lg);
  border: 1px solid var(--border);
  display: flex; flex-direction: column; max-height: 70vh;
  /* An empty board is still a board. Without this the columns collapse to
     their headers and the screen reads as a failed load. */
  min-height: 188px;
}
.board-col > header {
  display: flex; align-items: center; gap: 8px; padding: 12px 14px 10px; flex: none;
}
.board-col > header .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--col); flex: none; }
.board-col > header b { font-size: 12.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
.board-col > header .n {
  margin-left: auto; font-size: 11px; font-weight: 700; padding: 2px 8px;
  border-radius: var(--r-pill); background: var(--surface-3); color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.board-col .col-body {
  display: flex; flex-direction: column; gap: 8px;
  padding: 0 8px 10px; overflow-y: auto; flex: 1;
}
.board-col.over { border-color: var(--brand); background: var(--brand-soft); }

.bcard {
  background: var(--surface); border: 1px solid var(--border-2);
  border-radius: var(--r-md); padding: 12px; cursor: pointer;
  border-left: 3px solid var(--col, var(--border-2));
  transition: box-shadow .14s ease, transform .12s ease;
}
.bcard:hover { box-shadow: var(--shadow-md); transform: translateY(-1px); }
.bcard.dragging { opacity: .45; }
.bcard .bc-top { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.bcard .bc-top .tno { flex: 1; min-width: 0; }
/* Two lines, then an ellipsis: a card that grows to fit a long subject makes
   the column below it unreadable. */
.bcard b {
  font-size: 13.5px; font-weight: 600; line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.bcard .bc-foot {
  display: flex; align-items: center; gap: 8px; margin-top: 10px;
  font-size: 11.5px; color: var(--muted);
}
.bcard .bc-foot .avatar { width: 22px; height: 22px; font-size: 9px; }
.bcard .bc-foot .right { margin-left: auto; }
.board .empty { padding: 20px 8px; font-size: 12px; }
.board .empty .big { display: none; }

/* --------------------------------------------------- the rail status list */
/*
   Counts beside the navigation, as in both support references. The point is
   that the sidebar answers "is there anything waiting for me" without a
   click — which is the question somebody opens this app to ask.
*/
.rail .nav-counts { padding: 2px 12px 8px; display: none; }
@media (min-width: 1024px) { .rail .nav-counts { display: block; } }
.rail .nav-counts a {
  display: flex; align-items: center; gap: 12px;
  padding: 8px 12px; border-radius: var(--r-sm);
  font-size: 12.5px; font-weight: 500; color: var(--muted); margin-bottom: 0;
}
.rail .nav-counts a:hover { background: var(--surface-2); color: var(--text); }
.rail .nav-counts a .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--col); flex: none; }
.rail .nav-counts a .n {
  margin-left: auto; font-size: 11.5px; font-weight: 700; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.rail .nav-counts a.zero { opacity: .5; }

/* ------------------------------------------------------------ empty state */
/*
   A circle behind the glyph, so an empty panel reads as a considered state
   rather than as something that failed to load.
*/
.empty .big {
  width: 60px; height: 60px; border-radius: 50%; margin: 0 auto 12px;
  display: grid; place-items: center;
  background: var(--surface-3); color: var(--muted);
}

/* ------------------------------------------------------------ filter bars */
/* The filter row already has the tinted backing; what it lacked was an edge,
   so on a long list it bled into the first row. */
.filters { border-bottom: 1px solid var(--border); }

/* ------------------------------------------- "there is more, sideways" */
/*
   A row that scrolls sideways has to say so. The filter bar and the board both
   run past the right edge on a narrow screen, and a control chopped in half at
   the edge reads as a rendering fault rather than as something to scroll: the
   eye has no way to tell a clipped checkbox from a broken one.

   A mask fades the last few pixels out instead, which is unmistakably "this
   continues". It is applied only while the element actually overflows — a
   fade on a row that fits is a smudge for no reason — and the class is set by
   the same code that builds the row.
*/
.scrolls-x {
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 36px), transparent 100%);
  mask-image: linear-gradient(90deg, #000 calc(100% - 36px), transparent 100%);
}
/* Once it is scrolled to the end there is nothing more to promise. */
.scrolls-x.at-end {
  -webkit-mask-image: none;
  mask-image: none;
}

/* ------------------------------------------------------------ entrances */
/* Board columns and choice tiles arrive with the same short stagger as the
   tiles, so a redesigned screen does not have one region that pops. */
.board-col, .pick-grid .pick { animation: rise .34s cubic-bezier(.22, 1, .36, 1) both; }
.board-col:nth-child(2), .pick-grid .pick:nth-child(2) { animation-delay: .04s; }
.board-col:nth-child(3), .pick-grid .pick:nth-child(3) { animation-delay: .08s; }
.board-col:nth-child(4), .pick-grid .pick:nth-child(4) { animation-delay: .12s; }
.board-col:nth-child(n+5), .pick-grid .pick:nth-child(n+5) { animation-delay: .16s; }

/* ========================================================= public form */
/*
   The company's one public QR, on the screen that manages it.
   The code is the largest thing here on purpose: the whole point of this
   screen is that somebody prints it and puts it on a wall.
*/
.qr-share { display: flex; gap: 24px; align-items: flex-start; flex-wrap: wrap; }
.qr-art {
  flex: none; padding: 16px; border-radius: var(--r-lg);
  /* White regardless of theme. A QR inverted for dark mode scans on about half
     the phones that try it, and the half that fail give no clue why. */
  background: #ffffff; border: 1px solid var(--border-2);
}
.qr-art img, .qr-art svg { display: block; width: 188px; height: 188px; }
.qr-side { flex: 1 1 280px; min-width: 0; }
.qr-side .guest-link { margin: 4px 0 12px; }
.qr-side p { margin: 0; }

/* ------------------------------------------------------------- the poster */
/*
   Printed, never shown. It lives in this page rather than in a second document
   so `window.print()` is the whole feature: nothing to keep in step, and no
   popup for a browser to block.
*/
.poster { display: none; }

@media print {
  /* On this screen the poster is the document. Everything that manages it —
     the cards, the toggles, the explanation — is scaffolding. */
  .content:has(.poster) > .stack > *:not(.poster) { display: none !important; }
  .poster {
    display: block !important; text-align: center;
    padding: 12mm 10mm; color: #000; break-inside: avoid;
  }
  .poster-kicker {
    font-size: 15pt; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
    color: #444;
  }
  .poster h1 { font-size: 34pt; line-height: 1.1; margin: 6mm 0 4mm; letter-spacing: -.02em; }
  .poster-lead { font-size: 14pt; line-height: 1.45; margin: 0 auto; max-width: 150mm; color: #222; }
  .poster-qr {
    display: block; width: 92mm; height: 92mm; margin: 10mm auto 6mm;
    background: #fff;
    /* No border: a quiet zone of white is what the scanner wants, and a frame
       drawn tight against the modules is the usual reason a printed code fails. */
  }
  .poster-url { font-family: var(--mono); font-size: 12pt; word-break: break-all; color: #333; }
  .poster-depts { margin: 9mm auto 0; max-width: 150mm; }
  .poster-depts-h { font-size: 13pt; font-weight: 700; margin-bottom: 3mm; }
  .poster-depts ul {
    list-style: none; padding: 0; margin: 0;
    display: flex; flex-wrap: wrap; justify-content: center; gap: 3mm 5mm;
  }
  .poster-depts li {
    font-size: 13pt; padding: 2mm 5mm; border: 1pt solid #999; border-radius: 3mm;
  }
  .poster-foot { margin-top: 10mm; font-size: 11pt; color: #444; }
  @page { margin: 8mm; }
}

/* ==================================================== accordion navigation */
/*
   One control, two places: the groups in the desktop rail and the groups in
   the phone's More sheet. They are the same idea — a heading you can fold —
   and writing them twice is how they end up disagreeing about what a chevron
   means. The rail adds its own spacing above; everything structural is here.
*/
.nav-group { min-width: 0; }
.group-label {
  display: flex; align-items: center; gap: 8px; width: 100%;
  border: 0; background: none; cursor: pointer; font: inherit; text-align: left;
  font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint); padding: 14px 4px 8px;
  border-radius: var(--r-sm);
  /* A full tap target on a touch screen; on a mouse --tap-min is zero and the
     heading keeps the compact height the rail is designed around. */
  min-height: var(--tap-min);
  /* It is a heading people tap repeatedly; a selection that appears on the
     second tap makes it feel like the tap missed. */
  user-select: none; -webkit-user-select: none;
}
.group-label:hover { color: var(--muted); }
.group-label:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.group-label:disabled { cursor: default; }
.gl-txt { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* What is waiting inside a folded group. Folding a section away must never
   hide the fact that something in it wants attention. */
.gl-count {
  flex: none; font-size: 10px; font-weight: 700; letter-spacing: 0; padding: 1px 7px;
  border-radius: var(--r-pill); background: var(--warn); color: var(--on-warn);
}
.nav-group.open .gl-count { display: none; }

/* The chevron, drawn from two rules: nothing to load, and it rotates about
   its own centre rather than about a glyph's baseline. */
.gl-chev {
  flex: none; width: 10px; height: 10px; position: relative;
  transition: transform .22s ease;
}
.gl-chev::before, .gl-chev::after {
  content: ""; position: absolute; top: 4px; width: 6.5px; height: 1.6px;
  background: currentColor; border-radius: 1px;
}
.gl-chev::before { left: 0; transform: rotate(45deg); }
.gl-chev::after { right: 0; transform: rotate(-45deg); }
.nav-group.open .gl-chev { transform: rotate(180deg); }

/* Height through a grid track rather than max-height: a guessed max-height
   either clips a long group or eases through empty space on a short one. */
.nav-group-body {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows .24s cubic-bezier(.22, 1, .36, 1);
}
.nav-group.open .nav-group-body { grid-template-rows: 1fr; }
.nav-group-inner { overflow: hidden; min-height: 0; }

/* In the sheet the rows are the sheet's own, so the group supplies only the
   heading and the fold. The 4px side padding lines the heading up with the row
   icons below it — the sheet already carries the body's own gutter. */
.sheet-groups .group-label { padding: 14px 4px 10px; }
.sheet-groups .nav-group { border-bottom: 1px solid var(--border); }
.sheet-groups .nav-group:last-child { border-bottom: 0; }
.sheet-groups .nav-group.open { padding-bottom: 6px; }
.sheet-groups .sheet-list a:last-child,
.sheet-groups .sheet-list button:last-child { border-bottom: 0; }
/* The screen you are already on, so a sheet opened mid-task says where you
   are rather than only where you could go. */
.sheet-list a.on { color: var(--brand-ink); font-weight: 650; }
.sheet-list a.on .ico { background: var(--brand-soft); color: var(--brand-ink); }

/* ==================================================== wide list rows */
/*
   A list row on a wide screen.

   Stacked, a row is four lines tall and leaves most of a 1500px pane empty —
   the title at the far left, the tail at the far right, and nothing between
   them. That reads as a phone layout someone has stretched, which is what it
   was.

   From 1280px the same markup lays out as columns. Two things make that work,
   and the first release of it had neither:

   · It is opt-in, via `cols` on the row. The rule was written against the
     ticket row's four children and then applied to every .list-row in the app,
     including ones whose fourth child is something else entirely — which is
     how the employees list ended up with its username, badges and timestamps
     landing at a different x on every line.

   · The tracks are fixed widths, not `auto`. A column sized to its own content
     is a column that moves with every row; a list only reads as a table when
     the cells line up down the page, and that is the whole reason for doing
     this.

   The four children are positional rather than named — title, detail, context,
   badges — because that is the shape every list row in this app already has,
   and a row that does not have it simply does not ask for `cols`.
*/
@media (min-width: 1280px) {
  .list-row.cols .mid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 17rem) 14rem;
    grid-template-rows: auto auto;
    column-gap: 24px;
    align-items: center;
  }
  .list-row.cols .mid > :nth-child(1) { grid-column: 1; grid-row: 1; }
  .list-row.cols .mid > :nth-child(2) { grid-column: 1; grid-row: 2; }
  .list-row.cols .mid > :nth-child(3) { grid-column: 2; grid-row: 1 / span 2; }
  .list-row.cols .mid > :nth-child(4) { grid-column: 3; grid-row: 1 / span 2; }
  .list-row.cols .mid > :nth-child(3),
  .list-row.cols .mid > :nth-child(4) { justify-self: start; margin-top: 0; }
  /* Badges keep to one line: wrapping one row and not the next is the same
     raggedness in a different place. */
  .list-row.cols .mid > :nth-child(4) { flex-wrap: nowrap; }

  /*
     A fixed tail — a width, not a minimum.

     The tail sizes to its own text, and "never signed in" is 60px wider than
     "8h ago". With .mid taking whatever is left, that made .mid a different
     width on every row, which moved every column inside it by up to 24px down
     the list. A column that moves is not a column.

     13rem fits the longest thing either list puts there ("never signed in ·
     12/4 ›", "3d 4h overdue ›") with room to spare, and anything longer
     truncates rather than shoving the columns about.
  */
  .list-row.cols .tail {
    display: flex; align-items: center; justify-content: flex-end;
    gap: 12px; flex: 0 0 13rem; overflow: hidden;
  }

  /* A little more air, now that a row is half as tall. */
  .list-row.cols { padding-top: 16px; padding-bottom: 16px; }
}
