/* Toast Progress Animation - keyframes used by inline styles with dynamic duration */
@keyframes toast-progress {
  from {
    width: 100%;
  }
  to {
    width: 0%;
  }
}

/* ========================================================================
 * Page Header — shared layout for all main pages
 * Used together with components/page_header.html
 * ====================================================================== */
.page-header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.25rem;
  /* THE HEADER PAINTS OVER WHAT FOLLOWS IT, and this is why.
   *
   * `.v-animate-fade-up` animates a transform and runs `forwards`, so every
   * card wearing it keeps a stacking context for good. The "?" popover's
   * z-50 is then trapped inside the header's own context, and the KPI row
   * below — same class, later in the DOM, z-index auto — painted straight
   * over the open panel. It read as a popover that opens half-clipped.
   *
   * 20, which is a layer of its own between the sticky tab strip (10) and the
   * top bar (30). It took two wrong answers to get here and both are worth
   * knowing: at 20 with the bar still on 10 the title and its buttons painted
   * over the search field; at 1 the tab strip - sticky, opaque, z-10 - ate the
   * first line of the panel. Neither was fixable without giving the top bar a
   * layer of its own, which is what topbar.html now does. */
  position: relative;
  z-index: 20;
}
.page-header-row > .page-header {
  min-width: 0;
  flex: 1 1 auto;
}
.page-header-row .page-header__actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
}
@media (max-width: 640px) {
  .page-header-row {
    flex-wrap: wrap;
    align-items: flex-start;
  }
  .page-header-row .page-header__actions {
    flex-wrap: wrap;
  }
}

/* ========================================================================
 * Page top spacing — enforce identical Y-position for every page title.
 * - Pages should NOT use -mt-* / -mx-* negative margins on their first
 *   child. Use this class instead if you need full-width subnavs.
 * - The wrapper inside #content-area carries pt-3 sm:pt-3 (12px);
 *   this rule is the one that wins, so change both together.
 * - 12px at every width. The wide breakpoint used to add 4px on top of a
 *   70px bar, so the first thing on the page started a fifth of the way down
 *   the viewport. The bar is 48px now and the gap does not need to grow with
 *   the screen: what grows with the screen is how many rows fit.
 * ====================================================================== */
.content-area > div { /* the px-4 pt-3 wrapper */
  padding-top: 0.75rem !important;
}
/* The document scrolls under a fixed 48px bar. A boosted navigation ends with
   htmx's scrollIntoView on the swapped node (after our scroll reset), which
   parked the page title under the bar. Reserve the bar's height. */
#content-area,
.content-area > div {
  scroll-margin-top: 3rem;
}
@media (min-width: 640px) {
  .content-area > div {
    padding-top: 0.75rem !important;
  }
}

/* GRC page rhythm (14-sep-2026, Nerea: "en GRC salen los títulos demasiado
   pegados al nav de arriba"). base.html stamps data-module on the wrapper.
   The shared 12px left the title 12-31px under the bar depending on each
   page's action buttons and its own mt-4 patches, and the tab rule 12px above
   the first card. One rhythm for the whole module:
     bar -> header row 20px, header row min 36px (the md button, so the h1
     sits at the same Y with or without actions), row -> tabs 12px,
     tab rule -> content 20px. Per-template mt-* on the header row is ignored. */
.content-area > div[data-module="grc"] {
  padding-top: 1.25rem !important;
}
.content-area > div[data-module="grc"] .page-header-row {
  margin-top: 0 !important;
  min-height: 2.25rem;
}
.content-area > div[data-module="grc"] .page-header-row--above-tabs {
  margin-bottom: 0.75rem;
}
.content-area > div[data-module="grc"] .page-tabs:not(.page-tabs--inline) {
  margin-bottom: 1.25rem;
}

/* Kill any first-child top margin so titles always start at the same Y */
.content-area > div > :first-child,
.content-area > div > [x-data]:first-child > :first-child {
  margin-top: 0 !important;
}
/* Defensive: don't allow first-child negative margins to break out */
.content-area > div > .-mt-4,
.content-area > div > .-mt-6,
.content-area > div > [x-data]:first-child > .-mt-4,
.content-area > div > [x-data]:first-child > .-mt-6 {
  margin-top: 0 !important;
}

/* Standard h1 sizing across all main pages — override Tailwind text-* to
   ensure visual consistency. Affects only top-level page titles, not h1s
   inside specific component contexts (which should use h2/h3 anyway). */
.content-area h1 {
  font-size: 1.25rem !important;   /* 20px — --vs-t-page */
  line-height: 1.3 !important;
  font-weight: 600 !important;     /* --vs-w-title; 700 is not in the scale */
}
/* Default color for h1 unless the page explicitly overrides it (e.g.,
   welcome hero on a dark gradient background uses text-white). */
.content-area h1:not([class*="text-white"]):not([class*="text-purple"]):not([class*="text-violet"]) {
  color: #111827;                   /* gray-900 */
}
/* Allow inline/contenteditable titles (vulnerability detail) to keep
   their hover/focus styles by leaving the box intact. */
.content-area h1[contenteditable] {
  outline: none;
}

/* When .page-tabs is the first element of the page wrapper, kill any
   accidental top margin from sibling animations / custom wrappers so the
   nav y-position is identical across all pages. */
.content-area .page-tabs:first-child,
.content-area > div > .page-tabs:first-child,
.content-area > div > div > .page-tabs:first-child,
.content-area > div > div:first-child > .page-tabs:first-child {
  margin-top: 0 !important;
}

/* ========================================================================
 * Back link — pill style, used at the top of every detail page.
 * Use: <a class="back-link"><i class="fa-solid fa-arrow-left"></i> Back</a>
 * ====================================================================== */
.back-link {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.375rem 0.75rem;
  margin-left: -0.75rem;       /* visually align text with title underneath */
  margin-bottom: 1.375rem;     /* breathing room before the title row */
  font-size: 0.75rem;          /* text-xs */
  font-weight: 500;
  color: #9ca3af;              /* gray-400 */
  border-radius: 0.5rem;
  transition: background-color 0.15s ease, color 0.15s ease, transform 0.15s ease;
  text-decoration: none;
}
.back-link i {
  font-size: 0.625rem;         /* 10px */
  transition: transform 0.15s ease;
}
.back-link:hover {
  background-color: color-mix(in srgb, var(--color-primary) 6%, white);
  color: var(--color-primary);
}
.back-link:hover i {
  transform: translateX(-2px);
}

/* ========================================================================
 * Page Tabs: the sub-nav. Text-first: a hairline rule, quiet labels, and one
 * 2px underline exactly as wide as the active label. Piloted on Audits as
 * `--minimal`, promoted to the base on 4 Sep 2026; the tinted-block and
 * `--flat` variants are gone.
 *
 * Three things that are not decoration:
 *  - Spacing lives in the strip's `gap`, not in each tab's horizontal
 *    padding, so the underline hugs the word instead of a block of air.
 *  - The strip is a scroll container (overflow-x: auto), which CLIPS
 *    anything outside its padding box. The accent is drawn INSIDE the tab
 *    (bottom: 0) and tabs carry no negative margin; the old `bottom: -2px`
 *    underline was invisible on every strip in the app for exactly this.
 *  - The background is the page's own colour, read from the page. A sticky
 *    strip in a colour that is nearly the canvas reads as a band with a
 *    line along its top.
 * ====================================================================== */
.page-tabs {
  display: flex;
  align-items: stretch;
  gap: 1.375rem;
  border-bottom: 1px solid #e9e7f0;
  margin-bottom: 0.75rem;
  padding-left: 2px;
  overflow-x: auto;
  scrollbar-width: none;
  /* Sticky to the top of the scroll container (<main>). Opaque on purpose:
     content scrolls UNDER it. z-10, under the page header (20) whose "?"
     panel opens over this strip, and under the top bar (30). */
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--page-bg, var(--color-background, #fbfaff));
}
.page-tabs::-webkit-scrollbar {
  display: none;
}

/* A strip that lives INSIDE a card, not at the top of a page. `.page-tabs` is
   sticky and paints the page background by construction — both are wrong on a
   card, where the strip would stick to the app's top bar and show the canvas
   colour over white. The tab itself (13px, underline the width of the word,
   var(--color-primary) when active) is untouched: an in-card audience switch
   and a page sub-nav are the same control. */
.page-tabs--inline {
  position: static;
  background: transparent;
  margin-bottom: 0;
}

/* The record tab bar in a white card (`components/tabs.html` card=True and
   `_audit_tabs.html`). On a phone it scrolled sideways and hid half the tabs;
   it wraps onto a second row instead. Desktop is untouched. */
@media (max-width: 639.98px) {
  .tabs-card {
    flex-wrap: wrap;
    justify-content: flex-start;
    overflow-x: visible;
    row-gap: 0.375rem;
    padding-left: 0.75rem;
    padding-right: 0.75rem;
  }
}
/* Fade the edge that still has tabs behind it. The scrollbar is hidden, so
   without this a strip wider than the viewport reads as "these are all the
   tabs". The classes are toggled by VioletTabStrip; a strip that fits gets
   neither and stays flat. */
.page-tabs--fade-start {
  mask-image: linear-gradient(to right, transparent 0, #000 2.5rem);
}
.page-tabs--fade-end {
  mask-image: linear-gradient(to left, transparent 0, #000 2.5rem);
}
.page-tabs--fade-start.page-tabs--fade-end {
  mask-image: linear-gradient(to right, transparent 0, #000 2.5rem,
                              #000 calc(100% - 2.5rem), transparent 100%);
}
.page-tabs__tab {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  padding: 0.4375rem 0 0.5rem;    /* no horizontal padding on purpose */
  font-size: 0.8125rem;           /* 13px */
  font-weight: 500;
  letter-spacing: -0.006em;
  color: #6b7280;
  white-space: nowrap;
  text-decoration: none;
  background: transparent;
  cursor: pointer;
  transition: color 0.16s ease;
}
.page-tabs__tab i {
  font-size: 0.75rem;             /* 12px, one size down and desaturated */
  color: #b7b3c6;
  transition: color 0.16s ease;
}
.page-tabs__tab::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  border-radius: 2px 2px 0 0;
  background: var(--color-primary);
  opacity: 0;
  transform: scaleX(0.55);
  transform-origin: center bottom;
  transition: opacity 0.18s ease,
              transform 0.22s cubic-bezier(0.34, 1.26, 0.64, 1);
}
.page-tabs__tab:hover {
  color: #111827;
}
.page-tabs__tab:hover i {
  color: #6b7280;
}
/* Hover states the intent without claiming the tab. */
.page-tabs__tab:hover::after {
  opacity: 0.22;
  transform: scaleX(1);
}
.page-tabs__tab--active {
  color: var(--color-primary);
  font-weight: 600;
}
.page-tabs__tab--active i {
  color: var(--color-primary);
}
.page-tabs__tab--active::after {
  opacity: 1;
  transform: scaleX(1);
}
.page-tabs__tab:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--color-primary) 45%, transparent);
  outline-offset: -3px;
  border-radius: 6px;
}
.page-tabs__badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 17px;
  height: 17px;
  padding: 0 5px;
  font-size: 10px;
  font-weight: 600;
  border-radius: 9999px;
  background: #f1f0f6;
  color: #6b7280;
}
.page-tabs__tab--active .page-tabs__badge {
  background: color-mix(in srgb, var(--color-primary) 12%, white);
  color: var(--color-primary);
}
@media (prefers-reduced-motion: reduce) {
  .page-tabs__tab,
  .page-tabs__tab::after {
    transition: none;
  }
  .page-tabs__tab::after {
    transform: scaleX(1);
  }
}
/* A header that sits ON a tab strip: the strip's hairline is its rule, so
   the header keeps only enough air for the underline to breathe. */
.page-header-row--above-tabs {
  margin-bottom: 0.375rem;
}

/* ========================================================================
 * PR2 — Visual system unification
 * Standardize typography, cards, and spacing across all main pages.
 * ====================================================================== */

/* ── Typography scale ─────────────────────────────────────────────────── */
/* h2: section / card titles */
.content-area h2 {
  font-size: 0.9375rem;       /* 15px */
  line-height: 1.35;
  font-weight: 600;
}
.content-area h2:not([class*="text-white"]):not([class*="text-purple"]):not([class*="text-violet"]):not([class*="text-red"]):not([class*="text-blue"]):not([class*="text-emerald"]):not([class*="text-amber"]):not([class*="text-indigo"]):not([class*="text-pink"]) {
  color: #111827;              /* gray-900 — only when no explicit color override */
}
/* h3: subsection titles */
.content-area h3 {
  font-size: 0.8125rem;       /* 13px */
  line-height: 1.4;
  font-weight: 600;
}
.content-area h3:not([class*="text-white"]):not([class*="text-purple"]):not([class*="text-violet"]):not([class*="text-red"]):not([class*="text-blue"]):not([class*="text-emerald"]):not([class*="text-amber"]):not([class*="text-indigo"]):not([class*="text-pink"]) {
  color: #111827;
}
/* h4: micro labels (UPPERCASE eyebrow) */
.content-area h4 {
  font-size: 0.6875rem;       /* 11px — UPPERCASE only, see --vs-t-eyebrow */
  line-height: 1.3;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.content-area h4:not([class*="text-white"]):not([class*="text-purple"]):not([class*="text-violet"]):not([class*="text-red"]):not([class*="text-blue"]):not([class*="text-emerald"]):not([class*="text-amber"]):not([class*="text-indigo"]):not([class*="text-pink"]) {
  color: #6b7280;              /* gray-500 */
}

/* ── Standard card ─────────────────────────────────────────────────────
   Use class="card" instead of repeating
   "bg-white rounded-xl border border-gray-200 shadow-sm p-5". */
.card {
  background: #ffffff;
  border: 1px solid #e5e7eb;
  border-radius: var(--vs-radius, 6px);
  padding: 1.25rem;           /* p-5 */
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

/* ── TWO RADII, and this is where they are enforced ────────────────────────
   Measured on /violet/audits/ before this rule: SIX radii on one screen —
   6px used 211 times, 4px 177, 8px 139, plus 12px, the pill and the circle.
   Nothing chose them; each template picked a Tailwind rounding class by feel
   and the product ended up with six ideas of what a corner is.

   Rather than hunt `rounded-*` through 600 templates, the six classes are
   collapsed here onto the token. `rounded-full` is deliberately NOT in the
   list: the pill and the circle are the second radius, and they are what a
   lozenge, a chip, an avatar and a badge are made of.

   A component that genuinely needs its own corner opts out with
   `.no-radius-reset`, the same escape hatch the shadow rule below uses. */
.content-area .rounded,
.content-area .rounded-md,
.content-area .rounded-lg,
.content-area .rounded-xl,
.content-area .rounded-2xl,
.content-area .rounded-3xl {
  border-radius: var(--vs-radius, 6px) !important;
}
.content-area .rounded.no-radius-reset,
.content-area .rounded-md.no-radius-reset,
.content-area .rounded-lg.no-radius-reset,
.content-area .rounded-xl.no-radius-reset,
.content-area .rounded-2xl.no-radius-reset,
.content-area .rounded-3xl.no-radius-reset {
  border-radius: revert !important;
}
.content-area .shadow-md,
.content-area .shadow-lg,
.content-area .shadow-xl {
  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.04) !important; /* shadow-sm */
}
/* Exclude modals, dropdowns, and elements that legitimately need bigger shadows */
.content-area .shadow-md.no-card-reset,
.content-area .shadow-lg.no-card-reset,
.content-area .shadow-xl.no-card-reset {
  box-shadow: revert !important;
}
/* A dropdown panel is not a card: it floats over the page and needs its lift.
   Nobody ever added `no-card-reset`, so every `absolute … shadow-lg` panel
   (Columns, Saved views, the other bar menus) lost its shadow above AND its
   edge to the card rule in violet-system.css, and sat on the table as a
   borderless white box. Positioned absolutely is what makes it a popover. */
.content-area .absolute.shadow-md,
.content-area .absolute.shadow-lg,
.content-area .absolute.shadow-xl {
  box-shadow: 0 10px 28px -8px rgba(15, 15, 28, 0.16), 0 2px 6px rgba(15, 15, 28, 0.06) !important;
}

/* ── Soft background colors normalization ────────────────────────────────
   Reduce the noise of bg-*-50 backgrounds for non-status decoration. We
   keep red/orange/amber/green/yellow which carry semantic meaning, but
   neutralize indigo/blue/purple/pink/violet/teal/cyan that are pure decoration. */
/* (Soft normalization is left manual — auto-stripping risks breaking
   semantic widgets. Use card class instead in new code.) */

/* (Removed a duplicate `.page-header-row { margin-bottom }` rule here — it was
   already fully defined at the top of this file. Single source of truth.) */

/* ── Buttons (preview — will be expanded in PR3) ──────────────────────
   Don't redefine here yet; PR3 will introduce .btn-primary/.btn-secondary/.btn-ghost. */

/* ========================================================================
 * PR3 — Unified button system
 * Replaces the ~7-8 ad-hoc button variants with 3 canonical types
 * (primary, secondary, ghost) + 2 semantic modifiers (danger, success).
 * One size: the sm/lg/icon/group modifiers were never used by a single
 * template and are gone; components/button.html owns sizing.
 *
 * Usage:
 *   <button class="btn btn-primary">Save</button>
 *   <button class="btn btn-secondary"><i class="fa-solid fa-plus"></i> New</button>
 *   <button class="btn btn-ghost">Cancel</button>
 *   <button class="btn btn-primary btn-danger">Delete</button>
 * ====================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.4375rem 0.75rem;
  font-size: 0.8125rem;        /* 13px */
  font-weight: 500;
  line-height: 1;
  border-radius: var(--vs-radius, 6px);
  border: 1px solid transparent;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.15s ease,
              border-color 0.15s ease,
              color 0.15s ease,
              box-shadow 0.15s ease,
              transform 0.05s ease;
  user-select: none;
}
.btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 25%, transparent);
}
.btn:disabled,
.btn.is-disabled {
  opacity: 0.55;
  cursor: not-allowed;
  pointer-events: none;
}
.btn:active:not(:disabled) {
  transform: translateY(0.5px);
}
.btn i {
  font-size: 0.75rem;          /* icon ~12px */
}

/* ── Primary (filled) ──────────────────────────────────────────────────
   Flat. No shadow at rest and none on hover: a brand-hued glow under the one
   filled button on the screen is decoration, and it ignores the tenant colour
   in a white-labelled install. The hover darkens the fill instead. */
.btn-primary {
  background-color: var(--color-primary);
  color: #ffffff;
  border-color: var(--color-primary);
}
.btn-primary:hover:not(:disabled) {
  background-color: color-mix(in srgb, var(--color-primary) 92%, black);
  border-color: color-mix(in srgb, var(--color-primary) 92%, black);
}

/* ── Secondary (outlined, neutral) ───────────────────────────────────── */
.btn-secondary {
  background-color: #ffffff;
  color: #374151;              /* gray-700 */
  border-color: #e5e7eb;        /* gray-200 */
}
.btn-secondary:hover:not(:disabled) {
  background-color: #f9fafb;    /* gray-50 */
  border-color: #d1d5db;        /* gray-300 */
  color: #111827;
}

/* ── Ghost (link-like) ───────────────────────────────────────────────── */
.btn-ghost {
  background-color: transparent;
  color: #6b7280;              /* gray-500 */
  border-color: transparent;
}
.btn-ghost:hover:not(:disabled) {
  background-color: color-mix(in srgb, var(--color-primary) 6%, white);
  color: var(--color-primary);
}

/* ── Semantic modifiers (combine with btn-primary or btn-secondary) ──
   Override the colors for destructive / positive actions. */
.btn-primary.btn-danger {
  background-color: #dc2626;    /* red-600 */
  border-color: #dc2626;
}
.btn-primary.btn-danger:hover:not(:disabled) {
  background-color: #b91c1c;    /* red-700 */
  border-color: #b91c1c;
}
.btn-secondary.btn-danger {
  color: #b91c1c;
  border-color: #fecaca;        /* red-200 */
}
.btn-secondary.btn-danger:hover:not(:disabled) {
  background-color: #fef2f2;    /* red-50 */
  border-color: #f87171;
  color: #991b1b;
}
.btn-ghost.btn-danger {
  color: #b91c1c;
}
.btn-ghost.btn-danger:hover:not(:disabled) {
  background-color: #fef2f2;
  color: #991b1b;
}

.btn-primary.btn-success {
  background-color: #16a34a;    /* green-600 */
  border-color: #16a34a;
}
.btn-primary.btn-success:hover:not(:disabled) {
  background-color: #15803d;
  border-color: #15803d;
}

/* ========================================================================
 * PR3 — Visual normalization for existing inline-class buttons
 * Auto-apply consistent radius, transition, focus state to any <button>
 * or <a> inside .content-area that visually behaves as a button.
 * ====================================================================== */

/* Any element that already uses a primary-coloured background. Radius and
   weight only: the primary button is flat, so this rule may not put a
   brand-tinted glow back under every hand-rolled one. */
.content-area button[class*="bg-[var(--color-primary)]"],
.content-area a[class*="bg-[var(--color-primary)]"]:not(.page-tabs__tab) {
  border-radius: 0.5rem !important;
  font-weight: 600 !important;
  transition: background-color 0.15s ease, transform 0.05s ease !important;
}
.content-area button[class*="bg-[var(--color-primary)]"]:active:not(:disabled),
.content-area a[class*="bg-[var(--color-primary)]"]:not(.page-tabs__tab):active {
  transform: translateY(0.5px);
}

/* Outlined / secondary buttons: white bg + gray border */
.content-area button[class*="bg-white"][class*="border-gray-200"]:not(.btn),
.content-area a[class*="bg-white"][class*="border-gray-200"]:not(.btn):not(.page-tabs__tab) {
  border-radius: 0.5rem !important;
  font-weight: 600 !important;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease !important;
}
.content-area button[class*="bg-white"][class*="border-gray-200"]:not(.btn):hover,
.content-area a[class*="bg-white"][class*="border-gray-200"]:not(.btn):not(.page-tabs__tab):hover {
  border-color: #d1d5db !important;
  background-color: #f9fafb !important;
}

/* Focus-visible accent for any interactive primary/secondary button */
.content-area button:focus-visible,
.content-area a[role="button"]:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 25%, transparent) !important;
}

/* ========================================================================
 * Inline-edit hover hints
 * Make HTMX-bound inputs/selects/textareas inside the content area visually
 * hint that they are editable. Subtle by default, prominent on hover/focus.
 * Affects every element with `hx-post` (the universal marker for inline edit).
 * ====================================================================== */
.content-area input[hx-post],
.content-area select[hx-post],
.content-area textarea[hx-post] {
  transition: border-color 0.15s ease,
              background-color 0.15s ease,
              box-shadow 0.15s ease;
}
.content-area input[hx-post]:hover,
.content-area select[hx-post]:hover,
.content-area textarea[hx-post]:hover {
  border-color: color-mix(in srgb, var(--color-primary) 35%, white) !important;
  background-color: color-mix(in srgb, var(--color-primary) 2%, white);
}
.content-area input[hx-post]:focus,
.content-area select[hx-post]:focus,
.content-area textarea[hx-post]:focus {
  outline: none;
  border-color: var(--color-primary) !important;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 15%, transparent) !important;
  background-color: #ffffff;
}

/* Pencil hint that appears on hover of editable rows */
.content-area [data-inline-edit]:hover::after {
  content: "\f303";
  font-family: "Font Awesome 6 Free";
  font-weight: 900;
  font-size: 9px;
  color: color-mix(in srgb, var(--color-primary) 50%, transparent);
  margin-left: 6px;
  vertical-align: middle;
}

/* Keyboard-focus highlight for vulnerability list rows (used by j/k nav) */
.content-area tr.kbd-focus {
  outline: 2px solid color-mix(in srgb, var(--color-primary) 50%, transparent);
  outline-offset: -2px;
  background-color: color-mix(in srgb, var(--color-primary) 4%, white);
}

/* SECTIONED MODAL, the modal's save bar and its child layer (.vs-modal-*): moved
   to violet-system.css, which the client room's external base loads too, so
   the room's modals wear the same shell. */

/* ── A tall editor inside the modal shell (.vs-editor-pane) ──────────────
 *
 * NOT `.vs-modal-pane`: that name is already taken by the rail+panel canon
 * above, where it means "the scrolling right-hand pane". Two meanings on one
 * class is how a rule reaches a modal nobody was thinking about.
 *
 * The report export editor pins its two panes to a height, because a list you
 * drag rows around in and a live preview beside it both need a stable box.
 * It used to pin them to `54vh` — a fraction of the WINDOW, which has nothing
 * to do with the space the shell actually leaves: the panel is capped at 90vh
 * and inside it sit the modal header, the body padding, the report-status
 * strip, the template row and the sticky footer. 54vh always overshot, so the
 * body scrolled and the bottom of both panes sat behind the opaque footer —
 * 75px of them at a 640px-tall window, measured.
 *
 * `min()` keeps the 54vh intent where there is room and hands back the shell's
 * chrome where there is not. 20rem is that chrome, measured against the real
 * modal: 65px header + 48px body padding + 72px status strip + 54px template
 * row + 53px footer + the gaps between them.
 *
 * The floor is written into the same expression rather than as `min-height`,
 * so it cannot argue with the `min-h-0` these panes carry for their inner
 * scrolling. Below the floor, scrolling the modal body is the honest answer.
 */
.vs-editor-pane { height: max(16rem, min(54vh, calc(90vh - 20rem))); }

/* Tones for the context strip. One name per meaning so callers pass a state,
   not a colour — "done" stays green everywhere without anyone remembering it. */
.vs-modal-context__pill--done    { background: #d1fae5; color: #047857; }
.vs-modal-context__pill--active  { background: #dbeafe; color: #1d4ed8; }
.vs-modal-context__pill--danger  { background: #fee2e2; color: #b91c1c; }
.vs-modal-context__pill--warning { background: #fef3c7; color: #b45309; }
.vs-modal-context__pill--muted   { background: #f3f4f6; color: #4b5563; }

/* Tom Select dropdowns are rendered on <body> (see user_picker.js) so that a
   card or table cell with overflow-hidden cannot clip them. Once detached they
   no longer inherit the stacking context of whatever opened them, so they need
   a z-index above the global modal (z-50) and the nested overlays (z-70/z-80).
   Without this a picker inside a modal drops its list *behind* the modal.

   `position` and the geometry are set by violetAnchorPanel at open time, in
   viewport coordinates — Tom Select's own absolute placement is computed once
   and is wrong the moment a scrolling pane moves under it. `!important` on
   nothing here: the helper writes inline styles, which already win. */
.ts-dropdown { z-index: 10050; }
/* The bump the shared helper applies while it is anchoring, so the panel never
   flashes at Tom Select's own coordinates before being moved. */
.ts-dropdown.vs-anchored { position: fixed; }

/* ── File field ──────────────────────────────────────────────────────────
   The native control's button and "no file chosen" status are browser chrome
   in the browser's own language, and CSS cannot change either string — only
   ::file-selector-button is reachable, and only for styling. So the native
   chrome is clipped away and file_input.js renders the control instead.

   Clipped rather than display:none: a hidden input is skipped by constraint
   validation in some browsers, which would silently break `required`. */
.vs-file {
  display: flex; align-items: center; gap: .5rem;
  min-width: 0;
}
.vs-file__native {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap; border: 0;
}
.vs-file__button {
  display: inline-flex; align-items: center; gap: .5rem;
  flex-shrink: 0;
  padding: .5rem .875rem;
  border: 1px solid #e5e7eb; border-radius: .5rem;
  background: #fff;
  font-size: 13px; font-weight: 500; color: #374151;
  cursor: pointer;
  transition: border-color .15s, background .15s;
}
.vs-file__button:hover { background: #f9fafb; border-color: #d1d5db; }
.vs-file__button:focus-visible {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 15%, transparent);
}
.vs-file__button i { font-size: 11px; color: var(--color-primary); }
.vs-file__name {
  min-width: 0; flex: 1 1 auto;
  font-size: 13px; color: #374151;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.vs-file__name--empty { color: #9ca3af; }
.vs-file__clear {
  flex-shrink: 0;
  width: 1.5rem; height: 1.5rem;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: .375rem;
  background: transparent; color: #9ca3af;
  font-size: 11px; cursor: pointer;
}
.vs-file__clear:hover { background: #f3f4f6; color: #ef4444; }

/* FILTER BAR (.vs-filter*): moved to violet-system.css, which the client
   room's external base loads too, so both shells draw one filter row. */
/* ==========================================================================
 * CHIP — one shape for every piece of record metadata
 * ==========================================================================
 * The Finding header carried six chips in one row and six different shapes:
 * one with a coloured dot, one with an icon plus a tiny uppercase "IMPACT"
 * prefix, one with a dashed border, one solid, some with a chevron and some
 * without, and three different vertical paddings so they did not even line up.
 * Every one was twelve hand-written utility classes, and no two agreed.
 *
 * That is the tell. A person designing this builds one control and reuses it;
 * only a machine writes the same idea twelve ways. So: one definition here,
 * and the markup names it.
 *
 * What is allowed to vary is the COLOUR, because colour carries meaning here
 * (severity, status). What is not allowed to vary is the shape.
 *
 * Editability is signalled one way and one way only: a chevron. A chip without
 * one is not clickable, and a chip with one is.
 */
.vs-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  height: 26px;                     /* fixed, so a row of chips has one baseline */
  padding: 0 0.625rem;
  border-radius: 6px;               /* not a pill: a pill next to pill-shaped
                                       buttons makes both read as buttons */
  border: 1px solid transparent;
  font-size: 12px;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  background: #f3f4f6;
  color: #374151;
  border-color: #e5e7eb;
}
button.vs-chip, a.vs-chip { cursor: pointer; transition: filter .15s ease; }
button.vs-chip:hover, a.vs-chip:hover { filter: brightness(0.96); }
button.vs-chip:focus-visible, a.vs-chip:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
}
/* The one affordance that says "this opens". Nothing else may say it. */
.vs-chip__caret { font-size: 8px; opacity: .55; transition: transform .15s ease; }
.vs-chip[aria-expanded="true"] .vs-chip__caret { transform: rotate(180deg); }
/* Leading icon, when the chip needs one to be legible at a glance. */
.vs-chip > i:first-child { font-size: 10px; opacity: .75; }
/* A quiet prefix for chips whose value needs naming ("IMPACT: Medium").
   11px/.06em rather than 10px/.04em, so it is the same eyebrow the metric
   labels, the table headers and the filter labels use. It reads better AND
   sits quieter, which is the whole job of a label. */
.vs-chip__label {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  opacity: .62;
}

/* Tones. Severity keeps its own scale; everything else is one of three. */
.vs-chip--very-critical { background: #fee2e2; color: #991b1b; border-color: #fecaca; } /* = Critical */
.vs-chip--critical { background: #fee2e2; color: #991b1b; border-color: #fecaca; }
.vs-chip--high     { background: #ffedd5; color: #9a3412; border-color: #fed7aa; }
.vs-chip--medium   { background: #fef3c7; color: #92400e; border-color: #fde68a; }
.vs-chip--low      { background: #dcfce7; color: #166534; border-color: #bbf7d0; }
.vs-chip--info     { background: #eef2ff; color: #3730a3; border-color: #e0e7ff; }
.vs-chip--neutral  { background: #f3f4f6; color: #374151; border-color: #e5e7eb; }
.vs-chip--success  { background: #dcfce7; color: #166534; border-color: #bbf7d0; }
.vs-chip--warn     { background: #fef3c7; color: #92400e; border-color: #fde68a; }
.vs-chip--danger   { background: #dc2626; color: #ffffff; border-color: #dc2626; }
.vs-chip--brand    {
  background: color-mix(in srgb, var(--color-primary) 10%, white);
  color: var(--color-primary);
  border-color: color-mix(in srgb, var(--color-primary) 25%, white);
}
/* Monospace variant for identifiers (CVE, codes) — same shape, different face. */
.vs-chip--mono { font-family: "JetBrains Mono", ui-monospace, Menlo, monospace; font-weight: 500; }


/* =============================================================================
 * LOZENGES, INLINE CONTROLS, MODAL ASIDE — the record anatomy
 * ========================================================================== */
/* A status is a small lozenge: 11 px, medium weight, no uppercase, a tint at
   most. The old pills were bold uppercase blocks that shouted over the row. */
.vs-lozenge {
  display: inline-flex; align-items: center; gap: .25rem;
  padding: 2px 8px; border-radius: 4px;
  font-size: 11px; font-weight: 600; line-height: 16px;
  text-transform: none; letter-spacing: 0; white-space: nowrap;
}
.vs-lozenge--neutral  { background: #f3f4f6; color: #4b5563; }
.vs-lozenge--info     { background: #dbeafe; color: #1d4ed8; }
.vs-lozenge--ok       { background: #dcfce7; color: #15803d; }
.vs-lozenge--medium   { background: #fef3c7; color: #a16207; }
.vs-lozenge--critical { background: #fee2e2; color: #b91c1c; }
.vs-lozenge--very-critical { background: #fee2e2; color: #b91c1c; } /* = Critical */
/* The status control of a record: a lozenge that happens to be a select. */
.vs-lozenge-select {
  appearance: none; -webkit-appearance: none;
  padding: 3px 24px 3px 10px; border-radius: 4px; border: 1px solid transparent;
  font-size: 12px; font-weight: 600; line-height: 16px; cursor: pointer;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 20 20'><path d='M5 8l5 5 5-5' fill='none' stroke='currentColor' stroke-width='2'/></svg>");
  background-repeat: no-repeat; background-position: right 8px center;
}
.vs-lozenge-select:focus { outline: none; box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-primary) 25%, transparent); }
/* A field that saves itself. It used to be invisible until hovered — a
   border of `transparent` — so a panel of eight editable fields read as eight
   lines of text and nobody could tell what could be changed. A control looks
   like a control at rest; what it does NOT do is shout, so the frame is the
   same hairline every input in the product uses. */
.vs-inline-select {
  width: 100%; font-size: 13px; color: #111827; background: #fff;
  border: 1px solid #e5e7eb; border-radius: 6px; padding: 5px 8px;
  transition: border-color .15s, box-shadow .15s;
}
.vs-inline-select:hover { border-color: #d1d5db; }
.vs-inline-select:focus {
  outline: none; border-color: var(--color-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 18%, transparent);
}
select.vs-inline-select {
  cursor: pointer; appearance: none; -webkit-appearance: none; padding-right: 26px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 20 20'><path d='M5 8l5 5 5-5' fill='none' stroke='%236b7280' stroke-width='2'/></svg>");
  background-repeat: no-repeat; background-position: right 9px center;
}
/* A date field keeps the browser's own picker button, so it must not be
   pushed under the chevron this rule adds to selects. */
input.vs-inline-select[type="date"] { padding-right: 8px; }
/* The right column of a record modal: the facts, labelled. */
.vs-modal-aside {
  flex-shrink: 0; overflow-y: auto;
  padding: 1.25rem 1.25rem 1.5rem;
  background: #fafafa; border-top: 1px solid #e5e7eb;
}
@media (min-width: 640px) {
  .vs-modal-aside { width: 19rem; border-top: 0; border-left: 1px solid #e5e7eb; }
}
.vs-modal-aside__title {
  font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: #6b7280; margin-bottom: .75rem;
}
.vs-modal-aside__list > div { margin-bottom: .875rem; }
.vs-modal-aside__list dt { font-size: 11px; color: #6b7280; margin-bottom: 2px; }
.vs-modal-aside__list dd { margin: 0; font-size: 13px; color: #111827; }

/* Inside the Details column every control wears the product's field frame,
   the hairline of .vs-inline-select. It used to be quiet at rest, but only the
   native controls obeyed: dates and the owner read as plain text while the
   Tom Select ones kept their box, so one column mixed two looks. One frame. */
.vs-modal-aside select:not(.vs-lozenge-select),
.vs-modal-aside input:not([type="checkbox"]):not([type="file"]):not([type="hidden"]),
.vs-modal-aside .ts-wrapper .ts-control {
  font-size: 13px; color: #111827; background: #fff;
  border: 1px solid #e5e7eb; border-radius: 6px; padding: 5px 8px; min-height: 32px;
  box-shadow: none; transition: border-color .15s, box-shadow .15s;
}
.vs-modal-aside select:not(.vs-lozenge-select):hover,
.vs-modal-aside input:not([type="checkbox"]):not([type="file"]):not([type="hidden"]):hover,
.vs-modal-aside .ts-wrapper .ts-control:hover { border-color: #d1d5db; background: #fff; }
.vs-modal-aside select:focus,
.vs-modal-aside input:focus,
.vs-modal-aside .ts-wrapper.focus .ts-control {
  outline: none; border-color: var(--color-primary); background: #fff;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 18%, transparent);
}
/* A framed control takes its row and lines up with the label above it. */
.vs-modal-aside dd > select,
.vs-modal-aside dd > input:not([type="checkbox"]):not([type="file"]):not([type="hidden"]),
.vs-modal-aside dd > .ts-wrapper { width: 100%; margin-left: 0; }
.vs-modal-aside dd > .ts-wrapper .ts-control { width: 100%; margin-left: 0; }
.vs-modal-aside dd > .flex > input { width: 5.5rem; flex: 0 0 auto; }
/* Tom Select copies the select's classes to its wrapper, so a
   `vs-inline-select` enhanced by data-select drew the frame twice: once on the
   wrapper and once on the control inside it (the box inside a box of the task
   modal's Findings and Dependencies). The control carries the frame. */
.ts-wrapper.vs-inline-select { border: 0 !important; padding: 0 !important; background: transparent !important; box-shadow: none !important; }
/* Same box-in-a-box on a full-size form field: a select framed with utility
   classes and enhanced by data-select. Mark it `vs-ts-field`; the control
   inside keeps the one frame. */
.ts-wrapper.vs-ts-field { border: 0 !important; padding: 0 !important; background: transparent !important; box-shadow: none !important; }
/* ═══════════════════════════════════════════════════════════════════════════
   PRIORITY MATRIX (settings_priority_matrix.html, _priority_matrix_row.html)
   ───────────────────────────────────────────────────────────────────────────
   A table that reads as a table: a source per row, a colour card per severity
   column, nothing editable at rest. A source opens in the shared modal, a
   card in its popover. The cards wear the workspace's own colours, so they
   are data and stay inline; everything around them is tokens.

   No `filter` or `transform` on a card or its ancestors: the popover inside
   it is `position: fixed`, and either would make the card its containing
   block and pin the popover inside it. The card's hover is an overlay instead.
   ═══════════════════════════════════════════════════════════════════════════ */
.vs-pm-card {
  background: var(--vs-surface);
  border: 1px solid transparent;
  border-radius: var(--vs-radius-lg);
}
.vs-pm-bar {
  display: flex; align-items: center; flex-wrap: wrap; gap: .75rem 1.5rem;
  padding: .75rem 1.25rem;
  border-bottom: 1px solid var(--vs-rule);
}
.vs-pm-bar__band { display: flex; align-items: center; gap: .625rem; margin-left: auto; }
.vs-pm-bar__band .vs-filter-label { margin: 0; font-size: 12px; white-space: nowrap; }
select.vs-pm-band, .ts-wrapper.vs-pm-band { width: 11.5rem; }

/* The grid scrolls inside the card on a narrow screen, never the page. The
   source column takes what its name and three chips need, the cards the rest. */
.vs-pm-scroll { overflow-x: auto; }
.vs-pm-grid { min-width: 54rem; padding: .25rem 1.25rem 1.25rem; }
.vs-pm-row {
  display: grid;
  grid-template-columns: minmax(20rem, 26rem) repeat(4, minmax(7.5rem, 1fr));
  column-gap: .5rem;
  padding: .25rem 0;
}
.vs-pm-row--head { align-items: end; padding: .875rem 0 .375rem; }
.vs-pm-head-source { padding-left: .75rem; }
.vs-pm-colhead { display: flex; justify-content: center; }

/* The source: its name and what lands on it, the whole box opens the modal. */
.vs-pm-source { min-width: 0; display: flex; }
.vs-pm-source__box {
  position: relative; flex: 1; min-width: 0;
  display: flex; flex-direction: column; justify-content: center; gap: 3px;
  padding: .3125rem .625rem; border: 0; border-radius: var(--vs-radius);
  background: transparent; text-align: left; font: inherit; color: inherit;
}
button.vs-pm-source__box { cursor: pointer; transition: background-color .15s ease, box-shadow .15s ease; }
button.vs-pm-source__box:hover { background: var(--vs-surface-2); box-shadow: inset 0 0 0 1px var(--vs-rule); }
button.vs-pm-source__box:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
.vs-pm-source__name {
  padding-right: 1.5rem;
  font-size: 14px; font-weight: 600; line-height: 18px; color: var(--vs-ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.vs-pm-source__line { display: flex; align-items: center; gap: 4px; min-width: 0; height: 18px; overflow: hidden; }
.vs-pm-source__key { flex: 0 0 5.5rem; font-size: 11px; color: var(--vs-ink-3); white-space: nowrap; }
.vs-pm-tag {
  display: inline-block; flex: 0 1 auto; min-width: 2.75rem; max-width: 6.5rem; height: 18px; padding: 0 6px;
  border-radius: 4px; background: var(--vs-surface-3); color: var(--vs-ink-2);
  font-size: 11px; font-weight: 500; line-height: 18px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.vs-pm-more { flex-shrink: 0; font-size: 11px; font-weight: 500; color: var(--vs-ink-3); white-space: nowrap; }
.vs-pm-none { font-size: 11px; color: var(--vs-ink-4); }
.vs-pm-source__edit {
  position: absolute; top: .5rem; right: .625rem;
  font-size: 11px; color: var(--vs-ink-4); transition: color .15s ease;
}
button.vs-pm-source__box:hover .vs-pm-source__edit { color: var(--color-primary); }

/* A cell: its code large on its own colour, the count it links to below. */
.vs-pm-td { min-width: 0; display: flex; }
.vs-pm-cell {
  position: relative; flex: 1; min-width: 0; min-height: 4rem;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px;
  border-radius: var(--vs-radius);
  color: var(--vs-ink);
  box-shadow: inset 0 0 0 1px rgba(17, 24, 39, .07);
  transition: box-shadow .15s ease;
}
.vs-pm-cell.is-dark { color: #fff; }
.vs-pm-cell.is-empty { box-shadow: inset 0 0 0 1px var(--vs-rule-2); color: var(--vs-ink-4); }
.vs-pm-cell.is-open {
  box-shadow: inset 0 0 0 1px rgba(17, 24, 39, .07), 0 0 0 2px var(--vs-surface),
              0 0 0 4px color-mix(in srgb, var(--vs-ink) 35%, transparent);
}
.vs-pm-cell__hit {
  position: absolute; inset: 0; border: 0; border-radius: inherit;
  background: transparent; cursor: pointer; transition: background-color .15s ease;
}
.vs-pm-cell__hit:hover { background: rgba(17, 24, 39, .05); }
.vs-pm-cell.is-dark .vs-pm-cell__hit:hover { background: rgba(255, 255, 255, .09); }
.vs-pm-cell__hit:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.vs-pm-cell__code {
  position: relative; pointer-events: none;
  font-size: 16px; font-weight: 600; line-height: 1; letter-spacing: .01em;
  font-variant-numeric: tabular-nums;
}
.vs-pm-count {
  position: relative; z-index: 1;
  display: inline-flex; align-items: center; height: 18px; padding: 0 7px;
  border-radius: var(--vs-radius-pill);
  background: rgba(17, 24, 39, .08); color: inherit;
  font-size: 11px; font-weight: 500; line-height: 1; white-space: nowrap; text-decoration: none;
  font-variant-numeric: tabular-nums;
}
.vs-pm-cell.is-dark .vs-pm-count { background: rgba(255, 255, 255, .2); }
a.vs-pm-count { transition: background-color .15s ease; }
a.vs-pm-count:hover { background: rgba(17, 24, 39, .16); }
.vs-pm-cell.is-dark a.vs-pm-count:hover { background: rgba(255, 255, 255, .32); }
a.vs-pm-count:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }

/* The cell's popover: under the card, a pointer tying it to it; above it when
   the card is near the bottom of the screen (data-side, set on open). */
.vs-pm-pop {
  position: fixed; z-index: 60; width: 17.5rem; padding: .75rem .875rem .875rem;
  border: 1px solid var(--vs-rule); border-radius: var(--vs-radius-lg);
  background: var(--vs-surface); color: var(--vs-ink);
  box-shadow: 0 12px 28px -12px rgba(17, 24, 39, .25), 0 2px 6px rgba(17, 24, 39, .06);
  text-align: left; font-size: 13px; cursor: default;
}
.vs-pm-pop::before {
  content: ""; position: absolute; top: -6px; left: var(--pm-arrow, 50%);
  width: 11px; height: 11px; margin-left: -5.5px;
  background: var(--vs-surface);
  border-left: 1px solid var(--vs-rule); border-top: 1px solid var(--vs-rule);
  transform: rotate(45deg);
}
.vs-pm-pop[data-side="above"]::before {
  top: auto; bottom: -6px; border: 0;
  border-right: 1px solid var(--vs-rule); border-bottom: 1px solid var(--vs-rule);
}
.vs-pm-pop__ctx {
  display: flex; align-items: center; gap: .5rem; min-width: 0; margin: 0 0 .75rem;
  font-size: 12px; font-weight: 500; color: var(--vs-ink-2);
}
.vs-pm-pop__ctx .vs-sev { height: 22px; flex-shrink: 0; }
.vs-pm-pop__input {
  width: 100%; height: 34px; padding: 0 .625rem;
  border: 1px solid var(--vs-rule-2); border-radius: var(--vs-radius);
  background: var(--vs-surface); color: var(--vs-ink);
  font-size: 14px; font-weight: 600; letter-spacing: .02em;
}
.vs-pm-pop__input:focus {
  outline: none; border-color: var(--color-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 18%, transparent);
}
.vs-pm-pop__label { margin-top: .75rem; }
.vs-pm-swatches { display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); gap: 6px; }
.vs-pm-swatch {
  position: relative; display: flex; align-items: center; justify-content: center;
  aspect-ratio: 1; border: 0; border-radius: 5px; cursor: pointer;
  box-shadow: inset 0 0 0 1px rgba(17, 24, 39, .1);
  transition: box-shadow .15s ease;
}
.vs-pm-swatch:hover { box-shadow: inset 0 0 0 1px rgba(17, 24, 39, .1), 0 0 0 2px var(--vs-surface), 0 0 0 3px var(--vs-ink-4); }
.vs-pm-swatch.is-on { box-shadow: inset 0 0 0 1px rgba(17, 24, 39, .1), 0 0 0 2px var(--vs-surface), 0 0 0 4px var(--vs-ink); }
.vs-pm-swatch:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.vs-pm-swatch--custom {
  background: var(--vs-surface-2); color: var(--vs-ink-3); font-size: 10px;
  box-shadow: inset 0 0 0 1px var(--vs-rule-2);
}
.vs-pm-swatch--custom:hover { color: var(--vs-ink); }

/* "Add source": a dashed row under the last one, opening the same modal. */
.vs-pm-add {
  display: flex; align-items: center; justify-content: center; gap: .5rem;
  width: 100%; height: 2.75rem; margin-top: .5rem;
  border: 1px dashed var(--vs-rule-2); border-radius: var(--vs-radius);
  background: transparent; color: var(--vs-ink-3);
  font-size: 13px; font-weight: 500; cursor: pointer;
  transition: border-color .15s ease, color .15s ease, background-color .15s ease;
}
.vs-pm-add > i { font-size: 11px; }
.vs-pm-add:hover {
  border-color: var(--color-primary); color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 4%, transparent);
}
.vs-pm-add:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }

/* The save bar: the card's last strip, held at the bottom of the screen while
   the table is longer than it. */
.vs-pm-foot {
  position: sticky; bottom: 0; z-index: 5;
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  padding: .75rem 1.25rem;
  border-top: 1px solid var(--vs-rule);
  border-radius: 0 0 var(--vs-radius-lg) var(--vs-radius-lg);
  background: var(--vs-surface);
}
.vs-pm-dirty { display: inline-flex; align-items: center; gap: .375rem; font-size: 12px; color: var(--vs-ink-3); }
.vs-pm-dirty > i { font-size: 6px; color: var(--vs-medium); }

/* The source modal's Tools and Audit categories: every chosen one stays
   visible, so the box grows with them. Its own class, not
   `.vs-filter-control`, whose fixed height cut the pentesting row's
   twenty-five tools to the first line. */
.vs-pm-field-label {
  display: flex; align-items: center; gap: .5rem; margin-bottom: .375rem;
  font-size: 13px; font-weight: 500; color: var(--vs-ink-2);
}
.vs-pm-field-label__count {
  min-width: 1.25rem; height: 18px; padding: 0 6px; border-radius: var(--vs-radius-pill);
  background: var(--vs-surface-3); color: var(--vs-ink-3);
  font-size: 11px; font-weight: 600; line-height: 18px; text-align: center;
  font-variant-numeric: tabular-nums;
}
select.vs-pm-picker {
  width: 100%; min-height: 40px; border: 1px solid var(--vs-rule); border-radius: var(--vs-radius);
  background: var(--vs-surface); font-size: 12px;
}
.ts-wrapper.vs-pm-picker { width: 100%; min-height: 0; border: 0; padding: 0; background: transparent; }
.ts-wrapper.vs-pm-picker .ts-control {
  min-height: 40px; height: auto; padding: 6px 8px; gap: 4px; flex-wrap: wrap;
  border: 1px solid var(--vs-rule); border-radius: var(--vs-radius); font-size: 12px; box-shadow: none;
}
.ts-wrapper.vs-pm-picker.focus .ts-control {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 14%, transparent);
}
.ts-wrapper.vs-pm-picker .ts-control > .item {
  margin: 0; font-size: 12px; line-height: 20px; border-radius: 4px;
}

/* The multi-select of components/field.html (a finding's collaborators, its
   owners): the opposite case. Its floating label sits inside the frame, and
   the frame is the WRAPPER (Tom Select copied the select's utility classes
   onto it, pt-6 under the label included). So the control drops its own box
   and holds the chips on the line under the label, where a native select
   prints its value. Before, the control drew a thin empty box inside the
   field. Marked `vs-ts-float` by field.html. */
.ts-wrapper.vs-ts-float.multi .ts-control,
.ts-wrapper.vs-ts-float.multi.has-items .ts-control {
  border: 0 !important; padding: 0 !important; background: transparent !important;
  box-shadow: none !important; border-radius: 0 !important;
  min-height: 1.5rem !important; gap: 4px; align-items: center; cursor: text;
}
.ts-wrapper.vs-ts-float.focus {
  border-color: var(--color-primary) !important;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-primary) 10%, transparent) !important;
}
.ts-wrapper.vs-ts-float.focus ~ label { color: var(--color-primary); }
.ts-wrapper.vs-ts-float.multi .ts-control > .item {
  margin: 0 !important; padding: 2px 2px 2px 10px !important; border: 0 !important;
  border-radius: 9999px; font-size: 13px; line-height: 20px; color: #1f2937 !important;
  background: color-mix(in srgb, var(--color-primary) 8%, #fff) !important;
}
.ts-wrapper.vs-ts-float.multi .ts-control > .item.active {
  background: color-mix(in srgb, var(--color-primary) 16%, #fff) !important;
}
.ts-wrapper.vs-ts-float.plugin-remove_button .item .remove {
  border-left: 0 !important; margin-left: 2px !important; padding: 0 6px !important;
  border-radius: 9999px !important; color: #6b7280; line-height: 20px;
}
.ts-wrapper.vs-ts-float.plugin-remove_button .item .remove:hover {
  background: color-mix(in srgb, var(--color-primary) 16%, #fff); color: #111827;
}
.ts-wrapper.vs-ts-float.multi .ts-control > input { flex: 1 1 6rem !important; min-width: 6rem !important; }
/* The single select of components/_field_box.html enhanced with data-select
   (an automation rule's Trigger, From, To): same frame. The control drew its
   own box and caret inside the field's, two boxes and two arrows; the field
   already has its chevron. Specific enough to beat the modal Details rule. */
.ts-wrapper.vs-ts-float.single .ts-control,
.ts-wrapper.vs-ts-float.single .ts-control:hover,
.ts-wrapper.vs-ts-float.single.focus .ts-control,
.vs-modal-aside .ts-wrapper.vs-ts-float.single .ts-control,
.vs-modal-aside .ts-wrapper.vs-ts-float.single .ts-control:hover {
  border: 0 !important; padding: 0 !important; background: transparent !important;
  box-shadow: none !important; border-radius: 0 !important; min-height: 1.5rem !important;
  font-size: inherit !important; line-height: 1.5rem !important;
}
.ts-wrapper.vs-ts-float.single .ts-control::after { display: none !important; }
/* The multi-select of a record modal (a task's related findings): a real
   field height and readable text. As a bare Tom Select it collapsed to an
   empty 8px strip with 11px text. */
.ts-wrapper.vs-multi-select .ts-control {
  min-height: 38px !important; padding: 6px 8px !important; font-size: 14px !important;
  border: 1px solid #e5e7eb !important; border-radius: 6px !important; background: #fff !important;
}
.ts-wrapper.vs-multi-select .ts-control input { font-size: 14px !important; min-width: 8rem; }
.ts-wrapper.vs-multi-select .ts-control .item { font-size: 13px; padding: 2px 6px; border-radius: 4px; }
.ts-wrapper.vs-multi-select.focus .ts-control { border-color: var(--color-primary) !important; }
.ts-dropdown .option { font-size: 14px; }
/* The Coverage tab's testing-guide picker: a real field, full width of its
   box, the guide's name in body size and never clipped to "CIS Kubern…". */
.vs-guide-picker .ts-wrapper, .vs-guide-picker select { width: 100% !important; }
.vs-guide-picker .ts-wrapper .ts-control {
  min-height: 36px !important; padding: 6px 30px 6px 10px !important; font-size: 13px !important; font-weight: 600;
  border: 1px solid #e5e7eb !important; border-radius: 6px !important; background: #fff !important; box-shadow: none !important;
}
.vs-guide-picker .ts-wrapper .ts-control .item { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.vs-guide-picker .ts-wrapper:hover .ts-control { border-color: #d1d5db !important; }
.vs-guide-picker .ts-wrapper.focus .ts-control { border-color: var(--color-primary) !important; box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 18%, transparent) !important; }
.vs-guide-picker + .ts-dropdown, .ts-dropdown.vs-inline-select { min-width: 22rem; }
/* The person picker is Tom Select, and base.html frames it with !important
   for every form in the app. Inside the Details column it wears the same
   frame and height as the native controls next to it, so the override is
   !important too. */
.vs-modal-aside .ts-wrapper.single .ts-control,
.vs-modal-aside .ts-wrapper.single .ts-control:hover {
  background-color: #fff !important; border: 1px solid #e5e7eb !important;
  border-radius: 6px !important; padding: 5px 28px 5px 8px !important;
  font-size: 13px !important; line-height: 20px !important; min-height: 32px !important;
  box-shadow: none !important;
}
.vs-modal-aside .ts-wrapper.single .ts-control:hover { border-color: #d1d5db !important; background-color: #fff !important; }
.vs-modal-aside .ts-wrapper.single.focus .ts-control,
.vs-modal-aside .ts-wrapper.single.input-active .ts-control {
  border-color: var(--color-primary) !important; background-color: #fff !important; box-shadow: none !important;
}
.vs-modal-aside .ts-wrapper.single .ts-control::after { right: 10px; }
.vs-modal-aside__list > div { padding-bottom: .5rem; margin-bottom: .5rem; border-bottom: 1px solid #f3f4f6; }
.vs-modal-aside__list > div:last-child { border-bottom: 0; }
.vs-modal-aside__list dt { font-size: 11px; color: #6b7280; margin-bottom: 3px; letter-spacing: .01em; }
/* Two short facts on one row (start and due, priority and effort). */
.vs-modal-aside__pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: .625rem; }
.vs-modal-aside .text-\[11px\].text-gray-400 { color: #9ca3af; }
/* The context bar of a record: one line, the type on the left, the state on the right. */
.vs-modal-context { padding: .75rem 1.5rem; border-bottom: 1px solid #e5e7eb; }
.vs-modal-context__row { font-size: 12px; }
.vs-modal-context__eyebrow { font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: #6b7280; }
/* Big editable name in the content column */
.vs-record-title { width: 100%; font-size: 18px; font-weight: 600; color: #111827; letter-spacing: -.01em; background: transparent; border: 1px solid transparent; border-radius: 6px; padding: 4px 8px; margin-left: -8px; }
.vs-record-title:hover { border-color: #e5e7eb; }
.vs-record-title:focus { outline: none; border-color: var(--color-primary); background: #fff; }
.vs-record-text { width: 100%; font-size: 13px; color: #374151; line-height: 1.6; background: transparent; border: 1px solid transparent; border-radius: 6px; padding: 6px 8px; margin-left: -8px; resize: vertical; }
.vs-record-text:hover { border-color: #e5e7eb; }
.vs-record-text:focus { outline: none; border-color: var(--color-primary); background: #fff; }
/* A section inside a record modal (task, work plan). It was an uppercase
   tracked eyebrow, and a modal carries four of them at once: four small grey
   capitals down one pane read as a template, not as a document. A section
   heading names its section in the product's own voice and draws a hairline
   under it, the same way a panel head does on a page. */
.vs-record-section {
  font-size: 12px; font-weight: 600; letter-spacing: -0.006em; color: #111827;
  margin-bottom: .5rem; padding-bottom: .3rem; border-bottom: 1px solid #ece9f5;
}

/* DRILL affordance (.vs-drill): moved to violet-system.css, which the client
   room's external base loads too. */


/* MENU rows (.vs-menu__*): moved to violet-system.css, which the client
   room's external base loads too, so both top bars share one menu. */

/* The sidebar scrolls, and the scrollbar stays out of sight. The rail is a
   list you scroll with the wheel; the thumb painted a dark line down its
   right edge on every screen whose nav is taller than the viewport, which is
   most of them once a module is open. Same treatment as .vs-rail__track. */
/* The sidebar nav keeps its scrollbar: a visible bar tells the person there is
   more below, which a hidden one does not. Thin and light, so it reads as part
   of the lilac surface instead of the dark default the browser paints. */
.sidebar-scroll { scrollbar-width: thin; scrollbar-color: #d9d4e6 transparent; }
.sidebar-scroll::-webkit-scrollbar { width: 6px; }
.sidebar-scroll::-webkit-scrollbar-track { background: transparent; }
.sidebar-scroll::-webkit-scrollbar-thumb { background: #d9d4e6; border-radius: 3px; }

/* A quiet input: transparent at rest, hairline on hover, accent on focus.
   The Milestones panel edits twelve dates and twelve comments in place; a
   bordered box on each would draw 24 frames around a table. */
.vs-quiet-input {
  background: transparent; border: 1px solid transparent; border-radius: 6px;
  padding: 2px 6px; line-height: 1.3; outline: none; transition: border-color .12s ease;
}
.vs-quiet-input:hover { border-color: #e5e7eb; background: #fff; }
.vs-quiet-input:focus { border-color: var(--color-primary); background: #fff; box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-primary) 18%, transparent); }

/* Tom Select group headers as the product's eyebrow, and a taller list: the
   timer's task picker groups tasks by work plan, and the group name used to
   look like one more task. */
.ts-dropdown .optgroup-header { font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: #6b7280; background: #f9fafb; padding: 6px 10px 4px; border-top: 1px solid #f3f4f6; }
.ts-dropdown .optgroup:first-child .optgroup-header { border-top: 0; }
.ts-dropdown .optgroup .option { padding-left: 16px; }
.ts-dropdown-content { max-height: 320px; }

/* Date field: moved to static/css/date_field.css (loaded by the app AND the portal). */

/* A section reached from a record index lands under the fixed top bar and the sticky strip without this. */
[id^="sec-"] { scroll-margin-top: 6.5rem; }

/* ========================================================================
 * Count strip: a row of "dot · number · word" (Findings: Open / Pending
 * retest…, Scope: in scope / with findings…). The dot carries the colour;
 * the number is dark and the word is grey, so the row reads as data rather
 * than as four coloured labels separated by pipes.
 * ====================================================================== */
.vs-counts { display: flex; flex-wrap: wrap; align-items: center; gap: .375rem 1.25rem; font-size: 13px; line-height: 1.25rem; color: #4b5563; }
.vs-counts__item { display: inline-flex; align-items: center; gap: .4rem; white-space: nowrap; }
button.vs-counts__item { border-radius: .375rem; padding: .125rem .375rem; margin: -.125rem -.375rem; transition: background-color .12s; cursor: pointer; }
button.vs-counts__item:hover { background-color: #f3f4f6; }
.vs-counts__dot { width: 7px; height: 7px; border-radius: 9999px; flex-shrink: 0; background: #9ca3af; }
.vs-counts__n { font-weight: 600; color: #111827; font-variant-numeric: tabular-nums; }

/* ── Policy text ─────────────────────────────────────────────────────────
   The small formatting language of a policy (headings, lists, bold), rendered
   server-side by services/policy_markup.py. Plain CSS rather than utility
   classes: the markup is generated, and Tailwind is compiled from templates. */
.vs-policy-text { font-size: .875rem; line-height: 1.6; color: #374151; }
.vs-policy-text > :first-child { margin-top: 0; }
.vs-policy-text p { margin: 0 0 .625rem; }
.vs-policy-text h3 { font-size: .9375rem; font-weight: 600; color: #111827; margin: 1rem 0 .375rem; }
.vs-policy-text h4 { font-size: .875rem; font-weight: 600; color: #1f2937; margin: .75rem 0 .25rem; }
.vs-policy-text ul, .vs-policy-text ol { margin: 0 0 .625rem; padding-left: 1.25rem; }
.vs-policy-text ul { list-style: disc; }
.vs-policy-text ol { list-style: decimal; }
.vs-policy-text li { margin: 0 0 .25rem; }
.vs-policy-text strong { font-weight: 600; color: #111827; }

/* CVSS calculator (violet/cvss_calculator_panel.html).
   Head and foot hold their place, only the metrics scroll. The host has to be
   a flex column with a bounded height (#cvss-panel-content does that). */
.cvss-calc { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.cvss-calc__head,
.cvss-calc__foot { flex-shrink: 0; }
.cvss-calc__head { padding-bottom: 0.75rem; border-bottom: 1px solid #e5e7eb; }
.cvss-calc__body { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding-block: 0.75rem; }
.cvss-calc__foot { padding-top: 0.75rem; border-top: 1px solid #e5e7eb; }
/* A vector is one long token: break it anywhere, never cut it with an ellipsis. */
.cvss-calc__vector { white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-all; text-overflow: clip; }

/* ── The labelled field of the plan-item modals (components/_form_field.html) ──
   Task, milestone, work plan, project milestone, decision and follow-up share
   it in their create form and in the record that edits them. One frame for
   every control: a text box, a native select, the date box and the Tom
   Select wrappers all read 42px, 8px radius, the same hairline. */
.vs-form-control {
  display: block; width: 100%;
  padding: .625rem .75rem;
  font-size: .875rem; line-height: 1.25rem; color: #111827;
  background: #fff; border: 1px solid #e5e7eb; border-radius: .5rem;
  transition: border-color .15s, box-shadow .15s;
}
.vs-form-control::placeholder { color: #9ca3af; }
.vs-form-control:hover { border-color: #d1d5db; }
.vs-form-control:focus {
  outline: none; border-color: var(--color-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 18%, transparent);
}
.vs-form-control[readonly], .vs-form-control:disabled { background: #f9fafb; color: #6b7280; }
.vs-form-field .ts-wrapper.single .ts-control,
.vs-form-field .ts-wrapper.single .ts-control:hover {
  border-color: #e5e7eb !important; border-radius: .5rem !important;
  padding: .625rem 2.5rem .625rem .75rem !important; background-color: #fff !important;
}
.vs-form-field .ts-wrapper.single .ts-control:hover { border-color: #d1d5db !important; }
.vs-form-field .ts-wrapper.single.focus .ts-control { border-color: var(--color-primary) !important; }
.vs-form-field .ts-wrapper.multi .ts-control {
  min-height: 42px !important; padding: 6px 8px !important; font-size: .875rem !important;
  border: 1px solid #e5e7eb !important; border-radius: .5rem !important; background: #fff !important; box-shadow: none !important;
}
.vs-form-field .ts-wrapper.multi.focus .ts-control { border-color: var(--color-primary) !important; }
.vs-form-field__hint { margin-top: .25rem; font-size: 11px; color: #6b7280; }
/* The grid of a plan-item form: three columns from sm up, one on a phone. */
.vs-form-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1rem; }
@media (min-width: 640px) { .vs-form-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.vs-form-grid > .vs-span-2 { grid-column: span 1 / span 1; }
.vs-form-grid > .vs-span-3 { grid-column: 1 / -1; }
@media (min-width: 640px) { .vs-form-grid > .vs-span-2 { grid-column: span 2 / span 2; } }

/* ── Security graph: the network drawn by violet-charts.js (NetGraph) ──────
   Round nodes on a light dotted canvas, labels under them, edges in a quiet
   grey that darken (with their verb and arrow) only on hover. The colours of
   the nodes are their TYPE (the datum), set inline from the chart tokens; the
   rest reads the surface and ink tokens, so a dark page repaints it. */
.vg { display: flex; flex-direction: column; min-height: 0; }
.vg-stage { position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden; background: var(--vs-surface, #fff); }
.vg-svg { display: block; width: 100%; height: 100%; touch-action: none; user-select: none; cursor: grab; }
.vg-svg.is-panning { cursor: grabbing; }
.vg-dot { fill: var(--vs-rule-2, #d1d5db); }
.vg-link__hit { fill: none; stroke: transparent; stroke-width: 12px; }
.vg-link__line { fill: none; stroke: var(--vs-rule-2, #d1d5db); stroke-width: 1.4px; }
.vg-link--dashed .vg-link__line { stroke-dasharray: 4 4; }
.vg-link--hot .vg-link__line { stroke: var(--vs-ink-2, #4b5563); stroke-width: 2px; }
.vg-link.is-on .vg-link__line { stroke: var(--vs-ink-3, #6b7280); stroke-width: 1.8px; }
.vg-link__label { display: none; font-size: 11px; font-weight: 600; fill: var(--vs-ink-2, #4b5563); text-anchor: middle;
  dominant-baseline: central; paint-order: stroke; stroke: var(--vs-surface, #fff); stroke-width: 4px; stroke-linejoin: round;
  pointer-events: none; }
.vg-link.is-named .vg-link__label { display: block; }
.vg-arrowhead { fill: var(--vs-ink-3, #6b7280); }
.vg-node { cursor: pointer; outline: none; }
.vg-node__dot { stroke: var(--vs-surface, #fff); stroke-width: 2px; }
.vg-node__stack { opacity: .35; }
.vg-node__more { fill: var(--vs-surface, #fff); stroke-width: 1.5px; stroke-dasharray: 3 3; }
.vg-node__halo { fill: none; stroke: var(--vs-ink, #111827); stroke-width: 1.5px; }
.vg-node__hub { fill: none; stroke-width: 1.2px; stroke-dasharray: 3 3; }
.vg-node__sel { fill: none; stroke: var(--color-primary); stroke-width: 2.5px; display: none; }
.vg-node.is-selected .vg-node__sel, .vg-node:focus-visible .vg-node__sel { display: block; }
.vg-node__glyph, .vg-mark__glyph { font-family: "Font Awesome 6 Free"; font-weight: 900; fill: #fff; text-anchor: middle;
  dominant-baseline: central; pointer-events: none; }
.vg-node__count { font-size: 12px; font-weight: 700; fill: #fff; text-anchor: middle; dominant-baseline: central;
  font-variant-numeric: tabular-nums; pointer-events: none; }
.vg-node__count--more { font-size: 11px; }
.vg-node__name, .vg-node__sub { text-anchor: middle; paint-order: stroke; stroke: var(--vs-surface, #fff); stroke-width: 3px;
  stroke-linejoin: round; dominant-baseline: hanging; }
.vg-node__name { font-size: 12px; font-weight: 600; fill: var(--vs-ink, #111827); }
.vg-node__sub { font-size: 11px; fill: var(--vs-ink-3, #6b7280); }
.vg-mark__dot { stroke: var(--vs-surface, #fff); stroke-width: 1.5px; }
.vg-mark__glyph { font-size: 8.5px; }
.vg-mark__text { font-size: 8.5px; font-weight: 700; fill: #fff; text-anchor: middle; dominant-baseline: central; letter-spacing: .02em; }
.vg-svg.is-focus .vg-node:not(.is-on) { opacity: .2; }
.vg-svg.is-focus .vg-link:not(.is-on) { opacity: .1; }
.vg-tools { position: absolute; left: 12px; top: 12px; z-index: 2; display: flex; flex-direction: column; gap: 4px; }
.vg-tools__gap { height: 6px; }
.vg-tool { width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center; font-size: 13px;
  color: var(--vs-ink-2, #4b5563); background: var(--vs-surface, #fff); border: 1px solid var(--vs-rule, #e5e7eb);
  border-radius: var(--vs-radius, 6px); }
.vg-tool:hover { color: var(--vs-ink, #111827); background: var(--vs-surface-2, #f9fafb); }
.vg-tool[aria-expanded="true"] { color: var(--color-primary); border-color: var(--vs-rule-2, #d1d5db); }
.vg-tool:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
.vg-menu { position: absolute; left: 40px; z-index: 3; min-width: 200px; padding: 4px; background: var(--vs-surface, #fff);
  border: 1px solid var(--vs-rule, #e5e7eb); border-radius: var(--vs-radius, 6px); box-shadow: 0 4px 12px rgba(17, 24, 39, .08); }
.vg-menu[hidden] { display: none; }
.vg-menu__title { padding: 6px 8px 4px; font-size: 12px; font-weight: 500; color: var(--vs-ink-3, #6b7280); }
.vg-menu__item { display: flex; align-items: center; gap: 8px; width: 100%; padding: 6px 8px; font-size: 13px; text-align: left;
  color: var(--vs-ink, #111827); border-radius: 4px; cursor: pointer; }
.vg-menu__item:hover { background: var(--vs-surface-2, #f9fafb); }
.vg-menu__item > i { width: 14px; text-align: center; color: var(--vs-ink-3, #6b7280); }
.vg-menu__item > .vg-menu__check { margin-left: auto; color: var(--color-primary); }
.vg-menu__box { width: 14px; height: 14px; accent-color: var(--color-primary); }
.vg-count { position: absolute; left: 50%; bottom: 12px; z-index: 2; transform: translateX(-50%); padding: 3px 10px;
  font-size: 12px; color: var(--vs-ink-2, #4b5563); font-variant-numeric: tabular-nums; white-space: nowrap;
  background: var(--vs-surface, #fff); border: 1px solid var(--vs-rule, #e5e7eb); border-radius: var(--vs-radius, 6px); }
.vg-tip { position: absolute; z-index: 4; display: none; max-width: 300px; padding: 6px 10px; font-size: 12px; line-height: 1.4;
  color: #fff; background: rgba(17, 24, 39, .95); border-radius: 6px; pointer-events: none; overflow-wrap: anywhere; }
.vg-tip b { display: block; font-weight: 600; }
.vg-tip span { display: block; opacity: .78; }
.vg-legend { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px; padding: 8px 14px; font-size: 12px;
  color: var(--vs-ink-2, #4b5563); background: var(--vs-surface, #fff); border-top: 1px solid var(--vs-rule, #e5e7eb); }
.vg-legend[hidden] { display: none; }
.vg-legend__item { display: inline-flex; align-items: center; gap: 6px; }
.vg-legend__dot { width: 10px; height: 10px; border-radius: 9999px; }
.vg-legend__mark { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 3px;
  font-size: 8.5px; font-weight: 700; color: #fff; border-radius: 9999px; }
.vg-legend__mark--crown { background: var(--vs-warn, #d97706); }
.vg-legend__mark--public { background: var(--vs-neutral, #2563eb); }
.vg-legend__mark--kev { background: var(--vs-critical); border-radius: 4px; }
html.dark .vg-stage, html.dark .vg-legend, html.dark .vg-tool, html.dark .vg-menu, html.dark .vg-count { background: #0f172a; border-color: #1e293b; }
html.dark .vg-dot { fill: #334155; }
html.dark .vg-node__name { fill: #e5e7eb; stroke: #0f172a; }
html.dark .vg-node__sub { fill: #94a3b8; stroke: #0f172a; }
html.dark .vg-node__dot, html.dark .vg-mark__dot { stroke: #0f172a; }
html.dark .vg-link__line { stroke: #475569; }
html.dark .vg-link__label { fill: #e5e7eb; stroke: #0f172a; }
html.dark .vg-menu__item, html.dark .vg-legend, html.dark .vg-count, html.dark .vg-tool { color: #e5e7eb; }

/* ── A wide table says it is wide (static/js/table_scroll.js) ────────────
   A shade on the side that has more columns, and a scrollbar pinned to the
   bottom of the screen while the table's own one is out of sight. */
.vs-hscroll-wrap { position: relative; }
.vs-hscroll-shade {
  position: absolute; top: 1px; bottom: 1px; width: 28px; pointer-events: none;
  opacity: 0; transition: opacity 150ms ease; z-index: 2;
}
.vs-hscroll-shade--left { left: 1px; border-radius: 8px 0 0 8px; background: linear-gradient(to right, rgba(15, 23, 42, 0.10), rgba(15, 23, 42, 0)); }
.vs-hscroll-shade--right { right: 1px; border-radius: 0 8px 8px 0; background: linear-gradient(to left, rgba(15, 23, 42, 0.10), rgba(15, 23, 42, 0)); }
.vs-hscroll-wrap.is-more-left > .vs-hscroll-shade--left,
.vs-hscroll-wrap.is-more-right > .vs-hscroll-shade--right { opacity: 1; }
/* Fixed, placed under the table by the script (left and width): a sticky bar
   stuck inside the list's clipping frame and <main>, two screens down. */
.vs-hscroll-bar {
  position: fixed; bottom: 0; z-index: 30; height: 14px; overflow-x: auto; overflow-y: hidden;
  background: rgba(255, 255, 255, 0.92); border-top: 1px solid var(--vs-rule, #e5e7eb);
}
.vs-hscroll-bar[hidden] { display: none; }
.vs-hscroll-bar { scrollbar-width: thin; scrollbar-color: #94a3b8 transparent; }
.vs-hscroll-bar::-webkit-scrollbar { height: 10px; }
.vs-hscroll-bar::-webkit-scrollbar-thumb { background: #94a3b8; border-radius: 6px; }
.vs-hscroll-bar > div { height: 1px; }
/* ── Audit Posture hero (Dashboard ▸ Audit Lifecycle) ───────────────────
   Ring · title · drivers. The drivers column took the width its five tiles
   asked for and the title column, `1fr`, was left a few pixels: "Higher is
   better", the risk and "Open the Violet Index" broke one word per line
   (Nerea, 15-oct-2026). The title keeps the room its line needs; the
   drivers share what is left and their tiles shrink instead. */
@media (min-width: 1024px) {
  /* Below 1536px the three do not fit: ring and title share the first row
     and the drivers take a full row of their own under them. */
  .vs-posture-hero { grid-template-columns: auto minmax(0, 1fr); }
  .vs-posture-hero > :last-child {
    grid-column: 1 / -1; border-left: 0; padding-left: 0;
    border-top: 1px solid var(--vs-rule, #f3f4f6); padding-top: 1rem;
  }
}
@media (min-width: 1536px) {
  .vs-posture-hero { grid-template-columns: auto minmax(15rem, max-content) minmax(0, 1fr); }
  .vs-posture-hero > :last-child {
    grid-column: auto; border-top: 0; padding-top: 0;
    border-left: 1px solid #f3f4f6; padding-left: 1.5rem;
  }
}
