/*
  TagBadger theme — TagFinderWeb component classes.
  Loads after /Content/bootstrap.min.css and after tagbadger-tokens.css + tagbadger-fonts.css.

  Rule (brief §3.5 / §10, tokens.md §10): do NOT re-skin Bootstrap's color *variant* or
  *utility* classes (.btn-dark, .btn-light, .bg-dark, .bg-light, .bg-warning, .alert-*,
  .badge-*, .text-*) — that's the exact bug this redesign removes. Everything below is
  either a new `.tb-*` component class applied in markup alongside Bootstrap's structural
  classes (`.btn`, `.form-control`, `.table`), or a themed *base* (unvariant) component
  Bootstrap ships with no color of its own (`.table`, `.form-control`, `.card`, `.modal`).
*/

/* ── Base ─────────────────────────────────────────────────────────────── */

html, body {
  background: var(--tb-surface);
  color: var(--tb-on-surface);
  font-family: var(--tb-sans);
  font-size: 15px; /* web body floor, brief §5 */
  line-height: 1.55;
}

h1, h2, h3, h4, h5, h6 { font-weight: 750; letter-spacing: -.02em; color: var(--tb-on-surface); }
h2 { font-size: 26px; } /* web caps ~28px, brief §5 */
h3 { font-size: 19px; }
h4 { font-size: 16px; }
h5 { font-size: 14.5px; }

small, .small, .text-muted, .form-text { font-size: 13px; } /* web secondary floor, brief §5 */

code, pre, .tb-mono {
  font-family: var(--tb-mono);
  color: var(--tb-data);
}

/* Links — Direction B (WEB-26): brand green carries the affordance, per the one-pager.
   Underline appears on hover for reinforcement, not at rest. */
a {
  color: var(--tb-primary);
  text-decoration: none;
}
a:hover { text-decoration: underline; text-underline-offset: 2px; }
/* Nav-style links opt out of the underline-at-rest (they're chrome, not prose) */
.tb-nav-item, .navbar a, nav.navbar a, footer a {
  text-decoration: none;
}
.tb-nav-item:hover { text-decoration: none; }

/* ── Focus rings (WEB-09) ────────────────────────────────────────────── */
/* Bootstrap 4's .btn:focus / .form-control:focus draw a box-shadow glow but no
   outline; :focus-visible below adds a real 2px ring on top so keyboard focus is
   never silently eaten. */
a:focus-visible,
button:focus-visible,
.btn:focus-visible,
.tb-btn:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
.form-control:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--tb-focus);
  outline-offset: 2px;
  border-radius: 4px;
}
.form-control:focus, .btn:focus, .btn.focus {
  box-shadow: none; /* stop fighting our own ring with Bootstrap's blue glow */
}

/* ── Buttons ──────────────────────────────────────────────────────────── */
/* .tb-btn is applied alongside Bootstrap's own `.btn` (which still supplies
   padding/border-radius/line-height) — we only ever add color here, never
   touch `.btn` itself. */
.tb-btn {
  background: var(--tb-primary);
  color: var(--tb-on-primary);
  border: 1px solid var(--tb-primary);
  font-weight: 650;
  text-decoration: none; /* WEB-25: many .tb-btn are <a> elements — without this they
                            inherit the prose `a { underline }` rule above and read as
                            link/button hybrids ("Add Thing", "Back to Sessions"). */
  transition: background var(--tb-dur-micro) var(--tb-ease), border-color var(--tb-dur-micro) var(--tb-ease);
}
.tb-btn:hover { background: var(--tb-primary-hover); border-color: var(--tb-primary-hover); color: var(--tb-on-primary); }
.tb-btn:active { background: var(--tb-primary-pressed); border-color: var(--tb-primary-pressed); color: var(--tb-on-primary); }
.tb-btn:disabled, .tb-btn.disabled {
  background: var(--tb-disabled-fill); border-color: var(--tb-disabled-fill); color: var(--tb-disabled); cursor: not-allowed;
}

/* Ghost / secondary — outline only, no fill (Cancel, Back, Edit-in-place) */
.tb-btn--ghost {
  background: transparent;
  color: var(--tb-on-surface);
  border: 1px solid var(--tb-outline);
}
.tb-btn--ghost:hover { background: var(--tb-hover); border-color: var(--tb-on-surface); color: var(--tb-on-surface); }
.tb-btn--ghost:active { background: var(--tb-selected); color: var(--tb-on-surface); }

/* Outline primary — brand-colored border + text, transparent fill (WEB-57): for a page-primary
   action that shouldn't carry the one-filled-green-per-screen treatment (tokens.md's brand+matched
   dual-role rule) alongside a genuine verification action elsewhere on the same screen. Login,
   "View Customers" and Search were filled `.tb-btn` with nothing to actually fill for -- this reads
   as primary without competing with a real Matched pill or a page's one legitimate Save/Add fill. */
.tb-btn--outline {
  background: transparent;
  color: var(--tb-primary);
  border: 1px solid var(--tb-primary);
}
.tb-btn--outline:hover { background: var(--tb-primary-quiet); border-color: var(--tb-primary-hover); color: var(--tb-primary); }
.tb-btn--outline:active { background: var(--tb-primary-line); border-color: var(--tb-primary-pressed); }

/* Quiet — text-only treatment for de-emphasized/tertiary actions (WEB-25): per-row
   Archive (danger styling belongs to the confirm step, not every row) and Login's
   "Forgot my Password" (must not read as a peer of the primary Login action). */
.tb-btn--quiet {
  background: transparent;
  color: var(--tb-on-surface-variant);
  border: 1px solid transparent;
  font-weight: 550;
}
.tb-btn--quiet:hover { background: var(--tb-hover); border-color: transparent; color: var(--tb-on-surface); }
.tb-btn--quiet:active { background: var(--tb-selected); border-color: transparent; color: var(--tb-on-surface); }

/* Destructive — never filled (Direction A, tokens.md §3 rule 2). Delete / disconnect /
   disable / remove / archive / retire all land here, not on the primary fill. */
.tb-btn--danger {
  background: transparent;
  color: var(--tb-danger);
  border: 1px solid var(--tb-danger);
}
.tb-btn--danger:hover { background: var(--tb-state-unknown-fill); color: var(--tb-danger); border-color: var(--tb-danger); }
.tb-btn--danger:active { background: var(--tb-state-unknown-line); }

/* ── Card header (AddEdit forms — replaces `card-header bg-dark text-white`) ─ */
.tb-card-header {
  background: var(--tb-surface-container-high) !important;
  color: var(--tb-on-surface) !important;
  border-bottom: 1px solid var(--tb-outline-variant);
  font-weight: 700;
}
/* `!important` here is scoped to a brand-new class we introduce, not an escape
   hatch for a Bootstrap variant fight — Bootstrap's own `.card-header` sets a
   background via `.bg-*` utility composition in this codebase's markup
   (`class="card-header bg-dark text-white"` becomes `class="card-header tb-card-header"`),
   so nothing upstream is being out-cascaded; this simply guarantees intent. */
.card {
  background: var(--tb-surface-container);
  border: 1px solid var(--tb-outline-variant);
  border-radius: var(--tb-r-card);
}
.card-body { color: var(--tb-on-surface); }

/* ── App shell (WEB-56) ──────────────────────────────────────────────── */
/* Migrated off /css/simple-sidebar.css, which is deleted. That sheet was the stock
   Start Bootstrap template, loaded *after* this one so its `#sidebar-wrapper` /
   `#page-content-wrapper` rules out-cascaded ours, and its whole layout idea was to
   hide the rail by dragging it off-screen (`margin-left: -15rem`) — which took the
   brand with it. The brand now lives in the topbar and the rail collapses to icons. */
#wrapper {
  align-items: stretch;
  min-height: 100vh;
}
#sidebar-wrapper {
  flex: 0 0 15rem;
  width: 15rem;
  align-self: flex-start;       /* stretch would defeat the sticky below */
  position: sticky;
  top: 0;
  height: 100vh;
  max-height: 100vh;
  display: flex;
  flex-direction: column;
  z-index: 1030;
  background: var(--tb-surface-container-low);
  border-right: 1px solid var(--tb-outline-variant);
  transition: flex-basis var(--tb-dur-std) var(--tb-ease), width var(--tb-dur-std) var(--tb-ease), transform var(--tb-dur-std) var(--tb-ease);
}
#page-content-wrapper {
  flex: 1 1 auto;
  min-width: 0;                 /* without this a wide table stretches the whole shell */
}
/* Only the item list scrolls, so the tenant block and Log Out stay put and the
   selected item can't sit below the fold on a tall nav (was: the page scrolled and
   the rail went with it). */
.tb-nav {
  flex: 1 1 auto;
  overflow-y: auto;
  overflow-x: hidden;
  padding-bottom: 4px;
  /* Slim, rail-colored scrollbar instead of the stock Windows one. Standard
     properties win in Chromium ≥121 / Firefox; the ::-webkit-scrollbar rules
     below cover older Chromium. Tokens keep it correct in both themes. */
  scrollbar-width: thin;
  scrollbar-color: var(--tb-outline-variant) transparent;
}
.tb-nav::-webkit-scrollbar { width: 6px; }
.tb-nav::-webkit-scrollbar-thumb {
  background: var(--tb-outline-variant);
  border-radius: 3px;
}
.tb-nav::-webkit-scrollbar-track { background: transparent; }
.tb-sidebar__footer {
  margin-top: auto;
  padding: 8px;
  border-top: 1px solid var(--tb-outline-variant);
  /* WEB-68: now holds two controls -- the theme toggle beside Log Out, same footer the
     account/log-out chrome already lived in. */
  display: flex;
  align-items: center;
  gap: 6px;
}
.tb-sidebar__footer .tb-nav-item { flex: 1 1 auto; min-width: 0; }
/* First focusable element on the page; parked off-screen until it has focus. */
.tb-skip-link {
  position: absolute;
  left: 8px;
  top: -60px;
  z-index: 1080;
  padding: 9px 16px;
  border-radius: var(--tb-r-control);
  background: var(--tb-surface-container-high);
  color: var(--tb-on-surface);
  border: 1px solid var(--tb-outline);
  font-weight: 650;
  transition: top var(--tb-dur-micro) var(--tb-ease);
}
.tb-skip-link:focus {
  top: 8px;
  text-decoration: none;
  color: var(--tb-on-surface);
}
#tb-main:focus { outline: none; } /* the skip target is programmatic, not a control */

/* ── Sidebar: tenant block (WEB-55) ──────────────────────────────────── */
/* Who you are working on, and the way out of it. Width is no longer pinned to 15rem:
   WEB-56 replaced the off-screen hide with an icon rail, so the block simply follows
   whatever width the rail currently has. A long customer name wraps. */
#sidebar-wrapper .tb-tenant {
  padding: 12px 1.25rem 13px;
  background: var(--tb-surface-container);
  border-bottom: 1px solid var(--tb-outline-variant);
}
.tb-tenant__eyebrow {
  font-family: var(--tb-mono);
  font-size: 9.5px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--tb-on-surface-faint); /* legal on -container, per tokens.css */
}
.tb-tenant__name {
  font-weight: 750;
  font-size: 15px;
  line-height: 1.3;
  color: var(--tb-on-surface);
  margin: 1px 0 5px;
  overflow-wrap: anywhere;
}
/* Stands in for the name in the collapsed rail, where no word fits (WEB-56). */
.tb-tenant__initial {
  display: none;
  width: 32px;
  height: 32px;
  margin: 0 auto;
  border-radius: var(--tb-r-control);
  align-items: center;
  justify-content: center;
  background: var(--tb-surface-container-high);
  color: var(--tb-on-surface);
  font-weight: 750;
  font-size: 14px;
}
/* The switch control is chrome, not prose: it opts out of the brand-green link
   colour the same way `.tb-nav-item` does, and earns its underline on hover. */
#sidebar-wrapper .tb-tenant__switch {
  font-size: 12.5px;
  color: var(--tb-on-surface-variant);
  text-decoration: none;
}
#sidebar-wrapper .tb-tenant__switch:hover {
  color: var(--tb-on-surface);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ── Sidebar: grouped nav (WEB-56) ───────────────────────────────────── */
/* Four labelled sections replace one flat list of eighteen anchors. The sysadmin
   items no longer wear a per-item `::before "ADMIN"` eyebrow (WEB-04's fix for the
   amber-nav bug, but repeated five times); they sit under one collapsible header. */
.tb-nav-group { padding: 10px 0 4px; }
.tb-nav-group + .tb-nav-group { border-top: 1px solid var(--tb-outline-variant); }
.tb-nav-group__label {
  font-family: var(--tb-mono);
  font-size: 9.5px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--tb-on-surface-faint);
  padding: 0 1.25rem 6px;
  margin: 0;
}
.tb-nav-group__toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  width: 100%;
  padding: 0 1.15rem 6px 1.25rem;
  background: transparent;
  border: 0;
  text-align: left;
  cursor: pointer;
}
.tb-nav-group__toggle .tb-nav-group__label { padding: 0; }
.tb-nav-group__chevron {
  display: inline-flex;
  color: var(--tb-on-surface-faint);
  transition: transform var(--tb-dur-micro) var(--tb-ease);
}
.tb-nav-group__toggle[aria-expanded="false"] .tb-nav-group__chevron { transform: rotate(-90deg); }
.tb-nav-group__toggle:hover .tb-nav-group__label,
.tb-nav-group__toggle:hover .tb-nav-group__chevron { color: var(--tb-on-surface); }

.tb-nav-list {
  list-style: none;
  margin: 0;
  padding: 0 8px 4px;
}
.tb-nav-list--closed { display: none; }
.tb-nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--tb-r-control);
  background: transparent;
  color: var(--tb-on-surface-variant);
  font-weight: 550;
  font-size: 13.5px;
  transition: background var(--tb-dur-micro) var(--tb-ease), color var(--tb-dur-micro) var(--tb-ease);
}
.tb-nav-item:hover {
  background: var(--tb-hover);
  color: var(--tb-on-surface);
}
/* Selected page — quiet green fill + green inset bar (WEB-26). One rule, not the two
   that used to fight (`.tb-nav-item.active` was dead markup, and `--admin`'s neutral
   inset bar out-ordered `--active`'s green one on any selected admin item). Set
   server-side by TagFinder.master.cs via Utils/SideNav's child→parent map, so an
   AddEdit/Detail/Import page lights its parent list item rather than nothing at all. */
.tb-nav-item--active,
.tb-nav-item--active:hover {
  background: var(--tb-primary-quiet);
  color: var(--tb-on-surface);
  box-shadow: inset 3px 0 0 var(--tb-primary);
}
/* Icons are stroked with `currentColor` (see Utils/SideNav.cs) so one glyph tracks the
   item's rest / hover / selected colour without a second rule. */
.tb-nav-icon { flex: 0 0 auto; }
.tb-nav-item__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── The one nav toggle (WEB-56) ─────────────────────────────────────── */
/* Replaces the text "Show/Hide Menu" button *and* Bootstrap's navbar-toggler, which
   rendered side by side at 375px and did different things. */
/* WEB-68: the theme toggle shares this ghost-icon-button base -- same affordance
   language (36px square, transparent until hover), different action. */
.tb-nav-toggle,
.tb-theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  padding: 0;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--tb-r-control);
  color: var(--tb-on-surface-variant);
  cursor: pointer;
}
.tb-nav-toggle:hover,
.tb-theme-toggle:hover { background: var(--tb-hover); color: var(--tb-on-surface); }

/* Desktop: collapse to an icon rail rather than removing the nav. */
@media (min-width: 768px) {
  #wrapper.tb-nav-collapsed #sidebar-wrapper { flex-basis: 4.25rem; width: 4.25rem; }
  #wrapper.tb-nav-collapsed .tb-nav-item__label,
  #wrapper.tb-nav-collapsed .tb-nav-group__label,
  #wrapper.tb-nav-collapsed .tb-nav-group__toggle,
  #wrapper.tb-nav-collapsed .tb-tenant__eyebrow,
  #wrapper.tb-nav-collapsed .tb-tenant__name,
  #wrapper.tb-nav-collapsed .tb-tenant__switch { display: none; }
  /* Nothing left to disclose at this width, so the sysadmin section shows in full. */
  #wrapper.tb-nav-collapsed .tb-nav-list--closed { display: block; }
  #wrapper.tb-nav-collapsed .tb-tenant__initial { display: flex; }
  #wrapper.tb-nav-collapsed .tb-tenant { padding-left: 0; padding-right: 0; }
  #wrapper.tb-nav-collapsed .tb-nav-item { justify-content: center; padding-left: 0; padding-right: 0; }
  #wrapper.tb-nav-collapsed .tb-nav-group { padding-top: 6px; }
  /* WEB-68: at 4.25rem the toggle (36px) and the icon-only Log Out row no longer fit side by
     side -- stack instead of overflowing the rail. */
  #wrapper.tb-nav-collapsed .tb-sidebar__footer { flex-direction: column; gap: 4px; }
}

/* Narrow: the rail slides in over the content, dismissable by the scrim or Escape. */
.tb-nav-scrim { display: none; }
@media (max-width: 767.98px) {
  #sidebar-wrapper {
    position: fixed;
    top: 0;
    left: 0;
    height: 100vh;
    transform: translateX(-100%);
    box-shadow: var(--tb-e2);
  }
  #wrapper.tb-nav-open #sidebar-wrapper { transform: none; }
  #wrapper.tb-nav-open .tb-nav-scrim {
    display: block;
    position: fixed;
    top: 0; right: 0; bottom: 0; left: 0;
    z-index: 1020;
    background: rgba(0, 0, 0, .45);
  }
}

@media (prefers-reduced-motion: reduce) {
  #sidebar-wrapper,
  .tb-nav-item,
  .tb-nav-toggle,
  .tb-skip-link,
  .tb-nav-group__chevron { transition: none; }
}

.tb-topbar {
  display: flex;
  align-items: center;
  justify-content: flex-start;  /* .navbar's space-between would push brand off the toggle */
  gap: 12px;
  background: var(--tb-surface-container-low) !important;
  border-bottom: 1px solid var(--tb-outline-variant) !important;
}
/* WEB-25: Bootstrap's `.navbar-light .navbar-nav .nav-link` (0,3,0) out-specifies
   `.tb-topbar .nav-link` (0,2,0), so Home/Log Out were rendering in Bootstrap's
   black-alpha — near-invisible on the dark surface. Match-and-beat its specificity. */
.tb-topbar .nav-link,
.navbar-light.tb-topbar .navbar-nav .nav-link { color: var(--tb-on-surface-variant); }
.tb-topbar .nav-link:hover,
.navbar-light.tb-topbar .navbar-nav .nav-link:hover { color: var(--tb-on-surface); }

/* Brand mark (DS-06) — inline SVG so it can be filled from tokens, replacing the
   old misnamed/raster-only `tagfinder_logo_web.png` in the chrome. Geometry is
   the small/simplified subset (ears+head+blaze+nose, no cheeks/eyes) from
   docs/design/tagbadger-styleguide.html's "The mark" section. The PNG file
   itself is left alone — other surfaces may still reference it. */
/* WEB-56: the brand moved from the rail to the topbar and became a link there (the
   tenant dashboard, or whichever landing the user actually has) — the wordmark is the
   affordance every user clicks first, and in the rail it disappeared with the nav. */
.tb-brand { display: flex; align-items: center; gap: 9px; padding: 4px 0; color: inherit; text-decoration: none; }
a.tb-brand:hover, a.tb-brand:focus { text-decoration: none; color: inherit; }
.tb-brand-mark { flex: 0 0 auto; }
.tb-wordmark { font-weight: 800; letter-spacing: -.02em; font-size: 17px; color: var(--tb-on-surface); }

/* WEB-60: the topbar's Ctrl-K affordance -- pushed to the far edge of the bar so it reads as a
   distinct action from the brand link beside it, not a third nav item. */
.tb-search-trigger {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  padding: 7px 10px;
  background: var(--tb-surface-container);
  border: 1px solid var(--tb-outline-variant);
  border-radius: var(--tb-r-control);
  color: var(--tb-on-surface-variant);
  font-size: 13px;
  cursor: pointer;
}
.tb-search-trigger:hover { background: var(--tb-hover); color: var(--tb-on-surface); border-color: var(--tb-outline); }
.tb-search-trigger__kbd {
  font-family: var(--tb-mono);
  font-size: 10.5px;
  letter-spacing: .04em;
  padding: 1px 6px;
  border-radius: var(--tb-r-chip);
  background: var(--tb-surface-container-high);
  border: 1px solid var(--tb-outline-variant);
  color: var(--tb-on-surface-faint);
}
@media (max-width: 575.98px) {
  /* Icon-only at phone width -- aria-label on the button still names it "Search". */
  .tb-search-trigger__label, .tb-search-trigger__kbd { display: none; }
  .tb-search-trigger { padding: 7px 9px; }
}

footer.tb-footer {
  color: var(--tb-on-surface-faint);
  border-top: 1px solid var(--tb-outline-variant);
  font-family: var(--tb-mono);
  font-size: 12px;
  padding: 16px 0;
}

/* ── Filter bars (replaces `row alert alert-secondary`) ──────────────── */
.tb-filter-bar {
  background: var(--tb-surface-container-low);
  border: 1px solid var(--tb-outline-variant);
  border-radius: var(--tb-r-card);
  padding: var(--tb-s4) var(--tb-s4) var(--tb-s2);
  margin-left: 0; margin-right: 0;
}
.tb-filter-bar label {
  font-family: var(--tb-mono);
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--tb-on-surface-variant);
  margin-bottom: 4px;
}

/* ── Alerts / inline messages (replaces `alert alert-danger`/`-success`/`-info`) ─ */
.tb-alert {
  border-radius: var(--tb-r-control);
  border: 1px solid var(--tb-outline-variant);
  background: var(--tb-surface-container);
  color: var(--tb-on-surface);
  padding: var(--tb-s3) var(--tb-s4);
}
.tb-alert--error {
  border-color: var(--tb-state-unknown-line);
  background: var(--tb-state-unknown-fill);
  color: var(--tb-on-surface);
}
.tb-alert--error::before { content: "! "; color: var(--tb-danger); font-weight: 700; }
.tb-alert--ok {
  border-color: var(--tb-state-matched-line);
  background: var(--tb-state-matched-fill);
}
.tb-alert--ok::before { content: "\2713 "; color: var(--tb-state-matched); font-weight: 700; }
.tb-alert--info {
  border-color: var(--tb-outline);
  background: var(--tb-surface-container-high);
  color: var(--tb-on-surface-variant);
}
/* Attention-needed but not a failure (e.g. "confirm this action", "no customer
   assigned yet") — a triangle glyph carries the meaning instead of borrowing
   the reserved short/amber state hue (tokens.md §4: states are for
   reconciliation outcomes only). */
.tb-alert--warn {
  border-color: var(--tb-outline);
  background: var(--tb-surface-container-high);
  color: var(--tb-on-surface);
}
.tb-alert--warn::before { content: "\25B2 "; color: var(--tb-on-surface-variant); }

/* ── Status pills — the four verification states, text+glyph+hue (A11Y-01) ─ */
/* Never place on -high/-highest (tokens.md §4.1 — unknown-red drops to 3.60:1
   composited there); pills in this app only ever sit in table rows / detail
   panels on `surface` or `-container`, which is where the ratios were measured. */
.tb-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--tb-mono);
  font-size: 12px;
  font-weight: 650;
  padding: 3px 10px 3px 8px;
  border-radius: var(--tb-r-chip);
  line-height: 1.5;
  white-space: nowrap;
}
.tb-pill__glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px; height: 14px;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  font-size: 9px;
  font-weight: 800;
  line-height: 1;
}
.tb-pill--matched { color: var(--tb-state-matched); background: var(--tb-state-matched-fill); border: 1px solid var(--tb-state-matched-line); }
.tb-pill--short   { color: var(--tb-state-short);   background: var(--tb-state-short-fill);   border: 1px solid var(--tb-state-short-line); }
.tb-pill--over    { color: var(--tb-state-over);    background: var(--tb-state-over-fill);    border: 1px solid var(--tb-state-over-line); }
.tb-pill--unknown { color: var(--tb-state-unknown); background: var(--tb-state-unknown-fill); border: 1px solid var(--tb-state-unknown-line); }
/* Session/order lifecycle "Pending" is not one of the four reconciliation states —
   it gets a neutral dashed treatment (same idea as marketing's honest-status
   pills) rather than borrowing a state hue for a different meaning. */
.tb-pill--pending {
  color: var(--tb-on-surface-variant);
  background: transparent;
  border: 1px dashed var(--tb-outline);
}

/* ── Generic badges (write-back outcome, connection Active/Inactive — outcomes
     that are NOT one of the four verification states, so they never borrow a
     state hue; success is neutral+check, failure is outlined danger only) ── */
.tb-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: var(--tb-mono);
  font-size: 12px;
  padding: 3px 9px;
  border-radius: var(--tb-r-chip);
  border: 1px solid var(--tb-outline);
  color: var(--tb-on-surface-variant);
  background: transparent;
}
.tb-badge--ok { color: var(--tb-on-surface); border-color: var(--tb-outline); }
.tb-badge--ok::before { content: "\2713"; }
.tb-badge--fail { color: var(--tb-danger); border-color: var(--tb-danger); background: transparent; }
.tb-badge--fail::before { content: "!"; font-weight: 800; }
.tb-error-inline { font-size: 12.5px; color: var(--tb-on-surface-faint); margin-top: 3px; }

/* WEB-22: Test Connection button + its untested/success/failure badge */
.tb-test-connection { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* ── Secret / credential fields (WEB-07) ─────────────────────────────── */
.tb-secret-field input, input.tb-secret {
  font-family: var(--tb-mono);
  letter-spacing: .04em;
  background: var(--tb-surface-container-highest);
}
.tb-secret-label {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.tb-secret-label::after {
  content: "SECRET";
  font-family: var(--tb-mono);
  font-size: 9.5px;
  letter-spacing: .1em;
  color: var(--tb-on-surface-faint);
  border: 1px solid var(--tb-outline-variant);
  border-radius: var(--tb-r-chip);
  padding: 1px 5px;
}
.tb-secret-hint {
  font-family: var(--tb-mono);
  font-size: 12px;
  color: var(--tb-on-surface-faint);
}

/* ── Tables ───────────────────────────────────────────────────────────── */
.table {
  color: var(--tb-on-surface);
}
.table thead th {
  background: var(--tb-surface-container-lowest);
  color: var(--tb-on-surface-variant);
  border-bottom: 1px solid var(--tb-outline-variant);
  border-top: none;
  font-family: var(--tb-mono);
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  font-weight: 600;
  white-space: nowrap;
}
.table td, .table th {
  border-color: var(--tb-outline-variant);
  vertical-align: middle;
}
.table tbody tr {
  background: var(--tb-surface);
}
.table tbody tr:nth-of-type(even) {
  background: var(--tb-surface-container-low);
}
.table-hover tbody tr:hover, .table tbody tr:hover {
  background: var(--tb-hover);
}
.table-sm td, .table-sm th { padding: .4rem .5rem; }

/* ── Forms ────────────────────────────────────────────────────────────── */
label {
  color: var(--tb-on-surface-variant);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: .01em;
  margin-bottom: 4px;
}
.form-control {
  background: var(--tb-surface-container-highest);
  border: 1px solid var(--tb-outline);
  color: var(--tb-on-surface);
}
.form-control::placeholder { color: var(--tb-on-surface-faint); }
.form-control:focus {
  background: var(--tb-surface-container-highest);
  border-color: var(--tb-on-surface-variant);
  color: var(--tb-on-surface);
}
.form-control:disabled, .form-control[readonly] {
  background: var(--tb-surface-container);
  color: var(--tb-on-surface-faint);
}
/* ── Native <select>s ─────────────────────────────────────────────────
   Left on `appearance: auto`, Windows Chromium keeps painting the native
   menulist widget under the themed background — and with no `color-scheme`
   declared it paints it *light*, so it leaks out as white slivers at the
   rounded corners and a light caret gutter (the "double box" on
   AssetAddEdit's Location/Bundle), and the popup panel stays light too.
   Fix: drop the native widget entirely and draw our own caret from tokens —
   two gradient halves forming a chevron, because a hardcoded-color SVG
   data-URI couldn't follow the WEB-68 toggle, while token-fed gradients flip
   for free. `color-scheme` (below, on bare `select`) hands the UA-owned
   popup panel the right scheme, keyed off the same `[data-theme]` attribute
   the toggle flips (absent = dark, today's default). */
select { color-scheme: dark; }
[data-theme="light"] select { color-scheme: light; }
select.form-control,
.custom-select,
.dataTables_wrapper .dataTables_length select {
  appearance: none;
  -webkit-appearance: none;
  background-color: var(--tb-surface-container-highest);
  border: 1px solid var(--tb-outline);
  color: var(--tb-on-surface);
  border-radius: var(--tb-r-control);
  background-image:
    linear-gradient(45deg, transparent 50%, var(--tb-on-surface-variant) 50%),
    linear-gradient(135deg, var(--tb-on-surface-variant) 50%, transparent 50%);
  background-position: calc(100% - 17px) 50%, calc(100% - 12px) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 28px;
}
/* .form-control:focus / :disabled above set `background` as a *shorthand*,
   which would silently erase the caret layers — re-assert them at higher
   specificity. Disabled swaps the caret to the disabled ink. */
select.form-control:focus,
.custom-select:focus {
  background-image:
    linear-gradient(45deg, transparent 50%, var(--tb-on-surface-variant) 50%),
    linear-gradient(135deg, var(--tb-on-surface-variant) 50%, transparent 50%);
}
select.form-control:disabled,
.custom-select:disabled {
  background-image:
    linear-gradient(45deg, transparent 50%, var(--tb-disabled) 50%),
    linear-gradient(135deg, var(--tb-disabled) 50%, transparent 50%);
}
/* Firefox paints <option>s from author styles, not color-scheme; Chromium
   respects these on Windows too. Tokens flip with the theme. */
select.form-control option, select.form-control optgroup,
.custom-select option, .custom-select optgroup,
.dataTables_wrapper .dataTables_length select option {
  background-color: var(--tb-surface-container-high);
  color: var(--tb-on-surface);
}
/* On the dialog/input surface (-highest), tertiary text is illegal (tokens.md §2.1) —
   use -variant for any muted text painted inside a `.form-control`-adjacent well. */
.form-control ~ small.text-muted, .form-group small.text-muted {
  color: var(--tb-on-surface-variant);
}
.tb-required::after {
  content: " *";
  color: var(--tb-state-short);
}
.is-invalid, .form-control.is-invalid {
  border-color: var(--tb-danger);
}
.invalid-feedback {
  color: var(--tb-danger);
  font-size: 12.5px;
}
input[type="checkbox"], input[type="radio"] { accent-color: var(--tb-on-surface); }

/* ── DataTables (search box, pagination, sort carets, empty state) ──────
   Scoped to .dataTables_wrapper throughout — this is DataTables' own themeable
   surface, generated at runtime, not a sitewide Bootstrap fight. */
/* DataTables' Bootstrap dom wraps its controls in .row (-15px side margins). Inside
   .table-responsive (overflow-x: auto) that overflows the container by exactly 15px,
   so every list page grew a useless near-full-thumb horizontal scrollbar. Zero the
   row margins and the matching col padding; a genuinely wide table still scrolls. */
.table-responsive .dataTables_wrapper > .row {
  margin-left: 0;
  margin-right: 0;
}
.table-responsive .dataTables_wrapper > .row > [class*='col-'] {
  padding-left: 0;
  padding-right: 0;
}
/* The length <select> is themed by the native-select block in Forms above —
   don't repaint `background` (shorthand) here or it erases the caret layers. */
.dataTables_wrapper .dataTables_filter input {
  background: var(--tb-surface-container-highest);
  border: 1px solid var(--tb-outline);
  color: var(--tb-on-surface);
  border-radius: var(--tb-r-control);
}
.dataTables_wrapper .dataTables_filter label,
.dataTables_wrapper .dataTables_length label,
.dataTables_wrapper .dataTables_info {
  color: var(--tb-on-surface-variant);
  font-size: 13px;
}
table.dataTable thead th {
  position: relative;
}
table.dataTable thead .sorting:after,
table.dataTable thead .sorting:before,
table.dataTable thead .sorting_asc:after,
table.dataTable thead .sorting_asc:before,
table.dataTable thead .sorting_desc:after,
table.dataTable thead .sorting_desc:before {
  color: var(--tb-on-surface-faint);
  opacity: .9;
}
table.dataTable tbody td.dataTables_empty,
table.dataTable thead th.dataTables_empty {
  color: var(--tb-on-surface-faint);
  padding: var(--tb-s4);
  text-align: center;
  font-family: var(--tb-mono);
  font-size: 13px;
}
/* WEB-25: the shared empty-state block (Assets/js/tagbadger-datatables.js) renders
   inside DataTables' empty cell — undo the cell's mono/faint styling so the block
   reads as designed (.tb-state-block already carries its own type + color). */
table.dataTable tbody td.dataTables_empty .tb-state-block,
table.dataTable thead th.dataTables_empty .tb-state-block {
  font-family: var(--tb-sans);
  font-size: 14px;
  color: var(--tb-on-surface-variant);
}
.dataTables_wrapper .dataTables_paginate .paginate_button,
.dataTables_wrapper .page-item .page-link {
  color: var(--tb-on-surface-variant);
  background: transparent;
  border-color: var(--tb-outline-variant);
}
.dataTables_wrapper .page-item.active .page-link,
.dataTables_wrapper .dataTables_paginate .paginate_button.current {
  background: var(--tb-primary-quiet);
  border-color: var(--tb-primary-line);
  color: var(--tb-on-surface);
}
.dataTables_wrapper .page-item.disabled .page-link,
.dataTables_wrapper .dataTables_paginate .paginate_button.disabled {
  color: var(--tb-on-surface-faint);
  opacity: .5;
}
.dataTables_wrapper .dataTables_processing {
  background: var(--tb-surface-container);
  color: var(--tb-on-surface-variant);
  border: 1px solid var(--tb-outline-variant);
}

/* ── Selectize (dark theming — brief §3) ────────────────────────────────
   The pages that use Selectize (AssetAddEdit, ReaderAddEdit,
   TagRangeReservationAddEdit) link selectize.css from their own head
   placeholder, which renders AFTER this file's <link> — so Selectize's light
   skin (`.selectize-input { background:#fff }`) out-cascaded the
   equal-specificity theming that used to live here and the fields rendered
   white-on-dark. Every rule below carries a `body ` prefix purely to win that
   fight on specificity instead of load order — don't strip it.
   The double-box artifact on those pages had a second cause: Selectize copies
   the original select's `form-control` class onto its wrapper div, so
   Bootstrap + the theme painted the wrapper as a *second* field (dark, rounded)
   around the white `.selectize-input`. The first rule collapses the wrapper to
   nothing and lets `.selectize-input` be the one field. (selectize.bootstrap4.css
   ships the same collapse, but it 404'd on deploys until it was added to the
   csproj — and the theme must not depend on it anyway.) */
body .form-control.selectize-control {
  padding: 0;
  height: auto;
  border: 0;
  background: transparent;
  box-shadow: none;
}
body .selectize-input,
body .selectize-input.full,
body .selectize-input.dropdown-active,
body .selectize-control.single .selectize-input.input-active {
  background: var(--tb-surface-container-highest);
  color: var(--tb-on-surface);
  border: 1px solid var(--tb-outline);
  border-radius: var(--tb-r-control);
  box-shadow: none;
  /* .form-control metrics, so the field matches its input neighbours whether
     the bootstrap4 skin loaded or only selectize.css's default (8px) one */
  padding: .375rem .75rem;
  min-height: calc(1.5em + .75rem + 2px);
}
body .selectize-control .selectize-input.focus {
  border-color: var(--tb-on-surface-variant);
  box-shadow: none;
}
body .selectize-control .selectize-input.disabled {
  background-color: var(--tb-surface-container);
  color: var(--tb-on-surface-faint);
}
body .selectize-input > input,
body .selectize-input input { color: var(--tb-on-surface); }
body .selectize-input > input::placeholder { color: var(--tb-on-surface-faint); }
body .selectize-input.dropdown-active::before { background: var(--tb-outline-variant); }
/* caret: Selectize draws a hardcoded dark-gray border triangle — retint */
body .selectize-control.single .selectize-input:after {
  border-color: var(--tb-on-surface-variant) transparent transparent transparent;
}
body .selectize-control.single .selectize-input.dropdown-active:after {
  border-color: transparent transparent var(--tb-on-surface-variant) transparent;
}
body .selectize-control.multi .selectize-input > div {
  background: var(--tb-surface-container-high);
  color: var(--tb-on-surface);
  border: 1px solid var(--tb-outline-variant);
}
body .selectize-control.multi .selectize-input > div.active {
  background: var(--tb-selected);
  color: var(--tb-on-surface);
  border-color: var(--tb-outline);
}
body .selectize-dropdown,
body .selectize-dropdown.form-control {
  background: var(--tb-surface-container-highest);
  color: var(--tb-on-surface);
  border: 1px solid var(--tb-outline);
  border-radius: var(--tb-r-control);
  box-shadow: var(--tb-e2);
}
body .selectize-dropdown-content .option { color: var(--tb-on-surface); }
body .selectize-dropdown .optgroup-header {
  color: var(--tb-on-surface-variant);
  background: var(--tb-surface-container-highest);
}
body .selectize-dropdown .active,
body .selectize-dropdown .active.create,
body .selectize-dropdown .active:not(.selected) {
  background: var(--tb-hover);
  color: var(--tb-on-surface);
}
body .selectize-dropdown .selected {
  background: var(--tb-selected);
  color: var(--tb-on-surface);
}
body .selectize-dropdown [data-selectable] .highlight { background: var(--tb-primary-quiet); }

/* ── SimpleMDE / EasyMDE (dark theming — orphaned asset today, themed anyway
     so it doesn't render light-on-dark the day it's wired to a page) ────── */
.CodeMirror {
  background: var(--tb-surface-container-highest);
  color: var(--tb-on-surface);
  border: 1px solid var(--tb-outline);
}
.CodeMirror-cursor { border-left-color: var(--tb-on-surface); }
.editor-toolbar {
  background: var(--tb-surface-container-high);
  border: 1px solid var(--tb-outline);
  border-bottom: none;
}
.editor-toolbar a { color: var(--tb-on-surface-variant) !important; }
.editor-toolbar a:hover, .editor-toolbar a.active {
  background: var(--tb-hover);
  border-color: var(--tb-outline);
}
.editor-toolbar i.separator { border-color: var(--tb-outline-variant); }
.editor-preview, .editor-preview-side {
  background: var(--tb-surface-container);
  color: var(--tb-on-surface);
}
.CodeMirror-selected { background: var(--tb-selected); }

/* ── Empty / loading / error / permission-denied states (WEB-11) ────────
   Applied to a Panel around a Repeater/table when there is nothing to show,
   an API call failed, a request is in flight, or the user lacks access. */
.tb-state-block {
  border: 1px dashed var(--tb-outline-variant);
  border-radius: var(--tb-r-card);
  padding: var(--tb-s6) var(--tb-s4);
  text-align: center;
  color: var(--tb-on-surface-variant);
}
.tb-state-block .tb-state-icon {
  font-family: var(--tb-mono);
  font-size: 22px;
  color: var(--tb-on-surface-faint);
  margin-bottom: var(--tb-s2);
}
.tb-state-block--error {
  border-style: solid;
  border-color: var(--tb-state-unknown-line);
  background: var(--tb-state-unknown-fill);
  color: var(--tb-on-surface);
}
.tb-state-block--loading .tb-state-icon {
  animation: tb-pulse 1.1s var(--tb-ease) infinite;
}
@keyframes tb-pulse { 0%,100% { opacity: .35; } 50% { opacity: 1; } }
.tb-state-block--denied {
  border-color: var(--tb-outline);
  background: var(--tb-surface-container);
}

/* ── Session detail / rollup components (WEB-27, mockup section 03) ─────
   The 4-up state strip, order-identity detail header, per-line progress bar,
   and EPC/serial disclosure. Green appears here only as text/glyph accents
   (kicker, disclosure caret) — never a second filled action (WEB-26 rule). */

/* 4-up rollup strip — counts come from the server's SessionRollup (API-70) */
.tb-states {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--tb-outline-variant);
  border: 1px solid var(--tb-outline-variant);
  border-radius: var(--tb-r-card);
  overflow: hidden;
}
.tb-state {
  background: var(--tb-surface-container-low);
  padding: 13px 8px;
  text-align: center;
}
.tb-state__n {
  font-family: var(--tb-mono);
  font-size: 28px;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.tb-state__l {
  font-family: var(--tb-mono);
  font-size: 10.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--tb-on-surface-variant);
  margin-top: 7px;
}
.tb-state--matched .tb-state__n { color: var(--tb-state-matched); }
.tb-state--short   .tb-state__n { color: var(--tb-state-short); }
.tb-state--over    .tb-state__n { color: var(--tb-state-over); }
.tb-state--unknown .tb-state__n { color: var(--tb-state-unknown); }
/* Needs-attention underline: applied server-side when a short/unknown count > 0 */
.tb-state--attn.tb-state--short   { box-shadow: inset 0 -2px 0 var(--tb-state-short); }
.tb-state--attn.tb-state--unknown { box-shadow: inset 0 -2px 0 var(--tb-state-unknown); }

/* Order-identity detail header */
.tb-dethead {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 18px;
  flex-wrap: wrap;
}
.tb-dethead__kicker {
  font-family: var(--tb-mono);
  font-size: 11px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--tb-primary);
}
.tb-dethead__title { font-weight: 750; font-size: 19px; margin-top: 2px; }
.tb-dethead__title small { color: var(--tb-on-surface-variant); font-weight: 500; font-size: 14px; }
.tb-dethead__sub {
  font-family: var(--tb-mono);
  font-size: 11.5px;
  color: var(--tb-on-surface-variant);
  margin-top: 4px;
}
.tb-dethead .tb-pill { font-size: 13px; padding: 7px 14px; }

/* Handheld-handoff QR (WEB-59). Fixed white/near-black, not the --tb-surface/--tb-on-surface
   tokens -- deliberately the one card in this sheet that doesn't take the page theme: the vendored
   generator (Assets/qrcode/qrcode.js) always draws black modules on a white background, and
   the TC22R's imager needs that contrast regardless of whether the admin is on dark or light.
   A themed card here would put a light quiet zone on a dark page and a dark one on a light
   page -- backwards half the time. */
.tb-qr-card {
  display: inline-flex;
  align-items: center;
  gap: var(--tb-s4);
  background: #FFFFFF;
  border: 1px solid #D8DEE5;
  border-radius: var(--tb-r-card);
  padding: var(--tb-s3) var(--tb-s4);
  box-shadow: var(--tb-e1);
  max-width: 100%;
}
.tb-qr-card__img { display: block; line-height: 0; flex: 0 0 auto; }
.tb-qr-card__img img { display: block; max-width: 100%; height: auto; }
.tb-qr-card__cap { color: #12181E; font-size: 13px; max-width: 220px; }
.tb-qr-card__cap b { display: block; color: #12181E; font-weight: 700; margin-bottom: 2px; }

/* Alert with an inline action (e.g. write-back failure + retry control) */
.tb-alert--split {
  display: flex;
  gap: 12px;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
}

/* Phase panel */
.tb-phase {
  background: var(--tb-surface-container-low);
  border: 1px solid var(--tb-outline-variant);
  border-radius: var(--tb-r-card);
  padding: var(--tb-s4);
  margin-top: var(--tb-s4);
}
.tb-phase__head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.tb-phase__label {
  font-family: var(--tb-mono);
  font-size: 11px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--tb-primary);
}
.tb-phase__when { font-weight: 650; font-size: 14.5px; }
.tb-phase__when--pending { color: var(--tb-on-surface-variant); font-weight: 500; }
/* WEB-38: confirm-short action row under a phase's results. Ruled off rather than spaced apart, so a
   button that writes to the rental platform doesn't read as another row of the table above it. */
.tb-phase__foot {
  margin-top: var(--tb-s4);
  padding-top: var(--tb-s3);
  border-top: 1px solid var(--tb-outline-variant);
}
.tb-phase__foot .tb-error-inline { margin-top: var(--tb-s2); }
.tb-phase .table { margin-top: var(--tb-s3); margin-bottom: 0; }
.tb-line-sub {
  font-family: var(--tb-mono);
  font-size: 11px;
  color: var(--tb-on-surface-faint);
}

/* Slim per-line progress bar ("1 / 3") */
.tb-bar-row { display: flex; align-items: center; gap: 10px; }
.tb-bar-row .tb-bar-count {
  font-family: var(--tb-mono);
  font-size: 12px;
  color: var(--tb-on-surface-variant);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.tb-progress {
  flex: 1;
  height: 5px;
  border-radius: 3px;
  background: var(--tb-surface-container-high);
  overflow: hidden;
  min-width: 70px;
  max-width: 150px;
}
.tb-progress__fill { display: block; height: 100%; border-radius: 3px; }
.tb-progress__fill--matched { background: var(--tb-state-matched); }
.tb-progress__fill--short   { background: var(--tb-state-short); }
.tb-progress__fill--over    { background: var(--tb-state-over); }
.tb-progress__fill--unknown { background: var(--tb-state-unknown); }

/* Disclosure — raw EPC/serial lists collapsed behind a <details> */
details.tb-disclosure { margin-top: 8px; }
details.tb-disclosure summary {
  font-family: var(--tb-mono);
  font-size: 11.5px;
  color: var(--tb-on-surface-variant);
  cursor: pointer;
  list-style: none;
}
details.tb-disclosure summary::-webkit-details-marker { display: none; }
details.tb-disclosure summary::before { content: "\25B8  "; color: var(--tb-primary); }
details.tb-disclosure[open] summary::before { content: "\25BE  "; }
details.tb-disclosure .tb-disclosure-list {
  font-family: var(--tb-mono);
  font-size: 11.5px;
  color: var(--tb-on-surface-variant);
  background: var(--tb-surface-container-high);
  border: 1px solid var(--tb-outline-variant);
  border-radius: 8px;
  padding: 10px 12px;
  margin-top: 8px;
  line-height: 1.8;
  overflow-x: auto;
}

/* WEB-58: one-shot "just arrived" flash for a scan row the live-mirror script appended. Neutral tint,
   not one of the four reconciliation hues -- tokens.md §4.1 reserves --tb-state-* for verification
   states only, and "this row is new" isn't one. --tb-dur-sweep is the one duration token long enough to
   register as a lingering highlight rather than a blink; it's also a token, so prefers-reduced-motion's
   root override (near the top of this file) already zeroes it to a no-op end state with no separate
   override needed here. */
@keyframes tb-row-fresh-fade {
  0% { background-color: var(--tb-primary-quiet); }
  100% { background-color: transparent; }
}
.tb-row-fresh {
  animation: tb-row-fresh-fade var(--tb-dur-sweep) var(--tb-ease) 1 forwards;
}

/* ── Ops dashboard (WEB-28, mockup section 02) ───────────────────────────
   Reuses .tb-states/.tb-state (defined above) for the 4-up strip; adds a
   generic card wrapper for the Needs-attention/Recent-sessions/Connections
   panels and a connection health row. */
.tb-dash-panel {
  background: var(--tb-surface-container-low);
  border: 1px solid var(--tb-outline-variant);
  border-radius: var(--tb-r-card);
  padding: var(--tb-s4);
}
.tb-dash-panel h5 { margin-bottom: var(--tb-s3); }
/* WEB-58: Recent-sessions "still scanning" indicator. Reuses --tb-scan-live (already the dedicated
   "reader is live" hue, defined alongside --tb-scan-idle/--tb-scan-down) and the existing tb-pulse
   keyframe (see the loading-state block above) rather than inventing either a new color or a new
   animation for the same idea. Hidden by default; RecentSessionsPoll's script only ever removes
   --on (a session leaving Pending is one-way within a page view), never adds it. */
.tb-live-dot {
  display: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--tb-scan-live);
  margin-right: var(--tb-s2);
  vertical-align: middle;
}
.tb-live-dot--on {
  display: inline-block;
  animation: tb-pulse 1.1s var(--tb-ease) infinite;
}
.tb-conn-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  border-top: 1px solid var(--tb-outline-variant);
}
.tb-conn-row:first-of-type { border-top: none; padding-top: 0; }
.tb-conn-row__meta {
  font-family: var(--tb-mono);
  font-size: 11.5px;
  color: var(--tb-on-surface-variant);
  margin-top: 2px;
}
/* WEB-54: the connection row's right-hand stack — Active/Inactive over API-89's credential verdict.
   Right-aligned column rather than an inline pair so the "Checked <when>" sub-line sits under its own
   badge instead of pushing the Active badge around. */
.tb-conn-row__status {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
  text-align: right;
  flex-shrink: 0;
}

/* WEB-54: a Needs-attention row nobody is waiting on (API-88's Handled). De-emphasised, never hidden —
   the sign-off has to stay visible where it was made. Muted foreground + one surface step, not opacity:
   the row still has to be readable, and the badges inside it keep their own contrast. */
.tb-attn-row--handled > td { background: var(--tb-surface-container); color: var(--tb-on-surface-variant); }
.tb-attn-row--handled > td b { font-weight: 500; }
.tb-attn-row--handled .tb-pill { opacity: 0.8; }
/* Sits between the state pill and the "Signed off by …" attribution line. */
.tb-attn-row__resolved { margin-top: 4px; }

/* ── Misc chrome ──────────────────────────────────────────────────────── */
.modal-content {
  background: var(--tb-surface-container-highest);
  color: var(--tb-on-surface);
  border: 1px solid var(--tb-outline);
}
.modal-header, .modal-footer { border-color: var(--tb-outline-variant); }
.close { color: var(--tb-on-surface); text-shadow: none; }
hr { border-color: var(--tb-outline-variant); }

@media (prefers-reduced-motion: reduce) {
  .tb-state-block--loading .tb-state-icon { animation: none; }
  /* WEB-58: tb-pulse's 1.1s is hardcoded (matching the loading-state rule above, not a --tb-dur-*
     token), so unlike .tb-row-fresh this one needs its own override -- the dot still shows, it just
     stops pulsing. */
  .tb-live-dot--on { animation: none; }
}

/* ── Native file input (WEB-36) ───────────────────────────────────────────
   The one raw OS widget left in the app: AssetImport's Choose File button
   rendered in the browser default chrome, light-grey on the dark card. Styled
   via ::file-selector-button so the control stays a real <input type=file>
   (no label/JS shim) and keeps its native keyboard + drag-drop behavior. */
.tb-file {
  display: block;
  width: 100%;
  max-width: 480px;
  padding: 6px;
  color: var(--tb-on-surface);
  background: var(--tb-surface-container-high);
  border: 1px solid var(--tb-outline);
  border-radius: 6px;
  font-size: 14px;
}
.tb-file::file-selector-button {
  margin-right: 12px;
  padding: 6px 14px;
  color: var(--tb-on-surface);
  background: var(--tb-surface-container-highest);
  border: 1px solid var(--tb-outline);
  border-radius: 6px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 650;
  cursor: pointer;
  transition: background var(--tb-dur-micro) var(--tb-ease), border-color var(--tb-dur-micro) var(--tb-ease);
}
.tb-file::file-selector-button:hover {
  background: var(--tb-primary-quiet);
  border-color: var(--tb-primary);
}
.tb-file:focus-visible { outline: 2px solid var(--tb-focus); outline-offset: 2px; }

/* ── Format-guidance block (WEB-36) ──────────────────────────────────────
   Reference material, not the primary task: deliberately quieter than the
   upload controls above it. */
.tb-guide-heading {
  font-family: var(--tb-mono);
  font-size: 11px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--tb-on-surface-variant);
  margin-bottom: var(--tb-s2);
}
.tb-guide-note {
  font-size: 13px;
  color: var(--tb-on-surface-variant);
  margin-bottom: var(--tb-s3);
}
.tb-guide-table { font-size: 13px; }
.tb-guide-table th {
  font-family: var(--tb-mono);
  font-size: 10.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--tb-on-surface-variant);
  white-space: nowrap;
}
.tb-guide-table td { vertical-align: top; }
.tb-guide-table code {
  font-family: var(--tb-mono);
  font-size: 12px;
  color: var(--tb-on-surface);
  background: var(--tb-surface-container-high);
  border: 1px solid var(--tb-outline-variant);
  border-radius: 4px;
  padding: 1px 5px;
  white-space: nowrap;
}

/* Scope caption above a tb-states strip (WEB-37i) -- the dashboard's strip is
   today-only while the panel under it isn't, and an unlabelled row of zeros next
   to "5 need attention" read as a contradiction. */
.tb-states-scope {
  font-family: var(--tb-mono);
  font-size: 10.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--tb-on-surface-variant);
  margin-bottom: var(--tb-s2);
}

/* Opaque-identifier chip (WEB-48) -- a platform GUID or AssetID reduced to its
   first 8 characters with the full value on the element title. WEB-31 removed
   raw GUIDs from every surface that *names* something; the session-detail audit
   tables still have to reference one, so they reference it small. */
.tb-idchip {
  font-family: var(--tb-mono);
  font-size: 11px;
  color: var(--tb-on-surface-variant);
  background: var(--tb-surface-container-high);
  border: 1px solid var(--tb-outline-variant);
  border-radius: 4px;
  padding: 1px 5px;
  white-space: nowrap;
  cursor: help;
}

/* ── Ctrl-K command palette (WEB-60) ──────────────────────────────────── */
/* A backdrop + role="dialog" pair rather than Bootstrap's .modal -- see TagFinder.Master's WEB-60
   comment for why. z-index clears the sidebar's 1030 and the skip-link's 1080, the two highest values
   already in this file -- nothing in the chrome may ever sit on top of a modal dialog. */
.tb-search-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1090;
  background: rgba(0, 0, 0, .55);
}
.tb-search-overlay {
  position: fixed;
  z-index: 1091;
  top: 12vh;
  left: 50%;
  transform: translateX(-50%);
  width: min(600px, 92vw);
  max-height: 72vh;
  display: flex;
  background: var(--tb-surface-container-low);
  border: 1px solid var(--tb-outline);
  border-radius: var(--tb-r-modal);
  box-shadow: var(--tb-e3);
  overflow: hidden;
}
.tb-search-box { display: flex; flex-direction: column; min-height: 0; width: 100%; }

.tb-search-input-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--tb-outline-variant);
  color: var(--tb-on-surface-faint);
}
/* No box-shadow to cancel on plain focus (this isn't Bootstrap's .form-control) -- the ring on
   keyboard focus comes entirely from the shared input:focus-visible rule near the top of this file,
   deliberately not overridden here. */
.tb-search-input {
  flex: 1 1 auto;
  min-width: 0;
  border: 0;
  background: transparent;
  color: var(--tb-on-surface);
  font-size: 16px;
  font-family: var(--tb-sans);
}
.tb-search-input::placeholder { color: var(--tb-on-surface-faint); }

.tb-search-close {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: var(--tb-r-control);
  background: transparent;
  color: var(--tb-on-surface-faint);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.tb-search-close:hover { background: var(--tb-hover); color: var(--tb-on-surface); }

/* Doubles as the aria-live=polite announcement and the visible result count -- see
   TagFinder.Master's comment on #tb-search-status for why this is one element, not two. */
.tb-search-status {
  padding: 8px 16px;
  font-size: 12.5px;
  color: var(--tb-on-surface-faint);
  border-bottom: 1px solid var(--tb-outline-variant);
}

.tb-search-results {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 6px 0;
}

.tb-search-group + .tb-search-group {
  border-top: 1px solid var(--tb-outline-variant);
  margin-top: 6px;
  padding-top: 6px;
}
.tb-search-group__label {
  font-family: var(--tb-mono);
  font-size: 9.5px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--tb-on-surface-faint);
  padding: 4px 16px;
}

/* role="option" rows -- never real Tab stops (see the palette script's aria-activedescendant
   pattern), so the "selected" look comes from aria-selected, not a focus ring. */
.tb-search-hit {
  padding: 8px 16px;
  margin: 0 6px;
  border-radius: var(--tb-r-control);
  cursor: pointer;
}
.tb-search-hit__title { font-weight: 650; font-size: 14px; color: var(--tb-on-surface); }
.tb-search-hit__subtitle { font-size: 12.5px; color: var(--tb-on-surface-variant); margin-top: 1px; }
.tb-search-hit[aria-selected="true"] { background: var(--tb-selected); }

.tb-search-more, .tb-search-empty {
  padding: 8px 16px;
  font-size: 12.5px;
  color: var(--tb-on-surface-faint);
}

@media (max-width: 575.98px) {
  .tb-search-overlay {
    top: 0;
    left: 0;
    transform: none;
    width: 100%;
    max-width: 100%;
    height: 100%;
    max-height: 100%;
    border-radius: 0;
    border-width: 0;
  }
}
