/*
 * Component styles for the Revelation viewer.
 *
 * Two kinds of rules live here:
 *  1. Our own primitives (.rv-*) — Modal, Tabs, Toast, Button, Select.
 *  2. A small, *scoped* shim for the exact Bootstrap utility class names that
 *     shared main-app modules (table.js, toast.js) hard-code into the DOM
 *     they build. We import those modules unmodified (see CLAUDE.md's
 *     view/layout reuse pattern) rather than fork them, so this shim exists
 *     to style those specific class names without loading Bootstrap's CSS or
 *     Font Awesome. If table.js/toast.js ever add a new Bootstrap class,
 *     this file needs a matching rule.
 */

@import url('./tokens.css');

* { box-sizing: border-box; }

body {
  font-family: var(--rv-font);
  color: var(--rv-text);
  background: var(--rv-bg);
}

/* ---------------------------------------------------------------------- */
/* Button primitive                                                        */
/* ---------------------------------------------------------------------- */

.rv-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--rv-space-2);
  font: inherit;
  font-size: var(--rv-font-size-sm);
  font-weight: 600;
  padding: var(--rv-space-2) var(--rv-space-3);
  border-radius: var(--rv-radius-sm);
  border: 1px solid var(--rv-border-strong);
  background: var(--rv-surface);
  color: var(--rv-text);
  cursor: pointer;
  transition: background var(--rv-transition-fast), border-color var(--rv-transition-fast);
}
.rv-btn:hover:not(:disabled)  { background: var(--rv-row-hover); }
.rv-btn:disabled              { opacity: 0.5; cursor: not-allowed; }
.rv-btn:focus-visible         { outline: 2px solid var(--rv-accent); outline-offset: 1px; }
/* Same author-vs-UA precedence footgun as .rv-busy-overlay[hidden] below —
   .rv-btn's own `display: inline-flex` beats the UA stylesheet's
   [hidden] { display: none }, so a hidden .rv-btn would otherwise stay
   visible. */
.rv-btn[hidden] { display: none; }

.rv-btn-primary {
  background: var(--rv-accent);
  border-color: var(--rv-accent);
  color: var(--rv-accent-text);
}
.rv-btn-primary:hover:not(:disabled) { background: var(--rv-accent-hover); border-color: var(--rv-accent-hover); }

.rv-btn-danger {
  background: var(--rv-danger);
  border-color: var(--rv-danger);
  color: #fff;
}
.rv-btn-danger:hover:not(:disabled) { background: var(--rv-danger-hover); border-color: var(--rv-danger-hover); }

.rv-btn-icon {
  padding: var(--rv-space-2);
  aspect-ratio: 1;
  justify-content: center;
}

/* ---------------------------------------------------------------------- */
/* Select / input primitives                                               */
/* ---------------------------------------------------------------------- */

.rv-select, .rv-input {
  font: inherit;
  font-size: var(--rv-font-size-sm);
  padding: var(--rv-space-2) var(--rv-space-3);
  border-radius: var(--rv-radius-sm);
  border: 1px solid var(--rv-border-strong);
  background: var(--rv-surface);
  color: var(--rv-text);
}
.rv-select:focus-visible, .rv-input:focus-visible {
  outline: 2px solid var(--rv-accent);
  outline-offset: 1px;
}
.rv-input-block, .rv-select-block { width: 100%; }

/* A read-only field shown alongside real inputs in a form (e.g. the Details
   panel's Pool, which is derived from Lane rather than chosen directly) —
   same padding/font as .rv-input so it lines up, no border/background so it
   doesn't look editable. */
.rv-form-value {
  font-size: var(--rv-font-size-sm);
  padding: var(--rv-space-2) 0;
  color: var(--rv-text);
}

/* ---------------------------------------------------------------------- */
/* Entry form (system/interaction create/edit)                             */
/* ---------------------------------------------------------------------- */

.rv-entry-form { display: flex; flex-direction: column; gap: var(--rv-space-3); }
.rv-form-row { display: flex; flex-direction: column; gap: var(--rv-space-1); }
.rv-form-row label { font-size: var(--rv-font-size-sm); font-weight: 600; color: var(--rv-text-muted); }
.rv-form-row-checkbox { flex-direction: row; align-items: center; }
.rv-form-row-checkbox label { display: flex; align-items: center; gap: var(--rv-space-2); font-weight: 500; color: var(--rv-text); }
.rv-field-with-button { display: flex; gap: var(--rv-space-2); }
.rv-field-with-button .rv-input { flex: 1; }
.rv-form-section-heading {
  font-size: var(--rv-font-size-sm); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--rv-text-muted); margin-top: var(--rv-space-2);
  border-top: 1px solid var(--rv-border); padding-top: var(--rv-space-3);
}
.rv-add-property-row { display: flex; gap: var(--rv-space-2); }
.rv-add-property-row .rv-input { flex: 1; }
.rv-reorder-buttons { display: flex; gap: var(--rv-space-1); justify-content: flex-end; }
.rv-reorder-buttons .rv-btn-icon { width: 22px; height: 22px; font-size: 10px; padding: 0; }

/* ---------------------------------------------------------------------- */
/* Model properties editor (drawer's Model tab)                            */
/* ---------------------------------------------------------------------- */

.rv-drawer-help { font-size: var(--rv-font-size-sm); color: var(--rv-text-muted); margin: 0 0 var(--rv-space-3); }
.rv-model-property-list { display: flex; flex-direction: column; gap: var(--rv-space-2); margin-bottom: var(--rv-space-4); }
.rv-model-property-row {
  display: grid;
  grid-template-columns: minmax(120px, 25%) 1fr auto;
  align-items: center;
  gap: var(--rv-space-3);
}
.rv-model-property-label {
  font-size: var(--rv-font-size-sm); font-weight: 600; color: var(--rv-text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rv-model-property-row .rv-input,
.rv-model-property-row .rv-select { width: 100%; }
.rv-model-property-row .rv-input:disabled,
.rv-model-property-row .rv-select:disabled { opacity: 0.6; cursor: not-allowed; }

.rv-model-property-add {
  border-top: 1px solid var(--rv-border);
  padding-top: var(--rv-space-3);
  display: flex; flex-direction: column; gap: var(--rv-space-2);
}
.rv-model-property-add h3 {
  font-size: var(--rv-font-size-sm); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--rv-text-muted); margin: 0;
}
.rv-model-property-add .rv-input,
.rv-model-property-add .rv-select { width: 100%; }
.rv-field-error { color: var(--rv-danger); font-size: var(--rv-font-size-sm); min-height: 1.2em; }

/* ---------------------------------------------------------------------- */
/* JSON viewer shim — exact classNames JsonViewer.js hard-codes into the   */
/* HTML it renders (same reuse pattern as table.js/toast.js above).        */
/* ---------------------------------------------------------------------- */

#jsonTable.rv-wrap { white-space: pre-wrap; word-break: break-word; }
.json-clickable { cursor: pointer; border-radius: var(--rv-radius-sm); }
.json-clickable:hover { background: var(--rv-row-hover); }
.json-clickable:focus-visible { outline: 2px solid var(--rv-accent); outline-offset: 1px; }
.json-highlighted { background: var(--rv-row-selected); outline: 1px solid var(--rv-accent); border-radius: var(--rv-radius-sm); }
.json-item-toggle, .json-section-toggle, .json-layout-toggle {
  user-select: none; color: var(--rv-text-muted); cursor: pointer;
}
.json-section-toggle:focus-visible, .json-layout-toggle:focus-visible {
  outline: 2px solid var(--rv-accent); outline-offset: 1px; border-radius: var(--rv-radius-sm);
}
.rv-entry-form .cell-editor {
  min-height: 1.6em; padding: var(--rv-space-1) var(--rv-space-2);
  border: 1px solid var(--rv-border); border-radius: var(--rv-radius-sm); outline: none;
}
.rv-entry-form .cell-editor:focus { border-color: var(--rv-accent); }

/* ---------------------------------------------------------------------- */
/* Health report                                                           */
/* ---------------------------------------------------------------------- */

.rv-health-report table.table th { width: 45%; }
.rv-health-error { color: var(--rv-danger); font-weight: 600; }
.rv-health-warn  { color: var(--rv-warning); font-weight: 600; }
.rv-health-ok    { color: var(--rv-success); }

/* ---------------------------------------------------------------------- */
/* Modal primitive (native <dialog>)                                       */
/* ---------------------------------------------------------------------- */

.rv-modal {
  border: none;
  border-radius: var(--rv-radius-lg);
  padding: 0;
  background: var(--rv-surface-raised);
  color: var(--rv-text);
  box-shadow: var(--rv-shadow-lg);
  width: min(560px, 92vw);
  max-height: 85vh;
}
.rv-modal.rv-modal-lg { width: min(800px, 94vw); }
.rv-modal::backdrop {
  background: rgba(16, 20, 26, 0.45);
}
.rv-modal[open] { animation: rv-modal-in var(--rv-transition-med); }
@keyframes rv-modal-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

.rv-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--rv-space-4) var(--rv-space-5);
  border-bottom: 1px solid var(--rv-border);
}
.rv-modal-title { font-size: var(--rv-font-size-lg); font-weight: 600; margin: 0; }
.rv-modal-close {
  border: none;
  background: transparent;
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  color: var(--rv-text-muted);
  padding: var(--rv-space-1) var(--rv-space-2);
  border-radius: var(--rv-radius-sm);
}
.rv-modal-close:hover { background: var(--rv-row-hover); color: var(--rv-text); }

/* ---------------------------------------------------------------------- */
/* Help: a `?` beside a dialog's close button, or on the step bar          */
/* ---------------------------------------------------------------------- */

.rv-help-btn {
  inline-size: 1.75rem; block-size: 1.75rem; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--rv-border); border-radius: 50%;
  background: var(--rv-surface); color: var(--rv-text-muted);
  font-weight: 600; line-height: 1; cursor: pointer;
}
.rv-help-btn:hover { color: var(--rv-accent); border-color: var(--rv-accent); }
.rv-help-btn[aria-expanded="true"] { background: var(--rv-row-selected); color: var(--rv-accent); border-color: var(--rv-accent); }
.rv-help-btn:focus-visible { outline: 2px solid var(--rv-accent); outline-offset: 2px; }
.rv-modal-header .rv-help-btn { margin-inline-start: auto; margin-inline-end: var(--rv-space-2); }

.rv-modal-help {
  max-block-size: 45vh; overflow-y: auto;
  padding: var(--rv-space-3) var(--rv-space-5);
  background: var(--rv-bg); border-bottom: 1px solid var(--rv-border);
}
.rv-modal-help[hidden] { display: none; }

.rv-help { font-size: var(--rv-font-size-sm); line-height: 1.5; color: var(--rv-text); max-inline-size: 65ch; }
.rv-help section + section { margin-block-start: var(--rv-space-4); }
.rv-help h3 { font-size: var(--rv-font-size-md); font-weight: 600; margin: 0 0 var(--rv-space-1); text-wrap: balance; }
.rv-help p { margin: 0 0 var(--rv-space-2); }
.rv-help-list {
  display: grid; grid-template-columns: max-content 1fr;
  gap: var(--rv-space-1) var(--rv-space-3); margin: 0 0 var(--rv-space-2);
}
.rv-help-list dt { font-weight: 600; display: inline-flex; align-items: baseline; gap: var(--rv-space-1); white-space: nowrap; }
.rv-help-list dd { margin: 0; }
.rv-help-swatch { inline-size: 10px; block-size: 10px; border: 2px solid; border-radius: 2px; align-self: center; }
.rv-help-current { scroll-margin-block-start: var(--rv-space-2); }
.rv-help-current h3 { color: var(--rv-accent); }
@media (max-width: 480px) {
  .rv-help-list { grid-template-columns: 1fr; }
  .rv-help-list dd { margin-block-end: var(--rv-space-2); }
}

.rv-modal-body {
  padding: var(--rv-space-5);
  overflow-y: auto;
}
.rv-modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--rv-space-2);
  padding: var(--rv-space-4) var(--rv-space-5);
  border-top: 1px solid var(--rv-border);
}

/* ---------------------------------------------------------------------- */
/* Popover primitive (anchored, non-blocking)                              */
/* ---------------------------------------------------------------------- */

.rv-popover {
  position: fixed;
  z-index: var(--rv-z-modal);
  background: var(--rv-surface-raised);
  border: 1px solid var(--rv-border);
  border-radius: var(--rv-radius-md);
  box-shadow: var(--rv-shadow-md);
  min-width: 220px;
  max-width: 280px;
  padding: var(--rv-space-3);
  animation: rv-pop-in 100ms ease;
}
@keyframes rv-pop-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.rv-popover p { margin: 0 0 var(--rv-space-3); font-size: var(--rv-font-size-sm); color: var(--rv-text); }
.rv-popover .rv-popover-actions { display: flex; justify-content: flex-end; gap: var(--rv-space-2); }
/* "Change at" checklist - scrolls once a timeline has many steps, rather
   than growing the context toolbar one button per step. */
.rv-ctx-change-list { display: flex; flex-direction: column; gap: var(--rv-space-1); max-height: 220px; overflow-y: auto; }
.rv-ctx-change-row { display: flex; align-items: center; gap: var(--rv-space-2); font-size: var(--rv-font-size-sm); color: var(--rv-text); cursor: pointer; padding: 2px 0; }

/* Overflow / menu-style popover content */
.rv-menu { display: flex; flex-direction: column; min-width: 200px; }
.rv-menu-group-label {
  font-family: var(--rv-font); font-size: 10px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--rv-text-muted); padding: var(--rv-space-2) var(--rv-space-2) 4px;
}
.rv-menu-item {
  display: flex; align-items: center; gap: var(--rv-space-2);
  padding: var(--rv-space-2); border-radius: var(--rv-radius-sm);
  background: none; border: none; text-align: left; cursor: pointer;
  font-size: var(--rv-font-size-sm); color: var(--rv-text); width: 100%;
}
.rv-menu-item:hover:not(:disabled), .rv-menu-item:focus-visible { background: var(--rv-row-hover); outline: none; }
.rv-menu-item:disabled { color: var(--rv-text-muted); cursor: not-allowed; opacity: .65; }
.rv-menu-check { width: 1.1em; flex: 0 0 1.1em; text-align: center; }
.rv-menu-scroll { max-height: min(260px, 40vh); overflow-y: auto; }
.rv-menu-sep { border: none; border-top: 1px solid var(--rv-border); margin: var(--rv-space-1) 0; }

/* Saved-layout management */
.rv-layout-manager-list { display: flex; flex-direction: column; gap: var(--rv-space-2); min-width: min(620px, 78vw); }
.rv-layout-manager-row {
  display: grid; grid-template-columns: auto minmax(150px, 1fr) auto auto auto;
  gap: var(--rv-space-2); align-items: center;
}
.rv-layout-manager-base-label { grid-column: 2 / -1; color: var(--rv-text-muted); font-size: var(--rv-font-size-sm); }

/* GitHub dialogs (loaded lazily; styling remains in the shared primitives) */
.rv-github-browser { display: flex; flex-direction: column; gap: var(--rv-space-3); min-height: min(520px, 65vh); }
.rv-github-browser-controls { display: grid; grid-template-columns: 1fr 1fr minmax(150px, .8fr); gap: var(--rv-space-3); }
.rv-github-field { display: flex; flex-direction: column; gap: var(--rv-space-1); color: var(--rv-text-muted); font-size: var(--rv-font-size-sm); }
.rv-github-breadcrumb { display: flex; align-items: center; gap: var(--rv-space-1); min-height: 28px; flex-wrap: wrap; }
.rv-btn-link { border: 0; padding: 2px 4px; background: transparent; color: var(--rv-accent); cursor: pointer; border-radius: var(--rv-radius-sm); }
.rv-btn-link:hover, .rv-btn-link:focus-visible { background: var(--rv-row-hover); outline: none; }
.rv-github-file-list { flex: 1; min-height: 260px; max-height: 50vh; overflow: auto; border: 1px solid var(--rv-border); border-radius: var(--rv-radius-md); }
.rv-github-file-item { display: block; width: 100%; border: 0; border-bottom: 1px solid var(--rv-border); padding: var(--rv-space-3); text-align: left; background: var(--rv-surface); color: var(--rv-text); cursor: pointer; }
.rv-github-file-item:hover, .rv-github-file-item[aria-selected="true"] { background: var(--rv-row-selected); }
.rv-reference-browser { display: flex; flex-direction: column; gap: var(--rv-space-3); min-height: min(520px, 65vh); }
.rv-reference-browser-controls { display: grid; grid-template-columns: minmax(180px, .8fr) 1.2fr; gap: var(--rv-space-3); }
.rv-reference-model-list { flex: 1; min-height: 300px; max-height: 55vh; overflow: auto; border: 1px solid var(--rv-border); border-radius: var(--rv-radius-md); }
.rv-reference-model-item { display: flex; flex-direction: column; gap: 3px; width: 100%; border: 0; border-bottom: 1px solid var(--rv-border); padding: var(--rv-space-3); text-align: left; background: var(--rv-surface); color: var(--rv-text); cursor: pointer; }
.rv-reference-model-item span { color: var(--rv-text-muted); font-size: var(--rv-font-size-sm); }
.rv-reference-model-item:hover, .rv-reference-model-item[aria-selected="true"] { background: var(--rv-row-selected); }
@media (max-width: 620px) { .rv-reference-browser-controls { grid-template-columns: 1fr; } }
.rv-github-warning { padding: var(--rv-space-3); border-radius: var(--rv-radius-sm); background: color-mix(in srgb, var(--rv-warning) 14%, transparent); color: var(--rv-text); }
@media (max-width: 700px) { .rv-github-browser-controls { grid-template-columns: 1fr; } }
@media (max-width: 620px) {
  .rv-layout-manager-list { min-width: 0; }
  .rv-layout-manager-row { grid-template-columns: auto 1fr 1fr 1fr; }
  .rv-layout-manager-row .rv-input, .rv-layout-manager-base-label { grid-column: 1 / -1; }
  .rv-layout-manager-row .rv-btn { justify-content: center; }
}

/* ---------------------------------------------------------------------- */
/* Panel primitive (slide-in, non-blocking)                                */
/* ---------------------------------------------------------------------- */

.rv-panel {
  position: fixed;
  top: 0; bottom: 0; right: 0;
  width: min(340px, 92vw);
  background: var(--rv-surface-raised);
  border-left: 1px solid var(--rv-border);
  box-shadow: var(--rv-shadow-lg);
  display: flex; flex-direction: column;
  transform: translateX(100%);
  transition: transform var(--rv-transition-med);
  z-index: var(--rv-z-panel);
}
.rv-panel.rv-panel-open { transform: translateX(0); }
.rv-panel-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--rv-space-4); border-bottom: 1px solid var(--rv-border); flex-shrink: 0;
}
.rv-panel-title { font-size: var(--rv-font-size-lg); font-weight: 600; margin: 0; }
.rv-panel-close {
  border: none; background: transparent; font-size: 1.25rem; line-height: 1; cursor: pointer;
  color: var(--rv-text-muted); padding: var(--rv-space-1) var(--rv-space-2); border-radius: var(--rv-radius-sm);
}
.rv-panel-close:hover { background: var(--rv-row-hover); color: var(--rv-text); }
.rv-panel-body { flex: 1; overflow-y: auto; padding: var(--rv-space-4); }
.rv-panel-footer {
  display: flex; justify-content: flex-end; gap: var(--rv-space-2);
  padding: var(--rv-space-3) var(--rv-space-4); border-top: 1px solid var(--rv-border); flex-shrink: 0;
}

/* ---------------------------------------------------------------------- */
/* Context toolbar (floating, follows canvas selection)                    */
/* ---------------------------------------------------------------------- */

.rv-ctx-toolbar {
  position: fixed;
  z-index: var(--rv-z-panel);
  display: flex; align-items: stretch;
  background: var(--rv-text);
  border-radius: var(--rv-radius-md);
  box-shadow: var(--rv-shadow-md);
  overflow: hidden;
  font-size: var(--rv-font-size-sm);
}
:root[data-theme="dark"] .rv-ctx-toolbar { background: var(--rv-surface-raised); border: 1px solid var(--rv-border-strong); }
/* A step's toggles: on at the step being edited, changed in it. */
.rv-ctx-toolbar .rv-ctx-step[aria-pressed="true"] { background: var(--rv-accent, #2563eb); color: #fff; }
/* A lifted arrow's toolbar: what it stands for, and nothing to edit. */
.rv-ctx-toolbar .rv-ctx-note { padding: 6px 10px; color: var(--rv-surface-raised); white-space: nowrap; }
:root[data-theme="dark"] .rv-ctx-toolbar .rv-ctx-note { color: var(--rv-text); }
.rv-ctx-toolbar input, .rv-ctx-toolbar select {
  background: transparent; border: none; color: var(--rv-text-inverse);
  font: inherit; font-weight: 600; padding: var(--rv-space-2) var(--rv-space-2);
  min-width: 0;
}
:root[data-theme="dark"] .rv-ctx-toolbar input, :root[data-theme="dark"] .rv-ctx-toolbar select { color: var(--rv-text); }
.rv-ctx-toolbar input { width: 11ch; }
.rv-ctx-toolbar input:focus, .rv-ctx-toolbar select:focus { outline: none; background: rgba(255,255,255,.12); }
.rv-ctx-toolbar button {
  background: none; border: none; color: var(--rv-text-inverse); cursor: pointer;
  padding: var(--rv-space-2) var(--rv-space-3); display: flex; align-items: center; gap: 5px;
  border-left: 1px solid rgba(255,255,255,.16); white-space: nowrap;
}
:root[data-theme="dark"] .rv-ctx-toolbar button { color: var(--rv-text); border-left-color: var(--rv-border); }
.rv-ctx-toolbar button:hover { background: rgba(255,255,255,.14); }
:root[data-theme="dark"] .rv-ctx-toolbar button:hover { background: var(--rv-row-hover); }
.rv-ctx-toolbar button.rv-ctx-danger:hover { background: var(--rv-danger); color: #fff; }
.rv-ctx-toolbar button.rv-ctx-active { background: var(--rv-accent); color: #fff; }

/* Drag-to-connect handle — sits on the selected system's right edge. Sized
   and shaped as an arrow (not a small dot) so it reads as a draggable
   connector at a glance. */
.rv-connect-handle {
  position: fixed; z-index: var(--rv-z-panel);
  width: 28px; height: 28px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 15px; line-height: 1; color: var(--rv-accent-text);
  background: var(--rv-accent); border: 2px solid var(--rv-surface);
  box-shadow: var(--rv-shadow-sm); cursor: grab;
  /* Dragged via pointer events (ContextToolbar.js) — appended to
     document.body, outside the diagram SVG's own touch-action:none, so it
     needs its own or a touch-drag here scrolls the page instead. */
  touch-action: none;
}
.rv-connect-handle:hover  { transform: scale(1.15); }
.rv-connect-handle:active { cursor: grabbing; }

/* Selected-interaction endpoint handles — hollow at FROM, filled at TO, so
   the direction reads at a glance without needing the arrowhead. Drag either
   one onto a different system to reconnect that end. */
.rv-edge-endpoint {
  position: fixed; z-index: var(--rv-z-panel);
  width: 16px; height: 16px; border-radius: 50%;
  border: 2.5px solid var(--rv-accent);
  box-shadow: var(--rv-shadow-sm); cursor: grab;
  touch-action: none; /* see .rv-connect-handle's comment above */
}
.rv-edge-endpoint-source { background: var(--rv-surface); }
.rv-edge-endpoint-target { background: var(--rv-accent); }
.rv-edge-endpoint:hover  { transform: scale(1.2); }
.rv-edge-endpoint:active { cursor: grabbing; }

/* These two handles are sized for mouse precision (28px/16px) — on a
   coarse (touch) pointer, enlarge the actual hit area to the standard
   ~44px minimum touch target via an invisible centered box, without
   changing how either handle looks for mouse users. */
@media (pointer: coarse) {
  .rv-connect-handle,
  .rv-edge-endpoint {
    position: fixed; /* re-stated: containing block for the ::before below */
  }
  .rv-connect-handle::before,
  .rv-edge-endpoint::before {
    content: '';
    position: absolute;
    top: 50%; left: 50%;
    width: 44px; height: 44px;
    transform: translate(-50%, -50%);
  }
}

.rv-connect-drag-overlay {
  position: fixed; inset: 0; width: 100vw; height: 100vh;
  z-index: var(--rv-z-modal); pointer-events: none;
}
.rv-connect-drag-overlay line {
  stroke: var(--rv-accent); stroke-width: 2; stroke-dasharray: 6 4;
}
/* The glow alone can be too subtle to notice at a glance, especially on
   sparse shapes (Actor/DataStore) where most of the bounding box is
   unpainted — reinforce it with a direct stroke override on every child
   shape, the same proven pattern test.html uses for its own drop-target
   highlight, so the vertex's outline itself visibly turns blue. */
.rv-connect-drop-target { filter: drop-shadow(0 0 8px var(--rv-accent)) drop-shadow(0 0 2px var(--rv-accent)); }
.rv-connect-drop-target > rect:not(.rv-hitarea),
.rv-connect-drop-target > ellipse,
.rv-connect-drop-target > circle,
.rv-connect-drop-target > path,
.rv-connect-drop-target > polygon,
.rv-connect-drop-target > line {
  stroke: var(--rv-accent) !important;
  stroke-width: 3 !important;
}

/* Sequence-diagram reorder: no dedicated handle or overlay element — the
   selected message, fragment tab, or participant is dragged directly, the
   same way a Default-layout vertex drag moves the shape itself (see
   ContextToolbar.js's show()/_startReorderDrag and
   GraphAPI.enableVertexEditing), constrained to one axis. The cursor
   (grab/grabbing, set inline to out-specificity the shape's own inline
   cursor:pointer — see SVGRenderer.js) is the only affordance that it's
   draggable. Only wired up while the Sequence layout is active. */

/* Selected edge: GraphAPI.highlight() already adds .rv-selected and a blue
   drop-shadow filter, but a soft glow around a 1-2px line reads as barely
   different from unselected — the line itself needs to turn blue. Targets
   the visible stroke path only (SVGRenderer._edge()'s first child is an
   invisible wide hit-stroke with stroke="transparent", excluded here so it
   stays untouched). Arrowheads use fill/stroke="context-stroke" so they
   pick up this same colour automatically. */
.rv-edge.rv-selected > path:not([stroke="transparent"]) {
  stroke: var(--rv-accent) !important;
}

/* Diagram redraw animation (see SVGRenderer.js's render()/_vertex()/_edge()).
   The JS-side `animate` flag is the real gate — this is defense-in-depth so
   the rules simply don't exist if the browser/OS prefers reduced motion. */
@media (prefers-reduced-motion: no-preference) {
  .rv-flip-anim { transition: transform var(--rv-transition-diagram); }

  /* These groups draw with absolute diagram coordinates (not relative to
     their own origin), so scaling needs transform-box:fill-box or it would
     shrink toward the SVG canvas's (0,0) corner instead of its own center. */
  .rv-vertex-enter, .rv-vertex-exit {
    transform-box: fill-box;
    transform-origin: center;
  }
  .rv-vertex-enter { animation: rv-shape-in  var(--rv-transition-diagram); }
  .rv-vertex-exit  { animation: rv-shape-out var(--rv-transition-diagram) forwards; }

  @keyframes rv-shape-in  { from { opacity: 0; transform: scale(0.85); } to { opacity: 1; transform: scale(1); } }
  @keyframes rv-shape-out { from { opacity: 1; transform: scale(1); }   to { opacity: 0; transform: scale(0.85); } }

  /* Edges: fade only — a scale() "grow-in" on a long diagonal line reads
     oddly (non-uniform visual weight along its length), unlike a vertex. */
  .rv-edge-enter    { animation: rv-fade-in  var(--rv-transition-diagram); }
  .rv-edge-exit     { animation: rv-fade-out var(--rv-transition-diagram) forwards; }
  .rv-edge-path-in  { animation: rv-fade-in  var(--rv-transition-diagram); }
  .rv-edge-outgoing { animation: rv-fade-out var(--rv-transition-diagram) forwards; pointer-events: none; }

  @keyframes rv-fade-in  { from { opacity: 0; } to { opacity: 1; } }
  @keyframes rv-fade-out { from { opacity: 1; } to { opacity: 0; } }
}

/* ---------------------------------------------------------------------- */
/* Tabs primitive                                                           */
/* ---------------------------------------------------------------------- */

.rv-tabs {
  display: flex;
  gap: var(--rv-space-1);
  border-bottom: 1px solid var(--rv-border);
  padding: 0 var(--rv-space-3);
}
.rv-tab {
  font: inherit;
  font-size: var(--rv-font-size-sm);
  font-weight: 600;
  color: var(--rv-text-muted);
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  padding: var(--rv-space-3) var(--rv-space-3);
  cursor: pointer;
  transition: color var(--rv-transition-fast), border-color var(--rv-transition-fast);
}
.rv-tab:hover               { color: var(--rv-text); }
.rv-tab.rv-tab-active       { color: var(--rv-accent); border-bottom-color: var(--rv-accent); }
.rv-tab:disabled            { opacity: 0.4; cursor: not-allowed; }

.rv-tab-panel { display: none; }
.rv-tab-panel.rv-tab-panel-active { display: block; }

/* ---------------------------------------------------------------------- */
/* Toast shim — exact classNames toast.js hard-codes into the DOM          */
/* ---------------------------------------------------------------------- */

#toastContainer {
  position: fixed;
  top: var(--rv-space-4);
  right: var(--rv-space-4);
  z-index: var(--rv-z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--rv-space-2);
}

.toast {
  border-radius: var(--rv-radius-md);
  opacity: 0;
  transform: translateX(12px);
  transition: opacity var(--rv-transition-med), transform var(--rv-transition-med);
}
.toast.show { opacity: 1; transform: translateX(0); }
.toast.border-0 { border: none; }
.toast.shadow   { box-shadow: var(--rv-shadow-md); }

.d-flex             { display: flex; }
.align-items-center { align-items: center; }
.flex-shrink-0       { flex-shrink: 0; }
.flex-grow-1         { flex-grow: 1; }
.px-3 { padding-left: var(--rv-space-3); padding-right: var(--rv-space-3); }
.py-2 { padding-top: var(--rv-space-2); padding-bottom: var(--rv-space-2); }
.p-0  { padding: 0; }
.ms-3 { margin-left: var(--rv-space-3); }
.fw-semibold { font-weight: 600; }
.toast-body { font-size: var(--rv-font-size-md); }

.text-bg-success { background: var(--rv-success); color: #fff; }
.text-bg-danger  { background: var(--rv-danger); color: #fff; }
.text-bg-primary { background: var(--rv-info); color: #fff; }

.btn-close {
  border: none;
  background: transparent;
  cursor: pointer;
  width: 1.2em;
  height: 1.2em;
  position: relative;
  opacity: 0.8;
}
.btn-close:hover { opacity: 1; }
.btn-close::before, .btn-close::after {
  content: '';
  position: absolute;
  top: 50%; left: 50%;
  width: 100%; height: 2px;
  background: currentColor;
  transform-origin: center;
}
.btn-close::before { transform: translate(-50%, -50%) rotate(45deg); }
.btn-close::after  { transform: translate(-50%, -50%) rotate(-45deg); }
.btn-close-white   { color: #fff; }

/* ---------------------------------------------------------------------- */
/* Table shim — exact classNames table.js hard-codes into the DOM          */
/* ---------------------------------------------------------------------- */

.table-scroll-container {
  overflow: auto;
  height: 100%;
  border: 1px solid var(--rv-border);
  border-radius: var(--rv-radius-md);
  background: var(--rv-surface);
}

table.table {
  /* auto (not 100%) so narrow columns (checkbox, short Type/From/To values)
     stay narrow instead of the browser's auto-layout algorithm stretching
     them to fill the drawer's width — the scroll container handles the
     rare case where content is wider than the panel. */
  width: auto;
  border-collapse: collapse;
  font-size: var(--rv-font-size-sm);
}
table.table th, table.table td {
  padding: var(--rv-space-2) var(--rv-space-3);
  border-bottom: 1px solid var(--rv-border);
  text-align: left;
  white-space: nowrap;
}
/* table.js's per-column filter <input> is a bare, unstyled <input type="text">
   (table.js:1009) — at its browser-default intrinsic width (~170-190px) it
   forces every column to that width regardless of the actual data, defeating
   width:auto above. Capped, not just narrowed, so the column can still
   shrink below it when the data is shorter still. */
table.table thead input[type="text"] {
  width: 4em;
  box-sizing: border-box;
}
/* :not(.highlighted) so a selected row's background can never lose to the
   stripe on a tie/specificity fight — table.table.table-striped tbody
   tr:nth-child(even) (3 classes) actually out-specifies plain
   tr.highlighted (2 classes), so on every even row the stripe silently won
   and the row never looked selected at all. Excluding .highlighted here
   means there's nothing left to out-specify — the two rules simply don't
   overlap. */
table.table.table-striped tbody tr:nth-child(even):not(.highlighted) { background: var(--rv-row-stripe); }
table.table tbody tr:hover                          { background: var(--rv-row-hover); }
/* Background alone reads as barely different from hover/stripe (same
   family of light tints) — the inset accent bar is what actually makes a
   selected row unambiguous at a glance, matching the stronger treatment
   .json-highlighted already gets (background + outline) below. inset
   box-shadow, not border-left, so it doesn't shift cell content over. */
table.table tbody tr.highlighted {
  background: var(--rv-row-selected);
  box-shadow: inset 3px 0 0 var(--rv-accent);
}
table.table tbody tr.dragging                       { opacity: 0.5; }
table.table th.drag-over, table.table td.drag-over  { outline: 2px dashed var(--rv-accent); outline-offset: -2px; }

tr.sticky-header th, tr.sticky-filter th {
  position: sticky;
  background: var(--rv-surface-raised);
}
tr.sticky-header th { top: 0; z-index: 2; }
tr.sticky-filter th { top: 2.4em; z-index: 1; }

.drag-handle-cell { width: 1.5em; text-align: center; }
/* table.js renders the handle as an inline SVG (see its comment) rather than
   a Font Awesome glyph — this bundle never loads Font Awesome, so a font-icon
   class here would render nothing. inline-flex centres the SVG in the cell;
   its size and colour come from this rule so it stays legible against the
   drawer's stripes/hover tint. */
.drag-handle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  color: var(--rv-text-muted);
  cursor: grab;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  touch-action: none;
}
.drag-handle svg { width: 11px; height: 18px; }
.drag-handle:hover { color: var(--rv-accent); }
.drag-handle:active { cursor: grabbing; }

.form-check-input {
  width: 1rem;
  height: 1rem;
  accent-color: var(--rv-accent);
  cursor: pointer;
}

/* table.js writes literal `<i class="fas fa-filter">` / `fa-grip-vertical`
 * markup — supply glyphs for just those two icons rather than loading the
 * Font Awesome font/CSS bundle. */
.fas.fa-filter::before        { content: '\1F50D'; font-size: 0.85em; } /* magnifier */
.fas.fa-grip-vertical::before { content: '\22EE';   font-size: 1em; }   /* vertical ellipsis */

/* Busy overlay — shown over the canvas while a synchronous, potentially
   slow operation runs (the routing pass is the one that actually matters
   here; see app.js's withBusySpinner). The spinner
   itself is purely reassurance — the work is genuinely synchronous JS, so
   the tab *will* stop responding to input for its duration regardless;
   showing this first (and yielding a frame before starting the work) just
   means the freeze is announced rather than silent. */
.rv-busy-overlay {
  position: absolute; inset: 0; z-index: var(--rv-z-panel);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--rv-space-3);
  background: color-mix(in srgb, var(--rv-bg) 70%, transparent);
  color: var(--rv-text-muted); font-size: var(--rv-font-size-sm);
}
/* Author-stylesheet rules always beat the UA stylesheet's own
   [hidden] { display: none } — regardless of specificity or source order,
   an author's "normal" declaration overrides a UA "normal" one — so the
   unconditional `display: flex` above silently wins over the `hidden`
   attribute entirely, toggling it in JS did nothing visible. This needs to
   be spelled out explicitly, at higher specificity than the bare class. */
.rv-busy-overlay[hidden] { display: none; }
.rv-spinner {
  width: 32px; height: 32px; border-radius: 50%;
  border: 3px solid var(--rv-border);
  border-top-color: var(--rv-accent);
  animation: rv-spin 0.8s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .rv-spinner { animation: none; border-top-color: var(--rv-border); }
}
@keyframes rv-spin { to { transform: rotate(360deg); } }

/* SVG-native diagram legend. Its export-critical colours are explicit SVG
   attributes; these rules add live-canvas focus and collision feedback. */
.rv-diagram-legend:focus { outline: none; }
.rv-diagram-legend:focus .rv-legend-background {
  stroke: var(--rv-accent, #0969da);
  stroke-width: 2;
}
.rv-diagram-legend.rv-legend-invalid .rv-legend-background { fill: #fff1f0; }

/* ---------------------------------------------------------------------- */
/* Colour mapping panel (ui/ColourPanel.js)                                */
/* ---------------------------------------------------------------------- */

.rv-panel-help {
  max-block-size: 50vh; overflow-y: auto; flex-shrink: 0;
  padding: var(--rv-space-3) var(--rv-space-4);
  background: var(--rv-bg); border-bottom: 1px solid var(--rv-border);
}
.rv-panel-help[hidden] { display: none; }
.rv-panel-header .rv-help-btn { margin-inline-start: auto; margin-inline-end: var(--rv-space-2); }

.rv-colour-panel-host { width: min(380px, 94vw); }
.rv-colour-panel { display: flex; flex-direction: column; gap: var(--rv-space-3); }
.rv-colour-tabs { display: grid; grid-template-columns: 1fr 1fr; gap: 0; }
.rv-colour-tabs .rv-btn { justify-content: center; border-radius: 0; }
.rv-colour-tabs .rv-btn:first-child { border-radius: var(--rv-radius-sm) 0 0 var(--rv-radius-sm); }
.rv-colour-tabs .rv-btn:last-child { border-radius: 0 var(--rv-radius-sm) var(--rv-radius-sm) 0; border-inline-start: none; }
.rv-colour-tabs .rv-btn[aria-pressed="true"] { background: var(--rv-row-selected); color: var(--rv-accent); font-weight: 600; }

.rv-form-label { font-size: var(--rv-font-size-sm); font-weight: 600; color: var(--rv-text-muted); }
.rv-colour-hint { font-size: var(--rv-font-size-sm); color: var(--rv-text-muted); }

.rv-colour-scope, .rv-colour-types {
  border: 1px solid var(--rv-border); border-radius: var(--rv-radius-sm);
  padding: var(--rv-space-2) var(--rv-space-3); margin: 0;
  display: flex; flex-direction: column; gap: var(--rv-space-1);
}
.rv-colour-scope legend, .rv-colour-types legend { font-size: var(--rv-font-size-sm); font-weight: 600; color: var(--rv-text-muted); padding: 0 var(--rv-space-1); }
.rv-colour-scope label, .rv-colour-types label { display: flex; align-items: center; gap: var(--rv-space-2); font-size: var(--rv-font-size-sm); }

.rv-colour-palettes { display: flex; flex-direction: column; gap: var(--rv-space-1); }
.rv-colour-palette {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: var(--rv-space-2);
  padding: var(--rv-space-1) var(--rv-space-2); border: 1px solid transparent; border-radius: var(--rv-radius-sm); cursor: pointer;
}
.rv-colour-palette:hover { background: var(--rv-row-hover); }
.rv-colour-palette:has(input:checked) { border-color: var(--rv-accent); background: var(--rv-row-selected); }
.rv-colour-palette-name { font-size: var(--rv-font-size-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rv-colour-strip { display: inline-flex; border-radius: 3px; overflow: hidden; box-shadow: 0 0 0 1px var(--rv-border); }
.rv-colour-chip { inline-size: 14px; block-size: 14px; }
.rv-colour-edit-palettes { align-self: flex-start; }

.rv-colour-values { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.rv-colour-value {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto auto auto; align-items: center;
  gap: var(--rv-space-2); padding: 2px var(--rv-space-1); border-radius: var(--rv-radius-sm);
}
.rv-colour-value:hover { background: var(--rv-row-hover); }
.rv-colour-value-label { overflow-wrap: anywhere; font-size: var(--rv-font-size-sm); }
.rv-colour-count { font-size: var(--rv-font-size-sm); color: var(--rv-text-muted); font-variant-numeric: tabular-nums; }
.rv-colour-other .rv-colour-value-label { color: var(--rv-text-muted); font-style: italic; }
.rv-colour-pick {
  inline-size: 26px; block-size: 22px; padding: 0; border: 1px solid var(--rv-border); border-radius: 4px;
  background: none; cursor: pointer;
}
.rv-colour-pick::-webkit-color-swatch-wrapper { padding: 2px; }
.rv-colour-pick::-webkit-color-swatch { border: none; border-radius: 2px; }
.rv-colour-move, .rv-colour-unpin { padding: 0 var(--rv-space-1); min-block-size: 22px; font-size: var(--rv-font-size-sm); }
.rv-colour-notes { background: var(--rv-bg); border-radius: var(--rv-radius-sm); padding: var(--rv-space-2) var(--rv-space-3); font-size: var(--rv-font-size-sm); }
.rv-colour-notes p { margin: 0; }
.rv-colour-notes p + p { margin-block-start: var(--rv-space-1); }
.rv-colour-more summary { cursor: pointer; font-size: var(--rv-font-size-sm); font-weight: 600; color: var(--rv-text-muted); }
.rv-colour-more[open] { display: flex; flex-direction: column; gap: var(--rv-space-3); }

/* Your palettes (ui/PaletteDialog.js) */
.rv-palette-dialog { display: flex; flex-direction: column; gap: var(--rv-space-4); min-inline-size: min(520px, 86vw); }
.rv-palette-start { display: flex; flex-wrap: wrap; align-items: center; gap: var(--rv-space-2); }
.rv-palette-start label { font-size: var(--rv-font-size-sm); font-weight: 600; color: var(--rv-text-muted); }
.rv-palette-list { display: flex; flex-direction: column; gap: var(--rv-space-1); }
.rv-palette-editor { border-top: 1px solid var(--rv-border); padding-top: var(--rv-space-3); }
.rv-palette-add { align-self: flex-start; }
.rv-palette-actions, .rv-palette-files { display: flex; flex-wrap: wrap; gap: var(--rv-space-2); align-items: center; }
.rv-palette-note-warn::before { content: "⚠ "; }
