Redesign the interface

Deep blue-black theme with a violet-to-cyan accent, frosted floating
panels, self-hosted Inter, a welcome screen with a call to action, a
card-style inspector, accent tabs and toggles, animated progress, and
a restyled open dialog. In 3D the WebGL walls are now lit per side
with a darker foot so buildings read as solids, and the canvas clears
to transparent over the new background. Renamed to CodeNavigator.
This commit is contained in:
Andy Dixon
2026-04-12 15:04:26 +01:00
parent 186f99f436
commit de3dbf8601
7 changed files with 370 additions and 271 deletions
+12 -10
View File
@@ -170,7 +170,7 @@ function applySnapshot(snapshot){
fileById=new Map(files.map(file=>[file.id,file]));referenceDegree=new Map();edgesByFile=new Map();let known=0,inferred=0;for(const edge of edges){referenceDegree.set(edge.from,(referenceDegree.get(edge.from)||0)+1);referenceDegree.set(edge.to,(referenceDegree.get(edge.to)||0)+1);for(const id of [edge.from,edge.to]){const list=edgesByFile.get(id);if(list)list.push(edge);else edgesByFile.set(id,[edge]);}if(edge.confidence==='known')known++;else inferred++;}
const layers=new Map();for(const file of files){const layer=file.layer in layerLabels?file.layer:'unknown';layers.set(layer,(layers.get(layer)||0)+1);}sceneStats={definitions:snapshot.definitions??files.reduce((n,file)=>n+(file.symbolCount||0),0),totalLines:snapshot.totalLines??files.reduce((n,file)=>n+file.lines,0),known,inferred,layers};
computeLayout();fitScene();updateStats(snapshot);renderInspector();renderResults([]);renderHistory();
$('#breadcrumbText').textContent=currentName;$('#emptyState').hidden=files.length>0;
$('#breadcrumbText').textContent=currentName;$('#emptyState').hidden=files.length>0;$('#welcome').hidden=true;
}
function fitScene(){
@@ -203,7 +203,9 @@ function updateStats(snapshot={}){
function format(value){return Number(value||0).toLocaleString();}
function escapeHtml(value=''){return String(value).replace(/[&<>"]/g,char=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[char]));}
const syntaxPalette={plain:'#aab5af',comment:'#64726b',string:'#89b88f',number:'#83a8d8',keyword:'#d8a75b',type:'#79b8d8',function:'#d6cf8b',property:'#b397d5',operator:'#8d9c95',constant:'#d98585',tag:'#65b9aa'};
const syntaxPalette={plain:'#b6bfd0',comment:'#5c667a',string:'#8fd694',number:'#f5a97f',keyword:'#c69cff',type:'#6ec6ff',function:'#7aa7ff',property:'#5fd3c6',operator:'#8f9ab0',constant:'#ff8fa3',tag:'#5fd3c6'};
// Matches the renderer's tile top so code drawn over a 3D roof blends with the WebGL face.
const TILE_TOP='#0e1118';
const syntaxKeywords=new Set(`abstract as async await break case catch class const continue crate def default defer delete do else enum export extends extern false final finally fn for from func function get go if impl import in instanceof interface is lambda let loop match mod module mut namespace new nil None null of override package private protected pub public raise readonly require return self set static struct super switch this throw trait true try type typeof undefined union unsafe use using var virtual void where while with yield`.split(' '));
const syntaxConstants=new Set('true false null nil none undefined nan infinity self this super'.split(' '));
const tokenPattern=/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`|\b0x[\da-f]+\b|\b0b[01]+\b|#[\da-f]{3,8}\b|\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b|[A-Za-z_$][\w$-]*|\s+|./gi;
@@ -317,7 +319,7 @@ function selectFile(file,addHistory=true){
function renderInspector(){
const hint=$('#inspectHint'),content=$('#inspectContent');
if(!files.length){hint.hidden=false;hint.textContent='Open a local folder or public GitHub repository to begin.';content.innerHTML='';return;}
if(!files.length){hint.hidden=false;hint.textContent='Open a local folder or GitHub repository to begin.';content.innerHTML='';return;}
if(!selected){
hint.hidden=false;hint.textContent=expandedCoverageLayer?'Click a file or use ↑ and ↓ to move around the map':'Select an entity on the map';
const coverageRows=Object.entries(layerLabels).map(([key,label])=>`<button class="coverage-row${expandedCoverageLayer===key?' active':''}" type="button" data-coverage-layer="${key}" aria-expanded="${expandedCoverageLayer===key}"><i style="background:${palettes[paletteIndex][key]}"></i><span>${label}</span><small>${format(sceneStats.layers.get(key)||0)}</small><b aria-hidden="true">${expandedCoverageLayer===key?'−':'+'}</b></button>`).join('');
@@ -406,7 +408,7 @@ $$('.tab').forEach(tab=>tab.addEventListener('click',()=>switchTab(tab.dataset.t
function resizeOverlay(){const dpr=Math.min(devicePixelRatio||1,2),w=Math.floor(overlay.clientWidth*dpr),h=Math.floor(overlay.clientHeight*dpr);if(overlay.width!==w||overlay.height!==h){overlay.width=w;overlay.height=h;}overlayCtx.setTransform(dpr,0,0,dpr,0,0);}
function drawChip(ctx,text,x,y,maxWidth,accent,occupied,force=false,align='left'){
if(maxWidth<24)return false;
ctx.font='600 11px -apple-system, BlinkMacSystemFont, sans-serif';
ctx.font='600 11px Inter, system-ui, sans-serif';
let label=text;
if(ctx.measureText(label).width>maxWidth-10){let low=1,high=label.length;while(low<high){const mid=Math.ceil((low+high)/2);if(ctx.measureText(`${label.slice(0,mid)}…`).width<=maxWidth-10)low=mid;else high=mid-1;}label=`${label.slice(0,low)}…`;}
const width=Math.min(maxWidth,ctx.measureText(label).width+10);
@@ -414,7 +416,7 @@ function drawChip(ctx,text,x,y,maxWidth,accent,occupied,force=false,align='left'
const box={x:x-3,y:y-2,w:width+6,h:21};
if(!force&&occupied?.some(other=>box.x<other.x+other.w&&box.x+box.w>other.x&&box.y<other.y+other.h&&box.y+box.h>other.y))return false;
occupied?.push(box);
ctx.fillStyle='rgba(27,30,28,.9)';ctx.fillRect(x,y,width,17);
ctx.fillStyle='rgba(10,12,18,.88)';ctx.fillRect(x,y,width,17);
if(accent){ctx.fillStyle=accent;ctx.fillRect(x,y,2,17);}
ctx.fillStyle='rgba(237,241,238,.94)';ctx.fillText(label,x+5,y+2);
return true;
@@ -426,7 +428,7 @@ function drawFileChip(ctx,item,rect,accent,occupied,force=false){
return drawChip(ctx,item.name,anchor.x,anchor.y-8,maxWidth,accent,occupied,force,'center');
}
function drawOverlay(){
resizeOverlay();const ctx=overlayCtx,w=overlay.clientWidth,h=overlay.clientHeight;ctx.clearRect(0,0,w,h);ctx.save();ctx.font='600 11px -apple-system, BlinkMacSystemFont, sans-serif';ctx.textBaseline='top';
resizeOverlay();const ctx=overlayCtx,w=overlay.clientWidth,h=overlay.clientHeight;ctx.clearRect(0,0,w,h);ctx.save();ctx.font='600 11px Inter, system-ui, sans-serif';ctx.textBaseline='top';
codeTexturesPending=false;
codeTextureDeadline=performance.now()+5;
const hitIds=new Set(searchHits.map(hit=>hit.entityId??hit.id)),occupied=[],visible=visibleLayout(w,h);visibleScreenItems=visible;
@@ -454,7 +456,7 @@ function drawOverlay(){
if(rect.w>46&&rect.h>18&&codeScreenFont(item)<5.2&&labelCount<110&&drawFileChip(ctx,item,rect,null,occupied))labelCount++;
}
if(hitIds.size){
ctx.fillStyle='rgba(11,13,12,.48)';ctx.fillRect(0,0,w,h);
ctx.fillStyle='rgba(5,6,10,.55)';ctx.fillRect(0,0,w,h);
for(const {item,rect} of visible){if(!hitIds.has(item.id))continue;ctx.strokeStyle='#e8c74b';ctx.lineWidth=2;ctx.strokeRect(rect.x-.5,rect.y-.5,rect.w+1,rect.h+1);drawFileChip(ctx,item,rect,'#e8c74b',null,true);}
}
drawSelectedConnections(ctx);
@@ -465,7 +467,7 @@ function drawOverlay(){
function tileCorners(item){return [[item.x,item.y],[item.x+item.w,item.y],[item.x+item.w,item.y+item.h],[item.x,item.y+item.h]].map(([x,y])=>renderer.project([x,y,item.height||0]));}
function drawGroundGrid(ctx){
if(!layoutItems.length)return;
ctx.save();ctx.globalCompositeOperation='destination-over';ctx.strokeStyle='rgba(180,180,180,.12)';ctx.lineWidth=.75;ctx.beginPath();
ctx.save();ctx.globalCompositeOperation='destination-over';ctx.strokeStyle='rgba(150,160,255,.11)';ctx.lineWidth=.75;ctx.beginPath();
for(let x=-200;x<=1200;x+=100){const a=renderer.project([x,-200,0]),b=renderer.project([x,900,0]);ctx.moveTo(a.x,a.y);ctx.lineTo(b.x,b.y);}
for(let y=-200;y<=900;y+=100){const a=renderer.project([-200,y,0]),b=renderer.project([1200,y,0]);ctx.moveTo(a.x,a.y);ctx.lineTo(b.x,b.y);}
ctx.stroke();
@@ -556,7 +558,7 @@ function draw3dCode(ctx,visible){
for(const {item,rect} of faces){
const corners=tileCorners(item);if(corners.some(p=>!Number.isFinite(p.x)||!Number.isFinite(p.y)))continue;
ctx.save();ctx.beginPath();corners.forEach((p,i)=>i?ctx.lineTo(p.x,p.y):ctx.moveTo(p.x,p.y));ctx.closePath();ctx.clip();
ctx.fillStyle='#1c1c1c';ctx.fillRect(rect.x-1,rect.y-1,rect.w+2,rect.h+2);
ctx.fillStyle=TILE_TOP;ctx.fillRect(rect.x-1,rect.y-1,rect.w+2,rect.h+2);
const texture=buildCodeTexture(item,rect);
if(texture){
// Match the two triangles used by the WebGL top face exactly.
@@ -692,7 +694,7 @@ document.addEventListener('keydown',event=>{
});
window.addEventListener('resize',()=>{dirty=true;});
const dialog=$('#loadDialog');$('#loadButton').addEventListener('click',()=>dialog.showModal());
const dialog=$('#loadDialog');$('#loadButton').addEventListener('click',()=>dialog.showModal());$('#welcomeOpen').addEventListener('click',()=>dialog.showModal());
$('#localFolderButton').addEventListener('click',async()=>{
dialog.close();
if('showDirectoryPicker' in window){try{const handle=await window.showDirectoryPicker();const chosen=[];await collectHandles(handle,'',chosen);await uploadLocal(handle.name,chosen);}catch(error){if(error.name!=='AbortError')showError(error.message);}}
+10 -2
View File
@@ -3,16 +3,17 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<meta name="theme-color" content="#1b1b1b">
<meta name="theme-color" content="#0b0d13">
<meta name="description" content="Explore an entire codebase as a fast, searchable 2D and 3D landscape.">
<title>CodeNavigator</title>
<link rel="icon" href="./favicon.svg" type="image/svg+xml">
<link rel="preload" href="./inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<main id="app">
<header class="toolbar" aria-label="Codebase controls">
<div class="brand" aria-label="CodeNavigator"><svg viewBox="0 0 28 28" aria-hidden="true"><rect x="3" y="3" width="9" height="22" rx="2"/><rect x="16" y="3" width="9" height="9" rx="2"/><rect x="16" y="16" width="9" height="9" rx="2"/></svg><span>Codebase<span class="brand-sub">VIEWER</span></span></div>
<div class="brand" aria-label="CodeNavigator"><svg viewBox="0 0 28 28" aria-hidden="true"><defs><linearGradient id="brandGradient" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#9a7dff"/><stop offset=".5" stop-color="#5f9dff"/><stop offset="1" stop-color="#36d3c2"/></linearGradient></defs><rect x="3" y="11" width="6" height="14" rx="1.6" fill="url(#brandGradient)" opacity=".7"/><rect x="11" y="3" width="6" height="22" rx="1.6" fill="url(#brandGradient)"/><rect x="19" y="15" width="6" height="10" rx="1.6" fill="url(#brandGradient)" opacity=".5"/></svg><span class="brand-name">Code<em>Navigator</em></span></div>
<div class="tool-group">
<button class="tool primary" id="loadButton" title="Open codebase" aria-label="Open codebase"><svg viewBox="0 0 24 24"><path d="M3.5 7.5h6l2-2h4l2 2h3v11h-17z"/><path d="M3.5 9.5h17"/></svg></button>
<button class="tool active" id="mapButton" title="Code landscape" aria-label="Code landscape"><svg viewBox="0 0 24 24"><rect x="3.5" y="3.5" width="17" height="17" rx="2"/><path d="M3.5 10h17M10 3.5v17M10 15h10.5"/></svg></button>
@@ -48,6 +49,13 @@
<div class="viewport" id="viewport">
<canvas id="glCanvas"></canvas>
<canvas id="overlayCanvas"></canvas>
<div class="welcome" id="welcome">
<span class="eyebrow"><i></i>Codebase landscapes</span>
<h1>See your code<br><span>as a place.</span></h1>
<p>Every file becomes a tile sized by its lines, grouped by folder and coloured by purpose. Fly over it in 2D or 3D, search every symbol, and follow imports from file to file.</p>
<div class="welcome-actions"><button type="button" class="button-primary" id="welcomeOpen">Open a codebase</button><span><kbd>Ctrl</kbd> <kbd>O</kbd></span></div>
<div class="welcome-features"><span>Public &amp; private GitHub repos</span><span>Local folders</span><span>Symbol search</span><span>Import graph</span></div>
</div>
<div class="empty-state" id="emptyState" hidden>
<strong>No source files found</strong>
<span>Try another folder or public GitHub repository.</span>
Binary file not shown.
+11 -3
View File
@@ -62,8 +62,16 @@ void main(){
vec2 face = vFace < .5 || vFace > 4.5 ? vUnit.xy : vFace < 2.5 ? vUnit.xz : vUnit.yz;
float edge = min(min(face.x, 1.0-face.x), min(face.y, 1.0-face.y));
float border = smoothstep(0.0, fwidth(edge)*1.35 + .009, edge);
vec3 base = vec3(.11);
vec3 stroke = mix(vColor.rgb, vec3(1.0), .07);
// Tile tops match TILE_TOP in app.js so source drawn over a roof blends in; 2D gets a faint layer tint.
vec3 top = vec3(.055, .067, .094);
vec3 base = uMode < .5 ? mix(top, vColor.rgb, .07) : top;
vec3 stroke = mix(vColor.rgb, vec3(1.0), .12);
if(vFace > .5 && vFace < 4.5){
// Walls: a fixed key light per side and a darker foot make buildings read as solids.
float light = vFace < 1.5 ? .34 : vFace < 2.5 ? .2 : vFace < 3.5 ? .26 : .42;
base = mix(top, vColor.rgb, light) * (.55 + .45 * vUnit.z);
stroke = mix(stroke, base, .45);
}
outColor = vec4(mix(stroke, base, border), uAlpha);
}`;
@@ -114,7 +122,7 @@ export class LandscapeRenderer {
render(alpha=1) {
const gl=this.gl; this.resize();
gl.clearColor(.11,.11,.11,1); gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);
gl.clearColor(0,0,0,0); gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);
gl.useProgram(this.program); gl.bindVertexArray(this.vao);
const u=n=>gl.getUniformLocation(this.program,n);
+330 -255
View File
@@ -1,24 +1,37 @@
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 400 700;
font-display: swap;
src: url("./inter-latin.woff2") format("woff2");
}
:root {
color-scheme: dark;
--bg: #212121;
--bg-deep: #161616;
--panel: #1e1e1e;
--panel-2: #262626;
--line: #3d3d3d;
--line-soft: #2f2f2f;
--text: #e2e2e2;
--muted: #a3a3a3;
--dim: #626262;
--cyan: #65b3ff;
--green: #29c997;
--yellow: #d6b23d;
--orange: #d67d42;
--pink: #cf6fa4;
--blue: #488ad2;
--danger: #ef6b69;
--panel-width: 280px;
--accent: #cdcdcd;
--toolbar-height: 46px;
--bg: #07090d;
--bg-raised: #0d1017;
--surface: rgba(16, 19, 27, .74);
--surface-solid: #10131b;
--surface-2: rgba(255, 255, 255, .035);
--hover: rgba(255, 255, 255, .06);
--line: rgba(255, 255, 255, .08);
--line-strong: rgba(255, 255, 255, .15);
--text: #e9ecf3;
--muted: #9ba3b4;
--dim: #687083;
--accent: #8d7dff;
--accent-2: #36d3c2;
--accent-grad: linear-gradient(135deg, #9a7dff 0%, #5f9dff 50%, #36d3c2 100%);
--accent-soft: rgba(141, 125, 255, .16);
--accent-line: rgba(141, 125, 255, .5);
--green: #36d3a0;
--yellow: #f2c94c;
--danger: #ff6b7a;
--glass: blur(18px) saturate(150%);
--shadow: 0 18px 50px rgba(0, 0, 0, .45), 0 2px 8px rgba(0, 0, 0, .3);
--panel-width: 300px;
--toolbar-height: 52px;
--mono: "JetBrains Mono", "SFMono-Regular", ui-monospace, Consolas, monospace;
}
* { box-sizing: border-box; }
@@ -26,296 +39,358 @@
html, body, #app { width: 100%; height: 100%; overflow: hidden; }
body {
margin: 0;
background: var(--bg-deep);
background: var(--bg);
color: var(--text);
font: 12px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, sans-serif;
font: 12.5px/1.5 Inter, system-ui, -apple-system, "Segoe UI", sans-serif;
font-feature-settings: "cv11", "ss01";
-webkit-font-smoothing: antialiased;
letter-spacing: .005em;
}
button, input { font: inherit; }
button, input, select { font: inherit; }
button { color: inherit; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
::selection { background: rgba(141, 125, 255, .35); color: #fff; }
* { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,.14) transparent; }
kbd { font: 500 10px/1.5 var(--mono); border: 1px solid var(--line-strong); border-bottom-width: 2px; border-radius: 5px; padding: 0 5px; color: #cfd5e2; background: rgba(255,255,255,.04); }
/* Toolbar */
.toolbar {
position: relative;
z-index: 5;
height: var(--toolbar-height);
padding: 0 10px;
padding: 0 14px;
display: flex;
align-items: center;
gap: 7px;
background: linear-gradient(180deg, #222222, #1d1d1d);
border-bottom: 1px solid #383838;
box-shadow: 0 4px 20px #0002;
gap: 8px;
background: linear-gradient(180deg, rgba(18, 21, 30, .96), rgba(11, 13, 19, .96));
border-bottom: 1px solid var(--line);
}
.toolbar::after {
content: "";
position: absolute;
left: 0; right: 0; bottom: -1px;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(141,125,255,.55) 30%, rgba(54,211,194,.45) 70%, transparent);
opacity: .55;
}
.brand { display: flex; gap: 10px; align-items: center; padding-right: 14px; margin-right: 2px; border-right: 1px solid var(--line); }
.brand svg { width: 26px; height: 26px; filter: drop-shadow(0 0 10px rgba(141,125,255,.45)); }
.brand-name { font-size: 15px; font-weight: 650; letter-spacing: -.03em; line-height: 1; color: #f2f4f9; }
.brand-name em { font-style: normal; background: var(--accent-grad); -webkit-background-clip: text; background-clip: text; color: transparent; }
.tool-group { display: flex; align-items: center; gap: 3px; }
.tool, .metric {
height: 28px;
min-width: 28px;
height: 30px;
min-width: 30px;
border: 1px solid transparent;
border-radius: 8px;
border-radius: 9px;
background: transparent;
color: #a9a9a9;
color: var(--muted);
cursor: pointer;
transition: 100ms ease;
transition: background-color .15s, border-color .15s, color .15s, box-shadow .15s;
}
.tool { width: 28px; padding: 0; display: grid; place-items: center; }
.tool svg { width: 15px; height: 15px; display: block; margin: 0; fill: none; stroke: currentColor; stroke-width: 1.65; stroke-linecap: round; stroke-linejoin: round; }
.tool-link { display: grid; place-items: center; text-decoration: none; }
.github-link svg { width: 16px; height: 16px; margin: 0; fill: currentColor; stroke: none; }
.metric b { font: 500 15px/1 "SFMono-Regular", Consolas, monospace; }
.tool:hover, .metric:hover { color: #e8e8e8; background: #313131; }
.tool.active, .metric.active { color: #dadada; border-color: #525252; background: #333333; box-shadow: inset 0 1px #ffffff09; }
.tool.primary { border-color: #c7c7c7; color: #262626; background: #cdcdcd; }
.tool.primary:hover { background: #dedede; color: #1a1a1a; }
.tool { width: 30px; padding: 0; display: grid; place-items: center; }
.tool svg { width: 16px; height: 16px; display: block; fill: none; stroke: currentColor; stroke-width: 1.65; stroke-linecap: round; stroke-linejoin: round; }
.tool-link { text-decoration: none; }
.github-link svg { fill: currentColor; stroke: none; }
.metric { padding: 0 10px; display: flex; gap: 6px; align-items: center; font-weight: 500; }
.metric b { font: 500 13px/1 var(--mono); opacity: .8; }
.tool:hover, .metric:hover { color: var(--text); background: var(--hover); }
.tool.active, .metric.active {
color: #fff;
border-color: var(--accent-line);
background: linear-gradient(180deg, rgba(141,125,255,.24), rgba(141,125,255,.12));
box-shadow: 0 0 0 1px rgba(141,125,255,.08), 0 4px 14px rgba(141,125,255,.22), inset 0 1px rgba(255,255,255,.12);
}
.tool.primary {
color: #fff;
border-color: transparent;
background: var(--accent-grad);
box-shadow: 0 4px 16px rgba(95,157,255,.35), inset 0 1px rgba(255,255,255,.25);
}
.tool.primary:hover { filter: brightness(1.1); background: var(--accent-grad); }
.tool.palette { border-radius: 50%; }
.separator { width: 1px; height: 22px; background: #3c3c3c; margin: 0 4px; }
.metric-switch { display: flex; align-items: center; }
.metric { padding: 0 8px; display: flex; gap: 5px; align-items: center; }
.view-switch, .metric-switch { display: flex; align-items: center; padding: 3px; border: 1px solid var(--line); border-radius: 12px; background: rgba(0,0,0,.28); gap: 2px; }
.separator { width: 1px; height: 24px; background: var(--line); margin: 0 3px; }
.toolbar-spacer { flex: 1; }
.scene-stats { text-align: right; color: #b0b0b0; line-height: 1.25; min-width: 210px; }
.scene-stats small { display: block; color: #6b6b6b; }
.filter-wrap { position: relative; width: min(230px, 20vw); }
.filter-wrap input {
width: 100%; height: 29px; padding: 0 30px 0 10px;
border: 1px solid #424242; border-radius: 7px;
outline: none; color: #e3e3e3; background: #313131;
.scene-stats { text-align: right; color: var(--text); line-height: 1.25; margin-right: 8px; font-size: 11.5px; font-weight: 500; font-variant-numeric: tabular-nums; }
.scene-stats small { display: block; margin-top: 3px; color: var(--dim); font-weight: 400; }
.filter-wrap { position: relative; width: min(260px, 22vw); }
.filter-wrap::before {
content: "";
position: absolute; left: 11px; top: 50%;
width: 12px; height: 12px; transform: translateY(-50%);
background: currentColor; color: var(--dim);
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m20 20-3.5-3.5'/%3E%3C/svg%3E") center / contain no-repeat;
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m20 20-3.5-3.5'/%3E%3C/svg%3E") center / contain no-repeat;
pointer-events: none;
}
.filter-wrap input:focus { border-color: #999999; box-shadow: 0 0 0 2px rgba(71,145,210,.13); }
.filter-wrap input::placeholder { color: #848484; }
.filter-wrap kbd { position: absolute; right: 8px; top: 6px; color: #727272; font: 10px monospace; }
.live-dot { width: 7px; height: 7px; border-radius: 50%; background: #868686; box-shadow: 0 0 0 2px rgba(237,69,96,.12); }
.live-dot.connected { background: var(--green); box-shadow: 0 0 0 2px rgba(41,201,151,.12); }
.filter-wrap input {
width: 100%; height: 34px; padding: 0 34px 0 31px;
border: 1px solid var(--line); border-radius: 10px;
outline: none; color: var(--text); background: rgba(0,0,0,.3);
transition: border-color .15s, box-shadow .15s, background-color .15s;
}
.filter-wrap input::placeholder { color: var(--dim); }
.filter-wrap input:focus { border-color: var(--accent-line); background: rgba(0,0,0,.45); box-shadow: 0 0 0 4px rgba(141,125,255,.14); }
.filter-wrap kbd { position: absolute; right: 9px; top: 50%; transform: translateY(-50%); color: var(--dim); }
.live-dot { flex-shrink: 0; width: 8px; height: 8px; margin-left: 4px; border-radius: 50%; background: #5d6475; }
.live-dot.connected { background: var(--green); box-shadow: 0 0 0 3px rgba(54,211,160,.14), 0 0 10px rgba(54,211,160,.6); animation: pulse 2.6s ease-in-out infinite; }
@keyframes pulse { 50% { box-shadow: 0 0 0 5px rgba(54,211,160,.05), 0 0 14px rgba(54,211,160,.35); } }
/* Workspace and viewport */
.workspace { position: absolute; inset: var(--toolbar-height) 0 0; display: grid; grid-template-columns: minmax(0,1fr) var(--panel-width); }
.viewport { position: relative; min-width: 0; overflow: hidden; touch-action: none; user-select: none; background-color: #212121; background-image: linear-gradient(45deg,rgba(255,255,255,.012) 25%,transparent 25%),linear-gradient(-45deg,rgba(255,255,255,.012) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(255,255,255,.012) 75%),linear-gradient(-45deg,transparent 75%,rgba(255,255,255,.012) 75%); background-size: 24px 24px; background-position: 0 0,0 12px,12px -12px,-12px 0; cursor: grab; }
.viewport {
position: relative; min-width: 0; overflow: hidden; touch-action: none; user-select: none; cursor: grab;
background:
radial-gradient(1200px 700px at 30% -10%, rgba(141,125,255,.13), transparent 60%),
radial-gradient(900px 600px at 90% 110%, rgba(54,211,194,.09), transparent 60%),
radial-gradient(circle at 1px 1px, rgba(255,255,255,.045) 1px, transparent 0) 0 0 / 22px 22px,
var(--bg);
}
.viewport.dragging { cursor: grabbing; }
.viewport.panning { cursor: move; }
#glCanvas, #overlayCanvas { position: absolute; inset: 0; width: 100%; height: 100%; }
#overlayCanvas { pointer-events: none; }
.inspector { background: linear-gradient(160deg,#222222,#1d1d1d); border-left: 1px solid #393939; min-width: 0; overflow: hidden; }
.tabs { height: 40px; display: flex; align-items: center; gap: 4px; padding: 0 10px; border-bottom: 1px solid var(--line-soft); }
.tab { height: 27px; padding: 0 9px; border: 0; border-radius: 7px; background: transparent; color: #9c9c9c; cursor: pointer; }
.tab:hover { color: #bbbbbb; }
.tab.active { color: #dedede; background: #333333; box-shadow: inset 0 0 0 1px #454545; }
#resultBadge:not(:empty) { margin-left: 3px; padding: 1px 5px; border-radius: 8px; background: #404040; font-size: 9px; }
.panel { display: none; height: calc(100% - 40px); overflow: auto; padding: 13px 12px 20px; scrollbar-color: #545454 transparent; scrollbar-width: thin; }
.breadcrumb, .canvas-controls, .camera-help, .legend, .tooltip, .progress-card {
border: 1px solid var(--line);
background: var(--surface);
-webkit-backdrop-filter: var(--glass);
backdrop-filter: var(--glass);
box-shadow: var(--shadow), inset 0 1px rgba(255,255,255,.05);
}
/* Welcome */
.welcome { position: absolute; inset: 0; z-index: 1; display: grid; place-content: center; justify-items: start; padding: 32px; max-width: 760px; margin: 0 auto; cursor: default; }
.welcome .eyebrow { display: inline-flex; align-items: center; gap: 8px; padding: 5px 11px 5px 8px; border: 1px solid var(--line); border-radius: 999px; background: rgba(255,255,255,.03); color: var(--muted); font-size: 11px; font-weight: 500; }
.welcome .eyebrow i { width: 7px; height: 7px; border-radius: 50%; background: var(--accent-grad); box-shadow: 0 0 10px rgba(141,125,255,.8); }
.welcome h1 { margin: 20px 0 0; font-size: clamp(34px, 5.2vw, 60px); line-height: 1.02; font-weight: 700; letter-spacing: -.045em; color: #f4f6fb; }
.welcome h1 span { background: var(--accent-grad); -webkit-background-clip: text; background-clip: text; color: transparent; }
.welcome p { max-width: 520px; margin: 18px 0 0; color: var(--muted); font-size: 15px; line-height: 1.6; }
.welcome-actions { display: flex; align-items: center; gap: 16px; margin-top: 30px; color: var(--dim); font-size: 11px; }
.welcome-features { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 36px; }
.welcome-features span { padding: 6px 11px; border: 1px solid var(--line); border-radius: 999px; background: rgba(255,255,255,.02); color: var(--muted); font-size: 11px; }
.button-primary, .dialog-button, .github-row button {
height: 40px; padding: 0 20px;
border: 0; border-radius: 11px;
color: #fff; background: var(--accent-grad);
font-weight: 600; letter-spacing: -.005em; cursor: pointer;
box-shadow: 0 8px 24px rgba(95,157,255,.32), inset 0 1px rgba(255,255,255,.28);
transition: transform .15s, box-shadow .15s, filter .15s;
}
.button-primary:hover, .dialog-button:hover, .github-row button:hover { filter: brightness(1.08); transform: translateY(-1px); box-shadow: 0 12px 30px rgba(95,157,255,.42), inset 0 1px rgba(255,255,255,.28); }
.button-primary:active, .dialog-button:active, .github-row button:active { transform: none; filter: brightness(.95); }
/* Inspector */
.inspector { min-width: 0; overflow: hidden; background: linear-gradient(180deg, var(--bg-raised), var(--bg)); border-left: 1px solid var(--line); }
.tabs { height: 46px; display: flex; align-items: center; gap: 2px; margin: 0 12px; padding: 0; border-bottom: 1px solid var(--line); }
.tab { position: relative; height: 46px; padding: 0 10px; border: 0; background: transparent; color: var(--dim); font-weight: 500; cursor: pointer; }
.tab:hover { color: var(--text); }
.tab.active { color: #fff; }
.tab.active::after { content: ""; position: absolute; left: 8px; right: 8px; bottom: -1px; height: 2px; border-radius: 2px; background: var(--accent-grad); box-shadow: 0 0 12px rgba(141,125,255,.7); }
#resultBadge:not(:empty) { margin-left: 4px; padding: 1px 6px; border-radius: 999px; background: var(--accent-soft); color: #cfc8ff; font-size: 10px; font-weight: 600; }
.panel { display: none; height: calc(100% - 46px); overflow: auto; padding: 16px 14px 24px; }
.panel.active { display: block; }
.panel-hint, .result-summary { color: #888888; margin: 0 0 12px; }
.entity-title { margin-bottom: 12px; }
.entity-title small { display: block; color: #787878; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.entity-title h2 { margin: 6px 0 0; color: #ededed; font-size: 16px; font-weight: 550; letter-spacing: -.035em; overflow-wrap: anywhere; }
.meta-grid { display: grid; grid-template-columns: 1fr auto; gap: 5px 12px; padding: 10px 0; border-top: 1px solid var(--line-soft); border-bottom: 1px solid var(--line-soft); }
.meta-grid span { color: #888888; }
.meta-grid b { color: #d4d4d4; font-weight: 500; text-align: right; }
.section-title { margin: 14px 0 7px; color: #e4e4e4; font-weight: 600; }
.relation { display: flex; gap: 8px; align-items: center; padding: 5px 7px; margin: 0 -7px; border-radius: 4px; cursor: pointer; }
.relation:hover { background: #313131; }
.relation i { width: 6px; height: 6px; border-radius: 50%; background: var(--green); }
.relation span { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.relation small { color: #777777; }
.coverage-title { display: flex; align-items: baseline; justify-content: space-between; }
.coverage-title small, .coverage-list-head small { color: #727272; font-size: 9px; font-weight: 400; }
.panel-hint, .result-summary { margin: 0 0 14px; color: var(--muted); line-height: 1.6; }
.entity-title { margin-bottom: 14px; }
.entity-title small { display: block; color: var(--dim); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.entity-title h2 { margin: 5px 0 0; color: #f4f6fb; font-size: 19px; font-weight: 650; letter-spacing: -.035em; overflow-wrap: anywhere; }
.meta-grid { display: grid; grid-template-columns: 1fr auto; gap: 0 12px; padding: 4px 12px; border: 1px solid var(--line); border-radius: 12px; background: var(--surface-2); font-variant-numeric: tabular-nums; }
.meta-grid span, .meta-grid b { padding: 7px 0; border-bottom: 1px solid var(--line); }
.meta-grid span:nth-last-of-type(1), .meta-grid b:nth-last-of-type(1) { border-bottom: 0; }
.meta-grid span { color: var(--muted); }
.meta-grid b { color: var(--text); font-weight: 600; text-align: right; }
.section-title { margin: 20px 0 8px; color: var(--muted); font-size: 10.5px; font-weight: 600; text-transform: uppercase; letter-spacing: .08em; }
.relation { display: flex; gap: 9px; align-items: center; padding: 6px 8px; margin: 0 -8px; border-radius: 8px; cursor: pointer; }
.relation:hover { background: var(--hover); }
.relation i { width: 6px; height: 6px; border-radius: 50%; background: var(--accent-2); box-shadow: 0 0 8px rgba(54,211,194,.6); }
.relation span { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #cdd3df; }
.relation small { color: var(--dim); font-size: 10px; }
.coverage-title, .coverage-list-head, .source-title { display: flex; align-items: baseline; justify-content: space-between; }
.coverage-title small, .coverage-list-head small, .source-title small { color: var(--dim); font-size: 10px; font-weight: 400; text-transform: none; letter-spacing: 0; }
.coverage-menu { display: grid; gap: 2px; }
.coverage-row { width: 100%; min-height: 31px; display: grid; grid-template-columns: 7px 1fr auto 12px; gap: 8px; align-items: center; padding: 5px 7px; border: 1px solid transparent; border-radius: 5px; background: transparent; color: inherit; text-align: left; cursor: pointer; }
.coverage-row:hover { background: #313131; }
.coverage-row.active { border-color: #444444; background: #2e2e2e; }
.coverage-row i { width: 7px; height: 7px; border-radius: 2px; }
.coverage-row { width: 100%; min-height: 34px; display: grid; grid-template-columns: 8px 1fr auto 12px; gap: 10px; align-items: center; padding: 5px 9px; border: 1px solid transparent; border-radius: 9px; background: transparent; color: #d6dbe6; text-align: left; cursor: pointer; }
.coverage-row:hover { background: var(--hover); }
.coverage-row.active { border-color: var(--accent-line); background: var(--accent-soft); }
.coverage-row i { width: 8px; height: 8px; border-radius: 3px; box-shadow: 0 0 10px currentColor; }
.coverage-row span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.coverage-row small { color: #8e8e8e; font-variant-numeric: tabular-nums; }
.coverage-row b { color: #767676; font-size: 13px; font-weight: 400; text-align: center; }
.coverage-list-head { display: flex; justify-content: space-between; align-items: baseline; margin: 14px 0 6px; color: #dedede; font-weight: 600; }
.coverage-file-list { position: relative; height: min(39vh, 350px); min-height: 150px; overflow: auto; border: 1px solid #3b3b3b; border-radius: 6px; outline: none; background: #222222; scrollbar-color: #545454 transparent; scrollbar-width: thin; }
.coverage-file-list:focus { border-color: #808080; box-shadow: 0 0 0 2px rgba(75,145,204,.11); }
.coverage-row small { color: var(--dim); font-variant-numeric: tabular-nums; }
.coverage-row b { color: var(--dim); font-size: 14px; font-weight: 400; text-align: center; }
.coverage-list-head { margin: 18px 0 8px; color: var(--text); font-weight: 600; }
.coverage-file-list { position: relative; height: min(39vh, 350px); min-height: 150px; overflow: auto; border: 1px solid var(--line); border-radius: 12px; outline: none; background: rgba(0,0,0,.25); }
.coverage-file-list:focus { border-color: var(--accent-line); box-shadow: 0 0 0 3px rgba(141,125,255,.14); }
.coverage-list-spacer { width: 1px; }
.coverage-list-window { position: absolute; inset: 0 0 auto; }
.coverage-file { width: 100%; height: 43px; display: grid; grid-template-columns: 3px minmax(0,1fr) auto; gap: 8px; align-items: stretch; padding: 5px 7px; border: 0; border-bottom: 1px solid #2f2f2f; background: transparent; color: inherit; text-align: left; cursor: pointer; }
.coverage-file:hover { background: #2b2b2b; }
.coverage-file.active { background: #373737; box-shadow: inset 2px 0 #b2b2b2; }
.coverage-file { width: 100%; height: 43px; display: grid; grid-template-columns: 3px minmax(0,1fr) auto; gap: 10px; align-items: stretch; padding: 5px 10px; border: 0; border-bottom: 1px solid var(--line); background: transparent; color: inherit; text-align: left; cursor: pointer; }
.coverage-file:hover { background: var(--hover); }
.coverage-file.active { background: var(--accent-soft); box-shadow: inset 2px 0 var(--accent); }
.coverage-file > i { border-radius: 2px; }
.coverage-file > span { min-width: 0; align-self: center; }
.coverage-file strong, .coverage-file small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.coverage-file strong { color: #dedede; font-weight: 500; }
.coverage-file small { color: #747474; font-size: 10px; }
.coverage-file em { align-self: center; color: #7a7a7a; font-size: 9px; font-style: normal; font-variant-numeric: tabular-nums; }
.coverage-empty { padding: 18px 10px; color: #777777; text-align: center; }
.source-title { display: flex; justify-content: space-between; align-items: baseline; }
.source-title small { color: #767676; font-weight: 400; }
.code-preview { position: relative; height: min(46vh,430px); min-height: 220px; overflow: auto; border: 1px solid #3a3a3a; border-radius: 3px; outline: none; background: #171717; color: #afafaf; font: 10px/16px "SFMono-Regular", Consolas, monospace; tab-size: 2; scrollbar-color: #545454 transparent; scrollbar-width: thin; }
.code-preview:focus { border-color: #626262; box-shadow: 0 0 0 1px rgba(70,154,123,.15); }
.coverage-file strong { color: var(--text); font-weight: 500; }
.coverage-file small { color: var(--dim); font-size: 10px; }
.coverage-file em { align-self: center; color: var(--dim); font-size: 10px; font-style: normal; font-variant-numeric: tabular-nums; }
.coverage-empty { padding: 18px 10px; color: var(--dim); text-align: center; }
.code-preview { position: relative; height: min(46vh, 430px); min-height: 220px; overflow: auto; border: 1px solid var(--line); border-radius: 12px; outline: none; background: #06080c; color: #b6bfd0; font: 10.5px/16px var(--mono); tab-size: 2; }
.code-preview:focus { border-color: var(--accent-line); box-shadow: 0 0 0 3px rgba(141,125,255,.12); }
.source-spacer { position: relative; min-width: 100%; }
.source-window { position: absolute; inset: 0 auto auto 0; min-width: 100%; margin: 0; padding: 0; color: inherit; font: inherit; line-height: 16px; white-space: pre; }
.source-line { display: block; height: 16px; padding-right: 10px; }
.source-line::before { content: attr(data-line); position: sticky; left: 0; display: inline-block; width: 35px; margin-right: 10px; padding-right: 7px; border-right: 1px solid #303030; background: #171717; color: #555555; text-align: right; user-select: none; }
.tok-plain { color: #aab5af; }
.tok-comment { color: #64726b; font-style: italic; }
.tok-string { color: #89b88f; }
.tok-number { color: #83a8d8; }
.tok-keyword { color: #d8a75b; }
.tok-type { color: #79b8d8; }
.tok-function { color: #d6cf8b; }
.tok-property { color: #b397d5; }
.tok-operator { color: #8d9c95; }
.tok-constant { color: #d98585; }
.tok-tag { color: #65b9aa; }
.result-row { display: grid; grid-template-columns: 3px 1fr; gap: 8px; padding: 7px 3px; border-bottom: 1px solid #313131; cursor: pointer; }
.result-row:hover { background: #2c2c2c; }
.result-row i { background: var(--yellow); border-radius: 2px; }
.result-row strong { display: block; color: #dbdbdb; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.result-row small { display: block; color: #777777; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.source-window { position: absolute; inset: 0 auto auto 0; min-width: 100%; margin: 0; padding: 6px 0 0; color: inherit; font: inherit; line-height: 16px; white-space: pre; }
.source-line { display: block; height: 16px; padding-right: 12px; }
.source-line::before { content: attr(data-line); position: sticky; left: 0; display: inline-block; width: 40px; margin-right: 12px; padding-right: 8px; border-right: 1px solid var(--line); background: #06080c; color: #434b5c; text-align: right; user-select: none; }
.tok-plain { color: #b6bfd0; }
.tok-comment { color: #5c667a; font-style: italic; }
.tok-string { color: #8fd694; }
.tok-number { color: #f5a97f; }
.tok-keyword { color: #c69cff; }
.tok-type { color: #6ec6ff; }
.tok-function { color: #7aa7ff; }
.tok-property { color: #5fd3c6; }
.tok-operator { color: #8f9ab0; }
.tok-constant { color: #ff8fa3; }
.tok-tag { color: #5fd3c6; }
.result-row { display: grid; grid-template-columns: 3px minmax(0,1fr); gap: 10px; min-width: 0; margin: 0 -6px; padding: 8px 8px; border-radius: 9px; cursor: pointer; }
.result-row:hover { background: var(--hover); }
.result-row i { border-radius: 2px; background: var(--yellow); }
.result-row strong { display: block; color: var(--text); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.result-row small { display: block; color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.breadcrumb { position: absolute; left: 18px; bottom: 18px; max-width: calc(100% - 170px); display: flex; align-items: center; gap: 10px; padding: 7px 12px; border: 1px solid #414141; border-radius: 9px; background: #1c1c1ced; color: #ababab; box-shadow: 0 4px 16px #0002; }
/* Floating canvas chrome */
.breadcrumb { position: absolute; left: 16px; bottom: 16px; max-width: calc(100% - 190px); display: flex; align-items: center; gap: 10px; padding: 8px 14px 8px 8px; border-radius: 12px; color: var(--muted); }
#breadcrumbText { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.breadcrumb button { border: 0; padding: 2px; background: transparent; cursor: pointer; color: #b2b2b2; }
.fps { position: absolute; right: 22px; bottom: 65px; color: #a2a2a2; font: 10px monospace; }
.legend { position: absolute; right: 9px; top: 10px; display: none; width: 190px; padding: 6px; border: 1px solid #464646; border-radius: 6px; background: rgba(31,33,32,.94); box-shadow: 0 7px 25px rgba(0,0,0,.3); }
.legend.open { display: grid; gap: 2px; }
.legend button { width: 100%; display: flex; justify-content: space-between; gap: 12px; padding: 5px 6px; border: 0; border-radius: 4px; color: inherit; background: transparent; text-align: left; cursor: pointer; }
.legend button:hover { background: #343434; }
.legend button b { color: #909090; font-weight: 500; }
.legend i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: 6px; }
.tooltip { position: absolute; z-index: 3; max-width: 310px; padding: 7px 9px; pointer-events: none; border: 1px solid #515151; border-radius: 5px; background: rgba(27,29,28,.96); box-shadow: 0 5px 20px rgba(0,0,0,.35); color: #dedede; }
.camera-help { position: absolute; z-index: 2; left: 18px; top: 18px; width: fit-content; display: flex; gap: 13px; align-items: center; padding: 8px 12px; pointer-events: none; border: 1px solid rgba(110,118,113,.28); border-radius: 8px; background: rgba(27,29,28,.92); color: #a2a2a2; font-size: 10px; }
.camera-help[hidden] { display: none; }
.camera-help { pointer-events: auto; flex-wrap: wrap; max-width: calc(100% - 36px); }
.camera-help button { border: 1px solid #494949; border-radius: 5px; background: #292929; padding: 4px 7px; color: #ccc; cursor: pointer; font-size: 10px; }
.camera-help button:hover { background: #414141; color: #fff; }
.camera-help b { color: #b9b9b9; font-weight: 600; }
.tooltip small { display: block; color: #888888; }
.progress-card { position: absolute; z-index: 4; left: 50%; top: 22px; transform: translateX(-50%); width: min(420px, calc(100% - 30px)); padding: 12px 14px; border: 1px solid #474747; border-radius: 8px; background: rgba(37,40,38,.97); box-shadow: 0 12px 35px rgba(0,0,0,.34); }
.progress-copy { display: flex; justify-content: space-between; gap: 15px; }
.progress-copy span, .progress-count { color: #888888; }
.progress-track { height: 3px; margin: 10px 0 5px; overflow: hidden; background: #353535; border-radius: 2px; }
.progress-track i { display: block; height: 100%; width: 5%; background: linear-gradient(90deg,#8b8b8b,#818181); transition: width .18s ease; }
.progress-count { font-size: 10px; }
.empty-state { position: absolute; inset: 0; display: grid; place-content: center; text-align: center; gap: 4px; color: #c7c7c7; }
.empty-state span { color: #7a7a7a; }
.breadcrumb button { width: 26px; height: 26px; display: grid; place-items: center; border: 1px solid var(--line); border-radius: 8px; padding: 0; background: var(--surface-2); color: var(--text); cursor: pointer; }
.breadcrumb button:hover { border-color: var(--accent-line); background: var(--accent-soft); }
.fps { position: absolute; right: 20px; bottom: 66px; padding: 3px 8px; border-radius: 6px; background: rgba(0,0,0,.45); color: var(--accent-2); font: 10px var(--mono); }
.canvas-controls { position: absolute; right: 16px; bottom: 16px; display: flex; gap: 2px; padding: 4px; border-radius: 12px; }
.canvas-controls button { display: grid; place-items: center; width: 32px; height: 30px; border: 0; border-radius: 8px; background: transparent; color: var(--muted); font-size: 18px; cursor: pointer; }
.canvas-controls button:hover { background: var(--hover); color: #fff; }
.canvas-controls svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; }
.legend { position: absolute; right: 12px; top: 12px; display: none; width: 210px; padding: 6px; border-radius: 12px; }
.legend.open { display: grid; gap: 2px; animation: rise .18s ease-out; }
.legend button { width: 100%; display: flex; justify-content: space-between; gap: 12px; padding: 7px 8px; border: 0; border-radius: 8px; background: transparent; color: var(--text); text-align: left; cursor: pointer; }
.legend button:hover { background: var(--hover); }
.legend button b { color: var(--dim); font-weight: 500; font-variant-numeric: tabular-nums; }
.legend i { display: inline-block; width: 8px; height: 8px; margin-right: 8px; border-radius: 3px; }
.tooltip { position: absolute; z-index: 3; max-width: 320px; padding: 8px 11px; pointer-events: none; border-radius: 10px; color: #fff; font-weight: 500; }
.tooltip small { display: block; margin-top: 1px; color: var(--muted); font-weight: 400; }
.camera-help { position: absolute; z-index: 2; left: 16px; top: 16px; width: fit-content; max-width: calc(100% - 32px); display: flex; flex-wrap: wrap; gap: 12px; align-items: center; padding: 6px 12px 6px 6px; border-radius: 12px; color: var(--dim); font-size: 10.5px; }
.camera-help button { padding: 5px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-2); color: var(--text); font-size: 10.5px; cursor: pointer; }
.camera-help button:hover { border-color: var(--accent-line); background: var(--accent-soft); }
.camera-help b { color: var(--muted); font-weight: 600; }
@keyframes rise { from { opacity: 0; transform: translateY(4px); } }
dialog { width: min(560px, calc(100vw - 30px)); max-height: calc(100dvh - 32px); padding: 0; border: 1px solid #484848; border-radius: 18px; color: var(--text); background: linear-gradient(145deg,#2a2a2a,#1e1e1e); box-shadow: 0 32px 120px #0009, inset 0 1px #ffffff0c; }
dialog::backdrop { background: rgba(8,10,9,.7); backdrop-filter: blur(5px); }
.progress-card { position: absolute; z-index: 4; left: 50%; top: 20px; width: min(440px, calc(100% - 30px)); padding: 18px 44px 16px 18px; border-radius: 16px; transform: translateX(-50%); animation: drop .25s ease-out; }
@keyframes drop { from { opacity: 0; transform: translate(-50%, -8px); } }
.progress-copy { display: flex; flex-direction: column; gap: 3px; }
.progress-copy strong { color: #fff; font-size: 13.5px; font-weight: 600; letter-spacing: -.01em; }
.progress-copy span, .progress-count { color: var(--muted); }
#progressMessage { max-height: 100px; overflow: auto; overflow-wrap: anywhere; }
.progress-track { position: relative; height: 4px; margin: 13px 0 7px; overflow: hidden; border-radius: 999px; background: rgba(255,255,255,.07); }
.progress-track i { position: relative; display: block; height: 100%; width: 5%; overflow: hidden; border-radius: inherit; background: var(--accent-grad); box-shadow: 0 0 12px rgba(95,157,255,.6); transition: width .25s ease; }
.progress-track i::after { content: ""; position: absolute; inset: 0; background: linear-gradient(90deg, transparent, rgba(255,255,255,.55), transparent); transform: translateX(-100%); animation: shimmer 1.4s ease-in-out infinite; }
@keyframes shimmer { to { transform: translateX(100%); } }
.progress-count { font-size: 10.5px; font-variant-numeric: tabular-nums; }
.progress-dismiss { position: absolute; right: 8px; top: 8px; }
.progress-action { display: inline-grid; place-items: center; width: fit-content; height: 36px; margin-top: 14px; text-decoration: none; }
.empty-state { position: absolute; inset: 0; display: grid; place-content: center; gap: 4px; text-align: center; color: var(--text); }
.empty-state span { color: var(--muted); }
/* Dialogs */
dialog {
width: min(580px, calc(100vw - 30px));
max-height: calc(100dvh - 32px);
padding: 0;
border: 1px solid var(--line-strong);
border-radius: 22px;
color: var(--text);
background:
radial-gradient(600px 240px at 0% 0%, rgba(141,125,255,.16), transparent 70%),
radial-gradient(500px 260px at 100% 100%, rgba(54,211,194,.08), transparent 70%),
#0e1118;
box-shadow: 0 40px 140px rgba(0,0,0,.7), inset 0 1px rgba(255,255,255,.08);
}
dialog[open] { animation: pop .22s cubic-bezier(.2,.9,.3,1.2); }
@keyframes pop { from { opacity: 0; transform: scale(.97) translateY(6px); } }
dialog::backdrop { background: rgba(3,4,8,.62); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); }
.dialog-shell { padding: 30px; }
.dialog-head { display: flex; align-items: start; justify-content: space-between; margin-bottom: 17px; }
.dialog-head small { color: #b6b6b6; font-size: 9px; font-weight: 600; letter-spacing: .18em; }
.dialog-head h1 { margin: 8px 0 0; font-size: 27px; font-weight: 550; letter-spacing: -.04em; }
.close-button { width: 30px; height: 30px; flex-shrink: 0; display: grid; place-items: center; padding: 0; border: 0; border-radius: 6px; background: transparent; color: #919191; cursor: pointer; }
.close-button svg { display: block; width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; }
.close-button:hover { background: #313131; color: #fff; }
.source-card { width: 100%; margin-top: 12px; padding: 18px; border: 1px solid #454545; border-radius: 12px; background: #ffffff03; color: inherit; text-align: left; }
button.source-card { display: flex; align-items: center; gap: 12px; cursor: pointer; }
button.source-card:hover { border-color: #787878; background: #2f2f2f; }
.source-card > b { margin-left: auto; color: #828282; font-size: 17px; }
.source-icon { width: 34px; height: 34px; display: grid; place-items: center; border: 1px solid #4d4d4d; border-radius: 7px; color: #a9a9a9; background: #242424; font-size: 18px; }
.dialog-head { display: flex; align-items: start; justify-content: space-between; margin-bottom: 18px; }
.dialog-head small { background: var(--accent-grad); -webkit-background-clip: text; background-clip: text; color: transparent; font-size: 10px; font-weight: 700; letter-spacing: .2em; }
.dialog-head h1 { margin: 8px 0 0; font-size: 28px; font-weight: 700; letter-spacing: -.045em; color: #f4f6fb; }
.dialog-description { margin: 8px 0 2px; color: var(--muted); font-size: 13px; }
.close-button { width: 32px; height: 32px; flex-shrink: 0; display: grid; place-items: center; padding: 0; border: 1px solid transparent; border-radius: 10px; background: transparent; color: var(--dim); cursor: pointer; }
.close-button svg { display: block; width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }
.close-button:hover { border-color: var(--line); background: var(--hover); color: #fff; }
.source-card { width: 100%; margin-top: 12px; padding: 18px; border: 1px solid var(--line); border-radius: 16px; background: rgba(255,255,255,.025); color: inherit; text-align: left; transition: border-color .18s, background-color .18s, transform .18s, box-shadow .18s; }
button.source-card { display: flex; align-items: center; gap: 14px; cursor: pointer; }
button.source-card:hover { transform: translateY(-1px); border-color: var(--accent-line); background: rgba(141,125,255,.07); box-shadow: 0 10px 30px rgba(0,0,0,.3); }
.source-card:focus-within:not(button) { border-color: rgba(255,255,255,.14); }
.source-card > b { margin-left: auto; color: var(--dim); font-size: 18px; transition: transform .18s, color .18s; }
button.source-card:hover > b { transform: translateX(3px); color: #fff; }
.source-icon { width: 40px; height: 40px; display: grid; place-items: center; border: 1px solid rgba(141,125,255,.35); border-radius: 12px; color: #d9d2ff; background: linear-gradient(135deg, rgba(141,125,255,.22), rgba(54,211,194,.1)); }
.source-icon svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.source-card strong, .source-card small { display: block; }
.source-card small { margin-top: 2px; color: #808080; font-weight: 400; }
.github-card { display: grid; grid-template-columns: 34px 1fr; gap: 0 12px; }
.github-row { grid-column: 1 / -1; display: flex; gap: 7px; margin-top: 12px; }
.github-row input { flex: 1; min-width: 0; height: 34px; padding: 0 10px; border: 1px solid #474747; border-radius: 6px; outline: none; color: #e3e3e3; background: #212121; }
.github-row input:focus { border-color: #979797; }
.github-row button { border: 1px solid #c7c7c7; border-radius: 7px; padding: 0 14px; color: #262626; background: #cdcdcd; cursor: pointer; font-weight: 600; }
.github-account { grid-column: 1 / -1; margin: 8px 0 0; color: #808080; font-size: 11px; }
.github-account a, .github-account button { padding: 0; border: 0; background: none; color: #cfcfcf; font: inherit; text-decoration: underline; cursor: pointer; }
.progress-action { display: grid; place-items: center; width: fit-content; margin-top: 12px; text-decoration: none; }
.form-error { grid-column: 1 / -1; min-height: 16px; margin: 5px 0 -5px; color: var(--danger); font-size: 11px; }
.dialog-foot { margin: 15px 2px 0; color: #707070; font-size: 10px; }
.settings-dialog { width: min(460px, calc(100vw - 30px)); }
.source-card strong { color: #f1f3f8; font-size: 13.5px; font-weight: 600; }
.source-card small { margin-top: 2px; color: var(--muted); font-weight: 400; }
.github-card { display: grid; grid-template-columns: 40px 1fr; gap: 0 14px; align-items: center; }
.github-row { grid-column: 1 / -1; display: flex; gap: 8px; margin-top: 14px; }
.github-row input { flex: 1; min-width: 0; height: 42px; padding: 0 13px; border: 1px solid var(--line); border-radius: 11px; outline: none; color: var(--text); background: rgba(0,0,0,.35); font-family: var(--mono); font-size: 12px; transition: border-color .15s, box-shadow .15s; }
.github-row input::placeholder { color: var(--dim); }
.github-row input:focus { border-color: var(--accent-line); box-shadow: 0 0 0 4px rgba(141,125,255,.14); }
.github-row button { height: 42px; }
.github-account { grid-column: 1 / -1; margin: 10px 0 0; color: var(--muted); font-size: 11.5px; }
.github-account a, .github-account button { padding: 0; border: 0; background: none; color: #cfc8ff; font: inherit; text-decoration: none; border-bottom: 1px solid rgba(207,200,255,.35); cursor: pointer; }
.github-account a:hover, .github-account button:hover { color: #fff; border-bottom-color: #fff; }
.form-error { grid-column: 1 / -1; min-height: 16px; margin: 6px 0 -6px; color: var(--danger); font-size: 11px; }
.dialog-foot { display: flex; gap: 8px; align-items: center; margin: 16px 2px 0; color: var(--dim); font-size: 11px; line-height: 1.6; }
.dialog-foot::before { content: ""; flex-shrink: 0; width: 6px; height: 6px; border-radius: 50%; background: var(--green); box-shadow: 0 0 8px rgba(54,211,160,.7); }
.settings-dialog { width: min(480px, calc(100vw - 30px)); }
.settings-shell { padding: 28px; }
.settings-status { display: flex; align-items: center; justify-content: space-between; margin: -3px 0 13px; padding: 9px 11px; border: 1px solid #404040; border-radius: 7px; background: #222222; }
.settings-status span { display: flex; align-items: center; gap: 7px; color: #dddddd; font-weight: 600; }
.settings-status i { width: 7px; height: 7px; border-radius: 50%; background: var(--green); box-shadow: 0 0 0 3px rgba(41,201,151,.1); }
.settings-status small { color: #787878; font-size: 9px; letter-spacing: .07em; }
.settings-grid { padding: 12px 2px; }
.settings-note { display: grid; grid-template-columns: 32px 1fr; gap: 10px; align-items: start; margin-top: 14px; padding: 11px; border: 1px solid #383838; border-radius: 7px; background: #2a2a2a; }
.settings-note-icon { width: 32px; height: 32px; display: grid; place-items: center; border-radius: 6px; color: #a3a3a3; background: #222222; font-size: 16px; }
.settings-status { display: flex; align-items: center; justify-content: space-between; margin: -2px 0 8px; padding: 10px 12px; border: 1px solid var(--line); border-radius: 12px; background: rgba(0,0,0,.25); }
.settings-status span { display: flex; align-items: center; gap: 8px; color: var(--text); font-weight: 600; }
.settings-status i { width: 7px; height: 7px; border-radius: 50%; background: var(--green); box-shadow: 0 0 8px rgba(54,211,160,.7); }
.settings-status small { color: var(--dim); font-size: 10px; letter-spacing: .08em; }
.preference-row { display: flex; gap: 16px; justify-content: space-between; align-items: center; padding: 16px 0; border-bottom: 1px solid var(--line); }
.preference-row label { color: var(--text); font-weight: 500; }
.preference-row small { display: block; margin-top: 3px; color: var(--muted); font-size: 11px; font-weight: 400; }
.preference-row select { padding: 8px 10px; border: 1px solid var(--line-strong); border-radius: 10px; background: rgba(0,0,0,.35); color: var(--text); }
.switch { appearance: none; flex-shrink: 0; width: 38px; height: 22px; margin: 0; padding: 2px; border: 1px solid var(--line-strong); border-radius: 999px; background: rgba(255,255,255,.06); cursor: pointer; transition: background .2s, border-color .2s; }
.switch::before { content: ""; display: block; width: 16px; height: 16px; border-radius: 50%; background: #aab2c3; transition: transform .2s cubic-bezier(.3,1.3,.5,1), background .2s; }
.switch:checked { border-color: transparent; background: var(--accent-grad); }
.switch:checked::before { background: #fff; transform: translateX(16px); }
.shortcut-guide { margin-top: 18px; color: var(--muted); font-size: 11px; }
.shortcut-guide > span { display: block; margin-bottom: 10px; }
.shortcut-guide div { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; }
.settings-note { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; margin-top: 16px; padding: 12px; border: 1px solid var(--line); border-radius: 14px; background: rgba(255,255,255,.025); }
.settings-note-icon { width: 34px; height: 34px; display: grid; place-items: center; border-radius: 10px; color: #d9d2ff; background: var(--accent-soft); font-size: 16px; }
.settings-note p { margin: 0; }
.settings-note strong, .settings-note small { display: block; }
.settings-note strong { color: #dbdbdb; font-size: 11px; }
.settings-note small { margin-top: 3px; color: #7a7a7a; line-height: 1.45; }
.settings-actions { display: flex; justify-content: flex-end; margin-top: 17px; }
.dialog-button { min-width: 84px; height: 36px; padding: 0 18px; border: 1px solid #c7c7c7; border-radius: 8px; color: #262626; background: #cdcdcd; cursor: pointer; font-weight: 600; }
.dialog-button:hover, .github-row button:hover { background: #dedede; }
.settings-note strong { color: var(--text); font-size: 12px; }
.settings-note small { margin-top: 3px; color: var(--muted); line-height: 1.5; }
.settings-actions { display: flex; justify-content: flex-end; margin-top: 18px; }
.dialog-button { min-width: 90px; }
.brand { display: flex; gap: 10px; align-items: center; padding-right: 11px; margin-right: 4px; border-right: 1px solid var(--line); color: #dbdbdb; font-size: 14px; font-weight: 600; letter-spacing: -.035em; line-height: 1.2; }
.brand svg { width: 21px; height: 21px; fill: #bbbbbb; }
.brand-sub { display: block; margin-top: 3px; font-size: 8px; letter-spacing: .25em; font-weight: 500; color: #999999; }
.view-switch, .metric-switch { padding: 2px; border: 1px solid #363636; border-radius: 10px; background: #15151566; gap: 3px; }
.filter-wrap { width: min(240px, 20vw); }
.filter-wrap input { height: 30px; border-radius: 9px; border-color: #3d3d3d; background: #181818; padding-left: 13px; }
.filter-wrap kbd { top: 6px; border: 1px solid #444444; border-radius: 3px; padding: 0 4px; }
.filter-wrap input:focus, .github-row input:focus { border-color: #a4a4a4; box-shadow: 0 0 0 3px #a4a4a415; }
.scene-stats { min-width: 0; margin-right: 8px; font-size: 11px; }
.scene-stats small { color: #8e8e8e; margin-top: 3px; }
.live-dot { flex-shrink: 0; margin-left: 5px; }
.panel-hint, .result-summary { color: #a2a2a2; line-height: 1.7; }
.meta-grid { gap: 5px 10px; padding: 10px 0; font-variant-numeric: tabular-nums; }
.meta-grid span, .entity-title small { color: #9a9a9a; }
.section-title { margin-top: 16px; }
.coverage-menu { gap: 2px; }
.coverage-row { min-height: 29px; border-radius: 6px; padding: 4px 7px; }
.coverage-row:hover, .relation:hover, .result-row:hover { background: #ffffff06; }
.coverage-row.active { background: #333333; border-color: #525252; }
.coverage-title small, .coverage-list-head small, .coverage-file small, .result-row small { color: #989898; }
.coverage-file-list { border-radius: 9px; border-color: #3f3f3f; background: #191919; }
.coverage-file.active { background: #343434; box-shadow: inset 2px 0 #bbbbbb; }
.coverage-file-list:focus { border-color: #aaaaaa; box-shadow: 0 0 0 2px #a4a4a415; }
.result-row { min-width: 0; border-radius: 7px; padding: 9px 8px; grid-template-columns: 3px minmax(0,1fr); }
.code-preview { border-radius: 9px; }
.canvas-controls { position: absolute; right: 18px; bottom: 18px; display: flex; padding: 4px; gap: 3px; border: 1px solid #414141; border-radius: 10px; background: #1c1c1cf5; box-shadow: 0 4px 16px #0002; }
.canvas-controls button { display: grid; place-items: center; width: 30px; height: 28px; background: transparent; color: #c9c9c9; border: 0; border-radius: 6px; font-size: 18px; cursor: pointer; }
.canvas-controls button:hover { background: #353535; }
.canvas-controls svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; }
.progress-card { border-radius: 12px; border-color: #4c4c4c; padding: 18px; padding-right: 40px; background: #262626f5; }
.progress-copy { flex-direction: column; gap: 4px; }
#progressMessage { overflow-wrap: anywhere; max-height: 100px; overflow: auto; }
.progress-dismiss { position: absolute; right: 5px; top: 5px; }
.dialog-description { margin: 9px 0 3px; color: #a3a3a3; font-size: 12px; }
.source-icon { width: 36px; height: 36px; border-color: #4c4c4c; background: #313131; color: #c5c5c5; }
.source-icon svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.source-card small { color: #a1a1a1; }
.dialog-foot { color: #9a9a9a; line-height: 1.7; }
.github-row input { height: 39px; }
.settings-status { border-color: #414141; background: #1b1b1b; margin-bottom: 16px; }
.settings-note { background: #ffffff03; border-color: #3d3d3d; border-radius: 10px; }
.settings-note small { color: #a0a0a0; }
.preference-row { display: flex; gap: 16px; justify-content: space-between; align-items: center; padding: 16px 0; border-bottom: 1px solid var(--line-soft); }
.preference-row label { color: #dbdbdb; font-weight: 500; }
.preference-row small { display: block; font-size: 10px; font-weight: 400; color: #a0a0a0; margin-top: 3px; }
.preference-row select { padding: 7px 9px; border: 1px solid #4b4b4b; border-radius: 7px; background: #1e1e1e; color: #d0d0d0; font: inherit; }
.switch { appearance: none; flex-shrink: 0; width: 34px; height: 20px; border: 1px solid #575757; border-radius: 20px; background: #323232; cursor: pointer; margin: 0; padding: 2px; }
.switch::before { content: ''; display: block; width: 14px; height: 14px; border-radius: 50%; background: #9b9b9b; transition: transform .15s; }
.switch:checked { background: #b1b1b1; border-color: #b1b1b1; }
.switch:checked::before { background: #272727; transform: translateX(14px); }
.shortcut-guide { color: #a0a0a0; margin-top: 18px; font-size: 10px; }
.shortcut-guide > span { display: block; margin-bottom: 10px; }
.shortcut-guide div { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
kbd { font: 10px/1.5 ui-monospace, monospace; border: 1px solid #4b4b4b; border-radius: 4px; padding: 1px 5px; color: #d2d2d2; background: #1d1d1d; }
button:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible, [role="tab"]:focus-visible { outline: 2px solid #bbbbbb; outline-offset: 3px; }
button:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible, [role="tab"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
html[data-input-method="pointer"] button:focus { outline: none; }
button { transition: background-color .15s, border-color .15s, color .15s; }
button:active { filter: brightness(.93); }
::selection { background: #67676766; color: #f1f1f1; }
* { scrollbar-width: thin; scrollbar-color: #484848 transparent; }
@media (max-width: 1250px) { .scene-stats { display: none; } }
@media (max-width: 1250px) { .scene-stats { display: none; } }
@media (max-width: 900px) {
:root { --panel-width: 250px; }
.metric span, .scene-stats { display: none; }
.filter-wrap { width: 150px; }
.brand > span { display: none; }
.brand { padding-right: 10px; margin-right: 0; }
:root { --panel-width: 260px; }
.metric span, .brand-name { display: none; }
.brand { padding-right: 10px; }
.filter-wrap { width: 160px; }
.toolbar { gap: 4px; padding: 0 10px; }
}
@media (max-width: 670px) {
:root { --panel-width: 0px; --toolbar-height: 46px; }
.workspace { grid-template-columns: 1fr; grid-template-rows: minmax(160px,1fr) minmax(150px,28%); }
:root { --panel-width: 0px; }
.workspace { grid-template-columns: 1fr; grid-template-rows: minmax(160px,1fr) minmax(150px,30%); }
.inspector { display: block; border-left: 0; border-top: 1px solid var(--line); }
.tabs { height: 36px; }
.panel { height: calc(100% - 36px); padding: 10px 12px; }
.separator, #layersButton, #codeButton, .palette { display: none; }
.filter-wrap { width: auto; flex: 1; min-width: 65px; }
.brand, .metric-switch, #mapButton, .github-link, .toolbar-spacer { display: none; }
.tool { width: 30px; min-width: 30px; }
.tabs { height: 40px; }
.tab { height: 40px; }
.panel { height: calc(100% - 40px); padding: 10px 12px; }
.separator, #layersButton, #codeButton, .palette, .brand, .metric-switch, #mapButton, .github-link, .toolbar-spacer { display: none; }
.filter-wrap { flex: 1; width: auto; min-width: 65px; }
.dialog-shell { padding: 22px; }
.camera-help { gap: 8px; font-size: 9px; left: 10px; top: 10px; }
.welcome { padding: 20px; }
.welcome-features { display: none; }
.camera-help { left: 10px; top: 10px; gap: 8px; font-size: 9.5px; }
.breadcrumb { left: 10px; bottom: 10px; }
.canvas-controls { right: 10px; bottom: 10px; }
}