<!DOCTYPE html>
<html><head><meta charset="UTF-8"><meta http-equiv="Content-Security-Policy" content="default-src 'self' file: data: blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval' file: data: blob:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' data: https://fonts.gstatic.com; img-src 'self' data: blob: file:; connect-src 'self';"><title>Loctree Report</title><style>
/* ============================================
loctree Report — Vista Galaxy Black Steel
============================================ */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
/* ============================================
Theme: Light Mode (Default)
============================================ */
:root {
/* Light Theme Tokens */
--theme-bg-deep: #f5f7fa;
--theme-bg-surface: #ffffff;
--theme-bg-surface-elevated: #fafbfc;
--theme-text-primary: #1a1f26;
--theme-text-secondary: #4a5568;
--theme-text-tertiary: #718096;
--theme-accent: #3182ce;
--theme-accent-rgb: 49, 130, 206;
--theme-border: rgba(0, 0, 0, 0.1);
--theme-border-strong: rgba(0, 0, 0, 0.15);
--theme-hover: rgba(0, 0, 0, 0.04);
--theme-hover-strong: rgba(0, 0, 0, 0.08);
/* Scrollbar (Light) */
--theme-scrollbar: rgba(0, 0, 0, 0.15);
--theme-scrollbar-hover: rgba(0, 0, 0, 0.25);
/* Fallbacks for theme-aware scrollbars */
--scrollbar-bg: var(--theme-scrollbar, rgba(0, 0, 0, 0.15));
--scrollbar-bg-hover: var(--theme-scrollbar-hover, rgba(0, 0, 0, 0.25));
/* Gradients (Light) */
--gradient-nav: linear-gradient(135deg, rgba(255,255,255,0.98) 0%, rgba(245,247,250,0.95) 100%);
--gradient-sidebar: linear-gradient(180deg, rgba(250,251,252,0.98) 0%, rgba(245,247,250,0.95) 100%);
--gradient-main: linear-gradient(180deg, rgba(250,251,252,0.95) 0%, rgba(245,247,250,0.9) 100%);
/* Dimensions */
--radius-lg: 20px;
--radius-md: 12px;
--radius-sm: 6px;
--sidebar-width: 280px;
--header-height: 68px;
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
color-scheme: light dark;
}
/* Tooltip safety layer */
.tooltip-floating {
z-index: 9999 !important;
}
/* ============================================
Theme: Dark Mode (Vista Galaxy Black Steel)
============================================ */
.dark,
html.dark {
--theme-bg-deep: #0a0a0e;
--theme-bg-surface: #14171c;
--theme-bg-surface-elevated: #191d22;
--theme-text-primary: #e5ecf5;
--theme-text-secondary: #b2c0d4;
--theme-text-tertiary: #8897ad;
--theme-accent: #a3b8c7;
--theme-accent-rgb: 163, 184, 199;
--theme-border: rgba(114, 124, 139, 0.18);
--theme-border-strong: rgba(114, 124, 139, 0.28);
--theme-hover: rgba(255, 255, 255, 0.03);
--theme-hover-strong: rgba(255, 255, 255, 0.06);
/* Scrollbar (Dark) */
--theme-scrollbar: rgba(255, 255, 255, 0.15);
--theme-scrollbar-hover: rgba(255, 255, 255, 0.25);
--scrollbar-bg: var(--theme-scrollbar, rgba(255, 255, 255, 0.15));
--scrollbar-bg-hover: var(--theme-scrollbar-hover, rgba(255, 255, 255, 0.25));
/* Gradients (Dark) */
--gradient-nav: linear-gradient(135deg, rgba(10,10,14,0.95) 0%, rgba(32,36,44,0.85) 40%, rgba(120,132,144,0.15) 100%);
--gradient-sidebar: linear-gradient(180deg, rgba(12,12,16,0.95) 0%, rgba(24,28,34,0.9) 100%);
--gradient-main: linear-gradient(180deg, rgba(12,12,16,0.95) 0%, rgba(16,18,22,0.9) 55%, rgba(22,24,30,0.85) 100%);
}
/* Auto Dark Mode based on system preference */
@media (prefers-color-scheme: dark) {
:root:not(.light) {
--theme-bg-deep: #0a0a0e;
--theme-bg-surface: #14171c;
--theme-bg-surface-elevated: #191d22;
--theme-text-primary: #e5ecf5;
--theme-text-secondary: #b2c0d4;
--theme-text-tertiary: #8897ad;
--theme-accent: #a3b8c7;
--theme-accent-rgb: 163, 184, 199;
--theme-border: rgba(114, 124, 139, 0.18);
--theme-border-strong: rgba(114, 124, 139, 0.28);
--theme-hover: rgba(255, 255, 255, 0.03);
--theme-hover-strong: rgba(255, 255, 255, 0.06);
--gradient-nav: linear-gradient(135deg, rgba(10,10,14,0.95) 0%, rgba(32,36,44,0.85) 40%, rgba(120,132,144,0.15) 100%);
--gradient-sidebar: linear-gradient(180deg, rgba(12,12,16,0.95) 0%, rgba(24,28,34,0.9) 100%);
--gradient-main: linear-gradient(180deg, rgba(12,12,16,0.95) 0%, rgba(16,18,22,0.9) 55%, rgba(22,24,30,0.85) 100%);
}
}
/* Reset & Base */
*, *::before, *::after { box-sizing: border-box; }
body {
font-family: var(--font-sans);
background: var(--theme-bg-deep);
color: var(--theme-text-primary);
line-height: 1.5;
margin: 0;
height: 100vh;
overflow: hidden;
font-size: 13px;
}
a { color: inherit; text-decoration: none; }
code, pre { font-family: var(--font-mono); }
/* Layout Shell */
.app-shell {
display: flex;
height: 100vh;
width: 100vw;
overflow: hidden;
background: var(--theme-bg-deep);
}
/* Sidebar */
.app-sidebar {
width: var(--sidebar-width);
background: var(--gradient-sidebar);
border-right: 1px solid var(--theme-border);
display: flex;
flex-direction: column;
flex-shrink: 0;
z-index: 20;
}
.sidebar-header {
height: var(--header-height);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
border-bottom: 1px solid var(--theme-border);
}
/* Theme Toggle Button */
.theme-toggle {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: var(--radius-sm);
background: var(--theme-hover);
border: 1px solid var(--theme-border);
color: var(--theme-text-secondary);
cursor: pointer;
transition: all 0.2s ease;
flex-shrink: 0;
}
.theme-toggle:hover {
background: var(--theme-hover-strong);
color: var(--theme-text-primary);
border-color: var(--theme-border-strong);
}
/* Test Toggle Button */
.test-toggle-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
width: 100%;
padding: 8px 12px;
border-radius: var(--radius-sm);
background: var(--theme-hover);
border: 1px solid var(--theme-border);
color: var(--theme-text-secondary);
cursor: pointer;
transition: all 0.2s ease;
font-size: 12px;
font-weight: 500;
}
.test-toggle-btn:hover {
background: var(--theme-hover-strong);
color: var(--theme-text-primary);
border-color: var(--theme-border-strong);
}
#test-toggle-icon {
font-size: 16px;
transition: opacity 0.2s ease;
}
/* Show sun icon in dark mode, moon icon in light mode */
.theme-icon-light { display: block; }
.theme-icon-dark { display: none; }
.dark .theme-icon-light,
html.dark .theme-icon-light { display: none; }
.dark .theme-icon-dark,
html.dark .theme-icon-dark { display: block; }
@media (prefers-color-scheme: dark) {
:root:not(.light) .theme-icon-light { display: none; }
:root:not(.light) .theme-icon-dark { display: block; }
}
.logo-box {
display: flex;
align-items: center;
gap: 10px;
font-weight: 600;
font-size: 14px;
color: var(--theme-text-primary);
letter-spacing: 0.02em;
}
.logo-img {
width: 28px;
height: 28px;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
}
.logo-text {
display: flex;
flex-direction: column;
line-height: 1.2;
}
.sidebar-nav {
flex: 1;
overflow-y: auto;
padding: 24px 16px;
display: flex;
flex-direction: column;
gap: 4px;
}
/* Nav items styled like tab buttons - unified design */
.nav-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 16px;
border-radius: var(--radius-lg);
color: var(--theme-text-secondary);
transition: all 0.2s ease;
font-size: 13px;
font-weight: 500;
border: none;
background: transparent;
cursor: pointer;
text-decoration: none;
/* Prevent label overflow */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100%;
}
.nav-item:hover {
background: var(--theme-hover-strong);
color: var(--theme-text-primary);
}
.nav-item.active {
background: var(--theme-bg-surface);
color: var(--theme-accent);
box-shadow: 0 1px 3px rgba(0,0,0,0.12);
}
.nav-section-title {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--theme-text-tertiary);
margin: 24px 14px 8px;
}
/* Main Area */
.app-main {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
background: var(--gradient-main);
min-width: 0; /* Prevent flex overflow */
}
/* Sticky Header */
.app-header {
height: var(--header-height);
flex-shrink: 0;
background: var(--gradient-nav);
border-bottom: 1px solid var(--theme-border);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 32px;
backdrop-filter: blur(12px);
z-index: 10;
}
.header-title h1 {
margin: 0;
font-size: 16px;
font-weight: 600;
color: var(--theme-text-primary);
}
.header-title p,
.header-path {
margin: 2px 0 0;
font-size: 11px;
color: var(--theme-text-tertiary);
font-family: var(--font-mono);
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Header Stats Badges */
.header-stats {
display: flex;
gap: 8px;
}
.stat-badge {
display: flex;
flex-direction: column;
align-items: center;
padding: 8px 14px;
background: var(--theme-bg-surface);
border: 1px solid var(--theme-border);
border-radius: var(--radius-md);
min-width: 60px;
}
.stat-badge-value {
font-size: 16px;
font-weight: 600;
color: var(--theme-accent);
font-family: var(--font-mono);
}
.stat-badge-label {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--theme-text-tertiary);
margin-top: 2px;
}
/* Tabs */
.header-tabs {
display: flex;
gap: 6px;
background: rgba(0,0,0,0.2);
padding: 4px;
border-radius: var(--radius-md);
border: 1px solid var(--theme-border);
}
.tab-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 16px;
border-radius: 8px;
font-size: 12px;
font-weight: 500;
color: var(--theme-text-secondary);
cursor: pointer;
transition: all 0.2s;
background: transparent;
border: none;
/* Prevent label overflow */
white-space: nowrap;
flex-shrink: 0;
}
.tab-btn:hover {
color: var(--theme-text-primary);
background: var(--theme-hover);
}
.tab-btn.active {
background: rgba(163, 184, 199, 0.15);
color: var(--theme-accent);
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
/* Content Scroll Area */
.app-content {
flex: 1;
overflow-y: auto;
padding: 32px;
scroll-behavior: smooth;
}
/* Content Panels */
.content-container {
max-width: 1100px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 24px;
}
.panel {
background: var(--theme-bg-surface);
border: 1px solid var(--theme-border);
border-radius: var(--radius-lg);
padding: 24px;
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
}
.panel h3 {
margin-top: 0;
font-size: 14px;
font-weight: 600;
color: var(--theme-text-primary);
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
}
/* Tables & Lists */
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.data-table th {
text-align: left;
color: var(--theme-text-tertiary);
font-weight: 500;
padding: 12px 16px;
border-bottom: 1px solid var(--theme-border);
}
.data-table td {
padding: 12px 16px;
border-bottom: 1px solid rgba(114, 124, 139, 0.08);
color: var(--theme-text-secondary);
}
.data-table tr:last-child td { border-bottom: none; }
.data-table tr:hover td { background: var(--theme-hover); }
code {
background: rgba(0,0,0,0.2);
padding: 2px 6px;
border-radius: 4px;
color: var(--theme-accent);
font-size: 0.9em;
}
/* Analysis Summary */
.analysis-summary {
margin-bottom: 24px;
}
.analysis-summary h3 {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
}
.summary-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 16px;
}
.summary-stat {
background: var(--theme-bg-surface);
border: 1px solid var(--theme-border);
border-radius: var(--radius-md);
padding: 16px;
text-align: center;
}
.stat-value {
display: block;
font-size: 28px;
font-weight: 600;
color: var(--theme-accent);
margin-bottom: 4px;
}
.stat-label {
display: block;
font-size: 12px;
color: var(--theme-text-tertiary);
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Command Coverage Summary */
.coverage-summary {
padding: 12px 16px;
background: var(--theme-bg-surface-elevated);
border: 1px solid var(--theme-border);
border-radius: var(--radius-md);
margin-bottom: 16px;
font-size: 13px;
}
.text-warning {
color: #e67e22;
}
.text-muted {
color: var(--theme-text-tertiary);
}
/* AI Insights */
.insight-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 12px;
}
.insight-item {
padding: 16px;
border-radius: var(--radius-md);
background: var(--theme-hover);
border: 1px solid var(--theme-border);
display: flex;
gap: 12px;
}
.insight-icon { flex-shrink: 0; margin-top: 2px; }
.insight-content strong { display: block; margin-bottom: 4px; color: var(--theme-text-primary); }
.insight-content p { margin: 0; color: var(--theme-text-secondary); line-height: 1.5; }
/* Graph */
.graph-wrapper {
width: 100%;
height: calc(100vh - var(--header-height) - 64px);
background: var(--theme-bg-deep);
border-radius: var(--radius-lg);
border: 1px solid var(--theme-border);
overflow: hidden;
position: relative;
}
#cy { width: 100%; height: 100%; }
/* Scrollbar - theme-aware */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--theme-scrollbar, rgba(114, 124, 139, 0.2)); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--theme-scrollbar-hover, rgba(114, 124, 139, 0.4)); }
/* Firefox scrollbar */
* {
scrollbar-width: thin;
scrollbar-color: var(--theme-scrollbar, rgba(114, 124, 139, 0.2)) transparent;
}
/* Footer */
.app-footer {
margin-top: auto;
padding: 24px 16px;
text-align: center;
color: var(--theme-text-tertiary);
font-size: 11px;
border-top: 1px solid var(--theme-border);
}
/* ============================================
Section & Tab Visibility (CRITICAL)
============================================ */
/* Section views - only show active */
.section-view {
display: none;
height: 100%;
flex-direction: column;
}
.section-view.active {
display: flex;
}
/* Tab panels - only show active */
.tab-panel {
display: none;
}
.tab-panel.active {
display: block;
}
/* Tab bar alias for JS selector */
.tab-bar {
/* Inherits from .header-tabs */
}
/* ============================================
Graph Container & Toolbars
============================================ */
/* ============================================
Graph Split Layout (Side-by-Side)
============================================ */
.graph-split-container {
display: flex;
height: calc(100vh - var(--header-height) - 32px);
gap: 0;
position: relative;
}
.graph-left-panel {
width: 380px;
min-width: 280px;
max-width: 600px;
display: flex;
flex-direction: column;
background: var(--theme-bg-surface);
border-right: 1px solid var(--theme-border);
overflow: hidden;
}
.graph-left-panel .component-panel {
flex: 1;
overflow-y: auto;
margin: 0;
border: none;
border-radius: 0;
}
.graph-left-panel .component-panel-header {
position: sticky;
top: 0;
z-index: 5;
padding: 8px 10px;
font-size: 11px;
}
/* Compact table for left panel */
.graph-left-panel .component-panel table {
font-size: 11px;
}
.graph-left-panel .component-panel th {
padding: 6px 8px;
font-size: 10px;
}
.graph-left-panel .component-panel td {
padding: 4px 8px;
}
.graph-left-panel .component-panel code {
font-size: 10px;
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
}
.graph-left-panel .component-toolbar {
padding: 6px 10px;
font-size: 11px;
flex-wrap: wrap;
gap: 6px;
}
.graph-left-panel .component-toolbar label {
font-size: 10px;
}
.graph-left-panel .component-toolbar button {
padding: 3px 6px;
font-size: 10px;
}
.graph-left-panel .panel-actions {
gap: 6px;
}
.graph-left-panel .panel-actions label {
font-size: 10px;
}
.graph-left-panel .panel-actions input {
padding: 2px 4px;
width: 50px !important;
}
.graph-right-panel {
flex: 1;
display: flex;
flex-direction: column;
min-width: 400px;
overflow: hidden;
}
.graph-right-panel .graph-toolbar {
flex-shrink: 0;
margin: 0;
border-radius: 0;
border-left: none;
border-right: none;
}
.graph-right-panel .graph {
flex: 1;
min-height: 0;
border-radius: 0;
border: none;
border-top: 1px solid var(--theme-border);
}
/* Resize handle */
.graph-resize-handle {
width: 6px;
cursor: col-resize;
background: var(--theme-border);
transition: background 0.15s;
flex-shrink: 0;
}
.graph-resize-handle:hover,
.graph-resize-handle.active {
background: var(--theme-accent);
}
/* Graph container - fallback for non-split */
.graph {
width: 100%;
height: calc(100vh - var(--header-height) - 200px);
min-height: 400px;
background: var(--theme-bg-deep);
border-radius: var(--radius-md);
border: 1px solid var(--theme-border);
}
.graph-empty {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
color: var(--theme-text-tertiary);
font-style: italic;
background: var(--theme-bg-surface);
border-radius: var(--radius-md);
border: 1px dashed var(--theme-border);
}
/* Graph toolbars */
.graph-toolbar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: var(--theme-bg-surface);
border: 1px solid var(--theme-border);
border-radius: var(--radius-md);
margin-bottom: 12px;
font-size: 12px;
}
.graph-toolbar label {
display: flex;
align-items: center;
gap: 6px;
color: var(--theme-text-secondary);
}
.graph-toolbar input[type="text"],
.graph-toolbar input[type="number"],
.graph-toolbar select {
background: var(--theme-bg-deep);
border: 1px solid var(--theme-border);
border-radius: var(--radius-sm);
padding: 4px 8px;
color: var(--theme-text-primary);
font-size: 12px;
font-family: var(--font-mono);
}
.graph-toolbar input[type="checkbox"] {
accent-color: var(--theme-accent);
}
.graph-toolbar button {
background: var(--theme-bg-surface-elevated);
border: 1px solid var(--theme-border);
border-radius: var(--radius-sm);
padding: 4px 10px;
color: var(--theme-text-secondary);
font-size: 11px;
cursor: pointer;
transition: all 0.15s ease;
}
.graph-toolbar button:hover {
background: rgba(163, 184, 199, 0.1);
color: var(--theme-text-primary);
border-color: var(--theme-accent);
}
.component-toolbar {
background: var(--theme-bg-surface-elevated);
}
.graph-controls {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-left: auto;
}
/* Graph legend */
.graph-legend {
display: flex;
gap: 16px;
padding: 8px 0;
font-size: 11px;
color: var(--theme-text-tertiary);
}
.graph-legend span {
display: flex;
align-items: center;
gap: 6px;
}
.legend-dot {
width: 10px;
height: 10px;
border-radius: 50%;
display: inline-block;
}
/* Graph hint */
.graph-hint {
padding: 12px 16px;
background: rgba(163, 184, 199, 0.05);
border: 1px solid var(--theme-border);
border-radius: var(--radius-md);
font-size: 12px;
color: var(--theme-text-tertiary);
margin-top: 12px;
}
/* ============================================
Component Panel (Disconnected Components)
============================================ */
.component-panel {
background: var(--theme-bg-surface);
border: 1px solid var(--theme-border);
border-radius: var(--radius-md);
margin-bottom: 12px;
overflow: hidden;
}
.component-panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background: var(--theme-bg-surface-elevated);
border-bottom: 1px solid var(--theme-border);
font-size: 13px;
}
.component-panel-header strong {
color: var(--theme-text-primary);
}
.panel-actions {
display: flex;
align-items: center;
gap: 12px;
}
.panel-actions label {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--theme-text-secondary);
}
.panel-actions input {
background: var(--theme-bg-deep);
border: 1px solid var(--theme-border);
border-radius: var(--radius-sm);
padding: 4px 8px;
color: var(--theme-text-primary);
font-size: 12px;
}
.panel-actions button {
background: var(--theme-bg-deep);
border: 1px solid var(--theme-border);
border-radius: var(--radius-sm);
padding: 4px 10px;
color: var(--theme-text-secondary);
font-size: 11px;
cursor: pointer;
}
.panel-actions button:hover {
border-color: var(--theme-accent);
color: var(--theme-text-primary);
}
.component-panel table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
}
.component-panel th {
text-align: left;
padding: 10px 16px;
color: var(--theme-text-tertiary);
font-weight: 500;
border-bottom: 1px solid var(--theme-border);
background: var(--theme-bg-surface);
}
.component-panel td {
padding: 10px 16px;
color: var(--theme-text-secondary);
border-bottom: 1px solid rgba(114, 124, 139, 0.08);
}
.component-panel tr:hover td {
background: var(--theme-hover);
}
.component-panel button {
background: transparent;
border: 1px solid var(--theme-border);
border-radius: var(--radius-sm);
padding: 3px 8px;
color: var(--theme-text-tertiary);
font-size: 10px;
cursor: pointer;
}
.component-panel button:hover {
border-color: var(--theme-accent);
color: var(--theme-accent);
}
/* ============================================
Tauri Command Tables
============================================ */
.command-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
margin-top: 16px;
}
.command-table th {
text-align: left;
padding: 12px 16px;
color: var(--theme-text-tertiary);
font-weight: 500;
border-bottom: 1px solid var(--theme-border);
}
.command-table td {
padding: 12px 16px;
border-bottom: 1px solid rgba(114, 124, 139, 0.08);
color: var(--theme-text-secondary);
}
.command-pill {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-family: var(--font-mono);
font-size: 12px;
background: rgba(163, 184, 199, 0.1);
color: var(--theme-accent);
}
/* Module grouping */
.module-group {
margin-bottom: 24px;
}
.module-header {
font-size: 13px;
font-weight: 500;
color: var(--theme-text-secondary);
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid var(--theme-border);
}
/* FE↔BE Bridge Comparison Table */
.bridge-table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
margin-top: 16px;
background: var(--theme-bg-surface);
border: 1px solid var(--theme-border);
border-radius: var(--radius-md);
overflow: hidden;
}
.bridge-table thead th {
text-align: left;
padding: 10px 14px;
color: var(--theme-text-tertiary);
font-weight: 500;
background: var(--theme-bg-surface-elevated);
border-bottom: 1px solid var(--theme-border);
}
.bridge-table tbody td {
padding: 8px 14px;
border-bottom: 1px solid rgba(114, 124, 139, 0.08);
color: var(--theme-text-secondary);
vertical-align: top;
}
.bridge-table tbody tr:last-child td {
border-bottom: none;
}
.bridge-table tbody tr:hover td {
background: var(--theme-hover);
}
.bridge-table .status-cell {
font-weight: 500;
white-space: nowrap;
}
.bridge-table .loc-cell {
font-family: var(--font-mono);
font-size: 11px;
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
}
.bridge-table .loc-cell a {
color: var(--theme-accent);
}
.bridge-table .loc-cell a:hover {
text-decoration: underline;
}
/* Bridge row status colors */
.bridge-table tr.status-ok .status-cell {
color: #27ae60;
}
.bridge-table tr.status-missing .status-cell {
color: #e67e22;
}
.bridge-table tr.status-unused .status-cell {
color: var(--theme-text-tertiary);
}
.bridge-table tr.status-unregistered .status-cell {
color: #c0392b;
}
.bridge-table tr.status-missing {
background: rgba(230, 126, 34, 0.05);
}
.bridge-table tr.status-unregistered {
background: rgba(192, 57, 43, 0.05);
}
/* Gap details toggle */
.gap-details {
margin-top: 24px;
}
.gap-details summary {
cursor: pointer;
color: var(--theme-text-tertiary);
font-size: 12px;
padding: 8px 0;
}
.gap-details summary:hover {
color: var(--theme-text-secondary);
}
/* Text success color */
.text-success {
color: #27ae60;
}
/* ============================================
Utility Classes
============================================ */
.muted {
color: var(--theme-text-tertiary);
}
.icon-sm {
width: 16px;
height: 16px;
flex-shrink: 0;
}
/* Range slider styling */
input[type="range"] {
-webkit-appearance: none;
background: var(--theme-bg-deep);
border-radius: 4px;
height: 6px;
cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 14px;
height: 14px;
background: var(--theme-accent);
border-radius: 50%;
cursor: pointer;
}
/* ============================================
Quick Commands Panel (v0.6 features)
============================================ */
.quick-commands-panel {
background: var(--theme-bg-surface);
border: 1px solid var(--theme-border);
border-radius: var(--radius-lg);
padding: 20px 24px;
margin-top: 8px;
}
.quick-commands-panel h3 {
display: flex;
align-items: center;
gap: 10px;
margin: 0 0 16px 0;
font-size: 14px;
font-weight: 600;
color: var(--theme-text-primary);
}
.badge-new {
font-size: 9px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
background: linear-gradient(135deg, rgba(163, 184, 199, 0.2) 0%, rgba(79, 129, 225, 0.2) 100%);
color: var(--theme-accent);
padding: 3px 8px;
border-radius: 6px;
border: 1px solid rgba(163, 184, 199, 0.3);
}
.commands-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 16px;
}
.command-group {
background: var(--theme-bg-surface-elevated);
border: 1px solid var(--theme-border);
border-radius: var(--radius-md);
padding: 16px;
}
.command-group.highlight {
border-color: rgba(163, 184, 199, 0.3);
background: linear-gradient(135deg, var(--theme-bg-surface-elevated) 0%, rgba(163, 184, 199, 0.05) 100%);
}
.command-group h4 {
margin: 0 0 6px 0;
font-size: 13px;
font-weight: 600;
color: var(--theme-text-primary);
}
.command-group .command-desc {
margin: 0 0 12px 0;
font-size: 11px;
color: var(--theme-text-tertiary);
}
.command-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.command-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 10px;
background: var(--theme-bg-deep);
border: 1px solid var(--theme-border);
border-radius: var(--radius-sm);
font-size: 11px;
}
.command-item:hover {
border-color: var(--theme-border-strong);
}
.command-code {
flex: 1;
font-family: var(--font-mono);
font-size: 11px;
color: var(--theme-accent);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
background: transparent;
padding: 0;
}
.command-desc-inline {
font-size: 10px;
color: var(--theme-text-tertiary);
white-space: nowrap;
}
.copy-btn {
flex-shrink: 0;
background: transparent;
border: none;
padding: 2px 4px;
cursor: pointer;
font-size: 12px;
opacity: 0.6;
transition: opacity 0.15s;
}
.copy-btn:hover {
opacity: 1;
}
.commands-footer {
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid var(--theme-border);
}
.commands-footer p {
margin: 0;
font-size: 11px;
color: var(--theme-text-tertiary);
}
.commands-footer code {
font-size: 10px;
background: var(--theme-bg-deep);
padding: 2px 6px;
border-radius: 4px;
color: var(--theme-accent);
}
/* ============================================
Tree Component Styles
============================================ */
.tree-panel {
display: flex;
flex-direction: column;
gap: 12px;
}
.tree-header {
display: flex;
align-items: center;
gap: 12px;
}
.tree-header h3 {
margin: 0;
white-space: nowrap;
}
.tree-stats {
font-size: 13px;
color: var(--theme-text-muted);
padding: 4px 10px;
background: var(--theme-surface);
border-radius: 12px;
border: 1px solid var(--theme-border);
cursor: help;
}
.tree-controls {
display: flex;
gap: 4px;
}
.tree-btn {
padding: 6px 10px;
border: 1px solid var(--theme-border);
border-radius: 6px;
background: var(--theme-surface);
color: var(--theme-text);
cursor: pointer;
font-size: 14px;
transition: all 0.15s ease;
}
.tree-btn:hover {
background: var(--theme-bg-surface-elevated);
border-color: var(--theme-border-strong);
}
.tree-filter {
flex: 1;
padding: 8px 12px;
border: 1px solid var(--theme-border);
border-radius: 8px;
background: var(--theme-surface);
color: var(--theme-text);
font-size: 13px;
}
.tree-filter:focus {
outline: none;
border-color: var(--theme-accent);
}
.tree-container {
max-height: calc(100vh - 280px);
min-height: 400px;
overflow-y: auto;
padding-right: 8px;
}
.tree-node {
font-family: "JetBrains Mono", "SFMono-Regular", monospace;
font-size: 12px;
}
.tree-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px 8px;
border-radius: 4px;
cursor: default;
transition: background 0.1s ease;
}
.tree-row:hover {
background: var(--theme-bg-surface-elevated);
}
.tree-row-dir {
cursor: pointer;
}
.tree-row-dir:hover {
background: rgba(var(--theme-accent-rgb), 0.1);
}
.tree-left {
display: flex;
align-items: center;
gap: 4px;
min-width: 0;
flex: 1;
}
.tree-connector {
color: var(--theme-text-tertiary);
white-space: pre;
flex-shrink: 0;
}
.tree-chevron {
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
font-size: 10px;
color: var(--theme-text-secondary);
transition: transform 0.15s ease;
flex-shrink: 0;
}
.tree-chevron.collapsed {
transform: rotate(0deg);
}
.tree-chevron:not(.collapsed) {
transform: rotate(90deg);
}
.tree-icon {
flex-shrink: 0;
font-size: 14px;
}
.tree-path {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--theme-text);
}
.tree-highlight {
background: rgba(255, 200, 0, 0.3);
color: inherit;
padding: 0 2px;
border-radius: 2px;
}
.tree-right {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
margin-left: 12px;
}
.tree-loc-bar {
width: 60px;
height: 4px;
background: var(--theme-border);
border-radius: 2px;
overflow: hidden;
}
.tree-loc-fill {
height: 100%;
background: var(--theme-accent);
border-radius: 2px;
transition: width 0.2s ease;
}
.tree-loc {
color: var(--theme-text-tertiary);
font-size: 11px;
min-width: 60px;
text-align: right;
}
.tree-children {
overflow: hidden;
transition: max-height 0.2s ease, opacity 0.15s ease;
}
.tree-children.collapsed {
max-height: 0 !important;
opacity: 0;
pointer-events: none;
}
/* ============================================
Crowds Component Styles
============================================ */
.crowds-list {
display: flex;
flex-direction: column;
gap: 20px;
margin-top: 16px;
}
.crowd-card {
background: var(--theme-bg-surface-elevated);
border: 1px solid var(--theme-border);
border-radius: var(--radius-lg);
padding: 20px;
transition: border-color 0.2s ease;
}
.crowd-card:hover {
border-color: var(--theme-border-strong);
}
.crowd-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid var(--theme-border);
}
.crowd-pattern {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
}
.crowd-pattern code {
font-size: 14px;
font-weight: 600;
color: var(--theme-accent);
background: rgba(163, 184, 199, 0.1);
padding: 6px 12px;
border-radius: var(--radius-md);
}
.crowd-member-count {
font-size: 12px;
}
.crowd-score {
display: flex;
flex-direction: column;
align-items: center;
padding: 8px 16px;
background: var(--theme-bg-deep);
border-radius: var(--radius-md);
border: 2px solid var(--score-color, var(--theme-border));
min-width: 80px;
}
.score-value {
font-size: 24px;
font-weight: 700;
font-family: var(--font-mono);
color: var(--score-color, var(--theme-text-primary));
line-height: 1;
}
.score-label {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--theme-text-tertiary);
margin-top: 4px;
}
.crowd-issues {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
}
.issue-badge {
display: inline-block;
padding: 6px 12px;
border-radius: var(--radius-sm);
font-size: 11px;
font-weight: 500;
border: 1px solid;
}
.issue-critical {
background: rgba(192, 57, 43, 0.1);
border-color: rgba(192, 57, 43, 0.3);
color: #c0392b;
}
.issue-warning {
background: rgba(230, 126, 34, 0.1);
border-color: rgba(230, 126, 34, 0.3);
color: #e67e22;
}
.issue-info {
background: rgba(49, 130, 206, 0.1);
border-color: rgba(49, 130, 206, 0.3);
color: #3182ce;
}
.crowd-members {
margin-top: 12px;
}
.crowd-members .data-table {
font-size: 12px;
}
.crowd-members .data-table th {
padding: 8px 12px;
font-size: 11px;
}
.crowd-members .data-table td {
padding: 8px 12px;
}
.file-path {
max-width: 400px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
}
/* ============================================
Dead Code Component Styles
============================================ */
.dead-code-summary {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background: var(--theme-bg-surface-elevated);
border: 1px solid var(--theme-border);
border-radius: var(--radius-md);
margin-bottom: 16px;
}
.filter-toggle {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: var(--theme-text-secondary);
cursor: pointer;
}
.filter-toggle input[type="checkbox"] {
accent-color: var(--theme-accent);
cursor: pointer;
}
.dead-exports-table {
font-size: 13px;
}
.dead-exports-table .file-cell code,
.dead-exports-table .symbol-cell code {
font-family: var(--font-mono);
font-size: 12px;
}
.dead-exports-table .file-cell a {
color: var(--theme-accent);
text-decoration: none;
}
.dead-exports-table .file-cell a:hover {
text-decoration: underline;
}
.dead-exports-table .line-cell {
font-family: var(--font-mono);
font-size: 11px;
text-align: center;
color: var(--theme-text-tertiary);
}
.dead-exports-table .confidence-cell {
text-align: center;
}
.confidence-badge {
display: inline-block;
padding: 4px 10px;
border-radius: var(--radius-sm);
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.confidence-badge.confidence-very-high {
background: rgba(192, 57, 43, 0.15);
color: #c0392b;
border: 1px solid rgba(192, 57, 43, 0.3);
}
.confidence-badge.confidence-high {
background: rgba(230, 126, 34, 0.15);
color: #e67e22;
border: 1px solid rgba(230, 126, 34, 0.3);
}
.confidence-badge.confidence-medium {
background: rgba(49, 130, 206, 0.15);
color: #3182ce;
border: 1px solid rgba(49, 130, 206, 0.3);
}
.dead-exports-table .reason-cell {
font-size: 12px;
max-width: 300px;
color: var(--theme-text-secondary);
}
.dead-exports-table tr.confidence-very-high {
background: rgba(192, 57, 43, 0.03);
}
.dead-exports-table tr.confidence-high {
background: rgba(230, 126, 34, 0.03);
}
.dead-exports-table tr:hover {
background: var(--theme-hover-strong) !important;
}
/* ============================================
Cycles Component
============================================ */
/* Count badges */
.count-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 24px;
height: 20px;
padding: 0 8px;
border-radius: 10px;
font-size: 11px;
font-weight: 600;
font-family: var(--font-mono);
margin-left: auto;
}
.count-badge-success {
background: rgba(39, 174, 96, 0.15);
color: #27ae60;
border: 1px solid rgba(39, 174, 96, 0.3);
}
.count-badge-warning {
background: rgba(230, 126, 34, 0.15);
color: #e67e22;
border: 1px solid rgba(230, 126, 34, 0.3);
}
.count-badge-critical {
background: rgba(192, 57, 43, 0.15);
color: #c0392b;
border: 1px solid rgba(192, 57, 43, 0.3);
}
/* Empty state */
.cycles-empty {
padding: 32px;
text-align: center;
background: rgba(39, 174, 96, 0.05);
border-radius: var(--radius-md);
border: 1px dashed rgba(39, 174, 96, 0.3);
}
.cycles-empty p {
color: #27ae60;
font-size: 13px;
margin: 0;
}
/* Cycles section */
.cycles-section {
margin-bottom: 24px;
padding: 20px;
border-radius: var(--radius-md);
border: 1px solid var(--theme-border);
}
.cycles-section-strict {
background: rgba(192, 57, 43, 0.05);
border-color: rgba(192, 57, 43, 0.2);
}
.cycles-section-lazy {
background: rgba(230, 126, 34, 0.05);
border-color: rgba(230, 126, 34, 0.2);
}
.cycles-section-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
}
.cycles-section-header h4 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: var(--theme-text-primary);
}
.cycles-section-desc {
font-size: 12px;
color: var(--theme-text-secondary);
margin: 0 0 16px 0;
padding-left: 30px;
}
/* Cycles list */
.cycles-list {
display: flex;
flex-direction: column;
gap: 8px;
}
/* Individual cycle item */
.cycle-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: var(--theme-bg-surface);
border-radius: var(--radius-md);
border: 1px solid var(--theme-border);
}
.cycle-item-strict {
border-left: 3px solid #c0392b;
}
.cycle-item-lazy {
border-left: 3px solid #e67e22;
}
.cycle-number {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 32px;
height: 24px;
padding: 0 8px;
background: var(--theme-hover);
border: 1px solid var(--theme-border);
border-radius: 6px;
font-size: 11px;
font-weight: 600;
font-family: var(--font-mono);
color: var(--theme-text-tertiary);
flex-shrink: 0;
}
.cycle-path {
flex: 1;
font-family: var(--font-mono);
font-size: 12px;
color: var(--theme-text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
background: rgba(0, 0, 0, 0.2);
padding: 4px 8px;
border-radius: 4px;
}
/* ============================================
Pipeline Component Styles
============================================ */
.pipelines-panel {
max-width: 100%;
}
.pipelines-summary {
margin-bottom: 16px;
}
.pipeline-stats {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.stat-chip {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
}
.stat-total {
background: var(--theme-bg-surface-elevated);
color: var(--theme-text-secondary);
border: 1px solid var(--theme-border);
}
.stat-ok {
background: rgba(39, 174, 96, 0.15);
color: #27ae60;
}
.stat-missing {
background: rgba(231, 76, 60, 0.15);
color: #e74c3c;
}
.stat-unused {
background: rgba(149, 165, 166, 0.2);
color: #95a5a6;
}
.stat-unreg {
background: rgba(230, 126, 34, 0.15);
color: #e67e22;
}
.pipelines-filters {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 20px;
padding: 12px 16px;
background: var(--theme-bg-surface);
border: 1px solid var(--theme-border);
border-radius: var(--radius-md);
}
.filter-buttons {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.filter-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
border: 1px solid var(--theme-border);
border-radius: var(--radius-sm);
background: var(--theme-bg-surface);
color: var(--theme-text-secondary);
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
}
.filter-btn:hover {
background: var(--theme-bg-surface-elevated);
border-color: var(--theme-primary);
}
.filter-btn.active {
background: var(--theme-primary);
border-color: var(--theme-primary);
color: white;
}
.filter-count {
padding: 2px 6px;
background: rgba(255, 255, 255, 0.2);
border-radius: 8px;
font-size: 10px;
}
.filter-btn:not(.active) .filter-count {
background: rgba(0, 0, 0, 0.1);
}
.search-box {
flex: 0 0 auto;
}
.search-input {
padding: 8px 12px;
border: 1px solid var(--theme-border);
border-radius: var(--radius-sm);
background: var(--theme-bg-surface);
color: var(--theme-text-primary);
font-size: 13px;
min-width: 200px;
}
.search-input:focus {
outline: none;
border-color: var(--theme-primary);
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
}
.search-input::placeholder {
color: var(--theme-text-tertiary);
}
.no-results {
text-align: center;
padding: 32px;
}
.cards-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 16px;
}
.pipeline-card {
background: var(--theme-bg-surface);
border: 1px solid var(--theme-border);
border-radius: var(--radius-lg);
overflow: hidden;
transition: all 0.2s ease;
}
.pipeline-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.pipeline-card.status-ok {
border-left: 3px solid #27ae60;
}
.pipeline-card.status-missing {
border-left: 3px solid #e74c3c;
}
.pipeline-card.status-unused {
border-left: 3px solid #95a5a6;
}
.pipeline-card.status-unreg {
border-left: 3px solid #e67e22;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
cursor: pointer;
background: var(--theme-bg-surface-elevated);
}
.card-header:hover {
background: var(--theme-bg-hover);
}
.card-title {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.command-name {
font-family: var(--font-mono);
font-size: 14px;
font-weight: 600;
color: var(--theme-text-primary);
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 8px;
border-radius: 10px;
font-size: 11px;
font-weight: 500;
}
.status-badge.status-ok {
background: rgba(39, 174, 96, 0.15);
color: #27ae60;
}
.status-badge.status-missing {
background: rgba(231, 76, 60, 0.15);
color: #e74c3c;
}
.status-badge.status-unused {
background: rgba(149, 165, 166, 0.2);
color: #7f8c8d;
}
.status-badge.status-unreg {
background: rgba(230, 126, 34, 0.15);
color: #e67e22;
}
.expand-icon {
color: var(--theme-text-tertiary);
font-size: 12px;
transition: transform 0.2s ease;
}
/* Chain Visualization */
.chain-viz {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 16px;
background: var(--theme-bg-surface);
}
.chain-node {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 8px 16px;
border-radius: var(--radius-md);
min-width: 80px;
transition: all 0.2s ease;
}
.chain-node.active {
background: var(--theme-bg-surface-elevated);
border: 1px solid var(--theme-border);
}
.chain-node.inactive {
background: rgba(0, 0, 0, 0.05);
border: 1px dashed var(--theme-border);
opacity: 0.6;
}
.chain-node.fe.active {
border-color: #3498db;
background: rgba(52, 152, 219, 0.1);
}
.chain-node.be.active {
border-color: #9b59b6;
background: rgba(155, 89, 182, 0.1);
}
.node-icon {
font-size: 12px;
font-weight: 700;
padding: 4px 8px;
border-radius: 4px;
background: rgba(0, 0, 0, 0.1);
}
.chain-node.fe .node-icon {
background: #3498db;
color: white;
}
.chain-node.be .node-icon {
background: #9b59b6;
color: white;
}
.chain-node.inactive .node-icon {
background: var(--theme-text-tertiary);
color: white;
}
.node-label {
font-size: 11px;
color: var(--theme-text-secondary);
text-align: center;
}
.chain-arrow {
display: flex;
align-items: center;
color: var(--theme-border);
font-size: 14px;
}
.chain-arrow.active {
color: var(--theme-primary);
}
.arrow-line {
display: block;
width: 20px;
height: 2px;
background: currentColor;
}
.arrow-head {
font-weight: bold;
}
/* Card Details (Expanded) */
.card-details {
padding: 16px;
border-top: 1px solid var(--theme-border);
background: var(--theme-bg-surface);
}
.detail-section {
margin-bottom: 16px;
}
.detail-section:last-child {
margin-bottom: 0;
}
.detail-section h4 {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
font-weight: 600;
color: var(--theme-text-secondary);
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.location-list {
list-style: none;
padding: 0;
margin: 0;
}
.location-list li {
padding: 6px 10px;
background: var(--theme-bg-surface-elevated);
border-radius: var(--radius-sm);
margin-bottom: 4px;
font-size: 12px;
display: flex;
align-items: center;
gap: 4px;
}
.location-list .file-path {
font-family: var(--font-mono);
color: var(--theme-primary);
}
.location-list .line-num {
color: var(--theme-text-tertiary);
font-family: var(--font-mono);
}
.location-list .impl-name {
color: var(--theme-text-tertiary);
font-size: 11px;
}
.card-details .warning {
color: #e67e22;
font-size: 12px;
}
/* ============================================
Split Panel View (FE/BE Side-by-Side)
============================================ */
.split-panel-container {
display: grid;
grid-template-columns: 1fr 80px 1fr;
gap: 0;
min-height: 400px;
margin-top: 16px;
}
.split-panel {
background: var(--theme-bg-surface);
border-radius: var(--radius-md);
padding: 16px;
overflow-y: auto;
max-height: 600px;
border: 1px solid var(--theme-border);
}
.split-panel h4 {
margin: 0 0 12px 0;
font-size: 14px;
color: var(--theme-text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.panel-items {
display: flex;
flex-direction: column;
gap: 8px;
}
.split-item {
background: var(--theme-bg-surface-elevated);
border: 1px solid var(--theme-border);
border-radius: var(--radius-sm);
padding: 10px 12px;
cursor: pointer;
transition: border-color 0.2s;
}
.split-item:hover {
border-color: var(--theme-accent);
}
.split-item.status-ok {
border-left: 3px solid #27ae60;
}
.split-item.status-missing {
border-left: 3px solid #e74c3c;
}
.split-item.status-unused {
border-left: 3px solid #95a5a6;
}
.split-item-name {
font-family: var(--font-mono);
font-size: 13px;
font-weight: 600;
color: var(--theme-text-primary);
}
.split-item-location {
font-size: 11px;
color: var(--theme-text-tertiary);
margin-top: 4px;
}
.connection-svg {
width: 80px;
height: 100%;
min-height: 400px;
}
.connection-line {
stroke: var(--theme-accent);
stroke-width: 2;
fill: none;
}
.connection-line.missing {
stroke: #e74c3c;
stroke-dasharray: 4;
}
/* Split panel specific styles */
.split-panel-fe {
border-right: none;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.split-panel-be {
border-left: none;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.split-panel-connections {
display: flex;
align-items: stretch;
background: var(--theme-bg-surface);
border-top: 1px solid var(--theme-border);
border-bottom: 1px solid var(--theme-border);
}
.split-panel h4 {
display: flex;
align-items: center;
gap: 8px;
padding-bottom: 12px;
border-bottom: 1px solid var(--theme-border);
}
.split-item-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.split-item-status {
display: flex;
align-items: center;
}
.split-item-status.status-ok {
color: #27ae60;
}
.split-item-status.status-missing {
color: #e74c3c;
}
.split-item-status.status-unused {
color: #95a5a6;
}
.split-item-status.status-unreg {
color: #e67e22;
}
.split-item.status-unreg {
border-left: 3px solid #e67e22;
}
.split-item-placeholder {
background: rgba(231, 76, 60, 0.05);
border-style: dashed;
}
.split-item-placeholder .split-item-name {
display: flex;
align-items: center;
gap: 6px;
color: #e74c3c;
}
.split-item-location a {
color: var(--theme-accent);
}
.split-item-location a:hover {
text-decoration: underline;
}
/* View toggle buttons */
.view-toggle {
display: flex;
gap: 4px;
margin-left: auto;
}
.view-btn {
padding: 6px 10px;
border: 1px solid var(--theme-border);
background: var(--theme-bg-surface);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 14px;
color: var(--theme-text-secondary);
transition: all 0.15s ease;
}
.view-btn:hover {
background: var(--theme-bg-surface-elevated);
border-color: var(--theme-border-strong);
}
.view-btn.active {
background: var(--theme-accent);
color: white;
border-color: var(--theme-accent);
}
/* Communication type badge */
.comm-badge {
font-size: 11px;
padding: 2px 6px;
border-radius: 3px;
margin-left: 8px;
}
.comm-badge.comm-emit {
background: rgba(155, 89, 182, 0.2);
color: #9b59b6;
}
.comm-badge.comm-invoke {
background: rgba(52, 152, 219, 0.2);
color: #3498db;
}
/* ============================================
Health Score Gauge
============================================ */
/* Overview Hero - Health Gauge + Summary side by side */
.overview-hero {
display: flex;
align-items: flex-start;
gap: 32px;
padding: 24px;
background: var(--theme-bg-surface);
border: 1px solid var(--theme-border);
border-radius: var(--radius-lg);
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
}
.overview-hero > .health-gauge {
flex-shrink: 0;
}
.overview-summary-wrapper {
flex: 1;
min-width: 0;
}
.overview-summary-wrapper .analysis-summary {
margin-bottom: 0;
}
@media (max-width: 768px) {
.overview-hero {
flex-direction: column;
align-items: center;
gap: 20px;
}
.overview-summary-wrapper {
width: 100%;
}
}
.health-gauge {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 16px;
}
.gauge-svg {
display: block;
}
.gauge-progress {
transition: stroke-dashoffset 0.6s ease-out;
}
.gauge-status {
font-size: 13px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Compact inline health indicator */
.health-indicator {
display: inline-flex;
align-items: center;
gap: 4px;
font-family: var(--font-mono);
font-size: 12px;
}
/* ============================================
Audit Panel Component
============================================ */
.audit-panel {
background: var(--theme-bg-surface);
border: 1px solid var(--theme-border);
border-radius: var(--radius-lg);
padding: 24px;
}
.audit-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid var(--theme-border);
}
.audit-header h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: var(--theme-text-primary);
}
.health-badge {
display: flex;
align-items: baseline;
gap: 2px;
padding: 8px 16px;
border-radius: var(--radius-md);
font-family: var(--font-mono);
font-weight: 700;
border: 2px solid;
}
.health-badge.critical {
background: rgba(192, 57, 43, 0.1);
border-color: rgba(192, 57, 43, 0.5);
color: #c0392b;
}
.health-badge.warning {
background: rgba(230, 126, 34, 0.1);
border-color: rgba(230, 126, 34, 0.5);
color: #e67e22;
}
.health-badge.good {
background: rgba(39, 174, 96, 0.1);
border-color: rgba(39, 174, 96, 0.5);
color: #27ae60;
}
.health-value {
font-size: 24px;
}
.health-max {
font-size: 14px;
opacity: 0.6;
}
/* Audit sections */
.audit-section {
margin-bottom: 24px;
padding: 16px;
border-radius: var(--radius-md);
border: 1px solid var(--theme-border);
}
.audit-section:last-of-type {
margin-bottom: 16px;
}
.audit-critical {
background: rgba(192, 57, 43, 0.05);
border-color: rgba(192, 57, 43, 0.2);
}
.audit-warning {
background: rgba(230, 126, 34, 0.05);
border-color: rgba(230, 126, 34, 0.2);
}
.audit-quick-wins {
background: rgba(39, 174, 96, 0.05);
border-color: rgba(39, 174, 96, 0.2);
}
.audit-section-title {
display: flex;
align-items: center;
gap: 8px;
margin: 0 0 8px 0;
font-size: 14px;
font-weight: 600;
color: var(--theme-text-primary);
}
.audit-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border-radius: 4px;
font-size: 11px;
font-weight: 700;
font-family: var(--font-mono);
}
.audit-critical .audit-icon {
background: rgba(192, 57, 43, 0.2);
color: #c0392b;
}
.audit-warning .audit-icon {
background: rgba(230, 126, 34, 0.2);
color: #e67e22;
}
.audit-quick-wins .audit-icon {
background: rgba(39, 174, 96, 0.2);
color: #27ae60;
}
.audit-section-desc {
margin: 0 0 12px 0;
font-size: 12px;
color: var(--theme-text-tertiary);
}
/* Audit list */
.audit-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.audit-item {
padding: 8px 12px;
background: var(--theme-bg-surface);
border-radius: var(--radius-sm);
border: 1px solid var(--theme-border);
font-size: 13px;
transition: background 0.15s ease;
}
.audit-item:hover {
background: var(--theme-bg-surface-elevated);
}
.audit-checkbox-label {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
}
.audit-checkbox {
flex-shrink: 0;
width: 16px;
height: 16px;
accent-color: var(--theme-accent);
cursor: pointer;
}
.audit-checkbox:checked + .audit-symbol,
.audit-checkbox:checked + .audit-cycle,
.audit-checkbox:checked ~ span {
text-decoration: line-through;
opacity: 0.5;
}
.audit-symbol {
font-family: var(--font-mono);
font-size: 12px;
color: var(--theme-accent);
background: rgba(163, 184, 199, 0.1);
padding: 2px 6px;
border-radius: 4px;
}
.audit-cycle {
font-family: var(--font-mono);
font-size: 11px;
color: var(--theme-text-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 500px;
}
.audit-location {
font-size: 11px;
color: var(--theme-text-tertiary);
font-family: var(--font-mono);
}
.audit-category {
background: transparent;
border: none;
padding: 4px 0;
font-weight: 600;
}
.audit-category-icon {
color: var(--theme-text-secondary);
}
.audit-count {
font-family: var(--font-mono);
color: var(--theme-accent);
}
.audit-sub-item {
margin-left: 20px;
}
.audit-more {
font-style: italic;
color: var(--theme-text-tertiary);
background: transparent;
border: none;
}
/* Quick win categories */
.audit-category-cleanup {
color: var(--theme-text-secondary);
}
.audit-category-refactor {
color: #3498db;
}
.audit-category-optimize {
color: #9b59b6;
}
.audit-category-test {
color: #e67e22;
}
/* Empty state */
.audit-empty {
padding: 32px;
text-align: center;
background: rgba(39, 174, 96, 0.05);
border-radius: var(--radius-md);
border: 1px dashed rgba(39, 174, 96, 0.3);
}
.audit-empty p {
margin: 0;
color: #27ae60;
font-size: 14px;
}
/* Footer */
.audit-footer {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid var(--theme-border);
}
.audit-tip {
margin: 0;
font-size: 12px;
color: var(--theme-text-tertiary);
}
.audit-tip code {
font-size: 11px;
background: var(--theme-bg-deep);
padding: 2px 6px;
border-radius: 4px;
color: var(--theme-accent);
}
/* ============================================
Responsive
============================================ */
@media (max-width: 900px) {
.app-shell {
flex-direction: column;
}
.app-sidebar {
width: 100%;
height: auto;
border-right: none;
border-bottom: 1px solid var(--theme-border);
}
.sidebar-nav {
flex-direction: row;
overflow-x: auto;
padding: 12px;
}
.nav-section-title {
display: none;
}
.app-footer {
display: none;
}
.header-tabs {
flex-wrap: wrap;
}
.graph-toolbar {
flex-direction: column;
align-items: stretch;
}
.graph-controls {
margin-left: 0;
justify-content: center;
}
}
</style></head><body><div class="app-shell"><aside class="app-sidebar"><div class="sidebar-header"><div class="logo-box"><img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzYwIiBoZWlnaHQ9IjM2MCIgdmlld0JveD0iMCAwIDM2MCAzNjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgcm9sZT0iaW1nIiBhcmlhLWxhYmVsbGVkYnk9InRpdGxlIGRlc2MiPgogIDx0aXRsZSBpZD0idGl0bGUiPkxvY3RyZWUgTG9nbzwvdGl0bGU+CiAgPGRlc2MgaWQ9ImRlc2MiPk1pbmltYWxpc3Qgbm9kZSB0cmVlIC0gZHluYW1pYyBhbmQgc2xpZ2h0bHkgdW5zZXR0bGluZzwvZGVzYz4KCiAgPGRlZnM+CiAgICA8c3R5bGU+CiAgICAgIC5ub2RlIHsgZmlsbDogI2UwZTBlMDsgfQogICAgICAuc3RlbSB7IHN0cm9rZTogI2UwZTBlMDsgc3Ryb2tlLXdpZHRoOiAxMDsgc3Ryb2tlLWxpbmVjYXA6IHJvdW5kOyB9CiAgICA8L3N0eWxlPgogIDwvZGVmcz4KCiAgPCEtLSBSb3cgMSAtIDMgbm9kZXMgKHRvcCwgc3ltbWV0cmljLCB0aWdodGVuZWQgMjBweCkgLS0+CiAgPGNpcmNsZSBjbGFzcz0ibm9kZSIgY3g9Ijc1IiBjeT0iNTAiIHI9IjE2Ii8+CiAgPGNpcmNsZSBjbGFzcz0ibm9kZSIgY3g9IjE4MCIgY3k9IjUwIiByPSIxNiIvPgogIDxjaXJjbGUgY2xhc3M9Im5vZGUiIGN4PSIyODUiIGN5PSI1MCIgcj0iMTYiLz4KCiAgPCEtLSBSb3cgMiAtIDEgbm9kZSAodW5zZXR0bGluZ2x5IG9mZi1jZW50ZXIgdG8gbGVmdCkgLS0+CiAgPGNpcmNsZSBjbGFzcz0ibm9kZSIgY3g9IjE0MCIgY3k9IjEyMCIgcj0iMTYiLz4KCiAgPCEtLSBSb3cgMyAtIDMgbm9kZXMgKHN5bW1ldHJpYywgdGlnaHRlbmVkIDIwcHgpIC0tPgogIDxjaXJjbGUgY2xhc3M9Im5vZGUiIGN4PSI3NSIgY3k9IjE5MCIgcj0iMTYiLz4KICA8Y2lyY2xlIGNsYXNzPSJub2RlIiBjeD0iMTgwIiBjeT0iMTkwIiByPSIxNiIvPgogIDxjaXJjbGUgY2xhc3M9Im5vZGUiIGN4PSIyODUiIGN5PSIxOTAiIHI9IjE2Ii8+CgogIDwhLS0gU3RlbSAodmVydGljYWwsIHNoaWZ0ZWQgcmlnaHQsIHRoaWNrZXIpIC0tPgogIDxsaW5lIGNsYXNzPSJzdGVtIiB4MT0iMjEwIiB5MT0iMjIwIiB4Mj0iMjEwIiB5Mj0iMjc1Ii8+CgogIDwhLS0gUm9vdCBub2RlIGF0IGJvdHRvbSAtLT4KICA8Y2lyY2xlIGNsYXNzPSJub2RlIiBjeD0iMjA1IiBjeT0iMzEwIiByPSIxNiIvPgo8L3N2Zz4K" alt="loctree logo" class="logo-img"><div class="logo-text"><span style="color:var(--theme-accent)">Loctree</span><span style="opacity:0.5">Report</span></div></div><button class="theme-toggle" data-role="theme-toggle" title="Toggle light/dark mode"><svg class="theme-icon-light" xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 256 256"><path d="M120,40V16a8,8,0,0,1,16,0V40a8,8,0,0,1-16,0Zm72,88a64,64,0,1,1-64-64A64.07,64.07,0,0,1,192,128Zm-16,0a48,48,0,1,0-48,48A48.05,48.05,0,0,0,176,128ZM58.34,69.66A8,8,0,0,0,69.66,58.34l-16-16A8,8,0,0,0,42.34,53.66Zm0,116.68-16,16a8,8,0,0,0,11.32,11.32l16-16a8,8,0,0,0-11.32-11.32ZM192,72a8,8,0,0,0,5.66-2.34l16-16a8,8,0,0,0-11.32-11.32l-16,16A8,8,0,0,0,192,72Zm5.66,114.34a8,8,0,0,0-11.32,11.32l16,16a8,8,0,0,0,11.32-11.32ZM48,128a8,8,0,0,0-8-8H16a8,8,0,0,0,0,16H40A8,8,0,0,0,48,128Zm80,80a8,8,0,0,0-8,8v24a8,8,0,0,0,16,0V216A8,8,0,0,0,128,208Zm112-88H216a8,8,0,0,0,0,16h24a8,8,0,0,0,0-16Z"></path></svg><svg class="theme-icon-dark" xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 256 256"><path d="M233.54,142.23a8,8,0,0,0-8-2,88.08,88.08,0,0,1-109.8-109.8,8,8,0,0,0-10-10,104.84,104.84,0,0,0-52.91,37A104,104,0,0,0,136,224a103.09,103.09,0,0,0,62.52-20.88,104.84,104.84,0,0,0,37-52.91A8,8,0,0,0,233.54,142.23ZM188.9,190.34A88,88,0,0,1,65.66,67.11a89,89,0,0,1,31.4-26A106,106,0,0,0,96,56,104.11,104.11,0,0,0,200,160a106,106,0,0,0,14.92-1.06A89,89,0,0,1,188.9,190.34Z"></path></svg></button></div><nav class="sidebar-nav"><button data-tab="overview" class="nav-item active"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256" class="icon-sm"><path d="M104,48H48A16,16,0,0,0,32,64v56a16,16,0,0,0,16,16h56a16,16,0,0,0,16-16V64A16,16,0,0,0,104,48Zm0,72H48V64h56Zm104-72H152a16,16,0,0,0-16,16v56a16,16,0,0,0,16,16h56a16,16,0,0,0,16-16V64A16,16,0,0,0,208,48Zm0,72H152V64h56ZM104,152H48a16,16,0,0,0-16,16v56a16,16,0,0,0,16,16h56a16,16,0,0,0,16-16V168A16,16,0,0,0,104,152Zm0,72H48V168h56Zm104-72H152a16,16,0,0,0-16,16v56a16,16,0,0,0,16,16h56a16,16,0,0,0,16-16V168A16,16,0,0,0,208,152Zm0,72H152V168h56Z"></path></svg>Overview</button><button data-tab="audit" class="nav-item"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256" class="icon-sm"><path d="M200,32H163.74a47.92,47.92,0,0,0-71.48,0H56A16,16,0,0,0,40,48V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V48A16,16,0,0,0,200,32Zm-72,0a32,32,0,0,1,32,32H96A32,32,0,0,1,128,32Zm72,184H56V48H82.75A47.93,47.93,0,0,0,80,64v8a8,8,0,0,0,8,8h80a8,8,0,0,0,8-8V64a47.93,47.93,0,0,0-2.75-16H200ZM96,136a8,8,0,0,1,8-8h48a8,8,0,0,1,0,16H104A8,8,0,0,1,96,136Zm0,32a8,8,0,0,1,8-8h48a8,8,0,0,1,0,16H104A8,8,0,0,1,96,168Z"></path></svg>Audit</button><button data-tab="dups" class="nav-item"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256" class="icon-sm"><path d="M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z"></path></svg>Duplicates</button><button data-tab="dynamic" class="nav-item"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256" class="icon-sm"><path d="M215.79,118.17a8,8,0,0,0-5-5.66L153.18,90.9l14.66-73.33a8,8,0,0,0-13.69-7L37.71,143.17A8,8,0,0,0,44.22,156l57.6,11.52L87.16,240.83A8,8,0,0,0,95,248a7.72,7.72,0,0,0,1.57-.16l116.67-46.67a8,8,0,0,0,2.55-14.5ZM96.82,224,116,128a8,8,0,0,0-6.51-9.54L52.22,107,159.18,32,140,128a8,8,0,0,0,6.51,9.54l57.27,11.45Z"></path></svg>Dynamic imports</button> <button data-tab="crowds" class="nav-item"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256" class="icon-sm"><path d="M117.25,157.92a60,60,0,1,0-66.5,0A95.83,95.83,0,0,0,3.53,195.63a8,8,0,1,0,13.4,8.74,80,80,0,0,1,134.14,0,8,8,0,0,0,13.4-8.74A95.83,95.83,0,0,0,117.25,157.92ZM40,108a44,44,0,1,1,44,44A44.05,44.05,0,0,1,40,108Zm210.14,98.7a8,8,0,0,1-11.07-2.33A79.83,79.83,0,0,0,172,168a8,8,0,0,1,0-16,44,44,0,1,0-16.34-84.87,8,8,0,1,1-5.94-14.85,60,60,0,0,1,55.53,105.64,95.83,95.83,0,0,1,47.22,37.71A8,8,0,0,1,250.14,206.7Z"></path></svg>Crowds</button><button data-tab="cycles" class="nav-item"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256" class="icon-sm"><path d="M224,48V96a8,8,0,0,1-8,8H168a8,8,0,0,1,0-16h28.69L182.06,73.37a79.56,79.56,0,0,0-56.13-23.43h-.45A79.52,79.52,0,0,0,69.59,72.71,8,8,0,0,1,58.41,61.27a96,96,0,0,1,135,.79L208,76.69V48a8,8,0,0,1,16,0ZM186.41,183.29a80,80,0,0,1-112.47-.66L59.31,168H88a8,8,0,0,0,0-16H40a8,8,0,0,0-8,8v48a8,8,0,0,0,16,0V179.31l14.63,14.63A95.43,95.43,0,0,0,130,222.06h.53a95.36,95.36,0,0,0,67.07-27.33,8,8,0,0,0-11.18-11.44Z"></path></svg>Cycles</button><button data-tab="dead" class="nav-item"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256" class="icon-sm"><path d="M112,120a12,12,0,1,1,12-12A12,12,0,0,1,112,120Zm44-12a12,12,0,1,0,12,12A12,12,0,0,0,156,108Zm68,12a104,104,0,0,1-208,0c0-34,17.33-69.36,46.77-94.35A103.36,103.36,0,0,1,128,8a104.11,104.11,0,0,1,96,112Zm-16,0a88.11,88.11,0,0,0-85.33-88h0C136,32.58,104,54.37,77.07,86.76,49.55,117.8,32,154.29,32,192a88,88,0,0,0,176,0Zm-16,72a8,8,0,0,0,0-16H188l-3.57-4.76a8,8,0,0,0-12.86,0L168,156l-3.57-4.76a8,8,0,0,0-12.86,0L148,156l-3.57-4.76a8,8,0,0,0-12.86,0L128,156l-3.57-4.76a8,8,0,0,0-12.86,0L108,156l-3.57-4.76a8,8,0,0,0-12.86,0L88,156l-3.57-4.76a8,8,0,0,0-12.86,0L68,156a8,8,0,0,0,0,16Z"></path></svg>Dead Code</button><button data-tab="twins" class="nav-item"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256" class="icon-sm"><path d="M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z"></path></svg>Twins</button><button data-tab="coverage" class="nav-item"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256" class="icon-sm"><path d="M223.59,199.73,160,93.78V32h8a8,8,0,0,0,0-16H88a8,8,0,0,0,0,16h8V93.78L32.41,199.73A16,16,0,0,0,46.41,224H209.59a16,16,0,0,0,14-24.27ZM109.59,105.42l6.41-11.08V32h24V94.34l6.41,11.08L128,133.08ZM46.41,208l54.09-93.44L128,159.57l27.5-45L209.59,208Z"></path></svg>Coverage</button><button data-tab="graph" class="nav-item"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256" class="icon-sm"><path d="M208,152a32.06,32.06,0,0,0-25.87,13.26l-52.3-29.06a32,32,0,0,0,0-16.4l52.3-29.06A32.06,32.06,0,0,0,208,104a32,32,0,1,0-31.71-28.29L124,104.78a32,32,0,1,0,0,46.44l52.3,29.06A32,32,0,1,0,208,152ZM208,56a16,16,0,1,1-16,16A16,16,0,0,1,208,56ZM80,128a16,16,0,1,1,16,16A16,16,0,0,1,80,128Zm128,88a16,16,0,1,1,16-16A16,16,0,0,1,208,216Z"></path></svg>Graph</button><button data-tab="tree" class="nav-item"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256" class="icon-sm"><path d="M160,112h48a16,16,0,0,0,16-16V48a16,16,0,0,0-16-16H160a16,16,0,0,0-16,16V64H128a24,24,0,0,0-24,24v32H72v-8A16,16,0,0,0,56,96H24A16,16,0,0,0,8,112v32a16,16,0,0,0,16,16H56a16,16,0,0,0,16-16v-8h32v32a24,24,0,0,0,24,24h16v16a16,16,0,0,0,16,16h48a16,16,0,0,0,16-16V160a16,16,0,0,0-16-16H160a16,16,0,0,0-16,16v16H128a8,8,0,0,1-8-8V88a8,8,0,0,1,8-8h16V96A16,16,0,0,0,160,112ZM56,144H24V112H56v32Zm104,16h48v48H160Zm0-112h48V96H160Z"></path></svg>Tree</button></nav><div class="app-footer"><button id="toggle-tests-btn" title="Toggle test file visibility" class="test-toggle-btn"><span id="test-toggle-icon"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 256 256" class=""><path d="M223.59,199.73,160,93.78V32h8a8,8,0,0,0,0-16H88a8,8,0,0,0,0,16h8V93.78L32.41,199.73A16,16,0,0,0,46.41,224H209.59a16,16,0,0,0,14-24.27ZM109.59,105.42l6.41-11.08V32h24V94.34l6.41,11.08L128,133.08ZM46.41,208l54.09-93.44L128,159.57l27.5-45L209.59,208Z"></path></svg></span><span id="test-toggle-text">Hide Tests</span></button><div style="margin-top: 8px; font-size: 11px;">loctree v0.8.3<br><span style="color:var(--theme-text-tertiary)">Snapshot</span></div></div></aside><main class="app-main"><div id="section-view-0" class="section-view active"><header class="app-header"><div class="header-title"><h1>my-project</h1><p title="my-project" class="header-path">my-project</p><!></div><div class="header-stats"><span class="stat-badge"><span class="stat-badge-value">42</span><span class="stat-badge-label">files</span></span><span class="stat-badge"><span class="stat-badge-value">0</span><span class="stat-badge-label">LOC</span></span><span class="stat-badge"><span class="stat-badge-value">0</span><span class="stat-badge-label">dups</span></span></div></header><div class="app-content"><div data-tab-scope="my_project" data-tab-name="overview" class="tab-panel active"><div class="content-container"><div class="overview-hero"><div class="health-gauge"><svg viewBox="0 0 120 120" width="140" height="140" class="gauge-svg"><circle cx="60" cy="60" r="50" fill="none" stroke="#2a2a2a" stroke-width="10" stroke-linecap="round"></circle><circle cx="60" cy="60" r="50" fill="none" stroke="#e74c3c" stroke-width="10" stroke-linecap="round" stroke-dasharray="314.1592653589793" stroke-dashoffset="314.1592653589793" transform="rotate(-90 60 60)" class="gauge-progress"></circle><text x="60" y="55" text-anchor="middle" dominant-baseline="middle" fill="#e74c3c" font-size="32" font-weight="700" font-family="system-ui, -apple-system, sans-serif">0</text><text x="60" y="78" text-anchor="middle" dominant-baseline="middle" fill="#888" font-size="12" font-weight="500" font-family="system-ui, -apple-system, sans-serif">Health</text></svg><div class="gauge-status" style="color: #e74c3c;">Critical</div></div><div class="overview-summary-wrapper"><div class="analysis-summary"><h3><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256" class=""><path d="M104,48H48A16,16,0,0,0,32,64v56a16,16,0,0,0,16,16h56a16,16,0,0,0,16-16V64A16,16,0,0,0,104,48Zm0,72H48V64h56Zm104-72H152a16,16,0,0,0-16,16v56a16,16,0,0,0,16,16h56a16,16,0,0,0,16-16V64A16,16,0,0,0,208,48Zm0,72H152V64h56ZM104,152H48a16,16,0,0,0-16,16v56a16,16,0,0,0,16,16h56a16,16,0,0,0,16-16V168A16,16,0,0,0,104,152Zm0,72H48V168h56Zm104-72H152a16,16,0,0,0-16,16v56a16,16,0,0,0,16,16h56a16,16,0,0,0,16-16V168A16,16,0,0,0,208,152Zm0,72H152V168h56Z"></path></svg>Analysis Summary</h3><div class="summary-grid"><div class="summary-stat"><span class="stat-value">42</span><span class="stat-label">Files analyzed</span></div><div class="summary-stat"><span class="stat-value">0</span><span class="stat-label">Total LOC</span></div><div class="summary-stat"><span class="stat-value">0</span><span class="stat-label">Duplicate exports</span></div><div class="summary-stat"><span class="stat-value">0</span><span class="stat-label">Re-export files</span></div><div class="summary-stat"><span class="stat-value">0</span><span class="stat-label">Dynamic imports</span></div></div></div></div></div><div class="ai-summary-panel"><h3>AI Summary</h3><div class="health-badge health-good">Healthy</div><table class="summary-table"><tbody><tr><td>Files analyzed</td><td><strong>42</strong></td></tr><tr class=""><td>Missing handlers</td><td><strong>0</strong></td></tr><tr class=""><td>Unregistered handlers</td><td><strong>0</strong></td></tr><tr><td>Unused handlers</td><td><strong>0</strong></td></tr><tr><td>Duplicate exports</td><td><strong>0</strong></td></tr></tbody></table><p class="no-issues">No immediate action items.</p></div><!><!--<() />--><div class="quick-commands-panel"><h3><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256" class=""><path d="M216,48H40A16,16,0,0,0,24,64V192a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V64A16,16,0,0,0,216,48ZM40,64H216V192H40V64Zm84,84H92a8,8,0,0,1-5.66-13.66l32-32a8,8,0,0,1,11.32,11.32L103.31,140l26.35,26.34A8,8,0,0,1,124,148Zm92,0H152a8,8,0,0,1,0-16h64a8,8,0,0,1,0,16Z"></path></svg>Quick Commands<span class="badge-new">v0.6</span></h3><div class="commands-grid"><div class="command-group"><h4>Query API</h4><p class="command-desc">Fast graph queries without full analysis</p><div class="command-list"><div class="command-item"><code class="command-code">loct query who-imports my-project/src/main.ts</code><span class="command-desc-inline">Files that import target</span><button data-copy="loct query who-imports my-project/src/main.ts" title="Copy to clipboard" class="copy-btn">Copy</button></div><div class="command-item"><code class="command-code">loct query where-symbol useAuth</code><span class="command-desc-inline">Find symbol definitions</span><button data-copy="loct query where-symbol useAuth" title="Copy to clipboard" class="copy-btn">Copy</button></div><div class="command-item"><code class="command-code">loct query component-of my-project/src/main.ts</code><span class="command-desc-inline">Graph component containing file</span><button data-copy="loct query component-of my-project/src/main.ts" title="Copy to clipboard" class="copy-btn">Copy</button></div></div></div><div class="command-group"><h4>Snapshot Diff</h4><p class="command-desc">Compare snapshots to track changes</p><div class="command-list"><div class="command-item"><code class="command-code">loct diff --since main</code><span class="command-desc-inline">Compare against main branch</span><button data-copy="loct diff --since main" title="Copy to clipboard" class="copy-btn">Copy</button></div><div class="command-item"><code class="command-code">loct diff --since HEAD~5</code><span class="command-desc-inline">Delta since 5 commits ago</span><button data-copy="loct diff --since HEAD~5" title="Copy to clipboard" class="copy-btn">Copy</button></div></div></div><!><!></div><div class="commands-footer"><p><code>loctree://open?f=<file>&l=<line></code> - IDE integration URLs in SARIF output</p></div></div></div></div><div data-tab-scope="my_project" data-tab-name="audit" class="tab-panel"><div class="content-container"><div class="audit-panel panel"><div class="audit-header"><h3>Audit Summary</h3><div class="health-badge warning"><span class="health-value">50</span><span class="health-max">/100</span></div></div><!><!><!><div class="audit-empty"><p>Codebase is in good shape! No critical issues detected.</p></div><div class="audit-footer"><p class="audit-tip">Tip: Check items as you fix them to track progress. Run <code>loct health</code> to regenerate this report.</p></div></div></div></div><div data-tab-scope="my_project" data-tab-name="dups" class="tab-panel"><div class="content-container"><h3>Top duplicate exports</h3><p class="muted">None</p><h3>Re-export cascades</h3><p class="muted">None</p></div></div><div data-tab-scope="my_project" data-tab-name="dynamic" class="tab-panel"><div class="content-container"><h3>Dynamic imports</h3><p class="muted">None</p></div></div><div data-tab-scope="my_project" data-tab-name="commands" class="tab-panel"><div class="content-container"><h3>Tauri command coverage</h3><p class="muted">No Tauri commands detected in this root.</p></div></div><div data-tab-scope="my_project" data-tab-name="pipelines" class="tab-panel"><div class="content-container"><div class="panel"><h3><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256" class=""><path d="M237.66,77.66l-32,32A8,8,0,0,1,200,112H176v24a8,8,0,0,1-2.34,5.66l-32,32a8,8,0,0,1-11.32,0L112,155.31l-58.34,58.35a8,8,0,0,1-11.32-11.32L100.69,144,82.34,125.66a8,8,0,0,1,0-11.32l32-32A8,8,0,0,1,120,80h24V56a8,8,0,0,1,2.34-5.66l32-32a8,8,0,0,1,11.32,11.32L160,59.31V80a8,8,0,0,1-8,8H128.69l-24,24L123.31,136,160,99.31V72a8,8,0,0,1,8-8h28.69L226.34,34.34a8,8,0,0,1,11.32,11.32Z"></path></svg> Command Pipelines</h3><div class="graph-empty"><div style="text-align: center; padding: 32px;;"><svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="var(--theme-text-tertiary)" viewBox="0 0 256 256" class=""><path d="M237.66,77.66l-32,32A8,8,0,0,1,200,112H176v24a8,8,0,0,1-2.34,5.66l-32,32a8,8,0,0,1-11.32,0L112,155.31l-58.34,58.35a8,8,0,0,1-11.32-11.32L100.69,144,82.34,125.66a8,8,0,0,1,0-11.32l32-32A8,8,0,0,1,120,80h24V56a8,8,0,0,1,2.34-5.66l32-32a8,8,0,0,1,11.32,11.32L160,59.31V80a8,8,0,0,1-8,8H128.69l-24,24L123.31,136,160,99.31V72a8,8,0,0,1,8-8h28.69L226.34,34.34a8,8,0,0,1,11.32,11.32Z"></path></svg><p style="margin-top: 16px; color: var(--theme-text-secondary);">No Tauri commands detected</p><p class="muted" style="font-size: 12px; margin-top: 8px;">This view shows frontend invoke() calls and their backend handlers</p></div></div></div></div></div><div data-tab-scope="my_project" data-tab-name="crowds" class="tab-panel"><div class="content-container"><div class="panel"><h3>Crowds Analysis</h3><p class="muted">No crowds detected. Your codebase has well-distributed naming patterns.</p></div></div></div><div data-tab-scope="my_project" data-tab-name="cycles" class="tab-panel"><div class="content-container"><div class="panel"><h3><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#27ae60" viewBox="0 0 256 256" class=""><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm-8-80V80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm8,40a12,12,0,1,1,12-12A12,12,0,0,1,128,176Z"></path></svg>Circular Imports<span class="count-badge count-badge-success">0</span></h3><div class="cycles-empty"><p class="muted">No circular imports detected</p></div></div></div></div><div data-tab-scope="my_project" data-tab-name="dead" class="tab-panel"><div class="content-container"><div class="panel"><h3><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256" class=""><path d="M112,120a12,12,0,1,1,12-12A12,12,0,0,1,112,120Zm44-12a12,12,0,1,0,12,12A12,12,0,0,0,156,108Zm68,12a104,104,0,0,1-208,0c0-34,17.33-69.36,46.77-94.35A103.36,103.36,0,0,1,128,8a104.11,104.11,0,0,1,96,112Zm-16,0a88.11,88.11,0,0,0-85.33-88h0C136,32.58,104,54.37,77.07,86.76,49.55,117.8,32,154.29,32,192a88,88,0,0,0,176,0Zm-16,72a8,8,0,0,0,0-16H188l-3.57-4.76a8,8,0,0,0-12.86,0L168,156l-3.57-4.76a8,8,0,0,0-12.86,0L148,156l-3.57-4.76a8,8,0,0,0-12.86,0L128,156l-3.57-4.76a8,8,0,0,0-12.86,0L108,156l-3.57-4.76a8,8,0,0,0-12.86,0L88,156l-3.57-4.76a8,8,0,0,0-12.86,0L68,156a8,8,0,0,0,0,16Z"></path></svg>Dead Exports</h3><div class="graph-empty"><div style="text-align: center; padding: 32px;;"><svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="var(--theme-text-tertiary)" viewBox="0 0 256 256" class=""><path d="M112,120a12,12,0,1,1,12-12A12,12,0,0,1,112,120Zm44-12a12,12,0,1,0,12,12A12,12,0,0,0,156,108Zm68,12a104,104,0,0,1-208,0c0-34,17.33-69.36,46.77-94.35A103.36,103.36,0,0,1,128,8a104.11,104.11,0,0,1,96,112Zm-16,0a88.11,88.11,0,0,0-85.33-88h0C136,32.58,104,54.37,77.07,86.76,49.55,117.8,32,154.29,32,192a88,88,0,0,0,176,0Zm-16,72a8,8,0,0,0,0-16H188l-3.57-4.76a8,8,0,0,0-12.86,0L168,156l-3.57-4.76a8,8,0,0,0-12.86,0L148,156l-3.57-4.76a8,8,0,0,0-12.86,0L128,156l-3.57-4.76a8,8,0,0,0-12.86,0L108,156l-3.57-4.76a8,8,0,0,0-12.86,0L88,156l-3.57-4.76a8,8,0,0,0-12.86,0L68,156a8,8,0,0,0,0,16Z"></path></svg><p style="margin-top: 16px; color: var(--theme-text-secondary);">No dead exports detected</p><p class="muted" style="font-size: 12px; margin-top: 8px;">All exports are being imported somewhere in the codebase</p></div></div></div></div></div><div data-tab-scope="my_project" data-tab-name="twins" class="tab-panel"><div class="content-container"><div class="panel"><h3><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256" class=""><path d="M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z"></path></svg>Twins Analysis</h3><div class="graph-empty"><div style="text-align: center; padding: 32px;;"><svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="var(--theme-text-tertiary)" viewBox="0 0 256 256" class=""><path d="M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z"></path></svg><p style="margin-top: 16px; color: var(--theme-text-secondary);">Twins analysis not available</p><p class="muted" style="font-size: 12px; margin-top: 8px;">Run analysis with --twins flag to enable this feature</p></div></div></div></div></div><div data-tab-scope="my_project" data-tab-name="coverage" class="tab-panel"><div class="content-container"><div class="panel"><h3><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256" class=""><path d="M223.59,199.73,160,93.78V32h8a8,8,0,0,0,0-16H88a8,8,0,0,0,0,16h8V93.78L32.41,199.73A16,16,0,0,0,46.41,224H209.59a16,16,0,0,0,14-24.27ZM109.59,105.42l6.41-11.08V32h24V94.34l6.41,11.08L128,133.08ZM46.41,208l54.09-93.44L128,159.57l27.5-45L209.59,208Z"></path></svg>Coverage Gaps</h3><div class="graph-empty"><div style="text-align: center; padding: 32px;;"><svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="var(--theme-text-tertiary)" viewBox="0 0 256 256" class=""><path d="M223.59,199.73,160,93.78V32h8a8,8,0,0,0,0-16H88a8,8,0,0,0,0,16h8V93.78L32.41,199.73A16,16,0,0,0,46.41,224H209.59a16,16,0,0,0,14-24.27ZM109.59,105.42l6.41-11.08V32h24V94.34l6.41,11.08L128,133.08ZM46.41,208l54.09-93.44L128,159.57l27.5-45L209.59,208Z"></path></svg><p style="margin-top: 16px; color: var(--theme-text-secondary);">No coverage gaps detected</p><p class="muted" style="font-size: 12px; margin-top: 8px;">All production code has test coverage</p></div></div></div></div></div><div data-tab-scope="my_project" data-tab-name="graph" class="tab-panel"><!><!></div><div data-tab-scope="my_project" data-tab-name="tree" class="tab-panel"><div class="content-container"><div data-tab-scope="my_project" data-tab-name="tree" class="tree-panel"><div class="tree-header"><h3>Analyzed files </h3><span title="Only code files loctree can analyze (JS/TS, Python, Rust, Go, etc.) are shown" class="tree-stats">0 files · 0 LOC</span><div class="tree-controls"><button title="Expand all" class="tree-btn"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 256 256" class=""><path d="M216,48V96a8,8,0,0,1-16,0V67.31l-42.34,42.35a8,8,0,0,1-11.32-11.32L188.69,56H160a8,8,0,0,1,0-16h48A8,8,0,0,1,216,48ZM98.34,146.34,56,188.69V160a8,8,0,0,0-16,0v48a8,8,0,0,0,8,8H96a8,8,0,0,0,0-16H67.31l42.35-42.34a8,8,0,0,0-11.32-11.32ZM208,152a8,8,0,0,0-8,8v28.69l-42.34-42.35a8,8,0,0,0-11.32,11.32L188.69,200H160a8,8,0,0,0,0,16h48a8,8,0,0,0,8-8V160A8,8,0,0,0,208,152ZM67.31,56H96a8,8,0,0,0,0-16H48a8,8,0,0,0-8,8V96a8,8,0,0,0,16,0V67.31l42.34,42.35a8,8,0,0,0,11.32-11.32Z"></path></svg></button><button title="Collapse all" class="tree-btn"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 256 256" class=""><path d="M144,96V48a8,8,0,0,1,16,0V76.69l42.34-42.35a8,8,0,0,1,11.32,11.32L171.31,88H200a8,8,0,0,1,0,16H152A8,8,0,0,1,144,96ZM56,112h48a8,8,0,0,0,8-8V56a8,8,0,0,0-16,0V84.69L53.66,42.34A8,8,0,0,0,42.34,53.66L84.69,96H56a8,8,0,0,0,0,16ZM200,144H152a8,8,0,0,0-8,8v48a8,8,0,0,0,16,0V171.31l42.34,42.35a8,8,0,0,0,11.32-11.32L171.31,160H200a8,8,0,0,0,0-16Zm-96,8a8,8,0,0,0-8-8H48a8,8,0,0,0,0,16H76.69L34.34,202.34a8,8,0,0,0,11.32,11.32L88,171.31V200a8,8,0,0,0,16,0Z"></path></svg></button></div><input type="text" placeholder="Filter by path..." class="tree-filter"></div><div class="tree-container"><!></div></div></div></div></div></div><!></main></div><script>
(() => {
// -1. Copy Button Handler
document.querySelectorAll('.copy-btn[data-copy]').forEach(btn => {
btn.addEventListener('click', () => {
const text = btn.dataset.copy;
navigator.clipboard.writeText(text).then(() => {
const orig = btn.textContent;
btn.textContent = 'Copied';
setTimeout(() => btn.textContent = orig, 1500);
});
});
});
// 0. Theme Initialization & Toggle
const initTheme = () => {
const stored = localStorage.getItem('loctree-theme');
if (stored === 'dark') {
document.documentElement.classList.add('dark');
document.documentElement.classList.remove('light');
} else if (stored === 'light') {
document.documentElement.classList.add('light');
document.documentElement.classList.remove('dark');
} else {
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('dark');
}
}
};
const toggleTheme = () => {
const isDark = document.documentElement.classList.contains('dark') ||
(!document.documentElement.classList.contains('light') &&
window.matchMedia('(prefers-color-scheme: dark)').matches);
if (isDark) {
document.documentElement.classList.remove('dark');
document.documentElement.classList.add('light');
localStorage.setItem('loctree-theme', 'light');
} else {
document.documentElement.classList.add('dark');
document.documentElement.classList.remove('light');
localStorage.setItem('loctree-theme', 'dark');
}
document.querySelectorAll('[data-role="dark"]').forEach(chk => {
chk.checked = document.documentElement.classList.contains('dark');
});
};
initTheme();
const themeToggle = document.querySelector('[data-role="theme-toggle"]');
if (themeToggle) {
themeToggle.addEventListener('click', toggleTheme);
}
// 1. Sidebar Navigation (Tab Switching)
document.querySelectorAll('.sidebar-nav .nav-item[data-tab]').forEach(btn => {
btn.addEventListener('click', () => {
const tabName = btn.dataset.tab;
// Update Sidebar buttons
document.querySelectorAll('.sidebar-nav .nav-item').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Update all tab panels across all sections
document.querySelectorAll('.tab-panel').forEach(p => {
const isActive = p.dataset.tabName === tabName;
p.classList.toggle('active', isActive);
if (isActive && tabName === 'graph') {
window.dispatchEvent(new Event('resize'));
}
});
// Also update header tab-bar buttons if present (for visual sync)
document.querySelectorAll('.tab-bar .tab-btn').forEach(b => {
b.classList.toggle('active', b.dataset.tab === tabName);
});
});
});
// 2. Header Tab Switching (if still present, syncs with sidebar)
document.querySelectorAll('.tab-bar .tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
const tabName = btn.dataset.tab;
// Trigger sidebar button click to keep everything in sync
const sidebarBtn = document.querySelector(`.sidebar-nav .nav-item[data-tab="${tabName}"]`);
if (sidebarBtn) {
sidebarBtn.click();
}
});
});
// 3. Twins Section Toggle - handles collapsible sections in Twins tab
document.querySelectorAll('.twins-section-header[data-toggle]').forEach(btn => {
btn.addEventListener('click', () => {
const targetId = btn.dataset.toggle;
const content = document.getElementById(targetId);
const toggle = btn.querySelector('.twins-section-toggle');
if (content) {
const isHidden = content.style.display === 'none';
content.style.display = isHidden ? 'block' : 'none';
if (toggle) {
toggle.textContent = isHidden ? '▼' : '▶';
}
// Initialize Cytoscape graph when twins-exact-content is opened
if (isHidden && targetId === 'twins-exact-content' && window.__TWINS_DATA__) {
const container = document.getElementById('twins-graph-container');
if (container && typeof buildTwinsGraph === 'function') {
buildTwinsGraph(window.__TWINS_DATA__, 'twins-graph-container');
}
}
}
});
});
// 3b. Crowds Graph Toggle - handles graph view in Crowds tab
document.querySelectorAll('.crowds-section-header[data-toggle]').forEach(btn => {
btn.addEventListener('click', () => {
const targetId = btn.dataset.toggle;
const content = document.getElementById(targetId);
const toggle = btn.querySelector('.crowds-graph-toggle');
if (content) {
const isHidden = content.style.display === 'none';
content.style.display = isHidden ? 'block' : 'none';
if (toggle) {
toggle.textContent = isHidden ? '▼' : '▶';
}
// Initialize Cytoscape graph when crowds-graph-content is opened
if (isHidden && targetId === 'crowds-graph-content' && window.__CROWDS_DATA__) {
const container = document.getElementById('crowds-graph-container');
if (container && typeof buildCrowdsGraph === 'function') {
buildCrowdsGraph(window.__CROWDS_DATA__, 'crowds-graph-container');
}
}
}
});
});
// 3c. Pipeline Card Toggle - expand/collapse pipeline cards
document.querySelectorAll('.card-header[data-pipeline-toggle]').forEach(header => {
header.addEventListener('click', () => {
const card = header.closest('.pipeline-card');
const details = card.querySelector('.card-details');
const toggle = header.querySelector('.expand-icon');
if (details) {
const isHidden = details.style.display === 'none';
details.style.display = isHidden ? 'block' : 'none';
if (toggle) {
toggle.textContent = isHidden ? '▼' : '▶';
}
}
});
});
// 3d. Pipeline Filter Buttons - filter cards by status
document.querySelectorAll('[data-pipeline-filter]').forEach(btn => {
btn.addEventListener('click', () => {
const filter = btn.dataset.pipelineFilter;
// Update active button
document.querySelectorAll('[data-pipeline-filter]').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Filter cards
document.querySelectorAll('.pipeline-card').forEach(card => {
const status = card.dataset.pipelineStatus;
const show = filter === 'all' || status === filter;
card.style.display = show ? '' : 'none';
});
});
});
// 3e. Pipeline Search - filter cards by name
const pipelineSearch = document.querySelector('[data-pipeline-search]');
if (pipelineSearch) {
pipelineSearch.addEventListener('input', (e) => {
const query = e.target.value.toLowerCase();
document.querySelectorAll('.pipeline-card').forEach(card => {
const name = card.dataset.pipelineName || '';
const matchesSearch = name.includes(query);
// Also respect current filter
const currentFilter = document.querySelector('[data-pipeline-filter].active')?.dataset.pipelineFilter || 'all';
const status = card.dataset.pipelineStatus;
const matchesFilter = currentFilter === 'all' || status === currentFilter;
card.style.display = (matchesSearch && matchesFilter) ? '' : 'none';
});
});
}
// 3f. Pipeline View Toggle - switch between card grid and split panel views
document.querySelectorAll('[data-pipeline-view]').forEach(btn => {
btn.addEventListener('click', function() {
const mode = this.dataset.pipelineView;
// Toggle active button
document.querySelectorAll('[data-pipeline-view]').forEach(b => b.classList.remove('active'));
this.classList.add('active');
// Toggle views
const gridView = document.querySelector('.cards-grid');
const splitView = document.querySelector('.split-panel-container');
if (mode === 'split') {
if (gridView) gridView.style.display = 'none';
if (splitView) {
splitView.style.display = 'grid';
setTimeout(drawPipelineConnections, 100);
}
} else {
if (gridView) gridView.style.display = 'grid';
if (splitView) splitView.style.display = 'none';
}
});
});
// Draw SVG connections between FE and BE items in split panel view
function drawPipelineConnections() {
const svg = document.querySelector('.connection-svg');
if (!svg) return;
// Clear existing paths
svg.innerHTML = '';
const svgRect = svg.getBoundingClientRect();
document.querySelectorAll('[data-split-fe]').forEach(fe => {
const name = fe.dataset.splitFe;
const be = document.querySelector('[data-split-be="' + name + '"]');
const feRect = fe.getBoundingClientRect();
const y1 = feRect.top + feRect.height / 2 - svgRect.top;
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
if (be) {
const beRect = be.getBoundingClientRect();
const y2 = beRect.top + beRect.height / 2 - svgRect.top;
// Bezier curve from left to right
path.setAttribute('d', 'M 0 ' + y1 + ' C 40 ' + y1 + ', 40 ' + y2 + ', 80 ' + y2);
path.classList.add('connection-line');
} else {
// Missing handler - draw dashed line to middle
path.setAttribute('d', 'M 0 ' + y1 + ' L 60 ' + y1);
path.classList.add('connection-line', 'missing');
}
svg.appendChild(path);
});
}
// Redraw connections on window resize
window.addEventListener('resize', () => {
if (document.querySelector('.split-panel-container')?.style.display !== 'none') {
drawPipelineConnections();
}
});
// 4. Test Files Toggle - Hide/Show test file rows
const toggleTestsBtn = document.getElementById('toggle-tests-btn');
const toggleIcon = document.getElementById('test-toggle-icon');
const toggleText = document.getElementById('test-toggle-text');
// Initialize state from localStorage
const testsHidden = localStorage.getItem('loctree-hide-tests') === 'true';
const updateTestsVisibility = (hide) => {
const testItems = document.querySelectorAll('[data-is-test="true"]');
testItems.forEach(el => {
el.style.display = hide ? 'none' : '';
});
// Update button state
if (toggleText) {
toggleText.textContent = hide ? 'Show Tests' : 'Hide Tests';
}
if (toggleIcon) {
toggleIcon.style.opacity = hide ? '0.5' : '1';
}
// Save to localStorage
localStorage.setItem('loctree-hide-tests', hide ? 'true' : 'false');
};
// Apply initial state
updateTestsVisibility(testsHidden);
// Add click handler
if (toggleTestsBtn) {
toggleTestsBtn.addEventListener('click', () => {
const currentlyHidden = localStorage.getItem('loctree-hide-tests') === 'true';
updateTestsVisibility(!currentlyHidden);
});
}
})();
</script><!></body></html>