:root,
[data-theme="dark"] {
  /* Dark Color Palette */
  --color-bg: #0f1117;
  --color-surface: #1a1d27;
  --color-surface-elevated: #242836;
  --color-border: #2e3347;
  --color-text-primary: #e8eaed;
  --color-text-secondary: #9aa0b4;
  --color-accent-primary: #5b8def;
  --color-accent-primary-hover: #4a7bd9;
  --color-accent-secondary: #e8985e;
  --color-primary: #5b8def; /* alias used across templates; was undefined */
  --color-surface-hover: #24283b;
  --color-has-son: #5b8def;
  --color-has-sil: #e8985e;
  --color-success: #5ec26a;
  --color-danger: #e85b5b;
  --color-danger-hover: #d24848;
  --color-warning: #e8c95e;
  --graph-bg: #0f1117;
  
  /* Typography */
  --font-family: 'Inter', sans-serif;

  /* Two stacks, because these documents are two writing systems at once.
     Latin text set in a CJK face looks wrong and, worse, its parentheses and
     apostrophes come out full-width. Hanja set in a Latin face falls back
     glyph-by-glyph to whatever the system finds, which is where a rare Sinitic
     character turns into a box.

     The CJK stack names full Noto Serif CJK first (installed, complete through
     the extensions), then the Google-served Noto Serif JP loaded in index.html,
     then the system faces that ship with macOS, Windows and Korean systems, so
     there is something to render with whether or not the network is reachable.
     No web-served font covers all of Extension B; a locally installed full Noto
     Serif CJK is the only thing that does, and this stack prefers it. */
  --font-cjk: 'Noto Serif CJK JP', 'Noto Serif CJK KR', 'Noto Serif JP', 'Source Han Serif JP',
              'Hiragino Mincho ProN', 'Yu Mincho', 'Songti SC', 'SimSun',
              'Nanum Myeongjo', 'Batang', 'AppleMyungjo', serif;
  --font-latin: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
                Helvetica, Arial, sans-serif;
  
  /* Dimensions & Layout */
  --top-bar-height: 60px;
  --sidebar-width: 380px;
  --detail-panel-height: 200px;
  --border-radius: 6px;
  --border-radius-lg: 10px;
  --border-radius-sm: 4px;
  
  /* Transitions */
  --transition-fast: 0.2s ease;
  
  /* Shadows */
  --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.2);
  --shadow-md: 0 4px 8px rgba(0, 0, 0, 0.3);
  --shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.4);
}

[data-theme="light"] {
  /* Light Color Palette */
  --color-bg: #f8fafc;
  --color-surface: #ffffff;
  --color-surface-elevated: #f1f5f9;
  --color-border: #cbd5e1;
  --color-text-primary: #0f172a;
  --color-text-secondary: #475569;
  --color-accent-primary: #2563eb;
  --color-accent-primary-hover: #1d4ed8;
  --color-accent-secondary: #d97706;
  --color-primary: #2563eb;
  --color-surface-hover: #e2e8f0;
  --color-has-son: #2563eb;
  --color-has-sil: #d97706;
  --color-success: #16a34a;
  --color-danger: #dc2626;
  --color-danger-hover: #b91c1c;
  --color-warning: #d97706;
  --graph-bg: #f8fafc;
  
  /* Shadows */
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
  --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: var(--font-family);
  background-color: var(--color-bg);
  color: var(--color-text-primary);
  font-size: 14px;
  line-height: 1.5;
  overflow: hidden;
  font-feature-settings: 'tnum';
}

/* Base App Layout */
.app-container {
  display: flex;
  flex-direction: column;
  height: 100vh;
  width: 100vw;
}

/* Typography Utilities */
h1, h2, h3, h4, h5, h6 {
  font-weight: 600;
  color: var(--color-text-primary);
}

.text-secondary {
  color: var(--color-text-secondary);
}

.text-sm {
  font-size: 12px;
}

.w-full {
  width: 100%;
}

.w-auto {
  width: auto;
}

.flex-1 {
  flex: 1;
}

.hidden {
  display: none !important;
}

/* Scrollbars */
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

::-webkit-scrollbar-track {
  background: var(--color-bg);
}

::-webkit-scrollbar-thumb {
  background: var(--color-border);
  border-radius: 4px;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--color-text-secondary);
}

/* Top Bar */
.top-bar {
  height: var(--top-bar-height);
  background-color: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 20px;
  z-index: 10;
}

/* The tagline is the one expendable string in the bar, so it is the only
   flex item allowed to shrink: it ellipsises while the controls keep their
   natural size. Without this the whole bar squeezes and the view toggles get
   clipped by .view-toggles' own overflow:hidden. */
.top-bar-left {
  display: flex;
  align-items: center;
  gap: 16px;
  flex: 0 1 auto;
  min-width: 0;
}

.brand-group {
  display: flex;
  align-items: baseline;
  gap: 10px;
  min-width: 0;
}

.app-title {
  font-size: 19px;
  font-weight: 700;
  letter-spacing: 0.5px;
  flex: 0 0 auto;
  white-space: nowrap;
}

.app-subtitle {
  font-size: 13px;
  color: var(--color-text-secondary);
  font-weight: 400;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.top-bar-center {
  display: flex;
  align-items: center;
  gap: 20px;
  flex: 0 0 auto;
}

.top-bar-right {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 0 0 auto;
}

.nav-toggle-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 6px 14px;
  font-size: 13px;
  font-weight: 500;
  border-radius: var(--border-radius, 6px);
  border: 1px solid var(--color-border);
  background-color: var(--color-surface-elevated);
  color: var(--color-text-primary);
  cursor: pointer;
  transition: all var(--transition-fast);
  user-select: none;
  font-family: inherit;
  white-space: nowrap;
}

.nav-toggle-btn:hover {
  background-color: var(--color-border);
  border-color: var(--color-accent-primary, #5b8def);
}

.nav-toggle-btn.active {
  background-color: rgba(91, 141, 239, 0.15);
  border-color: var(--color-accent-primary, #5b8def);
  color: var(--color-accent-primary, #5b8def);
  font-weight: 600;
}

.nav-btn-icon {
  font-size: 14px;
  line-height: 1;
}

.nav-btn-text {
  line-height: 1;
}

.view-toggles {
  display: flex;
  background-color: var(--color-bg);
  border-radius: var(--border-radius);
  border: 1px solid var(--color-border);
  overflow: hidden;
}

.btn-toggle {
  background: none;
  border: none;
  color: var(--color-text-secondary);
  padding: 6px 16px;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: all var(--transition-fast);
}

.btn-toggle:hover {
  color: var(--color-text-primary);
  background-color: rgba(255, 255, 255, 0.05);
}

.btn-toggle.active {
  background-color: var(--color-surface-elevated);
  color: var(--color-text-primary);
}

/* Main Layout */
.main-layout {
  display: flex;
  flex: 1;
  overflow: hidden;
  position: relative;
}

/* Sidebar */
.sidebar {
  width: var(--sidebar-width, 380px);
  min-width: var(--sidebar-width, 380px);
  max-width: var(--sidebar-width, 380px);
  background-color: var(--color-surface);
  border-right: 1px solid var(--color-border);
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  transition: width var(--transition-fast), transform var(--transition-fast);
  overflow-y: auto;
  overflow-x: hidden;
  z-index: 15;
  position: relative;
}

.sidebar.collapsed {
  width: 0 !important;
  min-width: 0 !important;
  max-width: 0 !important;
  border-right: none !important;
  transform: translateX(-100%);
  visibility: hidden !important;
  pointer-events: none !important;
  display: none !important;
}

.sidebar-header {
  height: 48px;
  padding: 0 18px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
}

.sidebar-header h2 {
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--color-text-secondary);
}

.sidebar-content {
  flex: 1;
  overflow-y: auto;
  padding: 16px 18px;
}

.tool-section {
  display: none;
  flex-direction: column;
  gap: 16px;
}

.tool-section.active {
  display: flex;
}

.tool-group {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.tool-group h3 {
  font-size: 13px;
  color: var(--color-text-secondary);
  margin-bottom: 4px;
}

.divider {
  border: none;
  border-top: 1px solid var(--color-border);
  margin: 8px 0;
}

/* Form Elements */
.form-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.form-group.row {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
}

.form-group label {
  font-size: 12px;
  color: var(--color-text-secondary);
  font-weight: 500;
}

.text-input, .dropdown-input, .number-input {
  background-color: var(--color-bg);
  border: 1px solid var(--color-border);
  color: var(--color-text-primary);
  border-radius: var(--border-radius-sm);
  padding: 8px 12px;
  font-size: 13px;
  font-family: var(--font-family);
  transition: border-color var(--transition-fast);
  outline: none;
}

.text-input:focus, .dropdown-input:focus, .number-input:focus {
  border-color: var(--color-accent-primary);
}

.dropdown-input {
  appearance: none;
  background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%239aa0b4%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E");
  background-repeat: no-repeat;
  background-position: right 8px top 50%;
  background-size: 9px auto;
  padding-right: 22px;
}

.radio-group {
  display: flex;
  gap: 16px;
}

.radio-group label {
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  color: var(--color-text-primary);
}

.radio-group input[type="radio"] {
  accent-color: var(--color-accent-primary);
}

input[type="range"] {
  accent-color: var(--color-accent-primary);
  width: 100%;
}

/* Color Picker */
.color-picker {
  -webkit-appearance: none;
  border: none;
  width: 32px;
  height: 32px;
  border-radius: var(--border-radius-sm);
  cursor: pointer;
  padding: 0;
  background: none;
}

.color-picker::-webkit-color-swatch-wrapper {
  padding: 0;
}

.color-picker::-webkit-color-swatch {
  border: 1px solid var(--color-border);
  border-radius: var(--border-radius-sm);
}

.input-row {
  display: flex;
  gap: 8px;
  align-items: center;
}

.patriline-card {
  padding: 10px 12px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--border-radius, 6px);
  display: flex;
  flex-direction: column;
  gap: 7px;
  transition: border-color var(--transition-fast);
}

.patriline-card:hover {
  border-color: var(--color-accent-primary, #5b8def);
}

.appearance-row {
  display: grid;
  grid-template-columns: 80px 1fr;
  align-items: center;
}

/* Switch Toggle */
.form-group.toggle-row,
.toggle-row {
  display: flex !important;
  flex-direction: row !important;
  align-items: center !important;
  gap: 10px !important;
}

.switch {
  position: relative;
  display: inline-block;
  width: 40px;
  height: 20px;
}

.switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: var(--color-bg);
  border: 1px solid var(--color-border);
  transition: var(--transition-fast);
}

.slider:before {
  position: absolute;
  content: "";
  height: 14px;
  width: 14px;
  left: 2px;
  bottom: 2px;
  background-color: var(--color-text-secondary);
  transition: var(--transition-fast);
}

input:checked + .slider {
  background-color: var(--color-accent-primary);
  border-color: var(--color-accent-primary);
}

input:checked + .slider:before {
  background-color: #fff;
  transform: translateX(20px);
}

.slider.round {
  border-radius: 20px;
}

.slider.round:before {
  border-radius: 50%;
}

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 8px 16px;
  border-radius: var(--border-radius);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: all var(--transition-fast);
  border: 1px solid transparent;
  font-family: var(--font-family);
  outline: none;
}

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

.btn-primary {
  background-color: var(--color-accent-primary);
  color: #fff;
}

.btn-primary:hover {
  background-color: var(--color-accent-primary-hover);
}

.btn-secondary {
  background-color: var(--color-surface-elevated);
  border-color: var(--color-border);
  color: var(--color-text-primary);
}

.btn-secondary:hover {
  background-color: var(--color-border);
}

.btn-danger {
  background-color: var(--color-danger);
  color: #fff;
}

.btn-danger:hover {
  background-color: var(--color-danger-hover);
}

.icon-btn {
  background: none;
  border: none;
  color: var(--color-text-secondary);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 4px;
  border-radius: var(--border-radius-sm);
  transition: all var(--transition-fast);
}

.icon-btn:hover {
  color: var(--color-text-primary);
  background-color: rgba(255, 255, 255, 0.1);
}

.btn-group-h {
  display: flex;
  gap: 8px;
}

.badge {
  background-color: var(--color-bg);
  color: var(--color-text-primary);
  border-radius: 10px;
  padding: 2px 6px;
  font-size: 11px;
  font-weight: 700;
}

/* Content Area */
.content-area {
  flex: 1;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
}

.view-container {
  display: none;
  flex: 1;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}

.view-container.active {
  display: flex;
}

/* Table View */
.table-tabs {
  display: flex;
  background-color: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  padding: 0 16px;
}

.tab-btn {
  background: none;
  border: none;
  color: var(--color-text-secondary);
  padding: 12px 24px;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  transition: all var(--transition-fast);
}

.tab-btn:hover {
  color: var(--color-text-primary);
}

.tab-btn.active {
  color: var(--color-accent-primary);
  border-bottom-color: var(--color-accent-primary);
}

.table-wrapper {
  flex: 1;
  overflow: auto;
  position: relative;
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  text-align: left;
}

.data-table th, .data-table td {
  padding: 10px 16px;
  border-bottom: 1px solid var(--color-border);
  white-space: nowrap;
}

.data-table th {
  background-color: var(--color-surface-elevated);
  color: var(--color-text-secondary);
  font-weight: 600;
  font-size: 12px;
  text-transform: none;
  position: sticky;
  top: 0;
  z-index: 1;
  cursor: pointer;
  user-select: none;
}

.data-table th:hover {
  background-color: var(--color-border);
}

.data-table tbody tr {
  transition: background-color var(--transition-fast);
  cursor: pointer;
}

.data-table tbody tr:hover {
  background-color: rgba(255, 255, 255, 0.02);
}

.data-table tbody tr.selected {
  background-color: rgba(91, 141, 239, 0.1);
}

.data-table tbody tr.deleted {
  opacity: 0.5;
}

.data-table tbody tr.deleted td {
  text-decoration: line-through;
}

.data-table tbody tr.modified td {
  color: var(--color-warning);
}

.row-actions {
  display: flex;
  gap: 8px;
}

.doc-cell {
  white-space: normal;
  word-break: break-word;
  min-width: 180px;
  max-width: 380px;
  line-height: 1.5;
}

.doc-link {
  transition: all var(--transition-fast);
}

.doc-link:hover {
  background: var(--color-accent-primary, #5b8def) !important;
  color: #ffffff !important;
  border-color: var(--color-accent-primary, #5b8def) !important;
  text-decoration: none;
}

.table-pagination {
  height: 48px;
  background-color: var(--color-surface);
  border-top: 1px solid var(--color-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 16px;
}

.rows-per-page {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--color-text-secondary);
}

.pagination-controls {
  display: flex;
  align-items: center;
  gap: 16px;
  font-size: 13px;
}

.btn-icon {
  background: var(--color-surface-elevated);
  border: 1px solid var(--color-border);
  color: var(--color-text-primary);
  width: 28px;
  height: 28px;
  border-radius: var(--border-radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.btn-icon:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Graph View */
#graph-container {
  flex: 1;
  min-height: 0;
  background-color: var(--color-bg);
  overflow: hidden;
  cursor: grab;
  position: relative;
}

/* A monochrome graph gets a neutral ground. The default canvas is
   var(--color-bg) — a blue-tinted slate in dark, a cool off-white in light —
   and either is a colour behind a picture drawn without any. */
body[data-graph-scheme="monochrome"] #graph-container { background-color: #000000; }
html[data-theme="light"] body[data-graph-scheme="monochrome"] #graph-container { background-color: #ffffff; }

#graph-svg {
  width: 100%;
  height: 100%;
  display: block;
}

/* Autocomplete */
.autocomplete-wrapper {
  position: relative;
  width: 100%;
  box-sizing: border-box;
}

.autocomplete-wrapper .text-input {
  width: 100%;
  box-sizing: border-box;
}

.autocomplete-dropdown {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  width: 100%;
  box-sizing: border-box;
  background: var(--color-surface-elevated);
  border: 1px solid var(--color-border);
  border-radius: var(--border-radius);
  max-height: 280px;
  overflow-y: auto;
  z-index: 500;
  box-shadow: var(--shadow-lg);
}

.autocomplete-dropdown.hidden {
  display: none;
}

.autocomplete-item {
  padding: 8px 10px;
  cursor: pointer;
  border-bottom: 1px solid rgba(46, 51, 71, 0.4);
  transition: background var(--transition-fast);
  word-break: break-word;
}

.autocomplete-item:last-child {
  border-bottom: none;
}

.autocomplete-item:hover,
.autocomplete-item.active {
  background: rgba(91, 141, 239, 0.15);
}

.autocomplete-item.active {
  border-left: 3px solid var(--color-accent-primary);
  padding-left: 7px;
}

.autocomplete-item .ac-main-line {
  display: flex;
  align-items: baseline;
  gap: 4px;
  flex-wrap: wrap;
  line-height: 1.35;
}

.autocomplete-item .ac-name {
  font-size: 12.5px;
  color: var(--color-text-secondary);
}

.autocomplete-item .ac-name strong {
  color: var(--color-accent-primary);
  font-weight: 700;
}

.autocomplete-item .ac-hangul {
  font-size: 11.5px;
  color: var(--color-text-secondary);
}

.autocomplete-item .ac-hangul strong {
  color: var(--color-accent-primary);
  font-weight: 600;
}

.autocomplete-item .ac-mr {
  font-size: 13px;
  color: var(--color-text-primary);
  font-weight: 600;
}

.autocomplete-item .ac-mr strong {
  color: var(--color-accent-primary);
  font-weight: 700;
}

.autocomplete-item .ac-id {
  font-size: 11px;
  color: var(--color-text-secondary);
}

.autocomplete-item .ac-id strong {
  color: var(--color-accent-primary);
  font-weight: 600;
}

.autocomplete-item .ac-choronym {
  font-size: 11px;
  color: var(--color-accent-primary, #5b8def);
}

.autocomplete-item .ac-choronym strong {
  font-weight: 700;
}

.autocomplete-item .ac-alt-line {
  line-height: 1.3;
}

.autocomplete-empty {
  padding: 12px;
  text-align: center;
  font-size: 12px;
  color: var(--color-text-secondary);
  font-style: italic;
}

#graph-container:active {
  cursor: grabbing;
}

.graph-legend {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background-color: var(--color-surface-elevated);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--color-border);
  border-radius: var(--border-radius-lg, 8px);
  padding: 8px 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  pointer-events: auto;
  z-index: 10;
  box-shadow: var(--shadow-md);
  transition: background-color var(--transition-fast), border-color var(--transition-fast);
  max-width: 280px;
  font-size: 11px;
}

.legend-item {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  font-weight: 500;
  color: var(--color-text-primary);
  letter-spacing: 0.2px;
}

.legend-line {
  display: inline-block;
  width: 22px;
  height: 2px;
  border-radius: 1px;
  flex-shrink: 0;
}

.legend-line.has-son {
  background-color: var(--color-has-son);
}

.legend-line.has-sil {
  background-color: var(--color-has-sil);
  border-top: 2px dashed var(--color-has-sil);
  height: 0;
}

.legend-line.solid {
  border-top: 2.5px solid currentColor;
  height: 0;
}

.legend-line.dashed {
  border-top: 2px dashed currentColor;
  height: 0;
}

.legend-line.dotted {
  border-top: 2px dotted currentColor;
  height: 0;
}

.legend-line.dashdot {
  border-top: 2px dashed currentColor;
  position: relative;
  height: 0;
}

.legend-line.longdash {
  border-top: 2.5px dashed currentColor;
  height: 0;
}

.legend-line.densedot {
  border-top: 1.5px dotted currentColor;
  height: 0;
}

.legend-label-editable {
  cursor: text;
  padding: 1px 4px;
  border-radius: 3px;
  border: 1px solid transparent;
  font-size: 11px;
  line-height: 1.25;
  transition: border-color var(--transition-fast), background-color var(--transition-fast);
}

.legend-label-editable:hover {
  background-color: var(--color-surface-hover, rgba(0, 0, 0, 0.05));
  border-color: var(--color-border);
}

.legend-label-editable:focus {
  outline: none;
  background-color: var(--color-surface);
  border-color: var(--color-primary);
}

.node-label-dates {
  font-family: var(--font-sans);
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.2px;
}

.edge-label-group {
  cursor: pointer;
  user-select: none;
}

.edge-label-bg {
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.22));
}

.edge-label-text {
  font-family: var(--font-sans);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.1px;
}

.gen-axis-badges-container {
  cursor: grab;
  user-select: none;
}

.gen-axis-badges-container:active {
  cursor: grabbing;
}

.gen-axis-badge {
  cursor: grab;
}

.gen-axis-badge:hover .gen-axis-bg {
  filter: drop-shadow(0 2px 8px rgba(91, 141, 239, 0.4));
  stroke: var(--color-primary, #5b8def);
  stroke-width: 1.5;
}

.gen-axis-bg {
  filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.25));
  transition: stroke 0.15s ease, filter 0.15s ease;
}

.gen-axis-line {
  pointer-events: none;
}

.tooltip {
  position: absolute;
  background-color: var(--color-surface-elevated);
  border: 1px solid var(--color-border);
  border-radius: var(--border-radius);
  padding: 8px 12px;
  font-size: 12px;
  pointer-events: none;
  box-shadow: var(--shadow-md);
  z-index: 100;
  max-width: 250px;
}

.tooltip-title {
  font-weight: 600;
  margin-bottom: 4px;
}

.tooltip-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  margin-top: 2px;
}

.tooltip-label {
  color: var(--color-text-secondary);
}

/* Right Drawer: Source Verification (Toggle-Enabled, Resizable) */
.right-drawer {
  width: 520px;
  min-width: 380px;
  max-width: 85vw;
  background-color: var(--color-surface);
  border-left: 1px solid var(--color-border);
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  overflow-y: auto;
  overflow-x: hidden;
  z-index: 15;
  position: relative;
}

.resize-handle-left {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  width: 7px;
  cursor: col-resize;
  z-index: 50;
  background: transparent;
  transition: background 0.15s ease;
}

.resize-handle-left:hover,
.resize-handle-left.resizing {
  background: var(--color-accent-primary, #5b8def);
}

.right-drawer.collapsed {
  width: 0 !important;
  min-width: 0 !important;
  max-width: 0 !important;
  border-left: none !important;
  transform: translateX(100%);
  visibility: hidden !important;
  pointer-events: none !important;
  display: none !important;
}

.right-drawer .panel-header {
  height: 48px;
  padding: 0 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--color-border);
  background-color: var(--color-surface);
  flex-shrink: 0;
}

.right-drawer .panel-header h3 {
  font-size: 13px;
  font-weight: 600;
  color: var(--color-text-primary);
}

.right-drawer .panel-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.right-drawer .panel-content {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.detail-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  min-height: 160px;
  color: var(--color-text-secondary);
  font-style: italic;
  text-align: center;
  padding: 24px;
}

/* Vertical Arrangement Layout */
.detail-grid {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.detail-hero-card {
  background: var(--color-surface-elevated, #242836);
  border: 1px solid var(--color-border, #2e3347);
  border-radius: var(--border-radius-lg, 10px);
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  box-shadow: var(--shadow-sm);
}

.detail-actions-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-top: 4px;
}

.detail-actions-row .btn-full {
  grid-column: 1 / -1;
}

.detail-action-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
  padding: 8px 12px;
  border-radius: var(--border-radius, 6px);
  cursor: pointer;
  transition: all var(--transition-fast, 0.15s ease);
  border: 1px solid var(--color-border, #2e3347);
  background: var(--color-surface, #1a1d27);
  color: var(--color-text-primary, #e8eaed);
}

.detail-action-btn:hover {
  background: var(--color-bg-hover, rgba(91, 141, 239, 0.15));
  border-color: var(--color-accent-primary, #5b8def);
  color: #fff;
  transform: translateY(-1px);
}

.detail-action-btn.btn-primary {
  background: var(--color-accent-primary, #5b8def);
  border-color: var(--color-accent-primary, #5b8def);
  color: #fff;
}

.detail-action-btn.btn-primary:hover {
  filter: brightness(1.1);
  box-shadow: 0 2px 8px rgba(91, 141, 239, 0.35);
}

.detail-action-btn.btn-danger {
  background: var(--color-surface, #1a1d27);
  border-color: rgba(239, 68, 68, 0.35);
  color: var(--color-danger, #ef4444);
}

.detail-action-btn.btn-danger:hover {
  background: rgba(239, 68, 68, 0.15);
  border-color: var(--color-danger, #ef4444);
  color: #fff;
}

.floating-node-actions {
  position: absolute;
  z-index: 100;
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--color-surface-elevated, #242836);
  border: 1px solid var(--color-border, #2e3347);
  border-radius: 20px;
  padding: 4px 8px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
  pointer-events: auto;
  transform: translate(-50%, -100%);
  margin-top: -12px;
  transition: opacity 0.15s ease, transform 0.15s ease;
}

.floating-node-actions .pill-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: transparent;
  border: none;
  color: var(--color-text-primary, #e8eaed);
  font-size: 11.5px;
  font-weight: 600;
  padding: 4px 8px;
  border-radius: 12px;
  cursor: pointer;
  transition: all 0.12s ease;
  white-space: nowrap;
}

.floating-node-actions .pill-btn:hover {
  background: var(--color-accent-primary, #5b8def);
  color: #fff;
}

.floating-node-actions .pill-btn-danger:hover {
  background: var(--color-danger, #ef4444);
  color: #fff;
}

.hero-name-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.hero-name-hanja {
  font-size: 20px;
  font-weight: 700;
  color: var(--color-text-primary, #e8eaed);
  letter-spacing: 0.5px;
}

.hero-name-sub {
  font-size: 13px;
  color: var(--color-text-secondary, #9aa0b4);
  font-weight: 500;
}

.hero-badges-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 2px;
}

.badge-child-order {
  background: rgba(91, 141, 239, 0.18) !important;
  color: var(--color-accent-primary, #5b8def) !important;
  border: 1px solid rgba(91, 141, 239, 0.35) !important;
  font-weight: 600 !important;
  padding: 2px 8px !important;
  border-radius: 4px !important;
  font-size: 11px !important;
}

.badge-ghost {
  background: rgba(154, 160, 180, 0.15) !important;
  color: var(--color-text-secondary, #9aa0b4) !important;
  border: 1px dashed rgba(154, 160, 180, 0.4) !important;
  padding: 2px 8px !important;
  border-radius: 4px !important;
  font-size: 11px !important;
}

.badge-gen {
  background: rgba(94, 194, 106, 0.15) !important;
  color: var(--color-success, #5ec26a) !important;
  border: 1px solid rgba(94, 194, 106, 0.35) !important;
  padding: 2px 8px !important;
  border-radius: 4px !important;
  font-size: 11px !important;
}

.badge-choro {
  background: rgba(232, 152, 94, 0.15) !important;
  color: var(--color-accent-secondary, #e8985e) !important;
  border: 1px solid rgba(232, 152, 94, 0.35) !important;
  padding: 2px 8px !important;
  border-radius: 4px !important;
  font-size: 11px !important;
}

.detail-card-section {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.section-title {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--color-accent-primary, #5b8def);
  display: flex;
  align-items: center;
  gap: 6px;
}

.vertical-fields-grid {
  display: flex;
  flex-direction: column;
  gap: 10px;
  background: var(--color-surface-elevated, #242836);
  padding: 14px;
  border-radius: var(--border-radius, 6px);
  border: 1px solid var(--color-border, #2e3347);
}

.detail-field-v {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.detail-field-v .v-label {
  font-size: 11px;
  font-weight: 600;
  color: var(--color-text-secondary, #9aa0b4);
}

.detail-field-v .v-value {
  font-size: 13px;
  color: var(--color-text-primary, #e8eaed);
  word-break: break-word;
  line-height: 1.5;
}

.detail-field-v .v-value.mono {
  font-family: monospace;
  font-size: 12px;
  color: var(--color-text-secondary, #9aa0b4);
}

.detail-field-v .v-value.text-amber {
  color: var(--color-accent-secondary, #e8985e);
  font-weight: 600;
}

.detail-field-v .v-value.text-blue {
  color: var(--color-accent-primary, #5b8def);
  font-weight: 600;
}

.source-text-card {
  font-size: 13.5px;
  line-height: 1.7;
  color: var(--color-text-primary, #e8eaed);
  background-color: var(--color-bg, #0f1117);
  padding: 14px;
  border-radius: var(--border-radius, 6px);
  border: 1px solid var(--color-border, #2e3347);
  white-space: pre-wrap;
  word-break: break-word;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
}

.source-text-card.kr-card {
  background-color: var(--color-surface-elevated, #242836);
  border-left: 3px solid var(--color-accent-primary, #5b8def);
}

.empty-note {
  color: var(--color-text-secondary);
  font-style: italic;
  font-size: 12px;
}

.highlight-source, .highlight-target, .highlight-primary {
  background-color: rgba(91, 141, 239, 0.25);
  color: #92b8ff;
  padding: 1px 5px;
  border-radius: 3px;
  font-weight: 600;
}

/* Source Verification Form Styling */
.source-verification-form .form-group {
  margin-bottom: 8px;
}

.source-verification-form label {
  font-size: 11px;
  font-weight: 600;
  color: var(--color-text-secondary, #9aa0b4);
  margin-bottom: 3px;
  display: block;
}

.source-verification-form .form-control,
.source-verification-form .text-input,
.source-verification-form input,
.source-verification-form select,
.source-verification-form textarea {
  width: 100%;
  box-sizing: border-box;
  background: var(--color-bg, #0f1117);
  border: 1px solid var(--color-border, #2e3347);
  border-radius: var(--border-radius, 6px);
  color: var(--color-text-primary, #e8eaed);
  padding: 6px 10px;
  font-size: 13px;
  font-family: inherit;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.source-verification-form .form-control:focus,
.source-verification-form .text-input:focus,
.source-verification-form select:focus,
.source-verification-form textarea:focus {
  outline: none;
  border-color: var(--color-accent-primary, #5b8def);
  box-shadow: 0 0 0 2px rgba(91, 141, 239, 0.25);
}

.source-verification-form textarea.source-textarea {
  font-family: serif;
  font-size: 13.5px;
  line-height: 1.6;
  min-height: 90px;
}

/* Bottom Status Update Bar */
/* MJ-4. Three groups on one row: the corpus counts, the current message, the
   keyboard hints. As flex with `space-between` the middle group was sized by
   its own text, so a long status message — and Sebo writes whole sentences
   there — grew the box to 2391px inside a 430px bar, ran out past both ends of
   the viewport and printed straight over the selection stat on its left.
   Grid states the intent that flex could only imply: the outer two take the
   width they need, the message takes what is left and no more. Every track is
   minmax(0, …) so that a long word in any of the three shrinks and clips
   instead of pushing the row wider than the screen. */
.bottom-status-bar {
  height: 32px;
  min-height: 32px;
  background-color: var(--color-surface, #202431);
  border-top: 1px solid var(--color-border, #2e3347);
  display: grid;
  /* The middle track keeps a floor. Without one the left group's `auto` track
     takes every pixel it can use and the message — the only part of the bar
     that changes — is ellipsed to nothing at 430px. The floor yields to the
     viewport before it overflows it, so the bar still never scrolls. */
  grid-template-columns: minmax(0, auto) minmax(min(34%, 170px), 1fr) minmax(0, auto);
  align-items: center;
  column-gap: 12px;
  padding: 0 16px;
  font-size: 12px;
  color: var(--color-text-secondary, #9da7b3);
  z-index: 25;
  user-select: none;
  flex-shrink: 0;
  transition: background-color 0.3s ease;
}

.bottom-status-bar .status-left {
  display: flex;
  align-items: center;
  gap: 8px;
  /* Was flex-shrink:0, which let a long source-file list run underneath the
     centre status message. Let the group clip instead of overlapping. */
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
}

.bottom-status-bar .status-badge {
  font-weight: 600;
  color: var(--color-text-primary, #e8eaed);
}

.bottom-status-bar .status-sep {
  opacity: 0.4;
  font-size: 10px;
}

.bottom-status-bar .status-stat strong {
  color: var(--color-accent-primary, #5b8def);
  font-weight: 600;
}

.bottom-status-bar .status-center {
  color: var(--color-text-primary, #e8eaed);
  font-size: 12px;
  font-weight: 500;
  text-align: center;
  overflow: hidden;
  white-space: nowrap;
  /* MJ-4: the track is the width. `max-width: 55%` was measured against the
     bar, not against what the other two groups had already taken. */
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 2px 10px;
  border-radius: 12px;
  transition: all 0.25s ease;
}

/* MJ-4: the message is a flex child of .status-center, and a flex child will
   not shrink below its content unless it is told it may. This is the line that
   actually stops the overflow; the ellipsis is what the reader gets instead. */
.bottom-status-bar #status-message {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The dot reports liveness and must not be the thing that gets clipped. */
.bottom-status-bar .status-indicator-dot { flex: 0 0 auto; }

.bottom-status-bar .status-center.flash-update {
  background: rgba(91, 141, 239, 0.18);
  border: 1px solid rgba(91, 141, 239, 0.4);
  color: #fff;
}

.bottom-status-bar .status-center.flash-success {
  background: rgba(16, 185, 129, 0.18);
  border: 1px solid rgba(16, 185, 129, 0.4);
  color: #10b981;
}

.bottom-status-bar .status-center.flash-warning {
  background: rgba(245, 158, 11, 0.18);
  border: 1px solid rgba(245, 158, 11, 0.4);
  color: #f59e0b;
}

.bottom-status-bar .status-center.flash-reset {
  background: rgba(239, 68, 68, 0.18);
  border: 1px solid rgba(239, 68, 68, 0.4);
  color: #f87171;
}

.status-indicator-dot {
  width: 6.5px;
  height: 6.5px;
  border-radius: 50%;
  background: #10b981;
  display: inline-block;
  box-shadow: 0 0 6px rgba(16, 185, 129, 0.6);
}

.status-indicator-dot.active-op {
  background: var(--color-accent-primary, #5b8def);
  box-shadow: 0 0 8px rgba(91, 141, 239, 0.8);
  animation: pulse-dot 1.2s infinite ease-in-out;
}

@keyframes pulse-dot {
  0% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.35); opacity: 0.7; }
  100% { transform: scale(1); opacity: 1; }
}

.bottom-status-bar .status-right {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-shrink: 0;
}

.status-hint kbd {
  background-color: var(--color-surface-elevated, #242836);
  border: 1px solid var(--color-border, #2e3347);
  border-radius: 3px;
  padding: 1px 5px;
  font-size: 10px;
  font-family: inherit;
  color: var(--color-text-primary, #e8eaed);
}

/* Bottom Toast Notifications */
.toast-container {
  position: fixed;
  bottom: 42px;
  right: 20px;
  z-index: 10000;
  display: flex;
  flex-direction: column-reverse;
  gap: 8px;
  pointer-events: none;
  max-width: 420px;
}

.toast-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 16px;
  background-color: var(--color-surface-elevated, #242836);
  border: 1px solid var(--color-border, #2e3347);
  border-radius: var(--border-radius, 6px);
  color: var(--color-text-primary, #e8eaed);
  font-size: 13px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  pointer-events: auto;
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.25s ease-out, transform 0.25s ease-out;
}

.toast-item.visible {
  opacity: 1;
  transform: translateY(0);
}

.toast-item.toast-success {
  border-left: 3px solid #2ecc71;
}

.toast-item.toast-info {
  border-left: 3px solid var(--color-accent-primary, #5b8def);
}

.toast-item.toast-warning {
  border-left: 3px solid #f39c12;
}

.toast-item.toast-error {
  border-left: 3px solid #e74c3c;
}

.toast-icon {
  font-size: 15px;
  flex-shrink: 0;
}

.toast-message {
  line-height: 1.4;
  word-break: break-word;
}

.highlight-orange {
  background-color: rgba(232, 152, 94, 0.3);
  color: #ffb788;
  padding: 0 4px;
  border-radius: 2px;
}

/* Modals */
.modal-backdrop {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(15, 17, 23, 0.8);
  backdrop-filter: blur(4px);
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
}

.modal {
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--border-radius-lg);
  width: 100%;
  max-width: 500px;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-lg);
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 1001;
}

.modal-header {
  padding: 16px 20px;
  border-bottom: 1px solid var(--color-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.modal-header h3 {
  font-size: 16px;
}

.modal-body {
  padding: 20px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.modal-footer {
  padding: 16px 20px;
  border-top: 1px solid var(--color-border);
  display: flex;
  justify-content: flex-end;
  gap: 12px;
}

/* Node/Edge SVG Styles (to be manipulated by JS but baseline provided here) */
.node circle {
  transition: fill var(--transition-fast), stroke var(--transition-fast);
}

.node text {
  pointer-events: none;
  font-family: var(--font-family);
  fill: var(--color-text-primary);
  text-shadow: none;
}

.link {
  fill: none;
  transition: stroke var(--transition-fast), stroke-width var(--transition-fast);
}

.link.has-son {
  stroke: var(--color-has-son);
}

.link.has-sil {
  stroke: var(--color-has-sil);
}

/* Animations */
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.fade-in {
  animation: fadeIn 0.3s ease forwards;
}

/* Detail Panel Layout (Strict Single-Column) */
.detail-grid {
  display: flex !important;
  flex-direction: column !important;
  gap: 16px;
  width: 100%;
}

.detail-meta {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.detail-field {
  display: flex;
  gap: 12px;
  align-items: baseline;
}

.detail-field .label {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-secondary);
  min-width: 100px;
  flex-shrink: 0;
}

.detail-field .value {
  font-size: 13px;
  color: var(--color-text-primary);
  word-break: break-word;
}

.detail-source {
  overflow-y: auto;
}

.detail-source h4 {
  font-size: 12px;
  font-weight: 600;
  color: var(--color-text-secondary);
  margin-bottom: 6px;
  margin-top: 12px;
}

.detail-source h4:first-child {
  margin-top: 0;
}

.source-text,
.source-text-kr {
  font-size: 13px;
  line-height: 1.7;
  color: var(--color-text-primary);
  white-space: pre-wrap;
  word-break: break-word;
}

/* Text Highlighting */
mark.highlight-primary {
  background-color: rgba(232, 200, 94, 0.35);
  color: inherit;
  padding: 1px 2px;
  border-radius: 2px;
}

mark.highlight-source {
  background-color: rgba(91, 141, 239, 0.3);
  color: inherit;
  padding: 1px 2px;
  border-radius: 2px;
}

mark.highlight-target {
  background-color: rgba(232, 152, 94, 0.3);
  color: inherit;
  padding: 1px 2px;
  border-radius: 2px;
}

/* Badges */
.badge {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: 10px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
}

.badge-blue {
  background-color: rgba(91, 141, 239, 0.2);
  color: var(--color-has-son);
}

.badge-amber {
  background-color: rgba(232, 152, 94, 0.2);
  color: var(--color-has-sil);
}

.badge-default {
  background-color: rgba(154, 160, 180, 0.2);
  color: var(--color-text-secondary);
}

/* Form Controls for Modals */
.form-control {
  width: 100%;
  padding: 8px 12px;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--border-radius-sm);
  color: var(--color-text-primary);
  font-family: var(--font-family);
  font-size: 13px;
  transition: border-color var(--transition-fast);
}

.form-control:focus {
  outline: none;
  border-color: var(--color-accent-primary);
}

.form-control[readonly] {
  opacity: 0.5;
  cursor: not-allowed;
}

textarea.form-control {
  resize: vertical;
  min-height: 60px;
}

/* Loading Overlay & Progress Bar */
#loading-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(11, 15, 25, 0.88);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  z-index: 10000;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  opacity: 1;
  visibility: visible;
  transition: opacity 0.25s ease, visibility 0.25s ease;
}

#loading-overlay.hidden {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.loading-card {
  background: var(--color-surface, #1e2433);
  border: 1px solid var(--color-border, #333d52);
  border-radius: var(--border-radius-lg, 12px);
  box-shadow: 0 20px 35px -5px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.05);
  padding: 28px 32px;
  width: 90%;
  max-width: 460px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  animation: modalFadeIn 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}

.loading-header {
  display: flex;
  align-items: center;
  gap: 14px;
}

.loading-brand {
  display: flex;
  flex-direction: column;
}

.loading-title {
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--color-text-primary, #ffffff);
  letter-spacing: -0.01em;
}

.loading-tagline {
  font-size: 0.75rem;
  color: var(--color-text-secondary, #94a3b8);
  font-weight: 500;
  letter-spacing: 0.02em;
}

.spinner {
  width: 24px;
  height: 24px;
  border: 3px solid rgba(91, 141, 239, 0.2);
  border-top-color: var(--color-accent-primary, #5b8def);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
  flex-shrink: 0;
}

.loading-text {
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--color-text-primary, #f1f5f9);
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.loading-progress-wrapper {
  display: flex;
  align-items: center;
  gap: 12px;
}

.loading-progress-track {
  flex: 1;
  height: 8px;
  background: rgba(255, 255, 255, 0.08);
  border-radius: 9999px;
  overflow: hidden;
  position: relative;
}

.loading-progress-bar {
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, var(--color-accent-primary, #5b8def), #818cf8);
  border-radius: 9999px;
  transition: width 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  box-shadow: 0 0 12px rgba(91, 141, 239, 0.4);
}

.loading-progress-percent {
  font-size: 0.8125rem;
  font-weight: 600;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  color: var(--color-accent-primary, #5b8def);
  min-width: 36px;
  text-align: right;
}

.loading-subtext {
  font-size: 0.8125rem;
  color: var(--color-text-secondary, #94a3b8);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

/* Dropdown Items */
.dropdown-item:hover {
  background: var(--color-accent-primary) !important;
  color: #fff !important;
}

/* Table Actions Cell */
.actions-cell {
  white-space: nowrap;
  display: flex;
  gap: 4px;
}

.action-btn {
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px 6px;
  border-radius: var(--border-radius-sm);
  font-size: 14px;
  transition: background var(--transition-fast);
}

.action-btn:hover {
  background: var(--color-surface-elevated);
}

/* Deleted Row */
tr.deleted {
  opacity: 0.4;
  text-decoration: line-through;
}

tr.deleted td:first-child {
  border-left: 3px solid var(--color-danger);
}

/* Selected Row */
tr.selected {
  background-color: rgba(91, 141, 239, 0.1) !important;
}

tr.selected td:first-child {
  border-left: 3px solid var(--color-accent-primary);
}

/* Empty state */
.empty-state,
.detail-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-text-secondary);
  font-size: 13px;
  padding: 24px;
}

/* Text utilities */
.text-muted {
  color: var(--color-text-secondary);
  font-style: italic;
}

.text-center {
  text-align: center;
}

/* Pagination styles */
.pagination-info {
  font-size: 12px;
  color: var(--color-text-secondary);
}

.pagination-controls button {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: var(--color-text-primary);
  padding: 4px 10px;
  border-radius: var(--border-radius-sm);
  cursor: pointer;
  font-size: 12px;
  transition: background var(--transition-fast);
}

.pagination-controls button:hover:not(:disabled) {
  background: var(--color-surface-elevated);
}

.pagination-controls button.active {
  background: var(--color-accent-primary);
  border-color: var(--color-accent-primary);
  color: #fff;
}

.pagination-controls button:disabled {
  opacity: 0.3;
  cursor: not-allowed;
}

.pagination-controls .ellipsis {
  color: var(--color-text-secondary);
  padding: 0 4px;
}

/* Sort indicator */
.sort-indicator {
  font-size: 10px;
  color: var(--color-accent-primary);
}

/* Mobile Sidebar Backdrops — mobile-only: on desktop the sidebar sits inline
   (z-index 15), so a full-viewport backdrop would dim and block the whole UI */
.sidebar-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  z-index: 900;
  transition: opacity 0.25s ease;
}

/* The overlay drawer starts at 900px (see the responsive section below), so
   the dimming backdrop must appear at the same width. */
@media (max-width: 900px) {
  .sidebar-backdrop:not(.hidden) {
    display: block;
  }
}

.sidebar-backdrop.hidden,
.drawer-backdrop {
  display: none !important;
  opacity: 0;
  pointer-events: none;
}


/* Find Paths Multi-Node Rows */
.path-node-row {
  display: flex;
  flex-direction: column;
  gap: 3px;
  background: var(--color-surface, #1a1d27);
  border: 1px solid var(--color-border, #2e3347);
  border-radius: 6px;
  padding: 6px 8px;
  transition: border-color 0.15s ease;
}

.path-node-row:focus-within {
  border-color: var(--color-accent-primary, #5b8def);
}

.path-node-row-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 2px;
}

.path-node-label {
  font-size: 11px;
  font-weight: 600;
  color: var(--color-text-secondary, #9aa0b4);
  letter-spacing: 0.2px;
}

.path-node-remove-btn {
  background: transparent;
  border: none;
  color: var(--color-text-secondary, #9aa0b4);
  cursor: pointer;
  padding: 0 4px;
  font-size: 12px;
  line-height: 1;
  border-radius: 3px;
  transition: all 0.12s ease;
}

.path-node-remove-btn:hover {
  color: #ef4444;
  background: rgba(239, 68, 68, 0.15);
}

#btn-add-path-node {
  transition: all 0.15s ease;
}

#btn-add-path-node:hover {
  border-color: var(--color-accent-primary, #5b8def);
  color: var(--color-accent-primary, #5b8def);
  background: rgba(91, 141, 239, 0.1);
}

/* Floating Canvas Controls Toolbar */
.canvas-floating-toolbar {
  position: absolute;
  top: 16px;
  right: 16px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  z-index: 20;
}

.canvas-float-btn {
  width: 34px;
  height: 34px;
  border-radius: 8px;
  background: var(--color-surface, #1a1d27);
  border: 1px solid var(--color-border, #2e3347);
  color: var(--color-text-primary, #e8eaed);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  cursor: pointer;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
  transition: all 0.15s ease;
  user-select: none;
}

.canvas-float-btn:hover {
  background: var(--color-surface-hover, #24283b);
  border-color: var(--color-accent-primary, #5b8def);
  color: var(--color-accent-primary, #5b8def);
  transform: translateY(-1px);
}

.canvas-float-btn:active {
  transform: translateY(1px);
}


/* ══════════════════════════════════════════════════════════════════════════
   INSPECTION VIEW
   ══════════════════════════════════════════════════════════════════════════ */

/* Resizable Splitters */
.insp-splitter-h {
  width: 6px;
  background: var(--color-border, #2e3347);
  cursor: col-resize;
  z-index: 10;
  flex-shrink: 0;
  transition: background 0.15s ease;
  position: relative;
}

.insp-splitter-h:hover,
.insp-splitter-h.active {
  background: var(--color-accent-primary, #5b8def);
}

.insp-splitter-v {
  height: 6px;
  background: var(--color-border, #2e3347);
  cursor: row-resize;
  z-index: 10;
  flex-shrink: 0;
  transition: background 0.15s ease;
  position: relative;
}

.insp-splitter-v:hover,
.insp-splitter-v.active {
  background: var(--color-accent-primary, #5b8def);
}

/* Panel Collapse & Header Controls */
.insp-panel-btn-collapse {
  background: transparent;
  border: 1px solid transparent;
  color: var(--color-text-secondary);
  font-size: 11px;
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 4px;
  cursor: pointer;
  padding: 0;
  transition: all 0.15s ease;
}

.insp-panel-btn-collapse:hover {
  color: var(--color-text-primary);
  background: rgba(255, 255, 255, 0.08);
  border-color: var(--color-border);
}

.insp-panel-container.insp-panel-collapsed {
  flex: none !important;
  height: 38px !important;
  min-height: 38px !important;
  overflow: hidden !important;
}

.insp-panel-container.insp-panel-collapsed .insp-text-panel,
.insp-panel-container.insp-panel-collapsed .insp-entity-list,
.insp-panel-container.insp-panel-collapsed .insp-table-container {
  display: none !important;
}

/* ──────────────────────────────────────────────────────────────────────────
   Inspection tokens
   --------------------------------------------------------------------------
   The view has exactly two visual families, and they are told apart by FORM,
   not by hue:

     a node mention  = an ENTITY  → a highlighter chip (tinted box, ringed)
     edge evidence   = a CONNECTION → a rule running under the phrase

   Hue is then free to carry a second, orthogonal fact: *which* relationship an
   edge is. HAS_SON and HAS_SIL use --color-has-son / --color-has-sil verbatim,
   the same two values the legend chips and the Graph canvas draw with, so the
   colour a phrase is underlined in is exactly the colour its legend entry
   shows. Line style doubles the signal (HAS_SON solid, HAS_SIL dashed), so
   nothing here depends on colour alone.

   --insp-edge-hue survives as the *category* colour of the edges panel and the
   edges chip's chrome; it no longer claims to be the colour of any one edge.
   ────────────────────────────────────────────────────────────────────────── */
:root {
  --insp-node-hue: 91, 141, 239;        /* node mentions */
  --insp-edge-hue: 232, 152, 94;        /* the "edges" category (panel + chip chrome) */
  --insp-son-hue: 91, 141, 239;         /* == --color-has-son, verbatim */
  --insp-sil-hue: 232, 152, 94;         /* == --color-has-sil, verbatim */
  --insp-ok: #10b981;
  --insp-warn: #f59e0b;
  --insp-danger: #ef4444;

  --insp-mark-bg: rgba(var(--insp-node-hue), 0.15);
  --insp-mark-bg-hover: rgba(var(--insp-node-hue), 0.30);
  --insp-mark-ring: rgba(var(--insp-node-hue), 0.42);
  --insp-mark-ring-hover: rgba(var(--insp-node-hue), 0.85);
  --insp-mark-rule: rgba(var(--insp-node-hue), 0.62);
  --insp-mark-active: rgba(37, 99, 235, 0.88);
  --insp-mark-active-ink: #ffffff;

  --insp-ghost-bg: rgba(var(--insp-edge-hue), 0.10);
  --insp-ghost-rule: rgba(var(--insp-edge-hue), 0.70);

  --insp-ev-bg: rgba(var(--insp-edge-hue), 0.09);
  --insp-ev-bg-hover: rgba(var(--insp-edge-hue), 0.22);
  --insp-ev-rule: rgba(var(--insp-edge-hue), 0.65);
  --insp-ev-active-bg: rgba(234, 88, 12, 0.28);
  --insp-ev-active-rule: #ea580c;

  --insp-row-hover: rgba(var(--insp-node-hue), 0.10);
  --insp-row-selected: rgba(59, 130, 246, 0.20);
  --insp-cell-rule: rgba(255, 255, 255, 0.10);
  --insp-hairline: rgba(255, 255, 255, 0.06);
  --insp-scrim: rgba(6, 8, 14, 0.66);
  --insp-focus: #60a5fa;
}

[data-theme="light"] {
  --insp-node-hue: 37, 99, 235;
  --insp-edge-hue: 194, 106, 24;
  --insp-son-hue: 37, 99, 235;          /* == light --color-has-son #2563eb */
  --insp-sil-hue: 217, 119, 6;          /* == light --color-has-sil #d97706 */
  --insp-ok: #047857;
  --insp-warn: #b45309;
  --insp-danger: #b91c1c;

  --insp-mark-bg: rgba(var(--insp-node-hue), 0.13);
  --insp-mark-bg-hover: rgba(var(--insp-node-hue), 0.24);
  --insp-mark-ring: rgba(var(--insp-node-hue), 0.40);
  --insp-mark-ring-hover: rgba(var(--insp-node-hue), 0.85);
  --insp-mark-rule: rgba(var(--insp-node-hue), 0.70);
  --insp-mark-active: #1d4ed8;

  --insp-ghost-bg: rgba(var(--insp-edge-hue), 0.10);
  --insp-ghost-rule: rgba(var(--insp-edge-hue), 0.78);

  --insp-ev-bg: rgba(var(--insp-edge-hue), 0.10);
  --insp-ev-bg-hover: rgba(var(--insp-edge-hue), 0.22);
  --insp-ev-rule: rgba(var(--insp-edge-hue), 0.72);
  --insp-ev-active-bg: rgba(194, 106, 24, 0.26);
  --insp-ev-active-rule: #b45309;

  --insp-row-hover: rgba(var(--insp-node-hue), 0.07);
  --insp-row-selected: rgba(37, 99, 235, 0.13);
  --insp-cell-rule: rgba(15, 23, 42, 0.10);
  --insp-hairline: rgba(15, 23, 42, 0.07);
  --insp-scrim: rgba(15, 23, 42, 0.42);
  --insp-focus: #2563eb;
}

/* A single visible focus treatment for everything this view adds. */
#inspection-view :focus-visible,
.insp-modal :focus-visible {
  outline: 2px solid var(--insp-focus);
  outline-offset: 2px;
  border-radius: 4px;
}

/* ──────────────────────────────────────────────────────────────────────────
   Panel identity
   Each panel gets a coloured rail and a small-caps title, so "which panel am
   I in" is answerable at a glance rather than by reading the heading.
   ────────────────────────────────────────────────────────────────────────── */
#inspection-view .insp-panel-header {
  position: relative;
  min-height: 36px;
  background: var(--color-surface) !important;
  box-shadow: inset 0 -1px 0 var(--color-border);
}

#inspection-view .insp-panel-header::before {
  content: '';
  position: absolute;
  left: 0;
  top: 6px;
  bottom: 6px;
  width: 3px;
  border-radius: 0 2px 2px 0;
  background: var(--color-text-secondary);
  opacity: 0.55;
}

.insp-panel-header[data-insp-panel-header="hanmun"]::before { background: rgb(var(--insp-node-hue)); opacity: 1; }
.insp-panel-header[data-insp-panel-header="korean"]::before { background: var(--color-text-secondary); opacity: 0.7; }
.insp-panel-header[data-insp-panel-header="nodes"]::before  { background: rgb(var(--insp-node-hue)); opacity: 0.8; }
.insp-panel-header[data-insp-panel-header="edges"]::before  { background: rgb(var(--insp-edge-hue)); opacity: 0.9; }

/* The 原文 panel is the centrepiece: give its heading the most weight. */
.insp-panel-header[data-insp-panel-header="hanmun"] {
  min-height: 40px;
}

/* Reading comfort for the source text. A capped measure keeps Classical
   Chinese lines from running the full width of a 1600px window. */
#inspection-hanmun-panel,
#inspection-korean-panel {
  background: var(--color-bg);
  scroll-padding-top: 24px;
}

#inspection-hanmun-panel {
  line-height: 2.05;
  letter-spacing: 0.01em;
  text-align: justify;
  hanging-punctuation: allow-end;
}

#inspection-korean-panel { line-height: 1.95; }

/* 原文 and 국역 are one reading column split in two, so both bodies use the
   SAME measure and both fill the panel's full width. The cap is generous and
   in px (not ch, which differs between Hanja and Hangul and would make the two
   blocks different widths) — it only engages on very wide monitors. The cap
   lives on this inner body, never on the panel: capping the scroll container
   is what shrank the 국역 frame away from its box. */
:root { --insp-measure: 1100px; }

/* The reading text is hanja, so it is set in the hanja stack — this is the one
   place a rare character absolutely must not become a box. */
.insp-doc-body,
.insp-subject-header strong,
.insp-table .insp-input[data-field="姓名"],
.insp-table .insp-input[data-field="字"],
.insp-table .insp-input[data-field="號"],
.insp-table .insp-input[data-field="諡號"],
.insp-table .insp-input[data-field="alt_names"],
.insp-table .insp-input[data-field="姓貫"] {
  font-family: var(--font-cjk);
}

/* A romanization is Latin text and must be set as Latin text — including the
   parentheses around it, which come out full-width and mis-spaced when a CJK
   face supplies them. */
.insp-subject-mr {
  font-family: var(--font-latin);
  font-size: 13px;
  color: var(--color-text-secondary);
  font-feature-settings: normal;
}
.insp-subject-hangul {
  font-family: 'Noto Sans KR', 'Apple SD Gothic Neo', 'Malgun Gothic', var(--font-cjk);
  font-size: 13px;
  color: var(--color-text-secondary);
}

.insp-doc-body {
  display: block;
  width: 100%;
  max-width: var(--insp-measure);
  /* The curve gutter is added as padding-right; without border-box that padding
     is added TO the 100% width and the reading panel scrolls sideways. */
  box-sizing: border-box;
}

/* Both source panels fill the column and scroll internally. */
#insp-source-panels { min-height: 0; }

#insp-panel-hanmun,
#insp-panel-korean {
  min-width: 0;
  min-height: 38px;
}

#insp-panel-hanmun:not(.insp-panel-collapsed),
#insp-panel-korean:not(.insp-panel-collapsed) {
  overflow: hidden;
}

#inspection-hanmun-panel,
#inspection-korean-panel {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  overflow-y: auto;
}

#inspection-hanmun-panel.insp-editable,
#inspection-korean-panel.insp-editable {
  background: var(--color-surface-elevated);
  box-shadow: inset 0 0 0 2px var(--insp-warn);
}

.insp-editable .insp-doc-body { max-width: none; }

/* ──────────────────────────────────────────────────────────────────────────
   Entity marks in the source text
   Calm by default — a page with forty marks on it has to stay readable.

   THE ONE RULE: a node is a BOX, an edge is a LINE.

   A node mention names a person — a thing — so it gets a highlighter chip:
   a soft tint with a hairline ring around it, sitting *on* the characters.
   Edge evidence is the phrase that proves a link between two people — a
   relation, not a thing — so it gets a rule *under* the characters and no fill
   at all. Box versus line survives greyscale, low vision and a photocopier;
   neither category is identified by its hue.
   ────────────────────────────────────────────────────────────────────────── */
.insp-highlight {
  display: inline;
  background: var(--insp-mark-bg);
  box-shadow: inset 0 0 0 1px var(--insp-mark-ring);
  border-bottom: none;
  border-radius: 3px;
  /* Horizontal padding only: vertical padding on an inline box does not grow
     the line box, so forty chips never reflow the 原文 column. */
  padding: 0 0.14em;
  cursor: pointer;
  color: inherit;
  font-weight: 600;
  transition: background 0.12s ease, box-shadow 0.12s ease;
}

.insp-highlight:hover,
.insp-highlight.insp-hover {
  background: var(--insp-mark-bg-hover);
  box-shadow: inset 0 0 0 1px var(--insp-mark-ring-hover);
}

.insp-highlight.active,
.insp-highlight-active {
  background: var(--insp-mark-active) !important;
  color: var(--insp-mark-active-ink) !important;
  font-weight: 700 !important;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.35),
              0 0 0 2px rgba(96, 165, 250, 0.55) !important;
}

/* A ghost conduit is still an entity, so it keeps the chip form — but it is a
   person the text never names, and an unfilled, dashed ring says "inferred"
   without needing a colour to do it. */
.insp-highlight-ghost {
  background: var(--insp-ghost-bg);
  box-shadow: none;
  outline: 1px dashed var(--insp-ghost-rule);
  outline-offset: -1px;
}

.insp-highlight-ghost:hover,
.insp-highlight-ghost.insp-hover {
  background: rgba(var(--insp-edge-hue), 0.24);
  box-shadow: none;
  outline-color: rgb(var(--insp-edge-hue));
}

.insp-highlight-ghost.active,
.insp-highlight-ghost.insp-highlight-active {
  background: #c2410c !important;
  color: #ffffff !important;
  outline: 1px dashed #fdba74 !important;
  box-shadow: 0 0 0 2px rgba(251, 146, 60, 0.5) !important;
}

/* ── Edge evidence: a rule, in the relationship's own colour ───────────────
   Every one of these used to be drawn in --insp-edge-hue (amber), whatever
   the relationship was, while the legend showed HAS_SON in blue and HAS_SIL
   in amber — so a patrilineal phrase was underlined in the affinal colour.
   The rule now takes --color-has-son / --color-has-sil verbatim, and the dash
   pattern matches the legend glyph's own stroke: solid for HAS_SON, dashed for
   HAS_SIL. The legend is now a literal description of the mark.
   No background: the rule alone marks the extent, which keeps a 40-character
   evidence phrase from becoming a slab of colour over the Hanja. */
.insp-edge-ev {
  --insp-ev-ink: var(--insp-edge-hue);
  border-bottom: 2px solid rgba(var(--insp-ev-ink), 0.75);
  background: transparent;
  border-radius: 0;
  padding: 0;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease;
}

.insp-edge-ev-son { --insp-ev-ink: var(--insp-son-hue); border-bottom-style: solid; }
.insp-edge-ev-sil { --insp-ev-ink: var(--insp-sil-hue); border-bottom-style: dashed; }

.insp-edge-ev:hover,
.insp-edge-ev.insp-hover {
  background: rgba(var(--insp-ev-ink), 0.13);
  border-bottom-color: rgb(var(--insp-ev-ink));
}

.insp-edge-ev.insp-edge-ev-active {
  background: rgba(var(--insp-ev-ink), 0.20);
  border-bottom-width: 3px;
  border-bottom-color: rgb(var(--insp-ev-ink));
  box-shadow: 0 2px 0 rgba(var(--insp-ev-ink), 0.35);
}

/* ID suffix tags shown after highlighted names (e.g. P0001, G0001). The exact
   same chip appears in the ID column of the tables — that identity is the
   link between a row and its mark, so the two must not drift apart. */
.insp-id-suffix {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 10.5px;
  font-weight: 700;
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  color: rgb(var(--insp-node-hue));
  background: rgba(var(--insp-node-hue), 0.15);
  border: 1px solid rgba(var(--insp-node-hue), 0.42);
  padding: 0 3px;
  margin: 0 1px;
  border-radius: 4px;
  vertical-align: middle;
  line-height: 1.5;
  cursor: pointer;
  transition: all 0.12s ease;
  user-select: none;
}

[data-theme="dark"] .insp-id-suffix { color: #a8c5f5; }

.insp-id-suffix:hover {
  background: rgb(var(--insp-node-hue));
  color: #ffffff;
  border-color: var(--insp-focus);
}

.insp-id-suffix.active {
  background: var(--insp-mark-active);
  color: #ffffff;
  border-color: #93c5fd;
  box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.45);
}

.insp-id-suffix-ghost {
  color: rgb(var(--insp-edge-hue));
  background: rgba(var(--insp-edge-hue), 0.15);
  border-color: rgba(var(--insp-edge-hue), 0.42);
}

[data-theme="dark"] .insp-id-suffix-ghost { color: #f0b98a; }

.insp-id-suffix-ghost:hover {
  background: rgb(var(--insp-edge-hue));
  color: #ffffff;
}

.insp-id-suffix-ghost.active {
  background: #c2410c;
  color: #ffffff;
  border-color: #fdba74;
  box-shadow: 0 0 0 2px rgba(251, 146, 60, 0.5);
}

/* Flash animation for scroll-to-highlight */
@keyframes insp-flash-pulse {
  0%   { background: rgba(255, 230, 0, 0.8); }
  50%  { background: rgba(255, 230, 0, 0.3); }
  100% { background: rgba(255, 230, 0, 0.8); }
}

.insp-flash {
  animation: insp-flash-pulse 0.4s ease-in-out 3;
}

/* ──────────────────────────────────────────────────────────────────────────
   Mark controls (原文 panel header)
   The word "HIGHLIGHT" is gone: each chip is drawn in the treatment its marks
   actually get in the text, so the control IS the legend and a caption over it
   only repeated what the chips already say.

   The hierarchy is carried by containment instead. "nodes" stands alone on the
   left. "edges" and its two relationship filters share one inset tray, so
   "HAS_SON / HAS_SIL narrow edges" is a fact about the layout rather than
   something you have to be told; when edges is switched off the whole tray
   visibly goes dead.
   ────────────────────────────────────────────────────────────────────────── */
/* No container box: the chips carry their own state, so an outer frame only
   adds visual noise to a dense header strip. */
.insp-hl-group {
  display: flex !important;
  align-items: center;
  gap: 2px !important;
  flex-wrap: wrap;
  padding: 0;
  border: 0;
  background: transparent;
  font-size: 11px !important;
}

/* Hairline between the two KINDS of mark. It says "these are alternatives"
   without spending a word on it. */
.insp-hl-sep {
  flex: 0 0 auto;
  align-self: stretch;
  width: 1px;
  margin: 2px 5px;
  background: var(--color-border);
}

/* The tray: edges + the two filters that narrow it, in one recessed well. */
/* edges + its two relationship filters. Grouped by proximity only — the
   recessed well read as a second box inside the first. */
.insp-hl-branch {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 0;
  border-radius: 0;
  background: transparent;
  border: 0;
  transition: opacity 0.12s ease;
}

.insp-hl-rels {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding-left: 5px;
  margin-left: 4px;
  position: relative;
  border-left: 1px solid var(--color-border);
}

.insp-hl-rels.is-disabled {
  opacity: 0.45;
}

.insp-hl-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 7px;
  border-radius: 5px;
  border: 1px solid transparent;
  background: transparent;
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  font-weight: 600;
  color: var(--color-text-secondary);
  transition: background 0.12s ease, border-color 0.12s ease, opacity 0.12s ease;
}

/* The checkbox stays a real focusable control; it is just not drawn. */
.insp-hl-input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  opacity: 0;
  pointer-events: none;
}

.insp-hl-chip:hover { background: var(--color-surface-hover); }

.insp-hl-input:focus-visible + .insp-hl-glyph {
  box-shadow: 0 0 0 2px var(--insp-focus);
}

.insp-hl-label { line-height: 1.5; }

.insp-hl-chip-sub .insp-hl-label {
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-size: 9.5px;
  letter-spacing: 0.02em;
}

.insp-hl-count {
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-size: 9.5px;
  font-weight: 700;
  line-height: 1;
  padding: 2px 4px;
  min-width: 15px;
  text-align: center;
  border-radius: 4px;
  background: var(--color-surface-hover);
  color: var(--color-text-secondary);
}

.insp-hl-count.is-zero { opacity: 0.45; }

/* Glyph carries the line-style half of the legend. Flex + centred so the
   swatch sits on the label's optical centre instead of riding low on the
   text baseline (an inline SVG's default alignment). */
.insp-hl-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 13px;
  height: 11px;
  border-radius: 2px;
  flex-shrink: 0;
  line-height: 0;
}

/* The relationship chips carry the Graph view's real edge glyph. */
.insp-hl-glyph-edge {
  width: 24px;
  height: 12px;
  background: transparent !important;
  border: none !important;
}

.insp-edge-glyph {
  display: block;
  overflow: visible;
  flex-shrink: 0;
  vertical-align: middle;
}

/* ── OFF state: outline only, desaturated, dotted — nothing is being drawn ── */
.insp-hl-chip .insp-hl-glyph {
  background: transparent;
  border: 1px dashed var(--color-border);
}

.insp-hl-chip:not(:has(.insp-hl-input:checked)) {
  opacity: 0.55;
}

/* ── ON state: the chip wears the mark's own styling ── */
.insp-hl-chip:has(.insp-hl-input:checked) {
  color: var(--color-text-primary);
  border-color: var(--color-border);
  background: var(--color-surface-elevated);
}

/* nodes: the chip wears the mark's own BOX form — tint plus hairline ring. */
.insp-hl-chip-node:has(.insp-hl-input:checked) {
  background: var(--insp-mark-bg);
  border-color: var(--insp-mark-ring);
}
.insp-hl-chip-node:has(.insp-hl-input:checked) .insp-hl-glyph {
  background: var(--insp-mark-bg-hover);
  border: 1px solid var(--insp-mark-ring-hover);
  border-radius: 3px;
}
.insp-hl-chip-node:has(.insp-hl-input:checked) .insp-hl-count {
  background: rgba(var(--insp-node-hue), 0.22);
  color: var(--color-text-primary);
}

/* edges: the PARENT of the two relationship chips, so it must not claim a
   colour of its own — a coloured "edges" chip is exactly what used to imply
   that every edge mark was amber. It shows the LINE form in neutral ink and
   leaves the hues to HAS_SON / HAS_SIL immediately to its right, which are the
   marks the text is actually drawn with. */
.insp-hl-chip-edge:has(.insp-hl-input:checked) {
  background: var(--color-surface-elevated);
  border-color: var(--color-border);
}
/* The rule is drawn as a centred pseudo-element rather than the glyph box's
   bottom border, so it sits on the label's optical centre instead of riding
   along its baseline. The label itself carries no underline: the glyph is the
   sample, and underlining the word as well only made the chip look like a
   mark rather than a control. */
.insp-hl-chip-edge:has(.insp-hl-input:checked) .insp-hl-glyph {
  background: transparent;
  border: none;
  border-radius: 0;
  position: relative;
}
.insp-hl-chip-edge:has(.insp-hl-input:checked) .insp-hl-glyph::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 2px;
  margin-top: -1px;
  background: var(--color-text-secondary);
}
.insp-hl-chip-edge:has(.insp-hl-input:checked) .insp-hl-count {
  background: var(--color-surface-hover);
  color: var(--color-text-primary);
}

/* Relationship chips carry the Graph view's edge glyph verbatim — same dot,
   same stroke, same dash, same arrowhead — and, since the fix, the same two
   colours the evidence rules in the text are drawn in. The glyph alone is the
   sample; the label stays plain text. */
.insp-hl-chip-son:has(.insp-hl-input:checked) {
  border-color: rgba(var(--insp-son-hue), 0.5);
  background: rgba(var(--insp-son-hue), 0.10);
  color: rgb(var(--insp-son-hue));
}
[data-theme="dark"] .insp-hl-chip-son:has(.insp-hl-input:checked) { color: #a8c5f5; }

.insp-hl-chip-sil:has(.insp-hl-input:checked) {
  border-color: rgba(var(--insp-sil-hue), 0.5);
  background: rgba(var(--insp-sil-hue), 0.10);
  color: rgb(var(--insp-sil-hue));
}
[data-theme="dark"] .insp-hl-chip-sil:has(.insp-hl-input:checked) { color: #f0b98a; }

/* Off: the glyph desaturates with the rest of the chip rather than vanishing,
   so the legend still reads while the marks are hidden. */
.insp-hl-chip-son:not(:has(.insp-hl-input:checked)) .insp-edge-glyph,
.insp-hl-chip-sil:not(:has(.insp-hl-input:checked)) .insp-edge-glyph {
  filter: grayscale(1);
  opacity: 0.75;
}

.insp-hl-chip.is-disabled {
  cursor: not-allowed;
  opacity: 0.35;
}
.insp-hl-chip.is-disabled:hover { background: transparent; }

/* Fallback for engines without :has() — chips stay legible, just not styled
   by state. Modern Safari/Chrome/Firefox all support :has(). */
@supports not selector(:has(*)) {
  .insp-hl-chip { opacity: 1; border-color: var(--color-border); }
  .insp-hl-input { position: static; width: auto; height: auto; opacity: 1; pointer-events: auto; }
  .insp-hl-glyph { display: none; }
}









/* ── Armed link ──────────────────────────────────────────────────────────────
   "This person, then that one" needs no dialog: the source lights up, the bar
   says what will happen, and the next person clicked completes it. */
.insp-link-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 5px 12px;
  background: rgba(var(--insp-node-hue), 0.12);
  border-bottom: 1px solid rgba(var(--insp-node-hue), 0.45);
  font-size: 11.5px;
  flex: 0 0 auto;
}
.insp-link-bar[hidden] { display: none; }

.insp-link-lead { white-space: nowrap; }
.insp-link-hint { color: var(--color-text-secondary); }

.insp-link-rel { display: inline-flex; gap: 2px; }
.insp-link-relbtn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: var(--color-surface);
  color: var(--color-text-secondary);
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-size: 9.5px;
  font-weight: 700;
  cursor: pointer;
}
.insp-link-relbtn.is-on {
  border-color: rgb(var(--insp-node-hue));
  background: var(--color-surface-elevated);
  color: var(--color-text-primary);
}

.insp-link-cancel {
  padding: 2px 8px;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: transparent;
  color: var(--color-text-secondary);
  font-size: 10.5px;
  cursor: pointer;
}
.insp-link-cancel:hover { background: var(--color-surface-hover); color: var(--color-text-primary); }
.insp-link-cancel kbd {
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-size: 9px;
  padding: 0 3px;
  border: 1px solid var(--color-border);
  border-radius: 3px;
}

/* Every person becomes a target while a link is armed. */
body.insp-linking .insp-table tr[data-node-id]:hover > td,
body.insp-linking mark.insp-highlight:hover {
  outline: 1px dashed rgb(var(--insp-node-hue));
  cursor: crosshair;
}
.insp-table tr.is-link-source > td {
  background: rgba(var(--insp-node-hue), 0.18);
  box-shadow: inset 0 -1px 0 rgb(var(--insp-node-hue));
}

/* ── Progress ────────────────────────────────────────────────────────────────
   A hairline along the bottom edge of the control strip. It is positioned
   absolutely so nothing on the page moves when work starts or stops — the
   previous version was a full-height row that appeared between the controls and
   the text, shoving both down and back up again on every run. What is running
   is said on the button that was pressed. */
.insp-marks-bar.has-progress { position: relative; }

.insp-task-progress {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 2px;
  overflow: hidden;
  background: rgba(139, 92, 246, 0.18);
  pointer-events: none;
}
.insp-task-progress[hidden] { display: none; }

.insp-task-progress-fill {
  height: 100%;
  width: 0;
  background: #8b5cf6;
  transition: width 0.25s ease;
}

/* No total to count towards — a single call of unknown length. */
.insp-task-progress.is-indeterminate .insp-task-progress-fill {
  width: 30%;
  animation: insp-progress-slide 1.15s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
@keyframes insp-progress-slide {
  0%   { transform: translateX(-110%); }
  100% { transform: translateX(440%); }
}
@media (prefers-reduced-motion: reduce) {
  .insp-task-progress.is-indeterminate .insp-task-progress-fill {
    animation: none; width: 100%; opacity: 0.55;
  }
}

/* The button carries the running state, so it reads as one control doing one
   thing rather than a banner appearing elsewhere on the page. */
.insp-automap-btn.is-busy {
  opacity: 1;
  cursor: progress;
  color: #a78bfa;
  border-color: rgba(139, 92, 246, 0.5);
  background: rgba(139, 92, 246, 0.12);
}

/* One short pulse on whatever a run just turned up. */
@keyframes insp-flash-new {
  0%, 100% { box-shadow: 0 0 0 0 rgba(139, 92, 246, 0); }
  20%, 60% { box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.55); }
}
.insp-flash-new { animation: insp-flash-new 1.1s ease-in-out 2; border-radius: 3px; }
@media (prefers-reduced-motion: reduce) {
  .insp-flash-new { animation: none; box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.6); }
}

/* A relationship established in another record: file it there rather than
   delete a true claim that merely showed up on the wrong page. */
.insp-dispute-move {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border-radius: 4px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--color-accent-primary, #5b8def);
  font-size: 11px;
  cursor: pointer;
}
.insp-dispute-move:hover {
  background: rgba(91, 141, 239, 0.18);
  border-color: var(--color-accent-primary, #5b8def);
}

/* Accepting an unresolved relationship, beside the delete that was already there. */
.insp-edge-line-ok {
  margin-left: auto;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: var(--color-text-secondary);
  font-size: 11px;
  cursor: pointer;
  opacity: 0.55;
}
.insp-edge-line:hover .insp-edge-line-ok { opacity: 1; }
.insp-edge-line-ok:hover {
  background: rgba(34, 197, 94, 0.18);
  border-color: #22c55e;
  color: #22c55e;
}
.insp-edge-line-ok + .insp-edge-line-del { margin-left: 2px; }




/* ── The row-actions column is pinned ────────────────────────────────────────
   Both grids are wider than the panel they live in, so the last column — the
   one carrying ⇢ and × — sat off the right edge and had to be scrolled to. It
   was reachable and invisible, which is the same as missing: on a 1500px
   desktop the edges × was 71px past the edge and the nodes × 373px.

   Sticky pins it to the right of the scroll container at every width. It needs
   its own opaque background because the row scrolls underneath it, and a soft
   shadow so it reads as floating over the content rather than being part of it. */
.insp-table td:last-child,
.insp-table th:last-child {
  position: sticky;
  right: 0;
  background: var(--color-surface);
  box-shadow: -7px 0 8px -7px rgba(0, 0, 0, 0.55);
}
.insp-table td:last-child { z-index: 3; }
/* The header cell is pinned on both axes, so it outranks the rest of the row
   AND the rest of the header. */
.insp-table th:last-child {
  top: 0;
  z-index: 4;
  background: var(--color-surface-elevated);
}

/* Row state still has to read across the pinned cell, or a verified or disputed
   row would look interrupted at its right edge. */
.insp-table tr.is-disputed > td:last-child { background: rgba(239, 68, 68, 0.16); }
.insp-table tr.is-proposed > td:last-child { background: rgba(139, 92, 246, 0.15); }
.insp-table tr.selected > td:last-child { background: var(--color-surface-elevated); }
.insp-table tr:hover > td:last-child { background: var(--color-surface-hover); }

/* Pinned, so it no longer has to earn its place by being hovered. */
.insp-table td:last-child .insp-row-del,
.insp-table td:last-child .insp-row-link { opacity: 0.6; }
.insp-table tr:hover > td:last-child .insp-row-del,
.insp-table tr:hover > td:last-child .insp-row-link,
.insp-table td:last-child .insp-row-del:focus-visible,
.insp-table td:last-child .insp-row-link:focus-visible { opacity: 1; }

/* ── Batch suggestion bar ────────────────────────────────────────────────────
   A document can come back with a dozen suggestions and half as many disputes.
   Deciding each separately is right when they disagree and pure friction when
   they do not — so each grid can take or drop its whole share at once. */
.insp-suggest-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin: 0 0 6px;
  padding: 4px 8px;
  border: 1px solid rgba(139, 92, 246, 0.45);
  border-radius: 6px;
  background: rgba(139, 92, 246, 0.10);
  font-size: 11px;
  color: var(--color-text-secondary);
}
.insp-suggest-icon { flex: none; }
.insp-suggest-count b { color: #a78bfa; font-variant-numeric: tabular-nums; }

.insp-suggest-btn {
  padding: 2px 9px;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: var(--color-surface-elevated);
  color: var(--color-text-primary);
  font-size: 10.5px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}
.insp-suggest-btn:hover { background: var(--color-surface-hover); }
.insp-suggest-btn.is-accept {
  border-color: rgba(139, 92, 246, 0.6);
  color: #a78bfa;
}
.insp-suggest-btn.is-accept:hover { background: rgba(139, 92, 246, 0.2); }

/* The repair a run recommends, marked so "this is wrong" is followed by
   "so do THIS about it" — only the second is actionable. */
.insp-dispute-accept.is-recommended,
.insp-dispute-move.is-recommended {
  box-shadow: 0 0 0 1.5px currentColor inset;
  font-weight: 700;
}

.insp-dispute-note {
  display: block;
  font-size: 10px;
  line-height: 1.35;
  color: var(--color-warning, #e8c95e);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}


/* ═══════════════════════════════════════════════════════════════════════════
   COMPACT INSPECTION — phones and folded/short screens
   ───────────────────────────────────────────────────────────────────────────
   Four panels sharing a phone give each of them nothing: on a 390×844 handset
   the toolbar took 257px and the mark controls 134px, leaving the reading
   panels twenty-four pixels tall. Below the threshold the view shows ONE panel
   at a time behind a tab bar, and every strip above it becomes a single
   scrollable row so the text keeps the screen.

   The threshold is width OR height. An unfolded foldable in landscape is
   717×512 — wide enough for two panes, far too short for four.
   ═══════════════════════════════════════════════════════════════════════════ */

.insp-panel-tabs { display: none; }

@media (max-width: 820px), (max-height: 560px) {

  /* ── The tab bar ─────────────────────────────────────────────────────── */
  .insp-compact { flex-direction: column !important; }

  .insp-compact .insp-panel-tabs {
    display: flex;
    flex: 0 0 auto;
    gap: 2px;
    padding: 4px 6px;
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-border);
  }

  .insp-panel-tab {
    flex: 1 1 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    /* A finger, not a mouse: 40px is the smallest comfortable target. */
    min-height: 40px;
    padding: 0 6px;
    border: 1px solid transparent;
    border-radius: 6px;
    background: transparent;
    color: var(--color-text-secondary);
    font-size: 13px;
    font-weight: 700;
    cursor: pointer;
  }
  .insp-panel-tab.is-active {
    background: var(--color-surface-elevated);
    border-color: var(--color-border);
    color: var(--color-text-primary);
  }
  .insp-panel-tab-count {
    font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
    font-size: 10px;
    padding: 0 4px;
    border-radius: 8px;
    background: var(--color-surface-hover);
  }
  .insp-panel-tab-count:empty { display: none; }
  /* Something is waiting on a decision in a panel you cannot see. */
  .insp-panel-tab.has-pending .insp-panel-tab-count {
    background: rgba(139, 92, 246, 0.3);
    color: #a78bfa;
  }

  /* ── One panel at a time ─────────────────────────────────────────────── */
  .insp-compact > .insp-source-panels,
  .insp-compact > .insp-entity-panels {
    display: none !important;
    width: 100% !important;
    min-width: 0 !important;
    flex: 1 1 auto !important;
  }
  .insp-compact[data-active-panel="hanmun"] > .insp-source-panels,
  .insp-compact[data-active-panel="korean"] > .insp-source-panels,
  .insp-compact[data-active-panel="nodes"] > .insp-entity-panels,
  .insp-compact[data-active-panel="edges"] > .insp-entity-panels { display: flex !important; }

  .insp-compact .insp-splitter-h,
  .insp-compact .insp-splitter-v { display: none !important; }

  .insp-compact .insp-source-frames { flex-direction: column !important; }
  .insp-compact #insp-panel-hanmun,
  .insp-compact #insp-panel-korean,
  .insp-compact #insp-panel-nodes,
  .insp-compact #insp-panel-edges {
    display: none !important;
    flex: 1 1 auto !important;
    min-height: 0 !important;
    height: auto !important;
  }
  .insp-compact[data-active-panel="hanmun"] #insp-panel-hanmun,
  .insp-compact[data-active-panel="korean"] #insp-panel-korean,
  .insp-compact[data-active-panel="nodes"] #insp-panel-nodes,
  .insp-compact[data-active-panel="edges"] #insp-panel-edges { display: flex !important; }

  /* A single panel needs no header of its own — the tab already names it. The
     header's controls (char count, collapse, orientation) do not survive. */
  .insp-compact .insp-panel-header { display: none !important; }

  /* Mark controls belong to the text panels, so they only appear with them. */
  .insp-compact[data-active-panel="nodes"] .insp-marks-bar,
  .insp-compact[data-active-panel="edges"] .insp-marks-bar { display: none !important; }

  /* ── Strips become one scrollable row each ───────────────────────────── */
  .insp-compact-view .insp-toolbar,
  .insp-compact .insp-marks-bar {
    flex-wrap: nowrap !important;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
  }
  .insp-compact-view .insp-toolbar::-webkit-scrollbar,
  .insp-compact .insp-marks-bar::-webkit-scrollbar { display: none; }
  .insp-compact-view .insp-toolbar > * ,
  .insp-compact .insp-marks-bar > * { flex: 0 0 auto; }

  /* A flex item's default min-width is its CONTENT width, so a strip full of
     chips widens the column it sits in instead of scrolling inside it — which
     dragged the reading panel sideways with it. min-width:0 is what lets the
     overflow above actually take effect. */
  .insp-compact-view .insp-toolbar,
  .insp-compact .insp-marks-bar,
  .insp-compact .insp-detect-report,
  .insp-compact .insp-panel-tabs { min-width: 0; }
  /* `clip`, not `hidden`. Both stop the user scrolling, but `hidden` still makes
     the element a scroll CONTAINER — so when the browser scrolled a chip in the
     strip into view it scrolled this column too, and the reading text slid 111px
     off the left edge. `clip` cannot be scrolled at all, programmatically or
     otherwise, which is the actual requirement. */
  .insp-compact,
  .insp-compact > .insp-source-panels,
  .insp-compact > .insp-entity-panels,
  .insp-compact .insp-source-frames,
  .insp-compact .insp-panel-container {
    min-width: 0;
    /* BOTH axes, and !important. Two reasons it has to be written this way:
       index.html sets `overflow: hidden` inline on these, which beats a rule
       without !important; and per spec `overflow-x: clip` alongside a scrolling
       `overflow-y` computes back to `hidden`, so setting one axis silently
       achieved nothing. These containers never scroll on either axis — the text
       panel and the strips inside them do their own scrolling. */
    overflow: clip !important;
  }
  .insp-compact .insp-marks-spacer { display: none; }

  /* Selects sized for a desktop toolbar are wider than a phone. They live in a
     horizontally scrollable strip, but one control should still never need a
     pan of its own to read. */
  .insp-compact-view .insp-toolbar select.dropdown-input {
    min-width: 0 !important;
    max-width: 46vw !important;
  }
  .insp-compact-view #inspection-doc-select { max-width: 58vw !important; }
  .insp-compact-view .insp-toolbar label { font-size: 10px; }

  /* ── Touch targets ───────────────────────────────────────────────────── */
  /* A tick has to be hittable with a thumb. 18px is a mouse target; the box
     grows and its column grows with it rather than the box overflowing. */
  .insp-compact .insp-check,
  .insp-compact-view .insp-check { width: 22px; height: 22px; }
  .insp-compact .insp-table col.c-pick,
  .insp-compact .insp-table col.c-check { width: 34px; }
  .insp-compact .insp-row-del,
  .insp-compact .insp-row-link,
  .insp-compact .insp-row-rev,
  .insp-compact .insp-prop-accept,
  .insp-compact .insp-prop-reject,
  .insp-compact .insp-dispute-accept,
  .insp-compact .insp-dispute-keep,
  .insp-compact .insp-dispute-move {
    width: 28px;
    height: 28px;
    opacity: 1;           /* no hover on touch: never hide an action behind it */
  }
  .insp-compact .insp-table td { height: 38px; }
  .insp-compact .insp-input,
  .insp-compact .insp-select { font-size: 15px; }   /* 16px-ish: iOS stops zooming */

  /* ── Grids: drop the columns a phone cannot use ──────────────────────── */
  .insp-compact .insp-table col.c-roman,
  .insp-compact .insp-table col.c-hangul,
  .insp-compact .insp-table col.c-hao,
  .insp-compact .insp-table col.c-shi,
  .insp-compact .insp-table col.c-alt,
  .insp-compact .insp-table col.c-notes { width: 0; }
  .insp-compact .insp-table-edges col.c-id,
  .insp-compact .insp-table-edges col.c-order { width: 0; }

  /* These cells are collapsed, NOT removed.
     `display: none` on a cell in a table-layout:fixed table takes it out of the
     row, and every cell after it slides one column to the left — so the last
     column, the one holding ⇢ and ×, was picking up the zeroed Notes width and
     rendering 0px wide with its button hanging outside the row. Zeroing the
     cell in place keeps each cell over the column it belongs to. */
  .insp-compact .insp-table-nodes th:nth-child(6),
  .insp-compact .insp-table-nodes td:nth-child(6),
  .insp-compact .insp-table-nodes th:nth-child(7),
  .insp-compact .insp-table-nodes td:nth-child(7),
  .insp-compact .insp-table-nodes th:nth-child(10),
  .insp-compact .insp-table-nodes td:nth-child(10),
  .insp-compact .insp-table-nodes th:nth-child(11),
  .insp-compact .insp-table-nodes td:nth-child(11),
  .insp-compact .insp-table-nodes th:nth-child(12),
  .insp-compact .insp-table-nodes td:nth-child(12),
  .insp-compact .insp-table-nodes th:nth-child(13),
  .insp-compact .insp-table-nodes td:nth-child(13),
  .insp-compact .insp-table-edges th:nth-child(3),
  .insp-compact .insp-table-edges td:nth-child(3),
  .insp-compact .insp-table-edges th:nth-child(6),
  .insp-compact .insp-table-edges td:nth-child(6),
  .insp-compact .insp-table-edges th:nth-child(9),
  .insp-compact .insp-table-edges td:nth-child(9),
  .insp-compact .insp-table-edges th:nth-child(10),
  .insp-compact .insp-table-edges td:nth-child(10) {
    width: 0;
    max-width: 0;
    padding-left: 0;
    padding-right: 0;
    border-left-width: 0;
    border-right-width: 0;
    overflow: hidden;
    font-size: 0;
    line-height: 0;
    /* Hidden from a screen reader too, not merely shrunk: the cell has to stay
       in the row for the column mapping, but its contents are gone from the
       page in every sense that matters. */
    visibility: hidden;
  }
  .insp-compact .insp-table-nodes th:nth-child(6) > *,
  .insp-compact .insp-table-nodes td:nth-child(6) > *,
  .insp-compact .insp-table-nodes th:nth-child(7) > *,
  .insp-compact .insp-table-nodes td:nth-child(7) > *,
  .insp-compact .insp-table-nodes th:nth-child(10) > *,
  .insp-compact .insp-table-nodes td:nth-child(10) > *,
  .insp-compact .insp-table-nodes th:nth-child(11) > *,
  .insp-compact .insp-table-nodes td:nth-child(11) > *,
  .insp-compact .insp-table-nodes th:nth-child(12) > *,
  .insp-compact .insp-table-nodes td:nth-child(12) > *,
  .insp-compact .insp-table-nodes th:nth-child(13) > *,
  .insp-compact .insp-table-nodes td:nth-child(13) > *,
  .insp-compact .insp-table-edges th:nth-child(3) > *,
  .insp-compact .insp-table-edges td:nth-child(3) > *,
  .insp-compact .insp-table-edges th:nth-child(6) > *,
  .insp-compact .insp-table-edges td:nth-child(6) > *,
  .insp-compact .insp-table-edges th:nth-child(9) > *,
  .insp-compact .insp-table-edges td:nth-child(9) > *,
  .insp-compact .insp-table-edges th:nth-child(10) > *,
  .insp-compact .insp-table-edges td:nth-child(10) > * { display: none; }

  .insp-compact .insp-table-nodes { min-width: 430px; }
  .insp-compact .insp-table-edges { min-width: 0; }

  /* ── Modals fill the screen ──────────────────────────────────────────── */
  .insp-modal-backdrop { padding: 0 !important; }
  .insp-modal,
  .sebo-ai-modal {
    max-width: none !important;
    width: 100% !important;
    height: 100% !important;
    max-height: 100% !important;
    border-radius: 0 !important;
  }
  .sebo-ai-tabs { overflow-x: auto; scrollbar-width: none; }

  /* ── The status card spans the foot ──────────────────────────────────── */
  .insp-task-card { left: 8px; right: 8px; bottom: 8px; width: auto; }

  /* Dragging a frame to re-dock has no meaning when only one is shown. */
  .insp-compact .insp-dock-overlay { display: none !important; }

  /* Curves need somewhere to arc. The reason they were unreadable in a 390px
     column is that they had nowhere to go but across the text, so the fix is to
     give them a gutter rather than to remove them: the reading measure ends
     early and the curve layer owns the strip to its right. Everything that
     depends on the curves — the relationship colours, fade-verified, the
     hover lift, click-to-select — then works unchanged. */
  .insp-compact .insp-doc-body { padding-right: 62px; }
  .insp-compact .insp-edge-svg .edge-path {
    /* Thicker: a 1.7px stroke is a hard target for a fingertip, and a narrow
       column has fewer pixels of curve to see in the first place. */
    stroke-width: 2.2;
  }
  .insp-compact .insp-edge-svg .edge-path:hover,
  .insp-compact .insp-edge-svg .edge-group:hover .edge-path,
  .insp-compact .insp-edge-svg .edge-group.active .edge-path { stroke-width: 3.4; }
  .insp-compact .insp-fade-verified .insp-edge-svg .edge-group.is-verified .edge-path,
  .insp-fade-verified .insp-compact .insp-edge-svg .edge-group.is-verified .edge-path {
    stroke-width: 1.6;
  }

  /* ── The app's own header, on a phone ────────────────────────────────── */
  .insp-compact-view ~ * .topbar-btn .btn-label,
  body:has(.insp-compact-view) .topbar-btn .btn-label,
  body:has(.insp-compact-view) .theme-toggle-btn .btn-label,
  body:has(.insp-compact-view) .topbar-action .btn-label,
  /* Keep the icon; drop only the wording. Hiding every span left an empty box. */
  body:has(.insp-compact-view) #sources-menu-label { display: none; }
  body:has(.insp-compact-view) .topbar-btn,
  body:has(.insp-compact-view) .theme-toggle-btn { padding: 0 9px !important; }
  body:has(.insp-compact-view) .topbar-action { width: 32px; padding: 0 !important; }
  body:has(.insp-compact-view) .top-bar-right { gap: 4px !important; }
}

/* A phone held sideways has ~390px of height for everything. Give the reading
   panel back what the chrome can spare, without dropping targets below a thumb. */
@media (max-height: 480px) {
  .insp-panel-tab { min-height: 34px; font-size: 12px; }
  .insp-compact .insp-panel-tabs { padding: 2px 4px; }
  .insp-compact .insp-marks-bar { padding: 2px 8px; }
  .insp-compact .insp-hl-chip { padding: 2px 6px; }
  .insp-compact-view .insp-toolbar { padding: 4px 10px !important; }
  .insp-compact .insp-subject-header { padding: 4px 8px; margin-bottom: 8px; font-size: 12px; }
}

/* A folded phone's cover screen. Everything that can be an icon becomes one. */
@media (max-width: 400px) {
  /* A narrower gutter on a cover screen: still enough for a curve to bend in,
     without taking a fifth of the reading column. */
  .insp-compact .insp-doc-body { padding-right: 48px; }
  .insp-compact .insp-hl-label { display: none; }
  .insp-compact .insp-hl-chip-sub .insp-hl-label { display: inline; font-size: 8.5px; }
  .insp-compact .insp-batch-btn,
  .insp-compact .insp-suggest-btn { padding: 4px 7px; }
  .insp-compact .insp-detect-report { font-size: 10.5px; }
  .insp-panel-tab { font-size: 12px; padding: 0 3px; }
}

/* ── Foldables, unfolded ──────────────────────────────────────────────────
   With a hinge down the middle, put the text in one segment and the records in
   the other rather than straddling the fold. */
@media (horizontal-viewport-segments: 2) {
  .insp-split-container { flex-direction: row !important; }
  .insp-split-container .insp-panel-tabs { display: none !important; }
  .insp-source-panels { width: env(viewport-segment-width 0 0, 50%) !important; }
  .insp-entity-panels { width: auto !important; flex: 1 1 auto !important; }
  .insp-splitter-h { display: none !important; }
}

/* ── Task status card ────────────────────────────────────────────────────────
   A small fixed panel naming what is happening. The hairline it replaces was
   too quiet to notice — "no progress bar" really meant nothing legible appeared.
   Fixed-position and non-modal: it never moves the page and never blocks the
   work it describes. */
.insp-task-card {
  position: fixed;
  right: 18px;
  bottom: 46px;
  z-index: 1200;
  width: 300px;
  padding: 10px 12px 11px;
  border: 1px solid rgba(139, 92, 246, 0.45);
  border-radius: 8px;
  background: var(--color-surface-elevated);
  box-shadow: 0 8px 26px rgba(0, 0, 0, 0.34);
  font-size: 11.5px;
  color: var(--color-text-primary);
  transition: opacity 0.2s ease;
}
.insp-task-card[hidden] { display: none; }

.insp-task-card-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}

.insp-task-card-title {
  flex: 1 1 auto;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.insp-task-card-count {
  flex: none;
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-variant-numeric: tabular-nums;
  color: var(--color-text-secondary);
}

.insp-task-card-spinner {
  flex: none;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid rgba(139, 92, 246, 0.28);
  border-top-color: #8b5cf6;
  animation: insp-task-spin 0.7s linear infinite;
}
.insp-task-card.is-done .insp-task-card-spinner {
  border-color: #22c55e;
  border-top-color: #22c55e;
  animation: none;
}
@keyframes insp-task-spin { to { transform: rotate(360deg); } }

.insp-task-card-track {
  height: 3px;
  border-radius: 2px;
  background: rgba(139, 92, 246, 0.18);
  overflow: hidden;
}
.insp-task-card-fill {
  height: 100%;
  width: 0;
  background: #8b5cf6;
  transition: width 0.25s ease;
}
.insp-task-card.is-done .insp-task-card-fill { background: #22c55e; width: 100%; }
.insp-task-card.is-indeterminate .insp-task-card-fill {
  width: 32%;
  animation: insp-progress-slide 1.15s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}

/* A short running account, so the card says what was done and not only what is
   being done — a run that ends silently is the invisible-change problem again. */
.insp-task-card-steps {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.insp-task-card-steps:empty { display: none; }
.insp-task-step {
  font-size: 10.5px;
  color: var(--color-text-secondary);
  padding-left: 14px;
  position: relative;
}
.insp-task-step::before {
  content: '✓';
  position: absolute;
  left: 0;
  color: #22c55e;
}
.insp-task-step.is-error { color: var(--insp-danger, #ef4444); }
.insp-task-step.is-error::before { content: '✕'; color: var(--insp-danger, #ef4444); }

@media (prefers-reduced-motion: reduce) {
  .insp-task-card-spinner { animation: none; }
  .insp-task-card.is-indeterminate .insp-task-card-fill { animation: none; width: 100%; opacity: 0.55; }
}

@media (max-width: 700px) {
  .insp-task-card { left: 12px; right: 12px; width: auto; bottom: 12px; }
}

/* ── Detection report, inline ────────────────────────────────────────────── */
.insp-detect-report {
  padding: 6px 12px 8px;
  background: rgba(139, 92, 246, 0.07);
  border-bottom: 1px solid rgba(139, 92, 246, 0.3);
  font-size: 11.5px;
  flex: 0 0 auto;
}
.insp-detect-report[hidden] { display: none; }

.insp-detect-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

.insp-detect-title { font-weight: 700; color: #a78bfa; white-space: nowrap; }

.insp-detect-stat { color: var(--color-text-secondary); white-space: nowrap; }
.insp-detect-stat b { color: var(--color-text-primary); font-variant-numeric: tabular-nums; }
.insp-detect-stat.is-warn b { color: var(--color-warning, #e8c95e); }
.insp-detect-stat.is-prop b { color: #a78bfa; }

.insp-detect-hint { color: #a78bfa; font-weight: 600; white-space: nowrap; }

.insp-detect-close {
  border: 1px solid transparent;
  background: transparent;
  color: var(--color-text-secondary);
  border-radius: 4px;
  width: 20px; height: 20px;
  cursor: pointer;
  font-size: 11px;
  flex: none;
}
.insp-detect-close:hover { background: var(--color-surface-hover); color: var(--color-text-primary); }

.insp-detect-errors {
  margin: 6px 0 0;
  padding-left: 18px;
  color: var(--insp-danger, #ef4444);
}

.insp-detect-unresolved { margin-top: 6px; }
.insp-detect-unresolved summary {
  cursor: pointer;
  color: var(--color-warning, #e8c95e);
  font-weight: 600;
}
.insp-detect-unresolved ul {
  margin: 6px 0 0;
  padding: 0;
  list-style: none;
  max-height: 168px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.insp-detect-unresolved li {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 2px 6px;
  border-radius: 4px;
  background: var(--color-surface);
}
.insp-detect-kind {
  flex: 0 0 auto;
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-secondary);
  min-width: 96px;
}
.insp-detect-unresolved em { color: var(--color-text-secondary); font-size: 10.5px; }
.insp-detect-more { margin: 4px 0 0; color: var(--color-text-secondary); }

/* A proposed person, marked where the model found them. Dashed and violet:
   the same language the provisional rows use, so nothing implies it is data. */
mark.insp-highlight.is-proposed-mark {
  background: rgba(139, 92, 246, 0.18);
  border: 1px dashed rgba(139, 92, 246, 0.75);
  color: inherit;
  border-radius: 3px;
  padding: 0 1px;
}



/* ── A record the run thinks is wrong ────────────────────────────────────────
   A wrong relationship invents an ancestry, so a dispute is not a note — it is
   an action on the row: remove it, or say the doubt is unfounded. Red-tinted,
   because unlike a proposal this concerns something already in the corpus. */
.insp-table tr.is-disputed > td {
  background: rgba(239, 68, 68, 0.10);
  opacity: 1;
}
.insp-table tr.is-disputed > td:first-child {
  box-shadow: inset 3px 0 0 var(--insp-danger, #ef4444);
}
.insp-table tr.is-disputed:hover > td { background: rgba(239, 68, 68, 0.17); }

.insp-dispute-accept,
.insp-dispute-keep {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border-radius: 4px;
  border: 1px solid transparent;
  background: transparent;
  font-size: 11px;
  line-height: 1;
  cursor: pointer;
  opacity: 1;
}
.insp-dispute-accept { color: var(--insp-danger, #ef4444); }
.insp-dispute-accept:hover {
  background: rgba(239, 68, 68, 0.2);
  border-color: var(--insp-danger, #ef4444);
}
.insp-dispute-keep { color: var(--color-text-secondary); }
.insp-dispute-keep:hover {
  background: rgba(34, 197, 94, 0.16);
  border-color: #22c55e;
  color: #22c55e;
}

/* ── People in no relationship ───────────────────────────────────────────────
   A node with no HAS_SON and no HAS_SIL cannot join the network it is in. They
   are greyed rather than hidden: "recorded but connected to nobody" is a finding
   about the extraction, not noise to suppress, and some of them are people whose
   kinship simply has not been found yet. */
.insp-table tr.is-unlinked > td {
  opacity: 0.5;
  transition: opacity 0.12s ease;
}
.insp-table tr.is-unlinked:hover > td,
.insp-table tr.is-unlinked.selected > td,
.insp-table tr.is-unlinked.insp-hover > td { opacity: 1; }

/* Never fade a row the reader is being asked to decide about. */
.insp-table tr.is-unlinked.is-proposed > td { opacity: 1; }

.insp-table tr.is-unlinked.is-flash > td {
  opacity: 1;
  background: rgba(232, 201, 94, 0.22);
}

/* The mark rides on the row's leading edge, not in a cell: the id column is
   46px with overflow hidden, so a badge placed there is simply never seen. */
.insp-table tr.is-unlinked > td:first-child {
  box-shadow: inset 3px 0 0 rgba(232, 201, 94, 0.85);
}

.insp-unlinked-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin: 0 0 6px;
  padding: 4px 8px;
  border: 1px solid rgba(232, 201, 94, 0.4);
  border-radius: 6px;
  background: rgba(232, 201, 94, 0.09);
  font-size: 11px;
  color: var(--color-text-secondary);
}
.insp-unlinked-count b { color: var(--color-warning, #e8c95e); font-variant-numeric: tabular-nums; }
.insp-unlinked-corpus { opacity: 0.8; }

.insp-unlinked-btn {
  padding: 2px 8px;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: var(--color-surface-elevated);
  color: var(--color-text-primary);
  font-size: 10.5px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}
.insp-unlinked-btn:hover { background: var(--color-surface-hover); }
.insp-unlinked-btn.is-danger:hover {
  background: rgba(239, 68, 68, 0.14);
  border-color: var(--insp-danger);
  color: var(--insp-danger);
}

/* ── Model proposals: detected, not yet in the file ──────────────────────────
   A provisional row has to be unmistakable at a glance, or someone will read it
   as data. The dashed left rule and the violet wash say "this is not in your
   corpus yet"; accepting turns it into an ordinary row. */
.insp-table tr.is-proposed > td {
  background: rgba(139, 92, 246, 0.09);
  border-bottom-style: dashed;
  color: var(--color-text-primary);
}
.insp-table tr.is-proposed > td:first-child {
  box-shadow: inset 3px 0 0 #8b5cf6;
}
.insp-table tr.is-proposed:hover > td { background: rgba(139, 92, 246, 0.16); }

/* An edge whose people do not exist yet cannot be accepted until they do. */
.insp-table tr.is-proposed.is-unresolvable > td { opacity: 0.7; }
.insp-table tr.is-proposed .insp-id-suffix.is-missing {
  background: transparent;
  border-style: dashed;
  color: var(--color-text-secondary);
}

.insp-prop-badge { font-size: 11px; }

.insp-prop-conf {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  font-size: 9px;
  font-weight: 700;
  background: var(--color-surface-hover);
  color: var(--color-text-secondary);
}
.insp-prop-conf.high { background: rgba(34, 197, 94, 0.22); color: #22c55e; }
.insp-prop-conf.medium { background: rgba(232, 152, 94, 0.22); color: #e8985e; }
.insp-prop-conf.low { background: rgba(239, 68, 68, 0.18); color: #ef4444; }

/* Quoted or inferred, told apart at a glance.
   Inference is allowed now — the corpus is verified document by document, so a
   claim the text supports without stating is worth reading. What the reader
   must never have to guess is which kind they are looking at, so a claim with
   no verbatim quote behind it wears a dashed ring: present, legible, and not
   dressed up as something the document says. A quote that was GIVEN but does
   not match the text is a different matter and stays an alarm. */
.insp-prop-conf.is-inferred {
  border: 1px dashed currentColor;
  background: transparent;
}
.insp-prop-conf.is-unquoted {
  box-shadow: 0 0 0 1px #ef4444;
}

.insp-prop-name { font-weight: 600; }
.insp-prop-rel {
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-size: 10px;
  font-weight: 700;
}
.insp-prop-reason {
  font-size: 10.5px;
  color: var(--color-text-secondary);
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Editable before it is accepted: the model's reading of a name is a starting
   point, and correcting it in place is the whole point of showing it as a row. */
.insp-input.is-proposed-input {
  background: rgba(139, 92, 246, 0.10);
  border-color: rgba(139, 92, 246, 0.35);
}

.insp-id-suffix.is-proposed-id {
  background: rgba(139, 92, 246, 0.16);
  border-color: rgba(139, 92, 246, 0.5);
  color: #a78bfa;
  border-style: dashed;
}

/* A detail the model offered for a person who is already on file: shown in the
   cell it would fill, shaded, and NOT committed until the ✓ beside it is used. */
.insp-input.is-suggested {
  background: rgba(139, 92, 246, 0.13);
  border-color: rgba(139, 92, 246, 0.45);
  color: #a78bfa;
}

.insp-prop-accept,
.insp-prop-reject {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border-radius: 4px;
  border: 1px solid transparent;
  background: transparent;
  font-size: 11px;
  line-height: 1;
  cursor: pointer;
  transition: all 0.12s ease;
}
.insp-prop-accept { color: #22c55e; }
.insp-prop-reject { color: var(--color-text-secondary); }
.insp-prop-accept:hover {
  background: rgba(34, 197, 94, 0.18);
  border-color: #22c55e;
}
.insp-prop-reject:hover {
  background: rgba(239, 68, 68, 0.16);
  border-color: var(--insp-danger);
  color: var(--insp-danger);
}

/* Proposals never fade with the verified rule — they are the opposite of done. */
.insp-fade-verified .insp-table tr.is-proposed > td { opacity: 1; }

/* ── Shared control strip above both reading frames ──────────────────────────
   Everything that governs how 原文 and 국역 read lives here, once, instead of
   in one panel's header pretending to speak for both. */
.insp-marks-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  padding: 5px 10px;
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  flex: 0 0 auto;
}

.insp-marks-spacer { flex: 1 1 auto; }
.insp-marks-tail { display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto; }

/* The frames wrapper: the bar is a sibling above it, so switching between
   stacked and side-by-side never moves the controls. */
.insp-source-frames {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;
  position: relative;
  overflow: hidden;
}

/* The "fade verified" chip carries a tick rather than a mark sample: it is not
   a kind of mark, it is a rule about how marks that are done should read. */
.insp-hl-chip-verified .insp-hl-glyph::after {
  content: '✓';
  font-size: 9px;
  line-height: 1;
  color: var(--color-text-secondary);
}
.insp-hl-chip-verified:has(.insp-hl-input:checked) {
  border-color: rgba(34, 197, 94, 0.5);
  background: rgba(34, 197, 94, 0.10);
  color: #22c55e;
}
.insp-hl-chip-verified:has(.insp-hl-input:checked) .insp-hl-glyph {
  background: rgba(34, 197, 94, 0.16);
  border: 1px solid rgba(34, 197, 94, 0.5);
  border-radius: 3px;
}
.insp-hl-chip-verified:has(.insp-hl-input:checked) .insp-hl-glyph::after { color: #22c55e; }
/* This chip counts nothing — it is a rule, not a tally. */
.insp-hl-chip-verified .insp-hl-count { display: none; }

/* ── Faded-verified mode ─────────────────────────────────────────────────────
   Signed-off records stay on screen and stay clickable; they simply stop
   competing for attention with the ones still to check. Hover and selection
   bring any of them straight back to full strength. */
/* Two independent fades, each driven by its own record.
   A PERSON recedes when that person is ticked off: their mark in the text.
   A RELATIONSHIP recedes when the relationship is ticked off: its evidence
   rule, its inline badge, and its curve. Ticking a person must never quieten a
   relationship that has not been checked — that would make the display say
   something the data does not. */
.insp-fade-verified .insp-highlight.is-verified-mark,
.insp-fade-verified .insp-edge-ev.is-verified-mark,
.insp-fade-verified .insp-edge-badge.is-verified-mark {
  opacity: 0.35;
  filter: saturate(0.55);
  transition: opacity 0.12s ease, filter 0.12s ease;
}
.insp-fade-verified .insp-highlight.is-verified-mark:hover,
.insp-fade-verified .insp-edge-ev.is-verified-mark:hover,
.insp-fade-verified .insp-edge-badge.is-verified-mark:hover,
.insp-fade-verified .insp-highlight.is-verified-mark.insp-hover,
.insp-fade-verified .insp-edge-ev.is-verified-mark.insp-hover,
.insp-fade-verified .insp-edge-badge.is-verified-mark.insp-hover,
.insp-fade-verified .insp-edge-ev.is-verified-mark.insp-edge-ev-active {
  opacity: 1;
  filter: none;
}

/* The curve of a checked relationship. It drops well below the unchecked
   weight above, so a glance at the column tells you how much is left. */
.insp-fade-verified .insp-edge-svg .edge-group.is-verified .edge-path {
  opacity: 0.13;
  stroke-width: 1.2;
}
.insp-fade-verified .insp-edge-svg .edge-group.is-verified:hover .edge-path,
.insp-fade-verified .insp-edge-svg .edge-group.is-verified.active .edge-path,
.insp-fade-verified .insp-edge-svg .edge-group.is-verified.is-linked .edge-path {
  opacity: 1;
  stroke-width: 2.6;
}

.insp-fade-verified .insp-table tr.is-verified > td {
  opacity: 0.42;
  transition: opacity 0.12s ease;
}
.insp-fade-verified .insp-table tr.is-verified:hover > td,
.insp-fade-verified .insp-table tr.is-verified.selected > td,
.insp-fade-verified .insp-table tr.is-verified.insp-hover > td,
.insp-fade-verified .insp-table tr.is-verified.is-batch-selected > td { opacity: 1; }
/* The tick itself never fades — it is what tells you why the row is quiet. */
.insp-fade-verified .insp-table tr.is-verified > td.ta-center:has(.insp-check) { opacity: 1; }

/* ── Auto-save status chip ───────────────────────────────────────────────────
   Saving is automatic, so this reports rather than asks. It occupies the space
   the Save button used to, at a much lower visual weight: a state you glance at,
   not a button you have to remember to press. */
.autosave-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 34px;
  padding: 0 11px;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: transparent;
  color: var(--color-text-secondary);
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
  cursor: default;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}

/* ── Top-bar actions ───────────────────────────────────────────────────────
   The LLM settings button and the theme switch are the same kind of thing — a
   control that is always there and rarely pressed — so they are the same size
   and carry no outline of their own. A border around each made two boxes
   competing with the view switcher beside them, which is the control that
   actually matters up here. */
.topbar-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 32px;
  padding: 0 10px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--color-text-secondary);
  font-size: 12px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.topbar-action:hover {
  background: var(--color-surface-hover, rgba(148, 163, 184, 0.14));
  color: var(--color-text);
}
.topbar-action:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
/* Icon-only: a square, so it matches the labelled one's height exactly. */
.topbar-action.is-icon-only {
  width: 32px;
  padding: 0;
}
.topbar-action .btn-icon { font-size: 14px; }
#btn-ai-settings { color: #a78bfa; }
#btn-ai-settings:hover { background: rgba(139, 92, 246, 0.16); color: #c4b5fd; }

.autosave-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.55;
  flex: none;
}

/* Nothing to report: no border, no background, no word — just a dot, so the
   bar is not carrying a permanent label that says everything is normal. It
   returns to a full chip the moment there IS something to say (saving, unsaved,
   failed), which is the only reason it exists. */
.autosave-chip[data-state="saved"],
.autosave-chip[data-state="idle"] {
  color: var(--color-text-secondary);
  border-color: transparent;
  background: transparent;
  padding: 0 6px;
  gap: 0;
}
.autosave-chip[data-state="saved"] .autosave-text,
.autosave-chip[data-state="idle"] .autosave-text {
  display: none;
}
.autosave-chip[data-state="saved"] .autosave-dot,
.autosave-chip[data-state="idle"] .autosave-dot { opacity: 0.35; }
/* Hovering asks the question, so answer it. */
.autosave-chip[data-state="saved"]:hover .autosave-text,
.autosave-chip[data-state="idle"]:hover .autosave-text { display: inline; }
.autosave-chip[data-state="saved"]:hover,
.autosave-chip[data-state="idle"]:hover { gap: 6px; padding: 0 11px; }

.autosave-chip[data-state="pending"],
.autosave-chip[data-state="saving"] {
  color: #22c55e;
  border-color: rgba(34, 197, 94, 0.45);
  background: rgba(34, 197, 94, 0.08);
}
.autosave-chip[data-state="pending"] .autosave-dot,
.autosave-chip[data-state="saving"] .autosave-dot { animation: autosave-pulse 1s ease-in-out infinite; }

.autosave-chip[data-state="error"] {
  color: var(--insp-danger, #ef4444);
  border-color: rgba(239, 68, 68, 0.5);
  background: rgba(239, 68, 68, 0.08);
}

/* Another window is writing: the chip says so rather than the view changing
   under the reader with no explanation. */
.autosave-chip.is-syncing {
  color: var(--color-accent-primary, #5b8def);
  border-color: rgba(91, 141, 239, 0.45);
  background: rgba(91, 141, 239, 0.08);
}
.autosave-chip.is-syncing .autosave-dot { animation: autosave-pulse 1s ease-in-out infinite; }

/* The change stream is down, so other windows' edits will not arrive until it
   reconnects. Saving still works; only the live view of it is affected. */
.autosave-chip.is-offline { opacity: 0.6; }
.autosave-chip.is-offline .autosave-dot { box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.35); }

@keyframes autosave-pulse {
  0%, 100% { opacity: 0.35; }
  50%      { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .autosave-chip .autosave-dot { animation: none; }
}

/* The three review actions now sit in the Table view's own toolbar. */
.table-action-btn .btn-label { font-weight: 600; }

@media (max-width: 1200px) {
  .table-action-btn .btn-label { display: none; }
}

/* ── "This document has no principal figure" ─────────────────────────────────
   A recorded decision, so it is stated rather than shown as an empty space —
   blank is what an unexamined document looks like, and it means the opposite. */
.insp-subject-header.is-none {
  border-left-color: var(--color-border);
  background: var(--color-surface);
  opacity: 0.92;
}

.insp-subject-header.is-none .insp-subject-label {
  background: var(--color-surface-hover);
  color: var(--color-text-secondary);
  border-color: var(--color-border);
}

.insp-subject-none-text {
  color: var(--color-text-secondary);
  font-weight: 600;
}

.insp-subject-none-why {
  color: var(--color-text-secondary);
  font-size: 12px;
  opacity: 0.85;
}

.insp-subject-none-clear {
  margin-left: auto;
  padding: 2px 8px;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: transparent;
  color: var(--color-text-secondary);
  font-size: 10.5px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.insp-subject-none-clear:hover {
  background: var(--color-surface-hover);
  color: var(--color-text-primary);
}

/* The subject column header doubles as the "none" control, so the decision can
   be made without first handing the star to somebody. */
.insp-table th.td-subject-head { padding: 0; }

.insp-subject-head-btn {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  width: 100%;
  height: 100%;
  padding: 0 4px;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
  justify-content: center;
}
.insp-subject-head-btn:hover { background: var(--color-surface-hover); }
.insp-subject-head-btn.is-none {
  color: var(--color-warning, #e8c95e);
  background: rgba(232, 201, 94, 0.12);
}

/* ── Source frame orientation: stacked or side by side ───────────────────────
   The two source frames can sit one above the other (reading order) or beside
   each other (comparison order). Drag either frame's header to rearrange them;
   the ⇄/⇅ button in each header does the same thing without a drag. */
.insp-source-panels { position: relative; }

.insp-source-frames.insp-source-row > .insp-panel-container {
  min-height: 0;
}

/* Side by side, the frames need a floor in the other axis instead. */
.insp-source-frames.insp-source-row > .insp-panel-container.insp-panel-collapsed {
  flex: none !important;
  width: 38px !important;
  min-width: 38px !important;
  height: auto !important;
  min-height: 0 !important;
}
/* A collapsed frame in row mode turns its header on its side, so the title
   stays readable in a 38px column rather than being clipped to nothing. */
.insp-source-frames.insp-source-row > .insp-panel-collapsed .insp-panel-header {
  writing-mode: vertical-rl;
  height: 100%;
  padding: 10px 8px;
  justify-content: flex-start;
}

.insp-orient-btn { font-size: 12px; }

/* Docking: the four places a dragged frame can land. */
body.insp-docking, body.insp-docking * {
  cursor: grabbing !important;
  user-select: none !important;
}

.insp-panel-container.insp-panel-dragging { opacity: 0.55; }

/* A scrim behind the zones: without it the four targets are read against the
   document text and neither is legible. */
.insp-dock-overlay {
  position: absolute;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  background: rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(1.5px);
}
:root[data-theme="light"] .insp-dock-overlay { background: rgba(255, 255, 255, 0.55); }

.insp-dock-zone {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px dashed var(--color-border);
  border-radius: 8px;
  background: rgba(var(--insp-node-hue), 0.06);
  color: var(--color-text-secondary);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  transition: background 0.1s ease, border-color 0.1s ease, color 0.1s ease;
}

.insp-dock-zone.is-on {
  border-style: solid;
  border-color: rgb(var(--insp-node-hue));
  background: rgba(var(--insp-node-hue), 0.24);
  color: var(--color-text-primary);
}

.insp-dock-top    { top: 6px; left: 6px; right: 6px; height: 26%; }
.insp-dock-bottom { bottom: 6px; left: 6px; right: 6px; height: 26%; }
.insp-dock-left   { left: 6px; top: 30%; bottom: 30%; width: 30%; }
.insp-dock-right  { right: 6px; top: 30%; bottom: 30%; width: 30%; }

/* On a narrow screen the frames always stack: two 220px columns plus the
   entity panel does not fit, and a side-by-side reading column would be
   unusable long before that. */
@media (max-width: 1100px) {
  .insp-source-frames.insp-source-row { flex-direction: column !important; }
  .insp-source-frames.insp-source-row > .insp-panel-container { min-width: 0 !important; }
  .insp-orient-btn { display: none; }
}

/* ── Identify control + shared settings dialog ───────────────────────────────
   One button that runs the task and one gear that configures it, joined so they
   read as a single control rather than two competing actions. */
.insp-identify-group {
  display: inline-flex;
  align-items: stretch;
  flex: 0 0 auto;
}
.insp-identify-group .insp-identify-run {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
  margin-right: 0;
}
.insp-identify-group .insp-identify-gear {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  border-left-width: 0;
  padding-left: 7px;
  padding-right: 7px;
  min-width: 0;
}
.insp-identify-group .insp-identify-gear:hover { border-left-width: 0; }

.insp-identify-h {
  font-size: 12px;
  font-weight: 700;
  margin: 16px 0 6px;
  color: var(--color-text-primary);
}
.insp-identify-what { font-style: normal; }

/* The 原文 surface form shown beside a full name the 국역 supplied. */
.insp-ai-surface {
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-size: 10px;
  padding: 1px 5px;
  margin-left: 5px;
  border-radius: 3px;
  background: var(--color-surface-hover);
  color: var(--color-text-secondary);
}

/* Settings dialog: three panes behind a tab strip, so a long form does not
   become one endless scroll. */
.sebo-ai-modal { max-width: 720px; }

.sebo-ai-tabs {
  display: flex;
  gap: 2px;
  margin: 4px 0 14px;
  border-bottom: 1px solid var(--color-border);
}
.sebo-ai-tab {
  padding: 6px 12px;
  border: 1px solid transparent;
  border-bottom: 2px solid transparent;
  background: transparent;
  color: var(--color-text-secondary);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  border-radius: 5px 5px 0 0;
}
.sebo-ai-tab:hover { background: var(--color-surface-hover); }
.sebo-ai-tab.is-active {
  color: var(--color-text-primary);
  border-bottom-color: rgb(var(--insp-node-hue));
}

.sebo-ai-pane { display: none; }
.sebo-ai-pane.is-active { display: block; }

.sebo-ai-scopenote {
  margin: 6px 0 0;
  font-size: 11px;
  color: var(--color-text-secondary);
}

/* The "what to look for" set is meaningless without the model step, and a set
   with nothing ticked is a call that will find nothing. */
.sebo-ai-wants[hidden] { display: none; }
.sebo-ai-wants.is-empty {
  border-color: var(--color-warning, #e8c95e);
}
.sebo-ai-wants.is-empty legend::after {
  content: ' — nothing selected, so the model would be asked for nothing';
  color: var(--color-warning, #e8c95e);
  font-weight: 600;
}

/* A step the user has switched on but which cannot run yet. */
.insp-checkline.is-blocked { color: var(--color-warning, #e8c95e); }
.insp-checkline.is-blocked em { color: var(--color-warning, #e8c95e); opacity: 0.95; }

.sebo-ai-promptbar {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 6px;
}
.sebo-ai-promptstate {
  font-size: 11px;
  font-weight: 600;
  color: var(--color-text-secondary);
}
.sebo-ai-promptstate.is-custom { color: rgb(var(--insp-node-hue)); }

.sebo-ai-prompt {
  width: 100%;
  min-height: 260px;
  resize: vertical;
  padding: 10px 12px;
  border-radius: 6px;
  border: 1px solid var(--color-border);
  background: var(--color-bg);
  color: var(--color-text-primary);
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-size: 11.5px;
  line-height: 1.55;
  white-space: pre-wrap;
}
.sebo-ai-prompt:focus {
  outline: none;
  border-color: var(--insp-focus);
  box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.35);
}
.sebo-ai-prompt-note {
  margin: 8px 0 0;
  font-size: 11px;
  color: var(--color-text-secondary);
}

/* ── 原文 text size control ── */
.insp-fontsize {
  display: inline-flex;
  align-items: center;
  gap: 1px;
  border: 1px solid var(--color-border);
  border-radius: 5px;
  overflow: hidden;
  background: var(--color-bg);
}

.insp-fontsize-btn {
  background: transparent;
  border: none;
  color: var(--color-text-secondary);
  font-size: 10px;
  font-weight: 700;
  padding: 2px 5px;
  cursor: pointer;
  line-height: 1.5;
}

.insp-fontsize-btn:hover {
  background: var(--color-surface-hover);
  color: var(--color-text-primary);
}

.insp-fontsize-readout {
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-size: 9.5px;
  color: var(--color-text-secondary);
  padding: 0 3px;
  cursor: pointer;
  min-width: 28px;
  text-align: center;
}

/* ──────────────────────────────────────────────────────────────────────────
   Toolbar: progress, save state, verification
   ────────────────────────────────────────────────────────────────────────── */
.insp-progress {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  white-space: nowrap;
}

.insp-progress-bar {
  position: relative;
  display: inline-block;
  width: 92px;
  height: 6px;
  border-radius: 3px;
  background: var(--color-border);
  overflow: hidden;
  vertical-align: middle;
}

/* Two stacked fills: how far through the corpus you are (bright), and how
   much of it has been signed off (green, underneath). */
.insp-progress-done {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--insp-ok);
  opacity: 0.5;
  transition: width 0.25s ease;
}

.insp-progress-fill {
  position: absolute;
  inset: 0 auto 0 0;
  border-top: 2px solid rgb(var(--insp-node-hue));
  transition: width 0.25s ease;
}

.insp-progress-checked {
  font-size: 10.5px;
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  color: var(--insp-ok);
}

.insp-progress-checked.is-none { color: var(--color-text-secondary); opacity: 0.7; }
.insp-progress-checked.is-all { font-weight: 700; }

.insp-jump-btn,
.insp-verify-btn,
.insp-delete-doc-btn,
.insp-automap-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 10.5px;
  font-weight: 600;
  padding: 3px 8px;
  border-radius: 5px;
  border: 1px solid var(--color-border);
  background: var(--color-surface-elevated);
  color: var(--color-text-secondary);
  cursor: pointer;
  white-space: nowrap;
  transition: all 0.12s ease;
}

.insp-jump-btn:hover:not(:disabled),
.insp-verify-btn:hover:not(:disabled),
.insp-automap-btn:hover:not(:disabled) {
  color: var(--color-text-primary);
  border-color: rgb(var(--insp-node-hue));
  background: rgba(var(--insp-node-hue), 0.12);
}

.insp-jump-btn:disabled,
.insp-verify-btn:disabled,
.insp-delete-doc-btn:disabled,
.insp-automap-btn:disabled { opacity: 0.4; cursor: not-allowed; }

/* Destructive: removes an entire record from the source document. Muted until
   hovered so it never competes with the everyday controls beside it. */
.insp-delete-doc-btn {
  border-color: rgba(239, 68, 68, 0.35);
  color: var(--color-danger, #ef4444);
}

.insp-delete-doc-btn:hover:not(:disabled) {
  border-color: var(--color-danger, #ef4444);
  background: rgba(239, 68, 68, 0.12);
  color: var(--color-danger, #ef4444);
}

.insp-automap-btn {
  border-color: rgba(var(--insp-edge-hue), 0.5);
  color: rgb(var(--insp-edge-hue));
}
[data-theme="dark"] .insp-automap-btn { color: #f0b98a; }

.insp-automap-btn:hover:not(:disabled) {
  background: rgba(var(--insp-edge-hue), 0.15);
  border-color: rgb(var(--insp-edge-hue));
}

.insp-verify-btn.is-verified {
  color: var(--insp-ok);
  border-color: var(--insp-ok);
  background: rgba(16, 185, 129, 0.12);
}

.insp-checked-chip {
  transition: all 0.15s ease;
  background: var(--color-surface-elevated);
  border: 1px solid var(--color-border);
  color: var(--color-text-secondary);
  white-space: nowrap;   /* never let it fold into a three-line block */
}

.insp-checked-chip.is-partial {
  border-color: rgba(var(--insp-node-hue), 0.55);
  color: var(--color-text-primary);
}

.insp-checked-chip.is-complete {
  border-color: var(--insp-ok);
  color: var(--insp-ok);
  background: rgba(16, 185, 129, 0.13);
  font-weight: 700;
}

.insp-save-chip {
  font-size: 11px;
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  padding: 3px 8px;
  border-radius: 5px;
  border: 1px solid var(--color-border);
  color: var(--color-text-secondary);
  background: var(--color-surface-elevated);
  white-space: nowrap;
  cursor: pointer;
}

.insp-save-chip[data-state="dirty"] {
  color: var(--insp-warn);
  border-color: var(--insp-warn);
  background: rgba(245, 158, 11, 0.13);
}
.insp-save-chip[data-state="saving"] {
  color: rgb(var(--insp-node-hue));
  border-color: rgb(var(--insp-node-hue));
  background: rgba(var(--insp-node-hue), 0.13);
}
.insp-save-chip[data-state="saved"] {
  color: var(--insp-ok);
  border-color: var(--insp-ok);
  background: rgba(16, 185, 129, 0.13);
}
.insp-save-chip[data-state="error"] {
  color: var(--insp-danger);
  border-color: var(--insp-danger);
  background: rgba(239, 68, 68, 0.15);
  font-weight: 700;
}

.insp-overlay-note {
  font-size: 10px;
  color: var(--color-text-secondary);
  border: 1px dashed var(--color-border);
  border-radius: 5px;
  padding: 3px 7px;
  white-space: nowrap;
  opacity: 0.9;
}

.insp-overlay-chip {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 10px;
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  color: #7c3aed;
  background: rgba(139, 92, 246, 0.14);
  border: 1px solid rgba(139, 92, 246, 0.45);
  border-radius: 4px;
  padding: 1px 5px;
}

[data-theme="dark"] .insp-overlay-chip { color: #c4b5fd; }

.insp-overlay-revert {
  background: none;
  border: none;
  cursor: pointer;
  padding: 0 1px;
  color: inherit;
  font-size: 11px;
  line-height: 1;
}

/* ──────────────────────────────────────────────────────────────────────────
   Per-corpus subject term
   What a document's principal figure is called is genre-specific — 墓主 for an
   epitaph, 傳主 for a 列傳 biography, 卒記主 for a Sillok obituary. The term
   lives beside the Source selector because it is a property of the corpus.
   ────────────────────────────────────────────────────────────────────────── */
.insp-subject-label-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 32px;
  padding: 0 9px;
  border-radius: 6px;
  border: 1px solid var(--color-border);
  background: var(--color-surface-elevated);
  color: var(--color-text-primary);
  cursor: pointer;
  font-size: 12px;
  white-space: nowrap;
  transition: all 0.12s ease;
}

.insp-subject-label-btn:hover {
  border-color: #f59e0b;
  background: rgba(245, 158, 11, 0.12);
}

.insp-subject-label-icon { font-size: 11px; }

.insp-subject-label-hanja {
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.02em;
}

.insp-subject-label-en {
  font-size: 10.5px;
  color: var(--color-text-secondary);
}

.insp-subject-label-caret {
  font-size: 8px;
  color: var(--color-text-secondary);
}

/* ──────────────────────────────────────────────────────────────────────────
   Selection action ("＋ Add person from selection")
   Floats beside a sweep of the source text. It has to be findable the instant
   it appears and invisible the rest of the time, so: small, one line, anchored
   to the selection, and gone the moment the selection is.
   ────────────────────────────────────────────────────────────────────────── */
.insp-selact {
  position: fixed;
  z-index: 9600;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: min(520px, calc(100vw - 16px));
  padding: 4px 6px;
  border-radius: 8px;
  border: 1px solid var(--color-border);
  background: var(--color-surface-elevated, var(--color-surface));
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  font-size: 11.5px;
  animation: insp-selact-in 0.12s ease-out;
}

.insp-selact[hidden] { display: none; }

@keyframes insp-selact-in {
  from { opacity: 0; transform: translateY(3px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .insp-selact { animation: none; }
}

/* The characters themselves, echoed back so the user can confirm the span
   without looking away from the button they are about to press. */
.insp-selact-quote {
  flex: 0 0 auto;
  max-width: 12em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 1px 6px;
  border-radius: 4px;
  background: var(--insp-mark-bg);
  box-shadow: inset 0 0 0 1px var(--insp-mark-ring);
  font-weight: 700;
  font-size: 13px;
  line-height: 1.5;
}

.insp-selact-btn {
  flex: 0 0 auto;
  padding: 4px 9px;
  border-radius: 6px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text-primary);
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease;
}

.insp-selact-btn:hover {
  background: var(--color-surface-hover);
  border-color: var(--insp-focus);
}

.insp-selact-primary {
  background: rgba(var(--insp-node-hue), 0.16);
  border-color: var(--insp-mark-ring-hover);
}



.insp-selact-primary:hover {
  background: rgba(var(--insp-node-hue), 0.30);
}

.insp-selact-note {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 10.5px;
  line-height: 1.35;
}

.insp-selact-blocked { color: var(--insp-danger); }
.insp-selact-warn    { color: var(--insp-warn); }

/* The same quoted span, restated inside the Add-a-person form. */
.insp-form-mention {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 5px;
  margin: 0;
}

@media (max-width: 768px) {
  /* On a phone the bar would run off the edge next to a long explanation;
     stack it and let the note wrap under the buttons. */
  .insp-selact { flex-wrap: wrap; row-gap: 5px; }
  .insp-selact-btn { padding: 6px 10px; }
  .insp-selact-note { flex-basis: 100%; }
}

/* ── The inline editor popover ── */
.insp-pop {
  position: fixed;
  z-index: 9500;
  width: 330px;
  max-width: calc(100vw - 16px);
  padding: 12px 14px 11px;
  border-radius: 10px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text-primary);
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.4);
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.insp-pop[hidden] { display: none; }

.insp-pop-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.insp-pop-title { font-size: 13px; }

.insp-pop-close {
  background: transparent;
  border: none;
  color: var(--color-text-secondary);
  font-size: 18px;
  line-height: 1;
  width: 22px;
  height: 22px;
  border-radius: 5px;
  cursor: pointer;
}

.insp-pop-close:hover { background: var(--color-surface-hover); color: var(--color-text-primary); }

.insp-pop-lede {
  margin: 0;
  font-size: 11px;
  line-height: 1.5;
  color: var(--color-text-secondary);
}

.insp-pop-fields { display: flex; gap: 8px; }

.insp-pop-field {
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-secondary);
  flex: 1;
}

.insp-pop-field input {
  font-size: 13px;
  font-weight: 600;
  padding: 6px 8px;
  border-radius: 6px;
  border: 1px solid var(--color-border);
  background: var(--color-bg);
  color: var(--color-text-primary);
  text-transform: none;
  letter-spacing: normal;
  width: 100%;
  box-sizing: border-box;
}

.insp-pop-field:first-child { flex: 0 0 96px; }
.insp-pop-field:first-child input { font-size: 15px; }

.insp-pop-field input:focus {
  border-color: var(--insp-focus);
  box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.35);
  outline: none;
}

.insp-pop-suggest { display: flex; flex-direction: column; gap: 5px; }

.insp-pop-suggest-title {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-secondary);
}

.insp-pop-suggest-list {
  display: flex;
  flex-direction: column;
  gap: 3px;
  max-height: 208px;
  overflow-y: auto;
}

.insp-suggest {
  display: flex;
  align-items: baseline;
  gap: 6px;
  width: 100%;
  padding: 4px 7px;
  border-radius: 5px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--color-text-primary);
  cursor: pointer;
  text-align: left;
  font-size: 11.5px;
}

.insp-suggest:hover {
  background: var(--color-surface-hover);
  border-color: var(--color-border);
}

.insp-suggest-hanja { font-size: 13.5px; font-weight: 700; min-width: 44px; }

.insp-suggest-hangul {
  font-size: 10px;
  color: var(--color-text-secondary);
  min-width: 42px;
}

.insp-suggest-en { color: var(--color-text-secondary); flex: 1; }

/* The suggestion that matches the active corpus's genre is the recommended
   one, so it is badged rather than merely sorted first. */
.insp-suggest-fit {
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 1px 5px;
  border-radius: 3px;
  background: rgba(16, 185, 129, 0.16);
  border: 1px solid rgba(16, 185, 129, 0.45);
  color: var(--insp-ok);
}

.insp-suggest.is-fit { background: rgba(16, 185, 129, 0.06); }

.insp-suggest-current { color: var(--insp-ok); font-weight: 700; }

.insp-suggest.is-current {
  border-color: rgba(var(--insp-node-hue), 0.5);
  background: rgba(var(--insp-node-hue), 0.09);
}

.insp-pop-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding-top: 3px;
  border-top: 1px solid var(--color-border);
}

.insp-pop-note {
  font-size: 10px;
  color: var(--color-text-secondary);
  flex: 1;
  min-width: 0;
}

.insp-pop-actions { display: flex; gap: 6px; }
.insp-pop-actions .insp-btn { padding: 5px 11px; font-size: 11.5px; }

/* ──────────────────────────────────────────────────────────────────────────
   Creating and deleting records
   ────────────────────────────────────────────────────────────────────────── */
.insp-add-btn {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  margin-left: auto;
  padding: 2px 8px;
  font-size: 10.5px;
  font-weight: 700;
  border-radius: 5px;
  border: 1px solid rgba(16, 185, 129, 0.45);
  background: rgba(16, 185, 129, 0.10);
  color: var(--insp-ok);
  cursor: pointer;
  white-space: nowrap;
  transition: all 0.12s ease;
}

.insp-add-btn:hover:not(:disabled) {
  background: rgba(16, 185, 129, 0.22);
  border-color: var(--insp-ok);
}

.insp-add-btn:disabled { opacity: 0.4; cursor: not-allowed; }

/* Per-row actions. Quiet until the row is hovered, so a grid of forty people
   is not a wall of red crosses — but always reachable by keyboard. */
.insp-table col.c-rowacts { width: 48px; }
.insp-table col.c-del { width: 28px; }

.insp-table td.td-rowacts { white-space: nowrap; padding: 0 2px; }

/* The undo for a merge that fused two people. It sits with the other row
   actions but stays visible rather than fading to 0.35 like them: it appears
   only where a real identity error was found, and a repair nobody notices is
   the same as no repair. */
.insp-row-unmerge {
  opacity: 1;
  color: var(--insp-danger, #ef4444);
  border-color: rgba(239, 68, 68, 0.45);
}
.insp-row-unmerge:hover {
  background: rgba(239, 68, 68, 0.16);
  opacity: 1;
}

.insp-row-del,
.insp-row-link,
.insp-row-unmerge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border-radius: 4px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--color-text-secondary);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  opacity: 0.35;
  transition: all 0.12s ease;
}

.insp-table tr:hover .insp-row-del,
.insp-table tr:hover .insp-row-link,
.insp-table tr.selected .insp-row-del,
.insp-table tr.selected .insp-row-link { opacity: 1; }

.insp-row-del:focus-visible,
.insp-row-link:focus-visible { opacity: 1; }

.insp-row-del:hover {
  background: rgba(239, 68, 68, 0.16);
  border-color: var(--insp-danger);
  color: var(--insp-danger);
  opacity: 1;
}

.insp-row-link:hover {
  background: rgba(var(--insp-node-hue), 0.16);
  border-color: rgb(var(--insp-node-hue));
  color: rgb(var(--insp-node-hue));
  opacity: 1;
}

.insp-edge-line-del { opacity: 0.45; margin-left: auto; }

/* ── Direction control & batch actions on the Recognized Edges table ──────────
   HAS_SON and HAS_SIL are both directed, so the arrow between the two people
   is a claim, not decoration. Making the arrow itself the reverse control puts
   the correction exactly where the reader notices the error, and the glyph
   swaps to ⇄ on hover so what the click will do is visible before it happens.
   ───────────────────────────────────────────────────────────────────────── */
.insp-row-rev {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  width: 26px;
  height: 20px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: var(--color-text-secondary);
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}

.insp-row-rev .insp-rev-now {
  display: inline-flex;
  align-items: center;
}
.insp-row-rev .insp-rev-alt { display: none; }

/* On hover the kind glyph gives way to ⇄, so what the click will do is visible
   before it happens; the button keeps its width either way. */
.insp-row-rev .insp-rev-alt { font-size: 13px; }

/* The hover ring takes the row's own relationship colour, so the control never
   implies a hue the edge does not have. */
.insp-row-rev:hover,
.insp-row-rev:focus-visible {
  background: var(--color-surface-hover);
  border-color: var(--color-border);
  color: var(--color-text-primary);
}
.insp-table tr.is-son .insp-row-rev:hover,
.insp-table tr.is-son .insp-row-rev:focus-visible {
  background: rgba(var(--insp-son-hue), 0.16);
  border-color: rgb(var(--insp-son-hue));
  color: rgb(var(--insp-son-hue));
}
.insp-table tr.is-sil .insp-row-rev:hover,
.insp-table tr.is-sil .insp-row-rev:focus-visible {
  background: rgba(var(--insp-sil-hue), 0.16);
  border-color: rgb(var(--insp-sil-hue));
  color: rgb(var(--insp-sil-hue));
}
.insp-row-rev:hover .insp-rev-now,
.insp-row-rev:focus-visible .insp-rev-now { display: none; }
.insp-row-rev:hover .insp-rev-alt,
.insp-row-rev:focus-visible .insp-rev-alt { display: inline; }

/* Batch action bar. Always present: with nothing selected it is the only place
   the ⌘-click / ⇧-click gesture is written down. */
.insp-batch-bar {
  display: flex;
  align-items: center;
  gap: 5px;
  margin: 0 0 6px;
  padding: 4px 6px;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-surface);
  font-size: 11px;
}

.insp-batch-bar.has-selection {
  border-color: rgba(var(--insp-node-hue), 0.55);
  background: rgba(var(--insp-node-hue), 0.07);
}

.insp-batch-count {
  color: var(--color-text-secondary);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.insp-batch-bar.has-selection .insp-batch-count {
  color: rgb(var(--insp-node-hue));
  font-variant-numeric: tabular-nums;
}

.insp-batch-spacer { flex: 1 1 auto; }

.insp-batch-btn {
  flex: 0 0 auto;
  padding: 2px 8px;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: var(--color-surface-elevated);
  color: var(--color-text-primary);
  font-size: 10.5px;
  font-weight: 600;
  line-height: 1.6;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease, opacity 0.12s ease;
}

.insp-batch-btn:hover:not(:disabled) { background: var(--color-surface-hover); }

.insp-batch-btn:disabled {
  opacity: 0.4;
  cursor: default;
}

.insp-batch-btn.is-danger:hover:not(:disabled) {
  background: rgba(239, 68, 68, 0.14);
  border-color: var(--insp-danger);
  color: var(--insp-danger);
}

.insp-batch-btn.is-quiet {
  background: transparent;
  border-color: transparent;
  color: var(--color-text-secondary);
  font-weight: 500;
}
.insp-batch-btn.is-quiet:hover:not(:disabled) {
  background: var(--color-surface-hover);
  border-color: var(--color-border);
}

/* A batch-selected row reads as *marked for an action*, distinct from the
   single reading selection (.selected), which can coexist with it. */
.insp-table tr.is-batch-selected > td {
  background: rgba(var(--insp-node-hue), 0.11);
}
.insp-table tr.is-batch-selected > td:first-child {
  box-shadow: inset 3px 0 0 rgb(var(--insp-node-hue));
}
.insp-table tr.is-batch-selected .insp-row-del,
.insp-table tr.is-batch-selected .insp-row-link { opacity: 1; }

@media (max-width: 900px) {
  .insp-batch-bar { flex-wrap: wrap; row-gap: 4px; }
  .insp-batch-spacer { flex-basis: 100%; height: 0; }
}

.insp-edge-line:hover .insp-edge-line-del { opacity: 1; }

.insp-order-chip {
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-size: 11px;
  font-weight: 700;
  padding: 0 5px;
  border-radius: 3px;
  background: var(--color-surface-elevated);
  border: 1px solid var(--color-border);
  color: var(--color-text-secondary);
}

.insp-edge-line-arrow { color: var(--color-text-secondary); }

.insp-rel-badge {
  font-size: 9.5px;
  font-weight: 700;
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  color: #fff;
  padding: 1px 5px;
  border-radius: 3px;
  letter-spacing: 0.03em;
}

/* ── The two creation forms ── */
.insp-pop-wide { width: 400px; }

.insp-form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

.insp-field-id,
.insp-field-wide { grid-column: 1 / -1; }

.insp-field-hint {
  font-style: normal;
  font-weight: 500;
  text-transform: none;
  letter-spacing: normal;
  opacity: 0.75;
  margin-left: 3px;
}

.insp-field-id input {
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-size: 12.5px;
}

.insp-form-check { font-size: 11.5px; }
.insp-form-idnote { font-size: 10.5px; line-height: 1.5; }

.insp-edge-form {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 9px;
  align-items: start;
}

.insp-edge-form-mid {
  display: flex;
  flex-direction: column;
  gap: 7px;
  align-items: center;
  min-width: 130px;
}

.insp-edge-form-glyph {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 22px;
}

.insp-edge-form select[size] {
  width: 100%;
  font-size: 11.5px;
  padding: 3px;
  border-radius: 6px;
  border: 1px solid var(--color-border);
  background: var(--color-bg);
  color: var(--color-text-primary);
}

.insp-edge-form select[size] option { padding: 2px 4px; }

.insp-filter {
  font-size: 11px !important;
  font-weight: 400 !important;
  padding: 4px 6px !important;
  margin-bottom: 3px;
}

.insp-edge-form .insp-pop-field { min-width: 0; }

/* ──────────────────────────────────────────────────────────────────────────
   Subject header card and unresolved-edge list
   ────────────────────────────────────────────────────────────────────────── */
.insp-subject-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  margin-bottom: 14px;
  background: rgba(var(--insp-node-hue), 0.09);
  border: 1px solid rgba(var(--insp-node-hue), 0.25);
  border-left: 3px solid rgb(var(--insp-node-hue));
  border-radius: 6px;
  font-size: 14px;
  flex-wrap: wrap;
}

.insp-subject-label {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  background: rgb(var(--insp-node-hue));
  color: #ffffff;
  font-size: 11px;
  font-weight: 700;
  padding: 2px 7px;
  border-radius: 4px;
  letter-spacing: 0.04em;
  white-space: nowrap;
}

/* The English gloss is the first thing to go when the card gets tight —
   the hanja term alone still identifies the role. */
.insp-subject-label-gloss {
  font-size: 9.5px;
  font-weight: 600;
  opacity: 0.85;
  letter-spacing: 0.02em;
}

.insp-edge-annotations {
  margin-top: 18px;
  padding-top: 12px;
  border-top: 1px solid var(--color-border);
}

.insp-edge-annotations-header {
  font-size: 10px;
  font-weight: 700;
  color: var(--color-text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  margin-bottom: 6px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--color-border);
}

.insp-edge-line {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px;
  font-size: 13px;
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.15s ease;
  flex-wrap: wrap;
}

.insp-edge-line:hover,
.insp-edge-line.active {
  background: var(--insp-row-hover);
}

.insp-edge-line-icon {
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-size: 14px;
  line-height: 1;
  flex-shrink: 0;
}

/* SVG edge overlay on hanmun panel */
.insp-edge-svg {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: visible;
  z-index: 5;
}

/* Curves have to be readable as relationships, not just present. They were
   dialled right back when every one of them was drawn at the same weight and a
   dozen at once made the column unreadable; now that "fade verified" does the
   decluttering, an unchecked relationship can carry its proper presence — and
   has to, or there is nothing for the faded state to be quieter *than*. */
.insp-edge-svg .edge-path {
  fill: none;
  stroke-width: 1.7;
  opacity: 0.62;
  transition: all 0.15s ease;
  pointer-events: stroke;
  cursor: pointer;
}

/* Hovering a person lifts the relationships they are part of (setHoverNode
   marks those groups .is-linked) and pushes the rest further back, so "who is
   this connected to" is one hover rather than a hunt along a curve. */
.insp-edge-svg:has(.edge-group.is-linked) .edge-path { opacity: 0.14; }
.insp-edge-svg .edge-group.is-linked .edge-path {
  opacity: 0.95;
  stroke-width: 2.4;
}

.insp-edge-svg .edge-path-son { stroke: var(--color-has-son, #5b8def); }
.insp-edge-svg .edge-path-sil { stroke: var(--color-has-sil, #e8985e); stroke-dasharray: 5 3; }

.insp-edge-svg .edge-path:hover,
.insp-edge-svg .edge-group:hover .edge-path,
.insp-edge-svg .edge-group.active .edge-path {
  opacity: 1;
  stroke-width: 2.6;
}

.insp-edge-svg .edge-group.active .edge-path,
.insp-edge-svg .edge-path.active {
  opacity: 1 !important;
  stroke-width: 3.5 !important;
  filter: drop-shadow(0 0 5px currentColor);
}

/* ── The label on the connector ────────────────────────────────────────────
   A pill riding on the line's vertical run, out in the margin. It says what
   the document says — 長子, 次女壻 — and it is the easiest part of the edge to
   click, which is why it, rather than the 1.7px stroke, carries the pointer. */
.insp-edge-svg .edge-label-group { pointer-events: auto; cursor: pointer; }

.insp-edge-svg .edge-label-bg {
  fill: var(--color-surface);
  stroke-width: 1.2;
  opacity: 0.92;
  transition: opacity 0.15s ease;
}
.insp-edge-svg .edge-label-son .edge-label-bg { stroke: var(--color-has-son, #5b8def); }
.insp-edge-svg .edge-label-sil .edge-label-bg { stroke: var(--color-has-sil, #e8985e); }

.insp-edge-svg .edge-label-text {
  font-size: 10px;
  font-weight: 700;
  pointer-events: none;
}
.insp-edge-svg .edge-label-son .edge-label-text { fill: var(--color-has-son, #5b8def); }
.insp-edge-svg .edge-label-sil .edge-label-text { fill: var(--color-has-sil, #e8985e); }

/* The label follows its line: back when another person is hovered, forward
   when this relationship is the one being looked at. */
.insp-edge-svg:has(.edge-group.is-linked) .edge-label-group { opacity: 0.14; }
.insp-edge-svg .edge-group.is-linked .edge-label-group,
.insp-edge-svg .edge-group:hover .edge-label-group,
.insp-edge-svg .edge-group.active .edge-label-group { opacity: 1; }
.insp-edge-svg .edge-group.active .edge-label-bg { opacity: 1; stroke-width: 2; }

.insp-fade-verified .insp-edge-svg .edge-group.is-verified .edge-label-group { opacity: 0.3; }
.insp-fade-verified .insp-edge-svg .edge-group.is-verified:hover .edge-label-group,
.insp-fade-verified .insp-edge-svg .edge-group.is-verified.active .edge-label-group,
.insp-fade-verified .insp-edge-svg .edge-group.is-verified.is-linked .edge-label-group { opacity: 1; }

/* ── The phrase that names an attribute ────────────────────────────────────
   字覺老, 自號靈源叟, 諡曰文淸公 — the words that announce a person's other
   names. Lit, but more faintly than a person's own mark: this is the evidence
   for a detail, not the person, and it must never compete with the name it
   sits beside. A dotted underline rather than a box, so the characters stay
   the most legible thing in the line. */
.insp-attr-ev {
  background: rgba(148, 163, 184, 0.10);
  border-bottom: 1px dotted rgba(148, 163, 184, 0.55);
  border-radius: 2px;
  padding: 0 1px;
  transition: background 0.12s ease;
}
.insp-attr-ev:hover {
  background: rgba(148, 163, 184, 0.22);
  border-bottom-color: rgba(148, 163, 184, 0.9);
}

/* Each kind of name gets its own tint, matching nothing else in the view so it
   never reads as a relationship or a proposal. */
.insp-attr-ev[data-field="字"]        { background: rgba(56, 189, 248, 0.10); border-bottom-color: rgba(56, 189, 248, 0.5); }
.insp-attr-ev[data-field="字"]:hover  { background: rgba(56, 189, 248, 0.20); }
.insp-attr-ev[data-field="號"]        { background: rgba(45, 212, 191, 0.10); border-bottom-color: rgba(45, 212, 191, 0.5); }
.insp-attr-ev[data-field="號"]:hover  { background: rgba(45, 212, 191, 0.20); }
.insp-attr-ev[data-field="諡號"]      { background: rgba(251, 191, 36, 0.10); border-bottom-color: rgba(251, 191, 36, 0.5); }
.insp-attr-ev[data-field="諡號"]:hover{ background: rgba(251, 191, 36, 0.20); }
.insp-attr-ev[data-field="alt_names"] { background: rgba(167, 139, 250, 0.10); border-bottom-color: rgba(167, 139, 250, 0.5); }
.insp-attr-ev[data-field="alt_names"]:hover { background: rgba(167, 139, 250, 0.20); }

/* ── Editing the document's text ───────────────────────────────────────────
   One control with two states. Editing the source text is consequential enough
   to deserve an explicit start and an explicit end, and cheap enough that both
   should be one click. */
.insp-textedit-group {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.insp-textedit-btn {
  height: 24px;
  padding: 0 10px;
  border: 1px solid var(--color-border);
  border-radius: 5px;
  background: transparent;
  color: var(--color-text-secondary);
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.14s ease, color 0.14s ease, border-color 0.14s ease;
}
.insp-textedit-btn:hover:not(:disabled) {
  background: var(--color-surface-hover, rgba(148, 163, 184, 0.14));
  color: var(--color-text);
}
.insp-textedit-btn.is-save {
  border-color: rgba(34, 197, 94, 0.5);
  color: #22c55e;
  background: rgba(34, 197, 94, 0.08);
}
.insp-textedit-btn.is-save:hover:not(:disabled) { background: rgba(34, 197, 94, 0.2); }
/* Nothing changed yet: the button says so and refuses, rather than writing the
   file to prove it. */
.insp-textedit-btn.is-save:disabled {
  border-color: var(--color-border);
  color: var(--color-text-tertiary, var(--color-text-secondary));
  background: transparent;
  opacity: 0.55;
  cursor: default;
}
.insp-textedit-btn.is-cancel:hover { border-color: rgba(239, 68, 68, 0.6); color: #f87171; }

/* Editing is a mode, so the panel says so rather than looking like the reading
   view with the marks mysteriously gone. */
.insp-editable {
  outline: 1px dashed rgba(91, 141, 239, 0.5);
  outline-offset: -2px;
  background: rgba(91, 141, 239, 0.04);
}

/* ── Data-fidelity flags on the cell ───────────────────────────────────────
   A value that cannot be what its column means. Marked where the value is, not
   in a report elsewhere, because the cell is where it gets fixed. Amber doubts
   it; red says it cannot be right. */
.insp-input.is-fidelity-warn,
.insp-input.is-fidelity-error {
  border-style: solid;
  border-width: 1px;
}
.insp-input.is-fidelity-warn {
  border-color: rgba(251, 191, 36, 0.75);
  background: rgba(251, 191, 36, 0.08);
}
.insp-input.is-fidelity-error {
  border-color: rgba(239, 68, 68, 0.85);
  background: rgba(239, 68, 68, 0.10);
}
.insp-input.is-fidelity-warn:focus,
.insp-input.is-fidelity-error:focus { background: transparent; }

/* A conduit, beside the person it is named for. Dashed and hollow because he
   is inferred, not attested: the text never names him, and the mark must not
   look like the marks that quote real characters. */
.insp-ghost-badge {
  display: inline-flex;
  align-items: center;
  margin-left: 2px;
  padding: 0 3px;
  border: 1px dashed rgba(148, 163, 184, 0.75);
  border-radius: 3px;
  background: transparent;
  color: var(--color-text-secondary);
  font-size: 9.5px;
  line-height: 1.35;
  vertical-align: super;
  cursor: help;
}
.insp-ghost-badge:hover {
  border-color: var(--color-text-secondary);
  color: var(--color-text);
}

/* ── Inline edge badge ───/* ── Inline edge badge ─────────────────────────────────────────────────────
   Rides in the text flow directly after its target's id chip, carrying the
   Graph view's own dot-line-arrow glyph plus the birth order. Being a real
   inline element it is adjacent to its span by construction and can never
   cover the characters around it — which an absolutely-positioned badge did. */
.insp-edge-badge {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  vertical-align: middle;
  margin: 0 2px 0 1px;
  padding: 1px 4px;
  border-radius: 4px;
  border: 1px solid transparent;
  line-height: 1;
  cursor: pointer;
  user-select: none;
  transition: background 0.12s ease, border-color 0.12s ease;
}

.insp-edge-badge-son {
  background: rgba(var(--insp-node-hue), 0.11);
  border-color: rgba(var(--insp-node-hue), 0.38);
}

.insp-edge-badge-sil {
  background: rgba(var(--insp-edge-hue), 0.11);
  border-color: rgba(var(--insp-edge-hue), 0.38);
}

.insp-edge-badge:hover,
.insp-edge-badge.insp-hover { filter: brightness(1.25); }

.insp-edge-badge-son.insp-edge-ev-active {
  background: rgba(var(--insp-node-hue), 0.3);
  border-color: rgb(var(--insp-node-hue));
  box-shadow: 0 0 0 2px rgba(var(--insp-node-hue), 0.3);
}

.insp-edge-badge-sil.insp-edge-ev-active {
  background: rgba(var(--insp-edge-hue), 0.3);
  border-color: rgb(var(--insp-edge-hue));
  box-shadow: 0 0 0 2px rgba(var(--insp-edge-hue), 0.3);
}

/* Legible against dense Hanja — the old SVG label was 9.5px and vanished. */
.insp-edge-badge-order {
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.insp-edge-badge-son .insp-edge-badge-order { color: var(--color-has-son, #5b8def); }
.insp-edge-badge-sil .insp-edge-badge-order { color: var(--color-has-sil, #e8985e); }

[data-theme="dark"] .insp-edge-badge-son .insp-edge-badge-order { color: #a8c5f5; }
[data-theme="dark"] .insp-edge-badge-sil .insp-edge-badge-order { color: #f0b98a; }

/* ──────────────────────────────────────────────────────────────────────────
   The two entity grids
   These are spreadsheets. They should look like spreadsheets: ruled columns,
   cells with visible edit affordance, a verification stripe per row.
   ────────────────────────────────────────────────────────────────────────── */
.insp-grid-hint {
  display: flex;
  align-items: center;
  gap: 5px;
  flex-wrap: wrap;
  padding: 4px 12px 5px;
  font-size: 9.5px;
  color: var(--color-text-secondary);
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  opacity: 0.85;
}

.insp-grid-hint kbd {
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-size: 9px;
  padding: 0 3px;
  border: 1px solid var(--color-border);
  border-bottom-width: 2px;
  border-radius: 3px;
  background: var(--color-bg);
  color: var(--color-text-primary);
}

.insp-grid-hint-sep { opacity: 0.4; margin: 0 2px; }

.insp-panel-container.insp-panel-collapsed .insp-grid-hint { display: none; }

.insp-table-container {
  width: 100%;
  overflow-x: auto;
  overflow-y: auto;
}

/* table-layout:fixed + a colgroup is what actually keeps a header cell over
   its body cells. With auto layout the columns re-flow to content and the
   header drifts out of register with the rows below it. */
.insp-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: 13px;
  table-layout: fixed;
}

.insp-table-nodes { min-width: 826px; }
.insp-table-edges { min-width: 586px; }

/* Column register, shared by <colgroup> and the cells. */
.insp-table col.c-subject { width: 52px; }
.insp-table col.c-pick    { width: 26px; }
.insp-table col.c-check   { width: 30px; }
.insp-table col.c-glyph   { width: 30px; }
/* Wide enough for the id AND the "off" badge beside it. At 46px the badge —
   which is the only thing saying this person was never found in the document's
   text — was clipped to a sliver, so the row showed a mysterious fragment next
   to the id instead of a readable flag. */
.insp-table col.c-id      { width: 74px; }
.insp-table col.c-name    { width: 82px; }
.insp-table col.c-roman   { width: 94px; }
.insp-table col.c-hangul  { width: 72px; }
.insp-table col.c-clan    { width: 78px; }
/* 字 / 號 hold two or three graphs; 諡號 a little more; alt_names is a list. */
.insp-table col.c-zi      { width: 46px; }
.insp-table col.c-hao     { width: 46px; }
.insp-table col.c-shi     { width: 54px; }
.insp-table col.c-alt     { width: 92px; }
.insp-table col.c-person  { width: 66px; }
.insp-table col.c-arrow   { width: 30px; }
.insp-table col.c-rel     { width: 88px; }
.insp-table col.c-order   { width: 40px; }
.insp-table col.c-notes   { width: 120px; }

.insp-table th {
  position: sticky;
  top: 0;
  height: 28px;
  background: var(--color-surface-elevated);
  padding: 0 6px;
  text-align: left;
  font-weight: 700;
  font-size: 10px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-text-secondary);
  border-bottom: 1px solid var(--color-border);
  border-right: 1px solid var(--insp-hairline);
  z-index: 2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: middle;
}

.insp-table th:last-child { border-right: none; }

/* One row height for every row, whatever a cell contains. */
.insp-table td {
  height: 30px;
  padding: 0 5px;
  border-bottom: 1px solid var(--insp-hairline);
  border-right: 1px solid var(--insp-hairline);
  vertical-align: middle;
  overflow: hidden;
  white-space: nowrap;
}

.insp-table td:last-child { border-right: none; }

/* Alignment utilities, applied identically to header and body so a centred
   column is centred in both. */
.insp-table th.ta-center,
.insp-table td.ta-center { text-align: center; padding-left: 2px; padding-right: 2px; }

.insp-table td.td-id { white-space: nowrap; }

.insp-table td.td-arrow,
.insp-table th.td-arrow {
  color: var(--color-text-secondary);
  font-size: 12px;
  padding: 0;
}

.insp-table .insp-th-quiet {
  font-size: 8.5px;
  letter-spacing: 0.04em;
  opacity: 0.85;
}

/* Icon/control cells centre their contents on the row's optical middle. */
.insp-table td.ta-center > * { vertical-align: middle; }


/* ── Batch-select column ──────────────────────────────────────────────────────
   It sits next to the verification tick, so the two must never be mistaken for
   each other: the select box is tinted in the node hue and stands in a faintly
   recessed gutter, while ✓ keeps the default control colour. Its header box is
   select-all/none and shows a partial selection as indeterminate. */
.insp-table td.td-pick,
.insp-table th.td-pick {
  background: var(--insp-hairline);
  padding-left: 0;
  padding-right: 0;
}

.insp-table tr.is-batch-selected > td.td-pick {
  background: rgba(var(--insp-node-hue), 0.20);
}

.insp-check.insp-pick {
  accent-color: rgb(var(--insp-node-hue));
  cursor: pointer;
  margin: 0;
}

.insp-table td.td-rel-glyph .insp-edge-glyph {
  margin: 0 auto;
}

/* The notes cell is the one place a badge shares the cell with an input. */
.insp-table td.td-notes {
  display: table-cell;
  position: relative;
}

.insp-table td.td-notes .insp-badge-not-in-doc {
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  pointer-events: none;
}

.insp-table tr {
  transition: background 0.12s ease;
  cursor: pointer;
}

/* Verification stripe: the leftmost cell carries a status rail, so a row's
   state is readable without hunting for the checkbox. */
.insp-table tbody tr > td:first-child {
  position: relative;
  box-shadow: inset 3px 0 0 var(--insp-warn);
}

.insp-table tbody tr.is-verified > td:first-child {
  box-shadow: inset 3px 0 0 var(--insp-ok);
}

.insp-table tbody tr.is-unverified > td:first-child {
  box-shadow: inset 3px 0 0 rgba(148, 163, 184, 0.45);
}

.insp-table tr:hover { background: var(--insp-row-hover); }

/* The hover link between a grid row and its mark in the text. Both ends get
   the same amber rail so the pairing is unmistakable. */
.insp-table tr.insp-hover {
  background: var(--insp-row-hover);
  box-shadow: inset 0 0 0 1px rgba(var(--insp-node-hue), 0.45);
}

.insp-table tr.selected {
  background: var(--insp-row-selected) !important;
  box-shadow: inset 0 0 0 1px rgba(59, 130, 246, 0.65);
}

.insp-table tr.insp-row-muju { background: rgba(245, 158, 11, 0.09); }

.insp-row-not-in-doc { opacity: 0.5; }
.insp-row-not-in-doc:hover { opacity: 0.95; }

.insp-badge-not-in-doc {
  display: inline-block;
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 1px 4px;
  background: rgba(245, 158, 11, 0.16);
  border: 1px solid rgba(245, 158, 11, 0.4);
  border-radius: 3px;
  color: var(--insp-warn);
  font-style: normal;
  white-space: nowrap;
}

/* Cells read as editable at rest: a faint ruled underline, filled on hover,
   fully boxed on focus. */
.insp-input {
  width: 100%;
  background: transparent;
  border: 1px solid transparent;
  border-bottom: 1px dotted var(--insp-cell-rule);
  color: var(--color-text-primary);
  font-size: 13px;
  padding: 3px 5px;
  border-radius: 3px;
  box-sizing: border-box;
  font-family: inherit;
  vertical-align: middle;
  transition: border-color 0.12s, background 0.12s;
}

.insp-input::placeholder { color: var(--color-text-secondary); opacity: 0.5; }

.insp-input:hover {
  border-color: rgba(var(--insp-node-hue), 0.35);
  background: var(--color-surface-elevated);
}

.insp-input:focus {
  border: 1px solid var(--insp-focus) !important;
  background: var(--color-surface) !important;
  box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.4) !important;
  outline: none !important;
  z-index: 4;
  position: relative;
}

/* Ordinals are a numeric column: centred, tabular, and blank when unknown
   (the placeholder is an em dash, never a "#"). */
.insp-input-order {
  text-align: center;
  font-variant-numeric: tabular-nums;
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-size: 12px;
}

.insp-select {
  background: var(--color-surface-elevated);
  border: 1px solid var(--color-border);
  color: var(--color-text-primary);
  font-size: 11.5px;
  padding: 2px 5px;
  border-radius: 4px;
  box-sizing: border-box;
  vertical-align: middle;
  width: 100%;
  height: 22px;
}

.insp-select-rel {
  font-weight: 700;
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-size: 10.5px;
}

.insp-select-rel-son { color: var(--color-has-son, #5b8def); }
.insp-select-rel-sil { color: var(--color-has-sil, #e8985e); }

.insp-select:focus {
  border-color: var(--insp-focus) !important;
  box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.4) !important;
  outline: none !important;
}

.insp-btn-muju {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 2px;
  width: 100%;
  height: 21px;
  background: transparent;
  border: 1px solid rgba(245, 158, 11, 0.4);
  color: var(--insp-warn);
  font-size: 10px;
  padding: 0 3px;
  border-radius: 4px;
  cursor: pointer;
  white-space: nowrap;
  font-weight: 600;
  vertical-align: middle;
  transition: all 0.12s ease;
  overflow: hidden;
}

.insp-btn-muju-star { font-size: 9px; line-height: 1; }
.insp-btn-muju-txt { line-height: 1; }

.insp-btn-muju.active {
  background: #f59e0b;
  color: #1a1200;
  font-weight: 700;
  border-color: #f59e0b;
}

.insp-btn-muju:hover { background: rgba(245, 158, 11, 0.2); }
.insp-btn-muju.active:hover { background: #d97706; }

.insp-check {
  cursor: pointer;
  accent-color: var(--insp-ok);
  vertical-align: middle;
  width: 15px;
  height: 15px;
}

/* ──────────────────────────────────────────────────────────────────────────
   Empty and loading states
   ────────────────────────────────────────────────────────────────────────── */
.insp-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  text-align: center;
  padding: 34px 22px;
  max-width: 420px;
  margin: 0 auto;
  color: var(--color-text-secondary);
}

.insp-empty-compact { padding: 18px 14px; gap: 3px; }

.insp-empty-icon { font-size: 26px; line-height: 1; opacity: 0.75; }
.insp-empty-compact .insp-empty-icon { font-size: 20px; }

.insp-empty-title {
  font-size: 13.5px;
  font-weight: 700;
  color: var(--color-text-primary);
  font-style: normal;
}

.insp-empty-body {
  font-size: 12px;
  line-height: 1.6;
  font-style: normal;
}

.insp-empty-body code,
.insp-modal code {
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-size: 0.92em;
  padding: 1px 4px;
  border-radius: 3px;
  background: var(--color-surface-elevated);
  border: 1px solid var(--color-border);
  color: var(--color-text-primary);
}

.insp-empty-hint {
  font-size: 11px;
  line-height: 1.55;
  opacity: 0.8;
  font-style: italic;
  margin-top: 3px;
}

.insp-empty-spinner {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 2px solid var(--color-border);
  border-top-color: rgb(var(--insp-node-hue));
  animation: insp-spin 0.7s linear infinite;
}

@keyframes insp-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .insp-empty-spinner { animation-duration: 2.4s; }
  .insp-table tr,
  .insp-highlight,
  .insp-edge-ev { transition: none; }
}

/* ──────────────────────────────────────────────────────────────────────────
   Auto-map dialog
   ────────────────────────────────────────────────────────────────────────── */
body.insp-modal-open { overflow: hidden; }

.insp-modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 9000;
  background: var(--insp-scrim);
  backdrop-filter: blur(2px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.insp-modal-backdrop[hidden] { display: none; }

.insp-modal {
  width: min(620px, 100%);
  max-height: min(86vh, 780px);
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 12px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
  overflow: hidden;
  color: var(--color-text-primary);
}

.insp-modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--color-border);
  background: var(--color-surface-elevated);
}

.insp-modal-title {
  margin: 0;
  font-size: 15px;
  font-weight: 700;
  color: var(--color-text-primary);
}

.insp-modal-close {
  background: transparent;
  border: 1px solid transparent;
  color: var(--color-text-secondary);
  font-size: 20px;
  line-height: 1;
  width: 28px;
  height: 28px;
  border-radius: 6px;
  cursor: pointer;
}

.insp-modal-close:hover {
  background: var(--color-surface-hover);
  color: var(--color-text-primary);
}

.insp-modal-body {
  padding: 16px 18px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.insp-modal-lede {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--color-text-secondary);
}

/* The "this writes to disk" panel. Informative, not alarming: amber, not red,
   because writing is the intended behaviour. */
.insp-modal-writes {
  display: flex;
  gap: 10px;
  padding: 11px 13px;
  font-size: 12px;
  line-height: 1.6;
  border-radius: 8px;
  background: rgba(245, 158, 11, 0.09);
  border: 1px solid rgba(245, 158, 11, 0.35);
  color: var(--color-text-primary);
}

.insp-modal-writes-icon { font-size: 15px; line-height: 1.4; }
.insp-modal-writes strong { color: var(--insp-warn); }
.insp-automap-file { word-break: break-all; }

.insp-modal-field {
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 9px 12px 11px;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.insp-modal-field legend {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-secondary);
  padding: 0 5px;
}

.insp-radio,
.insp-checkline {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 12.5px;
  cursor: pointer;
  line-height: 1.5;
}

.insp-radio input,
.insp-checkline input {
  margin-top: 2px;
  accent-color: rgb(var(--insp-node-hue));
  cursor: pointer;
  flex-shrink: 0;
}

.insp-radio em,
.insp-checkline em {
  display: block;
  font-style: normal;
  font-size: 11px;
  color: var(--color-text-secondary);
  margin-top: 1px;
}

.insp-modal-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  padding: 12px 18px;
  border-top: 1px solid var(--color-border);
  background: var(--color-surface-elevated);
}

.insp-modal-foot-note {
  font-size: 11px;
  color: var(--color-text-secondary);
  flex: 1 1 180px;
}

.insp-modal-foot-actions { display: flex; gap: 8px; flex-wrap: wrap; }

.insp-btn {
  font-size: 12px;
  font-weight: 600;
  padding: 7px 14px;
  border-radius: 6px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text-primary);
  cursor: pointer;
  transition: all 0.12s ease;
}

.insp-btn:hover:not(:disabled) { background: var(--color-surface-hover); }
.insp-btn:disabled { opacity: 0.45; cursor: not-allowed; }

.insp-btn-ghost { border-color: transparent; color: var(--color-text-secondary); }

.insp-btn-secondary {
  border-color: rgb(var(--insp-node-hue));
  color: rgb(var(--insp-node-hue));
  background: rgba(var(--insp-node-hue), 0.1);
}
[data-theme="dark"] .insp-btn-secondary { color: #a8c5f5; }
.insp-btn-secondary:hover:not(:disabled) { background: rgba(var(--insp-node-hue), 0.2); }

.insp-btn-danger {
  border-color: var(--insp-warn);
  color: #1a1200;
  background: var(--insp-warn);
}
.insp-btn-danger:hover:not(:disabled) { filter: brightness(1.08); }
.insp-btn-danger:disabled { background: var(--color-surface); color: var(--color-text-secondary); border-color: var(--color-border); }

/* ── Auto-map results ── */
.insp-automap-result:empty { display: none; }

.insp-automap-banner {
  padding: 8px 12px;
  border-radius: 7px;
  font-size: 12px;
  line-height: 1.5;
  margin-bottom: 12px;
}

.insp-automap-banner.is-preview {
  background: rgba(var(--insp-node-hue), 0.11);
  border: 1px solid rgba(var(--insp-node-hue), 0.4);
}

.insp-automap-banner.is-applied {
  background: rgba(16, 185, 129, 0.12);
  border: 1px solid var(--insp-ok);
}

.insp-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(76px, 1fr));
  gap: 7px;
  margin-bottom: 12px;
}

.insp-stat {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 8px 9px;
  border: 1px solid var(--color-border);
  border-radius: 7px;
  background: var(--color-bg);
}

.insp-stat-n {
  font-size: 18px;
  font-weight: 700;
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  line-height: 1.1;
  color: var(--color-text-primary);
}

.insp-stat-l {
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-secondary);
}

.insp-automap-unmatched,
.insp-automap-per-doc {
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 9px 12px;
  margin-bottom: 10px;
  font-size: 12px;
}

.insp-automap-unmatched {
  border-color: rgba(245, 158, 11, 0.45);
  background: rgba(245, 158, 11, 0.07);
}

.insp-automap-unmatched > summary,
.insp-automap-per-doc > summary {
  cursor: pointer;
  font-weight: 700;
  font-size: 12px;
  color: var(--color-text-primary);
}

.insp-automap-unmatched > summary { color: var(--insp-warn); }

.insp-automap-unmatched-note {
  font-size: 11px;
  line-height: 1.55;
  color: var(--color-text-secondary);
  margin: 7px 0;
}

.insp-unmatched-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  max-height: 190px;
  overflow-y: auto;
}

.insp-unmatched-list li {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 7px;
  border-radius: 5px;
  background: var(--color-surface-elevated);
  border: 1px solid var(--color-border);
}

.insp-unmatched-name { font-weight: 700; font-size: 12.5px; }

.insp-unmatched-n,
.insp-unmatched-doc {
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-size: 9.5px;
  color: var(--color-text-secondary);
}

.insp-automap-clean {
  font-size: 12px;
  color: var(--insp-ok);
  margin: 0 0 10px;
}

.insp-automap-clean.is-warn { color: var(--insp-warn); }
.insp-stat-n.is-warn { color: var(--insp-warn); }

.insp-automap-table-wrap { overflow-x: auto; max-height: 240px; overflow-y: auto; margin-top: 8px; }

.insp-automap-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 11.5px;
}

.insp-automap-table th,
.insp-automap-table td {
  padding: 4px 8px;
  border-bottom: 1px solid var(--insp-hairline);
  text-align: left;
  white-space: nowrap;
}

.insp-automap-table th {
  position: sticky;
  top: 0;
  background: var(--color-surface-elevated);
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-secondary);
}

.insp-automap-table .insp-num,
.insp-automap-table th.insp-num { text-align: right; font-family: 'SF Mono', 'Menlo', 'Consolas', monospace; }
.insp-automap-table .insp-num.is-warn { color: var(--insp-warn); font-weight: 700; }
.insp-automap-table .insp-mono { font-family: 'SF Mono', 'Menlo', 'Consolas', monospace; }

.insp-automap-error {
  padding: 12px 14px;
  border-radius: 8px;
  border: 1px solid var(--insp-danger);
  background: rgba(239, 68, 68, 0.10);
  font-size: 12px;
  line-height: 1.6;
}

.insp-automap-error strong { color: var(--insp-danger); display: block; margin-bottom: 5px; }
.insp-automap-error p { margin: 0 0 6px; }
.insp-automap-error-hint { color: var(--color-text-secondary); font-size: 11px; }

/* ==========================================================================
   RESPONSIVE DESIGN — tablet & phone
   --------------------------------------------------------------------------
   Goal: every feature stays reachable at every size; small screens get a
   re-arranged layout, never a reduced feature set.

   Breakpoints
     > 1550px           desktop reference layout (untouched)
     <= 1550px          the brand tagline retires
     <= 1450px          dense desktop — top-bar action buttons go icon-only
     <= 1200px          top bar wraps to two rows (all controls stay visible)
     <= 1024px          tablet landscape — narrower sidebar, compact chrome
     <=  900px          small tablet / large phone — sidebar + source drawer
                        become off-canvas overlays, Inspection stacks
     <=  600px          phone — sheet modals, 44px targets, 16px inputs
     short landscape    phone on its side — top bar folds back to one row

   NOTE ON !important: index.html carries a large number of inline
   style="..." attributes (panel widths, control heights, modal widths).
   Inline styles out-rank any stylesheet rule, so the overrides below are
   marked !important purely to reach past them — not to win specificity
   fights with other rules in this file.
   ========================================================================== */


/* Once the ellipsised tagline is down to a couple of words it reads as
   damage rather than as a subtitle — retire it before it gets that far. */
@media screen and (max-width: 1550px) {
  .app-subtitle {
    display: none;
  }
}


/* ── Dense desktop (<= 1450px) ─────────────────────────────────────────────
   Measured natural widths: brand+tagline 441, centre controls 471, the six
   labelled action buttons 834 — ~1790px in total, so the labelled bar only
   fits on a wide monitor. Once the tagline has fully ellipsised (~1440px)
   the labels are the next thing to go; each button keeps its title/
   aria-label as accessible name and its counter badge, so nothing becomes
   unidentifiable. */
@media screen and (max-width: 1450px) {
  .topbar-btn .btn-label,
  .topbar-action .btn-label {
    display: none !important;
  }

  .topbar-btn {
    padding: 0 9px !important;
    gap: 4px !important;
  }

  /* Label gone, so the LLM button becomes the same square as the theme switch
     rather than a wide box holding one glyph. */
  .topbar-action { width: 32px; padding: 0 !important; gap: 0 !important; }
}


/* ── Tablet landscape / small laptop (<= 1024px) ───────────────────────── */
@media screen and (max-width: 1024px) {
  :root {
    --sidebar-width: 300px;
  }

  .top-bar {
    padding: 0 12px;
    gap: 8px;
  }

  .app-title {
    font-size: 16px;
  }

  .top-bar-center {
    gap: 10px;
  }

  .sidebar {
    width: var(--sidebar-width);
    min-width: var(--sidebar-width);
    max-width: var(--sidebar-width);
  }

  .right-drawer {
    width: 400px;
    min-width: 300px;
  }

  .graph-legend {
    bottom: 12px;
    left: 12px;
    max-width: 240px;
    font-size: 10.5px;
    padding: 6px 10px;
  }

  /* Inspection still side-by-side here, but the hard min-widths (280/320px)
     leave the document panel unreadable — let the document panel shrink. */
  .insp-source-panels {
    min-width: 0 !important;
  }

  .insp-entity-panels {
    min-width: 260px !important;
  }
}


/* ── Small tablet & large phone (<= 900px) ─────────────────────────────────
   Both side panels leave the flow and become overlays; Inspection stacks. */
@media screen and (max-width: 900px) {
  :root {
    --top-bar-height: 52px;
  }

  body {
    font-size: 13.5px;
    -webkit-text-size-adjust: 100%;
    overscroll-behavior: none;
  }

  /* Mobile browser chrome overlaps 100vh; dvh keeps the status bar visible. */
  @supports (height: 100dvh) {
    .app-container {
      height: 100dvh;
    }
  }

  .top-bar {
    height: var(--top-bar-height);
    padding: 0 10px;
    gap: 6px;
    flex-shrink: 0;
  }

  .top-bar-left {
    gap: 8px;
  }

  .app-title {
    font-size: 15px;
  }

  .top-bar-center {
    gap: 8px;
  }

  #theme-text {
    display: none;
  }

  /* Tools sidebar → off-canvas drawer (#sidebar-backdrop is already
     mobile-only and is wired to the same toggle in app.js).
     Positioned absolutely inside .main-layout (which is position:relative and
     overflow:hidden) rather than fixed to the viewport: app.js opens this
     drawer automatically when entering Graph view *without* showing the
     backdrop, so a viewport-fixed panel would sit on top of the top bar and
     swallow taps meant for the action buttons. Confined to the content area
     the header and status bar stay usable, and overflow:hidden on the parent
     clips the slid-out state for free. */
  .sidebar {
    position: absolute !important;
    top: 0;
    left: 0;
    bottom: 0;
    width: min(86vw, 380px) !important;
    min-width: 0 !important;
    max-width: min(86vw, 380px) !important;
    z-index: 1000 !important;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5) !important;
    transform: translateX(0);
    transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1) !important;
    display: flex !important;
    visibility: visible !important;
    pointer-events: auto !important;
  }

  /* Slide out instead of display:none so the drawer animates both ways. */
  .sidebar.collapsed {
    transform: translateX(-100%) !important;
    pointer-events: none !important;
    visibility: visible !important;
    display: flex !important;
    width: min(86vw, 380px) !important;
    min-width: 0 !important;
    border-right: 1px solid var(--color-border) !important;
  }

  .sidebar-header {
    height: 52px;
    padding: 0 14px;
  }

  .sidebar-content {
    padding: 12px 14px;
    -webkit-overflow-scrolling: touch;
  }

  /* Source Verification drawer → off-canvas overlay from the right, again
     scoped to .main-layout so the top bar stays reachable behind it. */
  .right-drawer {
    position: absolute !important;
    top: 0;
    right: 0;
    bottom: 0;
    width: min(92vw, 460px) !important;
    min-width: 0 !important;
    max-width: min(92vw, 460px) !important;
    z-index: 1000 !important;
    box-shadow: -10px 0 30px rgba(0, 0, 0, 0.5) !important;
    transform: translateX(0);
    transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1) !important;
    display: flex !important;
    visibility: visible !important;
  }

  .right-drawer.collapsed {
    transform: translateX(100%) !important;
    pointer-events: none !important;
    visibility: visible !important;
    display: flex !important;
    width: min(92vw, 460px) !important;
    min-width: 0 !important;
  }

  /* Drag-to-resize is a pointer affordance with no touch equivalent. */
  .resize-handle-left {
    display: none !important;
  }

  /* Table: the wide table scrolls inside its own box, never the page. */
  .table-wrapper {
    overflow: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
  }

  .data-table th,
  .data-table td {
    padding: 9px 12px;
  }

  .doc-cell {
    min-width: 150px;
    max-width: 260px;
  }

  /* Inspection: stack document / nodes / edges vertically. */
  .insp-split-container {
    flex-direction: column !important;
  }

  .insp-source-panels {
    width: 100% !important;
    min-width: 0 !important;
    max-width: none !important;
    flex: 1 1 56% !important;
    min-height: 180px;
    border-right: none !important;
    border-bottom: 1px solid var(--color-border) !important;
  }

  .insp-entity-panels {
    width: 100% !important;
    min-width: 0 !important;
    max-width: none !important;
    flex: 1 1 44% !important;
    min-height: 160px;
  }

  /* A col-resize gutter makes no sense once the panels are stacked. */
  .insp-splitter-h {
    display: none !important;
  }

  /* Row gutters stay, widened to a comfortable touch strip. */
  .insp-splitter-v {
    height: 10px;
  }

  .insp-toolbar {
    padding: 8px 10px !important;
    gap: 6px !important;
  }

  /* Let the toolbar's own flex-wrap break the control groups onto new lines;
     the inline min-widths keep every select wide enough to read. */
  .insp-toolbar .dropdown-input {
    max-width: none !important;
  }

  #inspection-source-select {
    flex: 1 1 auto;
  }

  /* Graph: canvas keeps the whole area; native gestures go to the zoom
     behaviour rather than to the page. */
  #graph-container,
  #graph-svg {
    touch-action: none;
  }

  .canvas-floating-toolbar {
    top: 10px !important;
    right: 10px !important;
    gap: 8px !important;
  }

  .graph-legend {
    bottom: 10px;
    left: 10px;
    max-width: 210px;
    max-height: 30vh;
    overflow-y: auto;
    font-size: 10px;
    padding: 6px 8px;
  }

  /* Status bar: the keyboard hints are meaningless on a touch device. */
  .bottom-status-bar {
    padding: 0 8px;
    height: 32px;
    font-size: 11px;
    column-gap: 8px;
    /* MJ-4: the bar used to scroll sideways to accommodate an overflow that
       should not exist. With the grid it fits, and a scrollport here would only
       hide the clipping it was papering over. */
    overflow: hidden;
  }

  .bottom-status-bar .status-right {
    display: none;
  }

  .bottom-status-bar .status-left {
    gap: 6px;
    white-space: nowrap;
  }

  .bottom-status-bar .status-center {
    padding: 2px 6px;
  }

  /* Modals: near-full-screen with an independently scrolling body. */
  .modal {
    width: 94vw !important;
    max-width: 94vw !important;
    max-height: 90vh;
  }

  .modal-body {
    max-height: none !important;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }

  /* Modal tab strips carry up to six buttons — let them wrap. */
  .modal .tab-controls {
    flex-wrap: wrap !important;
  }

  .toast-container {
    left: 12px;
    right: 12px;
    max-width: none;
  }

  /* Touch targets: 44px minimum on the primary controls. */
  .nav-toggle-btn,
  .btn-toggle {
    min-height: 44px;
  }

  .topbar-btn {
    height: 40px !important;
    min-width: 40px;
  }

  .icon-btn {
    min-width: 40px;
    min-height: 40px;
  }

  .insp-panel-btn-collapse {
    width: 32px;
    height: 32px;
  }

  .canvas-float-btn {
    width: 44px;
    height: 44px;
    font-size: 17px;
  }

  .floating-node-actions .pill-btn {
    min-height: 36px;
    padding: 6px 10px;
  }
}


/* ── Two-row top bar (<= 1200px) ───────────────────────────────────────────
   Even icon-only, brand + centre controls + action buttons need ~1135px on
   one line, so below 1200px the bar wraps deliberately instead of squeezing
   the controls until their labels clip:
       row 1 — brand / Tools button  +  the action buttons (icon-only strip)
       row 2 — source selector  +  Table / Inspection / Graph toggles
   Nothing is hidden behind an overflow menu; the view toggles in particular
   stay full-width and thumb-sized. */
@media screen and (max-width: 1200px) {
  .top-bar {
    height: auto;
    min-height: var(--top-bar-height);
    flex-wrap: wrap;
    align-items: center;
    row-gap: 6px;
    column-gap: 8px;
    padding: 6px 10px;
    flex-shrink: 0;
  }

  .top-bar-left {
    flex: 0 1 auto;
    min-width: 0;
    gap: 8px;
  }

  .brand-group {
    min-width: 0;
  }

  .app-title {
    white-space: nowrap;
  }

  /* flex-basis 0 keeps this group on row 1 next to the brand (an `auto`
     basis makes it wrap to a row of its own on phones). flex-start + an auto
     margin on the first child — rather than justify-content:flex-end — so
     that when the buttons DO overflow, the overflow lands on the scrollable
     end of the box instead of the unreachable start. */
  .top-bar-right {
    flex: 1 1 0;
    min-width: 0;
    justify-content: flex-start;
    gap: 5px !important;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
  }

  .top-bar-right > *:first-child {
    margin-left: auto;
  }

  .top-bar-right::-webkit-scrollbar {
    display: none;
  }

  .topbar-btn {
    flex: 0 0 auto;
    padding: 0 8px !important;
  }

  .nav-btn-text {
    display: none;
  }

  .nav-toggle-btn {
    padding: 6px 10px;
    min-width: 44px;
  }

  /* Row 2 — order:3 plus a 100% flex-basis forces its own line. */
  .top-bar-center {
    order: 3;
    flex: 1 0 100%;
    gap: 8px;
    justify-content: space-between;
    min-width: 0;
  }

  .sources-toggle-container {
    display: flex;
    align-items: center;
    gap: 4px;
    min-width: 0;
    flex: 0 1 auto;
  }

  #btn-sources-menu {
    min-width: 0;
    flex: 0 1 auto;
    overflow: hidden;
  }

  #sources-menu-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Popover must not run off the right edge of a narrow screen. */
  #sources-dropdown-panel {
    left: 0;
    min-width: 0 !important;
    width: min(92vw, 340px) !important;
    max-height: 60vh;
    overflow-y: auto;
  }

  #btn-configure-sources {
    flex: 0 0 auto;
  }

  /* The view toggles are the primary navigation: they never shrink (shrinking
     them makes the labels overlap), so the source button absorbs the
     remaining width and ellipsises its label instead. */
  .view-toggles {
    flex: 0 0 auto;
  }

  .btn-toggle {
    flex: 0 0 auto;
    white-space: nowrap;
    padding: 6px 10px;
  }
}


/* ── Phone (<= 600px) ─────────────────────────────────────────────────────── */
@media screen and (max-width: 600px) {
  :root {
    --top-bar-height: 48px;
  }

  .top-bar {
    padding: 6px 8px;
  }

  .app-title {
    font-size: 15px;
  }

  .top-bar-right {
    gap: 4px !important;
  }

  .topbar-btn {
    padding: 0 6px !important;
  }

  .topbar-btn .badge {
    font-size: 10px !important;
    padding: 1px 4px !important;
  }

  /* The emoji inside each action button carries .btn-icon, which on desktop
     renders as a 28px bordered chip. On a phone that chip is ~100px of pure
     decoration across the strip — flatten it to a plain glyph so all six
     buttons very nearly fit without scrolling. */
  .topbar-btn > .btn-icon {
    width: auto;
    height: auto;
    min-width: 0;
    padding: 0;
    background: none;
    border: none;
    font-size: 15px;
  }

  .btn-toggle {
    padding: 6px 7px;
    font-size: 12px;
  }

  /* 16px avoids iOS Safari's zoom-on-focus; the inline font-size:12px on
     several controls has to be out-ranked for that to take effect. */
  .text-input,
  .dropdown-input,
  .number-input,
  input[type="text"],
  input[type="number"],
  input[type="search"],
  select,
  textarea {
    font-size: 16px !important;
  }

  .sidebar .text-input,
  .sidebar .dropdown-input,
  .sidebar .number-input,
  .table-toolbar .text-input,
  .table-toolbar .dropdown-input,
  .table-toolbar .btn,
  .insp-toolbar .dropdown-input {
    min-height: 40px !important;
    height: auto !important;
  }

  /* Comfortable tick targets. The .switch reset below must follow, since the
     styled toggle relies on a zero-sized visually-hidden input. */
  input[type="checkbox"],
  input[type="radio"] {
    width: 20px;
    height: 20px;
  }

  .switch input {
    width: 0;
    height: 0;
  }

  /* ---- Table view ---- */
  .table-toolbar {
    padding: 8px 10px !important;
    gap: 8px !important;
  }

  /* Filter group and search group each take their own full-width row. */
  .table-toolbar > div {
    flex: 1 1 100% !important;
    min-width: 0;
  }

  .table-toolbar > div:last-child > div {
    flex: 1 1 auto;
    width: 100%;
  }

  #table-search {
    width: 100% !important;
    min-width: 0;
  }

  .data-table th,
  .data-table td {
    padding: 10px 10px;
    font-size: 12.5px;
  }

  /* Low-priority name variants are folded away on phones; every value is
     still reachable by tapping the row (Source Verification drawer) and in
     the Inspection view. Guarded with :has() so it only applies to the
     13-column Nodes table — the 7-column Edges table keeps every column.
     Hidden: 성명(3) · 字(8) · 號(9) · alt_names(10) · 諡號(11). */
  #data-table:has(thead th:nth-child(13)) tr > *:nth-child(3),
  #data-table:has(thead th:nth-child(13)) tr > *:nth-child(8),
  #data-table:has(thead th:nth-child(13)) tr > *:nth-child(9),
  #data-table:has(thead th:nth-child(13)) tr > *:nth-child(10),
  #data-table:has(thead th:nth-child(13)) tr > *:nth-child(11) {
    display: none;
  }

  .doc-cell {
    min-width: 130px;
    max-width: 200px;
  }

  .table-pagination {
    height: auto;
    min-height: 52px;
    flex-wrap: wrap;
    gap: 8px;
    padding: 8px 10px;
  }

  .table-pagination .btn-icon {
    width: 40px !important;
    height: 40px !important;
  }

  /* ---- Inspection view ---- */
  #inspection-hanmun-panel,
  #inspection-korean-panel {
    padding: 12px 14px !important;
  }

  /* The Hanmun panel header holds a title, two edge-type toggles and the
     collapse control; side by side they stack into a 90px-tall header on a
     phone. Wrap instead, and send the edge toggles to a row of their own. */
  .insp-panel-header {
    padding: 6px 10px !important;
    flex-wrap: wrap;
    row-gap: 6px;
  }

  /* The highlight group gets a full row of its own rather than competing with
     the panel title, and the relationship chips wrap under the edges chip. */
  .insp-edge-toggles,
  .insp-hl-group {
    order: 3;
    flex: 1 1 100%;
    flex-wrap: wrap;
    gap: 5px !important;
    row-gap: 5px;
    font-size: 11px !important;
    justify-content: flex-start;
  }

  .insp-hl-branch { flex-wrap: wrap; row-gap: 4px; }

  /* Touch targets: the chips are the primary control here. */
  .insp-hl-chip { padding: 4px 8px; }
  .insp-fontsize-btn { padding: 5px 8px; }

  .insp-grid-hint { display: none; }

  /* The 原文 measure cap is a desktop nicety; on a phone the column is
     already narrow, so let the text use the full width. */
  #inspection-hanmun-panel > :not(.insp-edge-svg):not(.insp-subject-header),
  #inspection-korean-panel > :not(.insp-edge-svg):not(.insp-subject-header) { max-width: none; }

  #inspection-doc-prev,
  #inspection-doc-next {
    width: 40px !important;
    height: 40px !important;
  }

  #inspection-source-select {
    min-width: 150px !important;
  }

  #inspection-doc-select {
    min-width: 130px !important;
  }

  /* Progress: the bar survives; the whole group is allowed to wrap so the
     verified count and the jump button are never pushed off the edge. */
  .insp-progress { flex-wrap: wrap; row-gap: 4px; }
  .insp-progress-bar { width: 64px; }

  /* The "edits write into the source document" note is explanatory prose and
     the widest thing in the strip. The Auto-map dialog restates it in full, so
     drop it here rather than let it overflow. */
  .insp-overlay-note { display: none; }

  /* Subject term: keep the hanja (which identifies the role) and the editing
     affordance; the English gloss is the part that can be recovered from the
     tooltip and the editor. */
  .insp-subject-label-en { display: none; }
  .insp-subject-label-gloss { display: none; }
  .insp-pop { width: calc(100vw - 16px); }
  .insp-pop-fields { flex-direction: column; }
  .insp-pop-field:first-child { flex: 1; }

  .insp-modal { max-height: 92vh; border-radius: 10px; }
  .insp-modal-foot { flex-direction: column; align-items: stretch; }
  .insp-modal-foot-actions > .insp-btn { flex: 1; }
  .insp-stats { grid-template-columns: repeat(2, 1fr); }

  /* ---- Graph view ---- */
  .graph-legend {
    bottom: 8px;
    left: 8px;
    max-width: 48vw;
    max-height: 25vh;
    font-size: 10px;
    padding: 5px 7px;
  }

  /* ---- Modals as full-screen sheets ---- */
  .modal {
    inset: 0 !important;
    width: 100% !important;
    max-width: 100% !important;
    height: 100% !important;
    max-height: 100% !important;
    transform: none !important;
    border: none !important;
    border-radius: 0 !important;
  }

  .modal-header {
    padding: 12px 14px !important;
    flex-shrink: 0;
  }

  .modal-header h3 {
    font-size: 15px;
  }

  .modal-body {
    padding: 14px !important;
    flex: 1 1 auto;
    min-height: 0;
  }

  /* The merge / conflict comparison cards are built in JS with an inline
     `grid-template-columns: 1fr 1fr`; an attribute-substring selector is the
     only way to reach an inline style from a stylesheet. Side-by-side
     "keep / duplicate" columns do not fit a phone, so stack them. */
  .modal-body [style*="grid-template-columns: 1fr 1fr"] {
    grid-template-columns: 1fr !important;
  }

  /* Record IDs (kr_093r_0010_0010_P07) are unbreakable words; without
     `anywhere` their min-content width keeps the grid wider than the screen.
     Scoped to the cards so button and badge labels still break on spaces. */
  .modal-body [style*="grid-template-columns"] > * {
    min-width: 0;
    overflow-wrap: anywhere;
  }

  .modal-footer {
    padding: 10px 14px !important;
    flex-wrap: wrap;
    gap: 8px !important;
    flex-shrink: 0;
  }

  .modal-footer .btn {
    flex: 1 1 auto;
    min-height: 44px;
  }

  /* Close buttons are the escape hatch on a full-screen sheet. */
  .modal-header .icon-btn {
    min-width: 44px;
    min-height: 44px;
  }

  /* ---- Misc ---- */
  .loading-card {
    padding: 20px 18px;
  }

  .form-group label {
    font-size: 12px;
  }

  .btn {
    padding: 8px 12px;
  }
}


/* ── Phone in landscape / any very short viewport ──────────────────────────
   Vertical space is the scarce resource, so the top bar goes back to a
   single row and the brand block yields its width to the controls. */
@media screen and (max-height: 520px) and (orientation: landscape) {
  .top-bar {
    flex-wrap: nowrap;
    height: 46px;
    min-height: 46px;
    row-gap: 0;
  }

  .brand-group {
    display: none;
  }

  .top-bar-center {
    order: 0;
    flex: 1 1 auto;
  }

  .top-bar-right {
    flex: 0 1 auto;
  }

  .insp-source-panels {
    flex: 1 1 50% !important;
  }
}


/* ── Touch / no-hover pointers ─────────────────────────────────────────────
   Nothing in the UI is hover-only, but the hover lift/translate effects read
   as a stuck state after a tap, and the drag gutters need a wider grab area. */
@media (hover: none) {
  .detail-action-btn:hover,
  .canvas-float-btn:hover,
  .canvas-float-btn:active {
    transform: none;
  }

  .data-table tbody tr:active {
    background-color: rgba(91, 141, 239, 0.14);
  }

  .insp-table tr:active {
    background: rgba(91, 141, 239, 0.14);
  }
}

/* Keyboard users must keep a visible focus ring even where hover styling
   carries the affordance on desktop. */
:focus-visible {
  outline: 2px solid var(--color-accent-primary);
  outline-offset: 2px;
}


/* ── Reduced motion ────────────────────────────────────────────────────────
   Drawers, toasts, the loading spinner and the status-bar flashes all
   animate; honour the OS preference and cut them to effectively instant. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
}


/* ── AI assist panel ─────────────────────────────────────────────────────── */
.insp-ai-btn { border-color: rgba(139, 92, 246, 0.5); color: #a78bfa; }
.insp-ai-btn:hover:not(:disabled) {
  border-color: #a78bfa; background: rgba(139, 92, 246, 0.12); color: #a78bfa;
}
.insp-ai-modal { max-width: 720px; }
.insp-ai-provider { position: relative; padding-right: 70px; }
.insp-ai-badge {
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  font-size: 9.5px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
  padding: 2px 6px; border-radius: 10px;
  background: var(--color-surface-elevated); color: var(--color-text-secondary);
  border: 1px solid var(--color-border);
}
.insp-ai-badge.is-on {
  background: rgba(16, 185, 129, 0.15); color: #10b981; border-color: rgba(16, 185, 129, 0.4);
}
.insp-ai-keynote { font-size: 11px; color: var(--color-text-secondary); line-height: 1.5; margin: 0 0 8px; }
.insp-ai-keyrow { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.insp-ai-key {
  flex: 1 1 260px; min-width: 0; padding: 6px 8px; font-size: 12px; border-radius: 5px;
  background: var(--color-bg); color: var(--color-text-primary);
  border: 1px solid var(--color-border); font-family: ui-monospace, monospace;
}
/* Endpoint row: same shape as the model row, shown only for a provider whose
   address the operator chooses. Monospace, because a URL is typed and checked
   character by character. */
.insp-ai-urlrow { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; font-size: 11.5px; }
.insp-ai-urlrow[hidden] { display: none; }
.insp-ai-urlrow > span,
.insp-ai-modelrow > span { flex: 0 0 62px; color: var(--color-text-secondary); }
.insp-ai-urlrow input {
  flex: 1 1 auto; padding: 5px 8px; font-size: 11.5px; border-radius: 5px;
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  background: var(--color-bg); color: var(--color-text-primary); border: 1px solid var(--color-border);
}

/* The one-line explanation of what an OpenAI-compatible endpoint covers. */
.insp-ai-provider-note {
  display: block;
  margin-top: 2px;
  font-size: 10.5px;
  line-height: 1.45;
  opacity: 0.75;
}

.insp-ai-modelrow { display: flex; gap: 8px; align-items: center; margin-top: 8px; font-size: 11.5px; }
.insp-ai-modelrow input {
  flex: 1 1 auto; padding: 5px 8px; font-size: 12px; border-radius: 5px;
  background: var(--color-bg); color: var(--color-text-primary); border: 1px solid var(--color-border);
}
.insp-ai-privacy { border-color: rgba(139, 92, 246, 0.35); }
.insp-ai-summary {
  display: flex; gap: 10px; flex-wrap: wrap; font-size: 11px;
  color: var(--color-text-secondary); padding: 6px 0; border-bottom: 1px solid var(--color-border);
}
.insp-ai-warnpill { color: var(--color-warning, #e8c95e); font-weight: 700; }
.insp-ai-h { font-size: 11.5px; margin: 12px 0 4px; color: var(--color-text-primary); }
.insp-ai-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.insp-ai-list li {
  display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline;
  padding: 6px 8px; border-radius: 5px; background: var(--color-surface-elevated);
  border: 1px solid var(--color-border); font-size: 12px;
}
.insp-ai-none { font-size: 11.5px; color: var(--color-text-secondary); margin: 2px 0; font-style: italic; }
.insp-ai-ev { font-size: 11px; padding: 1px 5px; border-radius: 3px; }
.insp-ai-ev.is-ok { background: rgba(16, 185, 129, 0.12); color: #10b981; }
.insp-ai-ev.is-warn { background: rgba(239, 68, 68, 0.12); color: #ef4444; }
.insp-ai-conf, .insp-ai-reason { font-size: 10.5px; color: var(--color-text-secondary); }
.insp-ai-rel { font-size: 10.5px; font-weight: 700; color: var(--color-accent-primary); }
.insp-ai-verdict { font-size: 10px; font-weight: 700; text-transform: uppercase; padding: 1px 5px; border-radius: 3px; }
.insp-ai-verdict.is-ok { background: rgba(16, 185, 129, 0.15); color: #10b981; }
.insp-ai-verdict.is-warn { background: rgba(232, 201, 94, 0.15); color: #e8c95e; }
.insp-ai-status { font-size: 12px; color: var(--color-text-secondary); }
.insp-ai-status.is-error { color: #ef4444; }
.insp-ai-notes, .insp-ai-foot { font-size: 11px; color: var(--color-text-secondary); margin-top: 8px; }


/* The wordmark doubles as the way home, so it has to look pressable without
   becoming a button competing with the view tabs beside it. */
.brand-group.is-home { cursor: pointer; border-radius: 6px; padding: 2px 6px; margin: -2px -6px;
  transition: background 0.12s ease; }
.brand-group.is-home:hover { background: var(--color-surface-hover, rgba(127,127,127,0.10)); }
.brand-group.is-home:focus-visible { outline: 2px solid var(--color-accent-primary, #5b8def); outline-offset: 2px; }

/* Waiting on its parts: present, legible, and plainly not pressable yet. */
.insp-verify-btn.is-waiting { opacity: 0.55; font-variant-numeric: tabular-nums; }
.insp-verify-btn:disabled { cursor: not-allowed; }

/* Scope, on the button that uses it. Reads as one control with the run button:
   what will run, and over what. */
.insp-identify-scope { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; }
.insp-identify-scope .insp-scope-txt { font-variant-numeric: tabular-nums; }
.insp-scope-menu {
  position: fixed; z-index: 9000; min-width: 300px; max-width: 340px;
  background: var(--color-surface-elevated, #1f2330);
  border: 1px solid var(--color-border, #2e3347);
  border-radius: 8px; padding: 4px; box-shadow: 0 10px 30px rgba(0,0,0,0.35);
}
.insp-scope-item {
  display: flex; gap: 8px; width: 100%; text-align: left; padding: 8px 10px;
  background: transparent; border: 0; border-radius: 6px; cursor: pointer;
  color: var(--color-text-primary, #e8eaed); font: inherit;
}
.insp-scope-item:hover { background: var(--color-surface-hover, rgba(127,127,127,0.12)); }
.insp-scope-item em { display: block; font-style: normal; font-size: 11.5px;
  color: var(--color-text-secondary, #9aa0a6); margin-top: 2px; line-height: 1.4; }
.insp-scope-tick { width: 12px; flex: 0 0 12px; color: var(--color-accent-primary, #5b8def); }

/* Column grips. Only on columns that hold text, and only visible when the
   pointer is near the header, so the grids stay as quiet as they were. */
.insp-table thead th.is-resizable { position: relative; }
.insp-colgrip {
  position: absolute; top: 0; right: -3px; width: 7px; height: 100%;
  cursor: col-resize; opacity: 0; transition: opacity 0.12s ease; touch-action: none;
}
.insp-colgrip::after {
  content: ''; position: absolute; top: 15%; bottom: 15%; left: 3px; width: 1px;
  background: var(--color-accent-primary, #5b8def);
}
.insp-table thead:hover .insp-colgrip { opacity: 0.45; }
.insp-colgrip:hover, .insp-colgrip:active { opacity: 1 !important; }
body.is-col-resizing { cursor: col-resize; user-select: none; }

/* Accept or refuse the repair for a flagged cell, right where the flag is. */
.insp-fixbar { display: inline-flex; gap: 2px; margin-left: 4px; vertical-align: middle; }
.insp-fixbar button {
  width: 17px; height: 17px; padding: 0; line-height: 1; font-size: 10px;
  border-radius: 4px; cursor: pointer; border: 1px solid transparent;
  background: transparent; color: var(--color-text-secondary, #9aa0a6);
}
.insp-fix-accept { color: #22c55e; border-color: rgba(34,197,94,0.45) !important; }
.insp-fix-accept:hover { background: rgba(34,197,94,0.16); }
.insp-fix-reject:hover { background: var(--color-surface-hover, rgba(127,127,127,0.14)); }

/* A suggestion that contradicts a record already on file. */
.insp-row.is-conflicting { box-shadow: inset 3px 0 0 #f59e0b; }
.insp-prop-clash { color: #f59e0b; font-weight: 600; }

/* A person whose own record lives on another document, shown here because a
   relationship on this one names them. Present, legible, and visibly not this
   record's to edit. */
.insp-table tr.is-context > td { opacity: 0.78; }

/* People a record implies but never names. A chip, not a mark: it stands
   outside the text because the text does not contain them, and it gives the
   connectors something to reach. */
.insp-implied-strip {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin: 0 0 8px; padding: 5px 8px;
  border: 1px dashed var(--color-border, #2e3347); border-radius: 6px;
  background: var(--color-surface-hover, rgba(127,127,127,0.06));
}
.insp-implied-label {
  font-size: 10.5px; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--color-text-secondary, #9aa0a6);
}
.insp-implied-chip {
  display: inline-flex; align-items: baseline; gap: 4px;
  padding: 2px 7px; border-radius: 10px; cursor: default;
  border: 1px dashed var(--color-accent-primary, #5b8def);
  color: var(--color-text-primary, #e8eaed);
  font-family: var(--font-cjk, serif); font-size: 13px;
}
.insp-implied-chip i {
  font-style: normal; font-size: 9.5px; opacity: 0.65;
  font-family: var(--font-latin, system-ui);
}

.insp-prop-refile { color: var(--color-accent-primary, #5b8def); font-weight: 600; }

/* Asking, in the page rather than over it. */
.insp-ask-backdrop {
  position: fixed; inset: 0; z-index: 10000; display: flex;
  align-items: center; justify-content: center; padding: 24px;
  background: rgba(0,0,0,0.42); backdrop-filter: blur(1.5px);
}
.insp-ask {
  width: min(460px, 100%); border-radius: 10px; padding: 18px 20px 16px;
  background: var(--color-surface-elevated, #1f2330);
  border: 1px solid var(--color-border, #2e3347);
  box-shadow: 0 18px 48px rgba(0,0,0,0.42);
  color: var(--color-text-primary, #e8eaed);
}
.insp-ask-title { margin: 0 0 8px; font-size: 15px; font-weight: 650; line-height: 1.35; }
.insp-ask-body { font-size: 13px; line-height: 1.55; color: var(--color-text-secondary, #9aa0a6); }
.insp-ask-body strong { color: var(--color-text-primary, #e8eaed); font-weight: 600; }
.insp-ask-list {
  margin: 8px 0 0; padding: 8px 10px; list-style: none; max-height: 190px; overflow-y: auto;
  border-radius: 6px; background: var(--color-surface, #171a24);
  font-family: var(--font-cjk, serif); font-size: 13px; line-height: 1.7;
}
.insp-ask-more { color: var(--color-text-secondary, #9aa0a6); font-family: var(--font-latin, system-ui); font-size: 12px; }
.insp-ask-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
.insp-ask-actions button {
  padding: 7px 14px; border-radius: 6px; font-size: 13px; font-weight: 600; cursor: pointer;
  border: 1px solid var(--color-border, #2e3347);
  background: var(--color-surface, #171a24); color: var(--color-text-primary, #e8eaed);
}
.insp-ask-no:hover { background: var(--color-surface-hover, rgba(127,127,127,0.14)); }
.insp-ask-yes { border-color: var(--color-accent-primary, #5b8def); background: var(--color-accent-primary, #5b8def); color: #fff; }
.insp-ask-yes:hover { filter: brightness(1.08); }
.insp-ask-yes.is-danger { border-color: #ef4444; background: #ef4444; }
.insp-ask-no:focus-visible, .insp-ask-yes:focus-visible { outline: 2px solid var(--color-accent-primary, #5b8def); outline-offset: 2px; }

/* Hanja is never italic, anywhere.
   Sinitic type has no italic form: browsers fake one by shearing the glyphs,
   which is not emphasis but distortion — and on a rare character it can make
   the strokes unreadable, which is the opposite of what a workbench for reading
   them is for. Emphasis in this interface is carried by weight, colour and
   rule, never by slant. This states it once for everything that holds a name. */
.insp-input,
.insp-highlight,
.insp-highlight-ghost,
.insp-ghost-badge,
.insp-implied-chip,
.insp-prop-name,
.insp-subject-label,
.insp-id-suffix,
.insp-ask-list,
.data-table td,
.data-table th,
[lang="zh"], [lang="ko"], [lang="ja"] {
  font-style: normal;
}

/* One review, stated once at the top of the task pane. */
.sebo-ai-onepass {
  margin: 0 0 14px; padding: 10px 12px; border-radius: 6px;
  font-size: 12.5px; line-height: 1.6;
  color: var(--color-text-secondary, #9aa0a6);
  background: var(--color-surface, #171a24);
  border: 1px solid var(--color-border, #2e3347);
}
.sebo-ai-onepass strong { color: var(--color-text-primary, #e8eaed); }

/* ── Identity ────────────────────────────────────────────────────────────────
   A queue on the left, one person at a time on the right. The question is
   always the same — are these records one man? — so the layout never changes
   shape and the reader's eye learns where the answer lives. */
#identity-view { display: none; height: 100%; flex-direction: column; overflow: hidden; }
#identity-view.active { display: flex; }
.ident-bar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 12px; border-bottom: 1px solid var(--color-border, #2e3347);
}
.ident-title { font-weight: 650; font-size: 13px; }
.ident-pair { display: flex; align-items: center; gap: 6px; font-size: 12px;
  color: var(--color-text-secondary, #9aa0a6); }
.ident-pair select { font-size: 12px; padding: 3px 6px; border-radius: 5px;
  background: var(--color-surface, #171a24); color: var(--color-text-primary, #e8eaed);
  border: 1px solid var(--color-border, #2e3347); }
.ident-filters { display: inline-flex; gap: 2px; }
.ident-filter {
  font-size: 11.5px; padding: 4px 9px; cursor: pointer; border-radius: 5px;
  border: 1px solid transparent; background: transparent;
  color: var(--color-text-secondary, #9aa0a6);
}
.ident-filter.is-on { background: var(--color-surface-hover, rgba(127,127,127,0.14));
  color: var(--color-text-primary, #e8eaed); border-color: var(--color-border, #2e3347); }
.ident-count { font-size: 11.5px; color: var(--color-text-secondary, #9aa0a6);
  font-variant-numeric: tabular-nums; }

.ident-body { display: grid; grid-template-columns: minmax(240px, 320px) 1fr;
  flex: 1; min-height: 0; }
.ident-queue { overflow-y: auto; border-right: 1px solid var(--color-border, #2e3347); padding: 6px; }
.ident-detail { overflow-y: auto; padding: 16px 20px; }
.ident-empty { padding: 24px 12px; font-size: 12.5px; color: var(--color-text-secondary, #9aa0a6); }

.ident-item {
  display: grid; grid-template-columns: 1fr auto; gap: 2px 8px; width: 100%;
  text-align: left; padding: 8px 10px; margin-bottom: 2px; cursor: pointer;
  border: 1px solid transparent; border-radius: 6px; background: transparent;
  color: var(--color-text-primary, #e8eaed);
}
.ident-item:hover { background: var(--color-surface-hover, rgba(127,127,127,0.10)); }
.ident-item.is-on { background: var(--color-surface-hover, rgba(127,127,127,0.16));
  border-color: var(--color-accent-primary, #5b8def); }
.ident-item.is-decided .ident-item-name { opacity: 0.55; }
.ident-item-name { font-family: var(--font-cjk, serif); font-size: 14px; font-style: normal; }
.ident-item-meta { grid-column: 1; font-size: 11px; color: var(--color-text-secondary, #9aa0a6); }
.ident-chip {
  grid-row: 1 / span 2; align-self: center; font-size: 10.5px; padding: 2px 7px;
  border-radius: 9px; white-space: nowrap; border: 1px solid transparent;
}
.ident-chip.certain { background: rgba(34,197,94,0.16); color: #22c55e; }
.ident-chip.likely { background: rgba(91,141,239,0.16); color: #5b8def; }
.ident-chip.doubtful { background: rgba(232,152,94,0.18); color: #e8985e; }
.ident-chip.contradictory { background: rgba(239,68,68,0.16); color: #ef4444; }

.ident-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
.ident-head h3 { margin: 0; font-family: var(--font-cjk, serif); font-size: 22px; font-style: normal; }
.ident-head-meta { font-size: 12px; color: var(--color-text-secondary, #9aa0a6); }

.ident-evidence { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 16px; }
.ident-evidence h4 { margin: 0 0 5px; font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--color-text-secondary, #9aa0a6); }
.ident-evidence ul { margin: 0; padding-left: 16px; font-size: 12.5px; line-height: 1.65; }
.ident-for ul { color: #22c55e; }
.ident-against ul { color: #e8985e; }
.ident-none { margin: 0; font-size: 12.5px; color: var(--color-text-secondary, #9aa0a6); }

.ident-records { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 18px; }
.ident-rec {
  flex: 1 1 210px; min-width: 200px; padding: 10px 12px; border-radius: 8px;
  border: 1px solid var(--color-border, #2e3347); background: var(--color-surface, #171a24);
}
.ident-rec header { display: flex; justify-content: space-between; font-size: 10.5px;
  color: var(--color-text-secondary, #9aa0a6); }
.ident-rec h4 { margin: 4px 0 8px; font-family: var(--font-cjk, serif); font-size: 16px; font-style: normal; }
.ident-rec dl { display: grid; grid-template-columns: auto 1fr; gap: 2px 8px; margin: 0; font-size: 12px; }
.ident-rec dt { color: var(--color-text-secondary, #9aa0a6); }
.ident-rec dd { margin: 0; font-family: var(--font-cjk, serif); font-style: normal; }
.ident-doc { font-family: var(--font-latin, ui-monospace, monospace) !important; font-size: 10.5px; opacity: 0.8; }
.ident-rec-notes { margin: 8px 0 0; font-size: 11.5px; line-height: 1.5;
  color: var(--color-text-secondary, #9aa0a6); }

.ident-verdict { border-top: 1px solid var(--color-border, #2e3347); padding-top: 14px; }
.ident-reason { display: block; font-size: 11.5px; color: var(--color-text-secondary, #9aa0a6); }
.ident-reason input { display: block; width: 100%; margin-top: 4px; padding: 7px 9px;
  border-radius: 6px; border: 1px solid var(--color-border, #2e3347);
  background: var(--color-surface, #171a24); color: var(--color-text-primary, #e8eaed); font-size: 13px; }
.ident-actions { display: flex; gap: 8px; margin: 12px 0 10px; flex-wrap: wrap; }
.ident-say { padding: 8px 16px; border-radius: 6px; font-size: 13px; font-weight: 600; cursor: pointer;
  border: 1px solid var(--color-border, #2e3347); background: var(--color-surface, #171a24);
  color: var(--color-text-primary, #e8eaed); }
.ident-say.is-same { border-color: rgba(34,197,94,0.5); color: #22c55e; }
.ident-say.is-diff { border-color: rgba(239,68,68,0.5); color: #ef4444; }
.ident-say:hover { filter: brightness(1.15); }
.ident-note { margin: 0; font-size: 11.5px; color: var(--color-text-secondary, #9aa0a6); }
.ident-note code { font-size: 11px; }

/* Identity: one type system.
   Hanja in the CJK face at a readable size, every label and romanization in the
   interface face. Mixing them per-field made each card look like three
   different documents. */
#identity-view, .ident-bar, .ident-queue, .ident-detail,
.ident-rec dt, .ident-rec header, .ident-item-meta, .ident-note, .ident-empty {
  font-family: var(--font-latin, system-ui, sans-serif);
}
.ident-item-name, .ident-head h3, .ident-rec h4,
.ident-evidence li, .ident-safe-note b + * {
  font-family: var(--font-cjk, serif);
}
/* A value is set in the script it is written in.
   Every dd used to take the CJK face, so "Kim Yun" and a document id were
   rendered in a serif meant for hanja — the card read as though the
   romanization were part of the Chinese. The class follows the content. */
.ident-rec dd { font-family: var(--font-latin, system-ui, sans-serif); }
.ident-rec dd.is-cjk { font-family: var(--font-cjk, serif); }
.ident-rec dl { align-items: baseline; }
.ident-rec dt, .ident-rec dd { display: flex; align-items: center; min-height: 18px; }
.ident-rec-reading {
  margin-left: 8px; font-size: 11.5px; font-weight: 400;
  color: var(--color-text-secondary, #9aa0a6);
  font-family: var(--font-cjk, serif);
}
.ident-rec h4 { display: flex; align-items: baseline; gap: 0; }

/* Selecting several, and settling them together. */
.ident-item { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 6px; }
.ident-open { display: grid; gap: 2px; text-align: left; background: transparent;
  border: 0; padding: 0; cursor: pointer; color: inherit; font: inherit; }
.ident-pick { width: 15px; height: 15px; cursor: pointer; }
.ident-chip.safe { background: rgba(34,197,94,0.16); color: #22c55e; }
.ident-safebar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 2px 2px 8px; padding: 8px 10px; border-radius: 7px;
  border: 1px dashed rgba(34,197,94,0.45); background: rgba(34,197,94,0.06);
  font-size: 11.5px; color: var(--color-text-secondary, #9aa0a6);
}
.ident-safebar b { color: #22c55e; }
.ident-safebar button {
  font-size: 11.5px; padding: 4px 9px; border-radius: 5px; cursor: pointer;
  border: 1px solid var(--color-border, #2e3347); background: var(--color-surface, #171a24);
  color: var(--color-text-primary, #e8eaed);
}
.ident-safebar button.is-go { border-color: rgba(34,197,94,0.6); color: #22c55e; font-weight: 600; }
.ident-safe-note {
  margin: 0 0 14px; padding: 8px 11px; border-radius: 6px; font-size: 12.5px;
  background: rgba(34,197,94,0.08); border: 1px solid rgba(34,197,94,0.3);
  color: var(--color-text-primary, #e8eaed);
}

/* Quiet when idle: a dot holding its place, not a label. */
.autosave-chip[data-state="idle"] {
  opacity: 0.35; padding-left: 6px; padding-right: 6px; border-color: transparent;
  background: transparent;
}
.autosave-chip[data-state="idle"]:hover { opacity: 0.7; }

.ident-ai { margin: 0 0 16px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
#ident-ask {
  font-size: 12.5px; padding: 7px 12px; border-radius: 6px; cursor: pointer;
  border: 1px solid rgba(139,92,246,0.5); background: rgba(139,92,246,0.10); color: #a78bfa;
}
#ident-ask:disabled { opacity: 0.6; cursor: default; }
.ident-ai-note { font-size: 11.5px; color: var(--color-text-secondary, #9aa0a6); }
.ident-ai-said { padding: 10px 12px; border-radius: 7px; border: 1px solid var(--color-border, #2e3347);
  background: var(--color-surface, #171a24); }
.ident-ai-said.same { border-color: rgba(34,197,94,0.4); }
.ident-ai-said.different { border-color: rgba(239,68,68,0.4); }
.ident-ai-verdict { margin: 0 0 5px; font-size: 13px; }
.ident-ai-conf { font-size: 11px; color: var(--color-text-secondary, #9aa0a6); margin-left: 6px; }
.ident-ai-grounds { margin: 0 0 5px; font-size: 12.5px; line-height: 1.6; }
.ident-ai-watch { margin: 0 0 5px; font-size: 12px; color: var(--color-text-secondary, #9aa0a6); }
.ident-ai-model { margin: 0; font-size: 11px; color: var(--color-text-secondary, #9aa0a6); }

/* Identity cards: one voice on the right.
   The values column mixed a CJK serif with the interface sans depending on the
   script of each value, so adjacent rows disagreed with each other. Hanja needs
   a CJK face, but it does not need a SERIF one — a sans CJK stack sits with the
   labels and the romanizations instead of arguing with them. */
.ident-rec { background: transparent; }
.ident-rec dd.is-cjk,
.ident-rec h4, .ident-rec-reading, .ident-rel-other, .ident-rel-kind {
  font-family: 'Noto Sans KR', 'Noto Sans CJK KR', 'Apple SD Gothic Neo',
               'Malgun Gothic', var(--font-latin, system-ui), sans-serif;
}
.ident-rels { list-style: none; margin: 8px 0 0; padding: 0; font-size: 11.5px; }
.ident-rels li { display: grid; grid-template-columns: auto 1fr auto auto; gap: 6px;
  align-items: center; padding: 2px 0; }
.ident-rel-dir { font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--color-text-secondary, #9aa0a6); }
.ident-rel-kind { font-size: 11px; opacity: 0.75; }
.ident-rel-doc { font-size: 10px; opacity: 0.6; font-family: var(--font-latin, monospace); }

/* A record Identity has ruled on. A mark, not a merge: the row is still itself. */
.tbl-ident { margin-left: 4px; font-size: 11px; cursor: help; }
.tbl-ident.same { color: #22c55e; }
.tbl-ident.different { color: #ef4444; }
.tbl-ident.unsure { color: var(--color-text-secondary, #9aa0a6); }

.ident-goto {
  display: inline-block; margin: 0 6px 3px 0; padding: 2px 7px; border-radius: 5px;
  font-size: 10.5px; cursor: pointer; font-family: var(--font-latin, monospace);
  border: 1px solid var(--color-border, #2e3347); background: transparent;
  color: var(--color-accent-primary, #5b8def);
}
.ident-goto:hover { background: var(--color-surface-hover, rgba(127,127,127,0.12)); }
.ident-rec dd.ident-doc { display: block; flex-wrap: wrap; }

/* Identity — a verdict covers the records that are ticked, not the cluster.
   A name gathers records; it does not prove they are one man, so the reader
   settles the ones he is sure of and the rest stay in the queue. */
.ident-picks {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0 0 8px; font-size: 11.5px; color: var(--color-text-secondary, #9aa0a6);
  font-family: var(--font-latin, system-ui, sans-serif);
}
.ident-picks-label { text-transform: uppercase; letter-spacing: 0.05em; font-size: 10px; }
#ident-picks-count { color: var(--color-text-primary, #e6e8ee); font-weight: 600; }
.ident-picks button {
  padding: 2px 8px; border-radius: 5px; font-size: 11px; cursor: pointer;
  border: 1px solid var(--color-border, #2e3347); background: transparent;
  color: inherit; font-family: inherit;
}
.ident-picks button:hover { background: var(--color-surface-hover, rgba(127,127,127,0.12)); }
.ident-picks-warn { color: #e8985e; }

.ident-rec-pick { display: inline-flex; align-items: center; gap: 5px; cursor: pointer; }
.ident-rec-box { width: 13px; height: 13px; cursor: pointer; margin: 0; }
.ident-rec { transition: opacity 0.12s ease, border-color 0.12s ease; }
.ident-rec.is-picked { border-color: rgba(91,141,239,0.55); }
.ident-rec.is-dropped { opacity: 0.42; }
.ident-rec-filed {
  margin: 4px 0 0; font-size: 10.5px; color: var(--color-text-secondary, #9aa0a6);
  font-family: var(--font-latin, system-ui, sans-serif);
}
.ident-filed {
  margin: 0 0 14px; font-size: 11.5px; color: var(--color-text-secondary, #9aa0a6);
  font-family: var(--font-latin, system-ui, sans-serif);
}
.ident-filed span { display: inline-block; margin-right: 10px; }
.ident-part { color: #5b8def; font-weight: 600; }
.ident-say:disabled { opacity: 0.4; cursor: default; }

/* Instructions for one record — what to tell the model before it runs.
   A single character wide until it has something to say. */
.insp-record-note.has-note {
  color: var(--color-accent-primary, #5b8def);
  border-color: rgba(91,141,239,0.55);
}
.insp-record-note-panel {
  position: fixed; z-index: 60; width: 440px; max-width: calc(100vw - 16px);
  padding: 12px 14px; border-radius: 9px;
  background: var(--color-surface, #171a23);
  border: 1px solid var(--color-border, #2e3347);
  box-shadow: 0 10px 34px rgba(0,0,0,0.45);
  font-family: var(--font-latin, system-ui, sans-serif);
}
.insp-record-note-panel label {
  display: block; font-size: 11.5px; margin-bottom: 6px;
  color: var(--color-text-secondary, #9aa0a6);
}
.insp-record-note-panel code { font-size: 11px; }
.insp-record-note-panel textarea {
  width: 100%; box-sizing: border-box; padding: 8px 10px; border-radius: 6px;
  font-size: 12.5px; line-height: 1.6; resize: vertical;
  background: var(--color-bg, #0f1116); color: var(--color-text-primary, #e6e8ee);
  border: 1px solid var(--color-border, #2e3347);
  font-family: var(--font-latin, system-ui, sans-serif);
}
.insp-record-note-actions {
  display: flex; align-items: center; gap: 8px; margin-top: 9px;
}
.insp-record-note-where {
  flex: 1; font-size: 10.5px; color: var(--color-text-secondary, #9aa0a6);
}
.insp-record-note-actions button {
  padding: 4px 11px; border-radius: 6px; font-size: 12px; cursor: pointer;
  background: transparent; color: inherit; font-family: inherit;
  border: 1px solid var(--color-border, #2e3347);
}
.insp-record-note-actions button.is-go {
  border-color: rgba(34,197,94,0.55); color: #22c55e; font-weight: 600;
}
.insp-record-note-actions button:hover { background: var(--color-surface-hover, rgba(127,127,127,0.12)); }

/* A collection-wide review must always show the way out. */
.insp-task-stop {
  margin-left: auto; padding: 2px 10px; border-radius: 5px; font-size: 11px;
  cursor: pointer; font-family: var(--font-latin, system-ui, sans-serif);
  background: transparent; color: #e8985e;
  border: 1px solid rgba(232,152,94,0.6);
}
.insp-task-stop:hover { background: rgba(232,152,94,0.14); }
.insp-task-stop:disabled { opacity: 0.55; cursor: default; }

/* What the decisions file is, said where the buttons are. */
.ident-format-panel {
  position: fixed; inset: 0; z-index: 70; display: flex; flex-direction: column;
  gap: 10px; margin: auto; width: 560px; max-width: calc(100vw - 24px);
  height: fit-content; max-height: 80vh; overflow-y: auto;
  padding: 18px 20px; border-radius: 10px;
  background: var(--color-surface, #171a23);
  border: 1px solid var(--color-border, #2e3347);
  box-shadow: 0 16px 48px rgba(0,0,0,0.5);
  font-family: var(--font-latin, system-ui, sans-serif);
  font-size: 12.5px; line-height: 1.6;
}
.ident-format-panel h4 {
  margin: 0; font-size: 13px; font-family: var(--font-latin, ui-monospace, monospace);
  color: var(--color-accent-primary, #5b8def);
}
.ident-format-panel p { margin: 0; color: var(--color-text-secondary, #9aa0a6); }
.ident-format-panel table { border-collapse: collapse; width: 100%; }
.ident-format-panel th {
  text-align: left; vertical-align: top; padding: 4px 10px 4px 0; white-space: nowrap;
  font-family: var(--font-latin, ui-monospace, monospace); font-size: 11.5px;
  font-weight: 600; color: var(--color-text-primary, #e6e8ee);
}
.ident-format-panel td {
  padding: 4px 0; vertical-align: top; color: var(--color-text-secondary, #9aa0a6);
}
.ident-format-panel code { font-size: 11.5px; }
.ident-format-note { border-top: 1px solid var(--color-border, #2e3347); padding-top: 10px; }
.ident-format-panel button {
  align-self: flex-end; padding: 5px 14px; border-radius: 6px; font-size: 12px;
  cursor: pointer; background: transparent; color: inherit; font-family: inherit;
  border: 1px solid var(--color-border, #2e3347);
}
#ident-format { min-width: 28px; }

/* One line each: where to carry on from, and how far to go. */
.insp-sweep-opt {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  margin: 9px 0 0; font-size: 12.5px; line-height: 1.5;
}
.insp-sweep-opt input[type="number"] {
  padding: 3px 6px; border-radius: 5px; font-size: 12px;
  background: var(--color-bg, #0f1116); color: var(--color-text-primary, #e6e8ee);
  border: 1px solid var(--color-border, #2e3347);
}
.insp-sweep-opt span { color: var(--color-text-secondary, #9aa0a6); font-size: 11.5px; }
.insp-sweep-opt code { font-size: 11.5px; }

/* Both ends of the range, and what that comes to. */
.insp-sweep-range {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 12px 0 4px; font-size: 12.5px;
}
.insp-sweep-range label { display: inline-flex; align-items: center; gap: 6px; }
.insp-sweep-range select {
  max-width: 15em; padding: 4px 7px; border-radius: 5px; font-size: 12px;
  background: var(--color-bg, #0f1116); color: var(--color-text-primary, #e6e8ee);
  border: 1px solid var(--color-border, #2e3347);
  font-family: var(--font-latin, system-ui, sans-serif);
}
.insp-sweep-range output {
  flex-basis: 100%; font-size: 11.5px; color: var(--color-accent-primary, #5b8def);
}
