@cadcrawl/cad-browser 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/client/assets/{index-HFIcPO3j.js → index-B37D4sOF.js} +15 -15
- package/dist/client/assets/index-CQQ62v1M.css +1 -0
- package/dist/client/index.html +2 -2
- package/package.json +1 -1
- package/src/analyzer.js +34 -0
- package/src/file-types.js +4 -1
- package/src/main.jsx +35 -12
- package/src/project-store.js +1 -1
- package/src/styles.css +46 -7
- package/dist/client/assets/index-C1XW2UmF.css +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{color:#252623;background:#f3f2ee;font-family:Segoe UI Variable Text,Segoe UI,sans-serif;font-size:14px;font-synthesis:none;--paper: #fbfaf7;--panel: #f5f4ef;--sidebar-bg: #efeee9;--ink: #252623;--muted: #71736d;--faint: #9a9c96;--line: #deddd7;--line-soft: #e9e8e2;--accent: #2f6657;--accent-soft: #e4eee9;--sidebar: 250px;--inspector: 360px}*{box-sizing:border-box}html,body,#root{min-width:1024px;min-height:100%;margin:0}body{overflow:hidden}button,input,select{font:inherit}button,select{color:inherit}button:focus-visible,input:focus-visible,select:focus-visible,article:focus-visible{outline:2px solid rgba(47,102,87,.45);outline-offset:2px}.app-shell{height:100vh;display:grid;grid-template:56px calc(100vh - 56px) / var(--sidebar) minmax(480px,1fr) var(--inspector);background:var(--panel)}.app-shell.sidebar-collapsed{grid-template-columns:0 minmax(480px,1fr) var(--inspector)}.topbar{grid-column:1 / -1;z-index:10;display:grid;grid-template-columns:minmax(270px,1fr) minmax(360px,620px) minmax(270px,1fr);align-items:center;padding:0 14px;background:#fbfaf7fa;border-bottom:1px solid var(--line)}.brand,.top-actions,.search,.status-pill{display:flex;align-items:center}.brand{gap:9px;font-size:14px;font-weight:650}.brand-mark{display:grid;place-items:center;width:30px;height:30px;color:#fff;background:#2b2d29;border-radius:7px}.project-chip{max-width:180px;padding:4px 8px;overflow:hidden;border-radius:5px;background:#efede7;color:var(--muted);font-size:12px;font-weight:500;text-overflow:ellipsis;white-space:nowrap}.search{height:36px;gap:9px;padding:0 10px;color:var(--muted);background:#efeee9;border:1px solid transparent;border-radius:7px}.search:focus-within{background:#fff;border-color:#bbbdb6;box-shadow:0 0 0 3px #2f665714}.search input{flex:1;min-width:0;border:0;outline:0;background:transparent;color:var(--ink);font-size:13px}.search input::placeholder{color:#92948e}.search button{display:grid;padding:2px;border:0;background:transparent;cursor:pointer}.search kbd,.shortcut-list kbd{padding:2px 6px;border:1px solid #d8d7d1;border-bottom-color:#c8c7c1;border-radius:4px;background:#f8f7f3;color:#777972;font-family:inherit;font-size:10px;box-shadow:0 1px #d6d5cf}.top-actions{justify-content:flex-end;gap:9px}.icon-button{display:grid;place-items:center;width:32px;height:32px;padding:0;border:1px solid var(--line);border-radius:6px;background:var(--paper);cursor:pointer}.icon-button:hover{background:#efeee9}.icon-button.quiet{border-color:transparent;background:transparent;color:var(--muted)}.status-pill{gap:7px;padding:5px 9px;border:1px solid var(--line);border-radius:999px;color:var(--muted);font-size:11px}.status-dot{width:6px;height:6px;border-radius:50%;background:#4f8968;box-shadow:0 0 0 3px #e2eee6}.sidebar{min-width:0;display:grid;grid-template-rows:46px 1fr 48px;overflow:hidden;background:var(--sidebar-bg);border-right:1px solid var(--line)}.sidebar-collapsed .sidebar{opacity:0;pointer-events:none}.sidebar-head{display:flex;align-items:center;justify-content:space-between;padding:0 10px 0 15px;color:var(--muted);font-size:11px;font-weight:700;letter-spacing:.07em;text-transform:uppercase}.tree-scroll{padding:2px 7px 14px;overflow:auto}.tree-row{width:100%;height:31px;display:flex;align-items:center;padding-left:calc(5px + var(--depth) * 15px);border-radius:5px;color:#54564f;font-size:12px}.tree-row:hover{background:#ffffffa6}.tree-row.active{background:#dcddd7;color:#242521;font-weight:600}.tree-toggle{flex:0 0 22px;height:27px;display:grid;place-items:center;padding:0;border:0;border-radius:4px;background:transparent;color:#898b84;cursor:pointer}.tree-toggle:hover{background:#484a4414;color:#4f514b}.tree-folder{min-width:0;flex:1;height:31px;display:flex;align-items:center;gap:7px;padding:0 7px 0 1px;border:0;background:transparent;color:inherit;font-weight:inherit;text-align:left;cursor:pointer}.tree-label{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tree-count{color:#999b95;font-size:10px;font-variant-numeric:tabular-nums}.sidebar-footer{display:flex;align-items:center;gap:15px;padding:0 15px;border-top:1px solid var(--line);color:#8b8d87;font-size:10px}.sidebar-footer strong{color:#555750}.sidebar-reveal{position:fixed;z-index:15;left:12px;top:68px;display:grid;place-items:center;width:34px;height:34px;padding:0;border:1px solid var(--line);border-radius:7px;background:var(--paper);box-shadow:0 5px 18px #1e201c1f;cursor:pointer}.workspace{min-width:0;overflow:auto;background:var(--paper)}.content-head{position:sticky;z-index:4;top:0;min-height:108px;display:flex;align-items:flex-end;justify-content:space-between;gap:24px;padding:20px 26px 18px;background:#fbfaf7f5;border-bottom:1px solid var(--line-soft);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px)}.content-title{min-width:0}.breadcrumbs{display:flex;align-items:center;gap:4px;margin-bottom:8px;color:var(--faint);font-size:11px}.breadcrumbs button{max-width:170px;padding:0;overflow:hidden;border:0;background:transparent;color:inherit;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.breadcrumbs button:hover{color:var(--ink)}.content-head h1{margin:0;overflow:hidden;color:#292a26;font-family:Georgia,Times New Roman,serif;font-size:25px;font-weight:500;letter-spacing:-.035em;text-overflow:ellipsis;white-space:nowrap}.content-head p{margin:5px 0 0;color:var(--muted);font-size:12px}.browser-toolbar{display:flex;align-items:center;gap:7px}.dropdown,.sort-control,.grid-scale,.view-switcher{height:32px;display:flex;align-items:center;border:1px solid var(--line);border-radius:6px;background:#f3f2ed;color:#686a64}.dropdown{position:relative}.dropdown-trigger{height:30px;display:flex;align-items:center;gap:6px;padding:0 8px;border:0;border-radius:5px;background:transparent;color:#5c5e58;font-size:11px;cursor:pointer}.dropdown-trigger>span{white-space:nowrap}.dropdown.open .dropdown-trigger,.dropdown-trigger:hover{background:#fff;color:var(--ink)}.dropdown-menu{position:absolute;z-index:20;top:calc(100% + 5px);left:0;min-width:100%;padding:5px;border:1px solid #d6d7d1;border-radius:7px;background:#fff;box-shadow:0 10px 28px #1e201c24}.dropdown-menu button{width:100%;min-width:130px;height:30px;display:flex;align-items:center;justify-content:space-between;gap:16px;padding:0 8px;border:0;border-radius:5px;background:transparent;color:#555750;font-size:11px;text-align:left;cursor:pointer}.dropdown-menu button:hover{background:#f1f2ee;color:var(--ink)}.dropdown-menu button.selected{color:#28584b;font-weight:650}.sort-control .dropdown{border:0;background:transparent}.sort-control .dropdown-trigger{padding-left:8px}.sort-control>button,.grid-scale button,.view-switcher button{display:grid;place-items:center;height:30px;padding:0;border:0;border-radius:5px;background:transparent;color:#74766f;cursor:pointer}.sort-control>button{width:29px;border-left:1px solid var(--line);border-radius:0 5px 5px 0}.grid-scale button{width:27px}.grid-scale button:hover:not(:disabled),.view-switcher button:hover,.sort-control>button:hover{background:#fff;color:var(--ink)}.grid-scale button:disabled{opacity:.28;cursor:default}.grid-scale span{min-width:25px;color:#62645e;font-size:10px;text-align:center;font-variant-numeric:tabular-nums}.grid-scale span:after{content:" col";color:#999b95}.view-switcher{padding:1px}.view-switcher button{width:29px;height:28px}.view-switcher button.active{background:#fff;color:var(--ink);box-shadow:0 1px 3px #1e201c1f}.file-grid{display:grid;grid-template-columns:repeat(var(--grid-columns, 4),minmax(0,1fr));gap:15px;padding:20px 26px 40px}.file-card{min-width:0;overflow:hidden;border:1px solid var(--line-soft);border-radius:8px;background:#fff;cursor:default;opacity:0;transform:translateY(4px);animation:reveal .25s ease forwards;animation-delay:var(--delay);transition:border-color .14s,background-color .14s}@keyframes reveal{to{opacity:1;transform:translateY(0)}}.file-card:hover{border-color:#bfc1ba;background:#fdfdfa}.file-card.selected{border-color:#4f7b6c;background:#fbfdfc}.thumb{position:relative;aspect-ratio:4 / 3;display:grid;place-items:center;overflow:hidden;background:#f4f3ef;border-bottom:1px solid var(--line-soft)}.thumb img{width:100%;height:100%;object-fit:contain}.placeholder{width:100%;height:100%;display:grid;place-content:center;justify-items:center;gap:8px;color:#878a83}.placeholder span{font-family:Georgia,Times New Roman,serif;font-size:11px;font-style:italic;font-weight:400}.placeholder.kind-cad{color:#557568;background:#edf2ef}.placeholder.kind-pdf{color:#9a5b49;background:#f4ece8}.placeholder.kind-text{color:#66705e;background:#eef0e9}.placeholder.kind-image{color:#636d82;background:#eceff3}.type-badge{position:absolute;left:8px;top:8px;padding:3px 6px;border:1px solid rgba(40,42,37,.12);border-radius:4px;background:#ffffffe6;color:#64665f;font-size:9px;font-weight:700;letter-spacing:.055em;text-transform:uppercase;-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.processing{position:absolute;inset:auto 8px 8px;display:flex;align-items:center;gap:6px;padding:6px 8px;border-radius:5px;background:#242621d6;color:#fff;font-size:10px}.processing svg,.analysis-note svg,.loading-screen>svg{animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.file-copy{padding:10px 11px 11px}.file-name{overflow:hidden;color:#31322e;font-size:12px;font-weight:620;text-overflow:ellipsis;white-space:nowrap}.file-copy span{display:block;margin-top:4px;overflow:hidden;color:var(--muted);font-size:10px;font-variant-numeric:tabular-nums;text-overflow:ellipsis;white-space:nowrap}.file-grid.density-compact{gap:8px;padding-inline:18px}.file-grid.density-compact .file-copy{padding:7px}.file-grid.density-compact .file-name{font-size:10px}.file-grid.density-compact .file-copy span{font-size:8px}.file-grid.density-compact .type-badge{left:5px;top:5px;padding:2px 4px;font-size:7px}.file-grid.density-micro{gap:5px;padding-inline:13px}.file-grid.density-micro .file-card{border-radius:5px}.file-grid.density-micro .file-copy{padding:5px 6px 6px}.file-grid.density-micro .file-name{font-size:8px}.file-grid.density-micro .file-copy span{display:none}.file-grid.density-micro .type-badge{left:3px;top:3px;padding:1px 3px;border-radius:3px;font-size:6px}.file-list{padding:13px 26px 36px}.file-list .file-card{display:grid;grid-template-columns:88px minmax(0,1fr);margin-bottom:7px}.file-list .thumb{height:68px;aspect-ratio:auto;border:0;border-right:1px solid var(--line-soft)}.file-list .placeholder svg{width:23px}.file-list .placeholder span,.file-list .type-badge{display:none}.file-list .file-copy{align-self:center}.inspector{min-width:0;overflow-y:auto;background:#f8f7f3;border-left:1px solid var(--line)}.empty-inspector{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:36px;color:var(--muted);text-align:center}.inspector-empty-mark{display:grid;place-items:center;width:44px;height:44px;border:1px solid var(--line);border-radius:10px;background:#fff;color:#787a74}.empty-inspector h2{margin:15px 0 5px;color:var(--ink);font-family:Georgia,Times New Roman,serif;font-size:18px;font-weight:400}.empty-inspector p{max-width:250px;margin:0;font-size:12px;line-height:1.55}.shortcut-list{width:210px;display:grid;grid-template-columns:1fr auto;align-items:center;gap:8px 12px;margin-top:22px;color:#7e807a;font-size:11px;text-align:left}.inspector-head{min-height:61px;display:grid;grid-template-columns:36px minmax(0,1fr) 32px;align-items:center;gap:10px;padding:11px 11px 11px 14px;border-bottom:1px solid var(--line-soft)}.inspector-title{min-width:0}.inspector-title strong,.inspector-title span{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.inspector-title strong{color:#2b2c28;font-size:13px;font-weight:650}.inspector-title span{margin-top:3px;color:var(--muted);font-size:11px}.type-icon{display:grid;place-items:center;width:36px;height:36px;border-radius:8px;background:#e8e7e1;color:#656760}.type-icon.kind-cad{background:#e2ece7;color:#46705f}.type-icon.kind-pdf{background:#f0e3dd;color:#9a513d}.type-icon.kind-text{background:#e8ece2;color:#5e6c53}.inspector-preview{position:relative;width:calc(100% - 28px);aspect-ratio:4 / 3;display:grid;place-items:center;margin:14px;padding:0;overflow:hidden;border:1px solid var(--line);border-radius:8px;background:#f0efea;cursor:zoom-in}.inspector-preview:disabled{cursor:default}.inspector-preview img{width:100%;height:100%;object-fit:contain}.inspector-actions{display:grid;grid-template-columns:1.3fr 1fr 34px;gap:7px;margin:-1px 14px 14px}.inspector-actions button{height:34px;display:flex;align-items:center;justify-content:center;gap:7px;padding:0 9px;border:1px solid #cccec7;border-radius:6px;background:#fff;color:#4c4e48;font-size:11px;font-weight:600;cursor:pointer}.inspector-actions button:hover{border-color:#aeb1a9;background:#fdfdfb}.inspector-actions .primary-action{border-color:#315f52;background:#315f52;color:#fff}.inspector-actions .primary-action:hover{background:#274f44}.analysis-note,.error-note{margin:0 14px 13px;padding:10px 11px;border-radius:6px;font-size:12px;line-height:1.45}.analysis-note{display:flex;align-items:center;gap:8px;background:#e5eee9;color:#4e695e}.error-note{background:#f2e3df;color:#884534}.inspector-section{padding:17px 18px 18px;border-top:1px solid var(--line-soft)}.inspector-section h3{margin:0 0 12px;color:#656861;font-size:11px;font-weight:700;letter-spacing:.065em;text-transform:uppercase}.property,.vector{min-height:32px;display:flex;align-items:center;justify-content:space-between;gap:14px;font-size:13px}.property span,.vector span{color:var(--muted)}.property strong{overflow:hidden;color:#292a27;font-weight:600;text-align:right;text-overflow:ellipsis}.vector code{color:#4f514b;font-family:Cascadia Mono,Consolas,monospace;font-size:11px}.dimension-strip{display:grid;grid-template-columns:repeat(3,1fr);gap:7px;margin-bottom:9px}.dimension-strip div{min-width:0;padding:10px;border-radius:6px;background:#ebeae5}.dimension-strip span,.dimension-strip strong{display:block}.dimension-strip span{margin-bottom:4px;color:#898b85;font-size:10px;font-weight:700}.dimension-strip strong{overflow:hidden;color:#292a27;font-size:13px;font-variant-numeric:tabular-nums;text-overflow:ellipsis}.swatches{display:grid;gap:8px}.swatch{display:grid;grid-template-columns:22px 1fr auto;align-items:center;gap:9px;font-size:12px}.swatch>span{width:22px;height:22px;border:1px solid rgba(0,0,0,.14);border-radius:5px}.swatch code,.model-tree{font-family:Cascadia Mono,Consolas,monospace}.swatch code{font-size:11px}.swatch small{color:var(--muted);font-size:11px}.material-name{margin-bottom:7px;font-size:12px;font-weight:600}.text-preview{max-height:180px;overflow:auto;margin-top:8px;padding:11px;border-radius:6px;background:#eeede8;color:#4e504a;font-family:Georgia,Times New Roman,serif;font-size:13px;line-height:1.6;white-space:pre-wrap}.plain-text-preview{max-height:340px;margin:10px 0 0;overflow:auto;padding:12px;border-radius:6px;background:#ebeae5;color:#41433e;font-family:Cascadia Mono,Consolas,monospace;font-size:11px;line-height:1.65;white-space:pre-wrap;overflow-wrap:anywhere}.text-limit-note{margin-top:8px;color:#8a6b43;font-size:11px}.model-tree{max-height:240px;margin:0;overflow:auto;padding:11px;border-radius:6px;background:#ebeae5;color:#474944;font-size:11px;line-height:1.65;white-space:pre}.inspector-footer{padding:14px 18px 20px;border-top:1px solid var(--line-soft)}.inspector-footer button{display:flex;align-items:center;gap:7px;padding:0;border:0;background:transparent;color:#60635c;font-size:11px;cursor:pointer}.inspector-footer button:hover{color:var(--ink)}.modal-backdrop{position:fixed;z-index:30;top:0;right:0;bottom:0;left:0;display:grid;place-items:center;padding:38px;background:#1e201c94;-webkit-backdrop-filter:blur(7px);backdrop-filter:blur(7px);animation:fade-in .14s ease}@keyframes fade-in{0%{opacity:0}}.preview-modal{width:min(1100px,91vw);height:min(820px,89vh);display:grid;grid-template-rows:58px 1fr;overflow:hidden;border:1px solid rgba(255,255,255,.2);border-radius:10px;background:#f8f7f3;box-shadow:0 28px 80px #14151257}.modal-head{display:flex;align-items:center;justify-content:space-between;padding:0 12px 0 18px;border-bottom:1px solid var(--line)}.modal-head strong,.modal-head span{display:block}.modal-head strong{font-size:13px}.modal-head span{margin-top:3px;color:var(--muted);font-size:11px}.modal-canvas{min-height:0;display:grid;place-items:center;padding:24px;background:#e9e8e3}.modal-canvas img{max-width:100%;max-height:100%;object-fit:contain;box-shadow:0 10px 34px #23242029}.empty-state{grid-column:1 / -1;min-height:300px;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--muted);text-align:center}.empty-state h2{margin:12px 0 4px;color:var(--ink);font-family:Georgia,Times New Roman,serif;font-size:18px;font-weight:400}.empty-state p{max-width:390px;margin:0;font-size:12px;line-height:1.5}.loading-screen{height:100vh;display:flex;align-items:center;justify-content:center;gap:10px;background:var(--paper);color:var(--muted);font-size:13px}.toast{position:fixed;z-index:40;left:50%;bottom:22px;display:flex;align-items:center;gap:8px;padding:9px 12px;border:1px solid rgba(255,255,255,.13);border-radius:7px;background:#252722f0;color:#fff;font-size:12px;box-shadow:0 10px 30px #191a1738;transform:translate(-50%)}.toast.error{background:#773226f5}@media(max-width:1220px){:root{--sidebar: 220px;--inspector: 330px}.content-head,.file-grid,.file-list{padding-inline:18px}.browser-toolbar{gap:5px}}
|
package/dist/client/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="theme-color" content="#f5f3ee" />
|
|
7
7
|
<title>CAD Browser</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-B37D4sOF.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CQQ62v1M.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/package.json
CHANGED
package/src/analyzer.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
1
2
|
import { inspectFile } from '@cadcrawl/cad-toolbox/src/inspect.js';
|
|
2
3
|
import { CAD_VIEW_DIRECTIONS } from '@cadcrawl/cad-toolbox/src/render-options.js';
|
|
3
4
|
import { renderStem } from './cache.js';
|
|
4
5
|
|
|
5
6
|
export async function analyzeFile(absolutePath, relativePath, cache, kind) {
|
|
7
|
+
if (kind === 'text') return analyzeTextFile(absolutePath);
|
|
8
|
+
|
|
6
9
|
const fields = new Set(['metadata', 'renders']);
|
|
7
10
|
if (kind === 'cad' && /\.(step|stp)$/i.test(relativePath)) fields.add('tree');
|
|
8
11
|
if (kind === 'pdf') fields.add('text');
|
|
@@ -29,3 +32,34 @@ export async function analyzeFile(absolutePath, relativePath, cache, kind) {
|
|
|
29
32
|
previewPath: result.renders?.[0] ?? null,
|
|
30
33
|
};
|
|
31
34
|
}
|
|
35
|
+
|
|
36
|
+
async function analyzeTextFile(absolutePath) {
|
|
37
|
+
const stat = await fs.stat(absolutePath);
|
|
38
|
+
const maximumBytes = 256 * 1024;
|
|
39
|
+
const bytesToRead = Math.min(stat.size, maximumBytes);
|
|
40
|
+
const handle = await fs.open(absolutePath, 'r');
|
|
41
|
+
let text;
|
|
42
|
+
try {
|
|
43
|
+
const buffer = Buffer.alloc(bytesToRead);
|
|
44
|
+
await handle.read(buffer, 0, bytesToRead, 0);
|
|
45
|
+
text = buffer.toString('utf8').replace(/^\uFEFF/, '');
|
|
46
|
+
} finally {
|
|
47
|
+
await handle.close();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const lines = text.length === 0 ? 0 : text.split(/\r\n|\r|\n/).length;
|
|
51
|
+
const words = text.trim() ? text.trim().split(/\s+/u).length : 0;
|
|
52
|
+
return {
|
|
53
|
+
metadata: {
|
|
54
|
+
text: {
|
|
55
|
+
lines,
|
|
56
|
+
words,
|
|
57
|
+
characters: text.length,
|
|
58
|
+
truncated: stat.size > maximumBytes,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
tree: null,
|
|
62
|
+
text,
|
|
63
|
+
previewPath: null,
|
|
64
|
+
};
|
|
65
|
+
}
|
package/src/file-types.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
export const CAD_EXTENSIONS = new Set(['.step', '.stp', '.stl', '.3mf']);
|
|
2
2
|
export const PDF_EXTENSIONS = new Set(['.pdf']);
|
|
3
3
|
export const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif', '.bmp', '.svg']);
|
|
4
|
+
export const TEXT_EXTENSIONS = new Set(['.md', '.txt']);
|
|
4
5
|
|
|
5
6
|
export function classifyExtension(extension) {
|
|
6
7
|
const normalized = extension.toLowerCase();
|
|
7
8
|
if (CAD_EXTENSIONS.has(normalized)) return 'cad';
|
|
8
9
|
if (PDF_EXTENSIONS.has(normalized)) return 'pdf';
|
|
9
10
|
if (IMAGE_EXTENSIONS.has(normalized)) return 'image';
|
|
11
|
+
if (TEXT_EXTENSIONS.has(normalized)) return 'text';
|
|
10
12
|
return 'file';
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export function canAnalyze(extension) {
|
|
14
|
-
|
|
16
|
+
const normalized = extension.toLowerCase();
|
|
17
|
+
return CAD_EXTENSIONS.has(normalized) || PDF_EXTENSIONS.has(normalized) || TEXT_EXTENSIONS.has(normalized);
|
|
15
18
|
}
|
package/src/main.jsx
CHANGED
|
@@ -10,7 +10,7 @@ import './styles.css';
|
|
|
10
10
|
|
|
11
11
|
const number = new Intl.NumberFormat('en-US', { maximumFractionDigits: 2 });
|
|
12
12
|
const date = new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'short' });
|
|
13
|
-
const TYPE_FILTERS = ['all', 'cad', 'pdf', 'image', 'file'];
|
|
13
|
+
const TYPE_FILTERS = ['all', 'cad', 'pdf', 'text', 'image', 'file'];
|
|
14
14
|
const SORT_OPTIONS = [
|
|
15
15
|
['name', 'Name'],
|
|
16
16
|
['modified', 'Modified'],
|
|
@@ -159,7 +159,7 @@ function App() {
|
|
|
159
159
|
ref={searchRef}
|
|
160
160
|
value={query}
|
|
161
161
|
onChange={(event) => setQuery(event.target.value)}
|
|
162
|
-
placeholder="Search files, paths, and
|
|
162
|
+
placeholder="Search files, paths, and document text"
|
|
163
163
|
aria-label="Search project"
|
|
164
164
|
/>
|
|
165
165
|
{query && <button onClick={() => setQuery('')} aria-label="Clear search"><X size={14} /></button>}
|
|
@@ -181,7 +181,7 @@ function App() {
|
|
|
181
181
|
</div>
|
|
182
182
|
<div className="sidebar-footer">
|
|
183
183
|
<div><strong>{project.counts.total}</strong> files</div>
|
|
184
|
-
<div><strong>{project.counts.engineering}</strong>
|
|
184
|
+
<div><strong>{project.counts.engineering}</strong> indexed</div>
|
|
185
185
|
</div>
|
|
186
186
|
</aside>
|
|
187
187
|
|
|
@@ -335,12 +335,21 @@ function TreeNode({ node, activeFolder, onFolder, rootName, depth = 0 }) {
|
|
|
335
335
|
const label = depth === 0 ? rootName : node.name;
|
|
336
336
|
return (
|
|
337
337
|
<div>
|
|
338
|
-
<
|
|
339
|
-
<
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
338
|
+
<div className={`tree-row ${activeFolder === node.path ? 'active' : ''}`} style={{ '--depth': depth }}>
|
|
339
|
+
<button
|
|
340
|
+
className="tree-toggle"
|
|
341
|
+
onClick={() => setOpen((value) => !value)}
|
|
342
|
+
aria-label={`${open ? 'Collapse' : 'Expand'} ${label}`}
|
|
343
|
+
aria-expanded={open}
|
|
344
|
+
>
|
|
345
|
+
{open ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
|
|
346
|
+
</button>
|
|
347
|
+
<button className="tree-folder" onClick={() => onFolder(node.path)} title={`Open ${label}`}>
|
|
348
|
+
{open ? <FolderOpen size={16} /> : <Folder size={16} />}
|
|
349
|
+
<span className="tree-label">{label}</span>
|
|
350
|
+
<span className="tree-count">{node.fileCount || countNodeFiles(node)}</span>
|
|
351
|
+
</button>
|
|
352
|
+
</div>
|
|
344
353
|
{open && node.children.filter((child) => child.type === 'directory').map((child) => (
|
|
345
354
|
<TreeNode key={child.path} node={child} activeFolder={activeFolder} onFolder={onFolder} rootName={rootName} depth={depth + 1} />
|
|
346
355
|
))}
|
|
@@ -372,14 +381,19 @@ function FileCard({ file, index, view, selected, onSelect, onOpen }) {
|
|
|
372
381
|
}
|
|
373
382
|
|
|
374
383
|
function FilePlaceholder({ file }) {
|
|
375
|
-
const Icon = file.kind === 'pdf' ? FileText : file.kind === 'image' ? FileImage : file.kind === 'cad' ? Boxes : File;
|
|
376
|
-
|
|
384
|
+
const Icon = file.kind === 'pdf' || file.kind === 'text' ? FileText : file.kind === 'image' ? FileImage : file.kind === 'cad' ? Boxes : File;
|
|
385
|
+
const label = file.kind === 'cad' ? 'CAD model' : file.kind === 'pdf' ? 'Drawing' : file.kind === 'text' ? 'Text document' : 'File';
|
|
386
|
+
return <div className={`placeholder kind-${file.kind}`}><Icon size={38} strokeWidth={1.35} /><span>{label}</span></div>;
|
|
377
387
|
}
|
|
378
388
|
|
|
379
389
|
function FileSummary({ file, compact }) {
|
|
380
390
|
const size = file.analysis?.metadata?.bounding_box?.size;
|
|
381
391
|
if (size) return <span>{size.map(formatDimension).join(' × ')}</span>;
|
|
382
392
|
if (file.kind === 'pdf' && file.analysis?.metadata?.pages) return <span>{file.analysis.metadata.pages} {file.analysis.metadata.pages === 1 ? 'page' : 'pages'}</span>;
|
|
393
|
+
if (file.kind === 'text' && file.analysis?.metadata?.text) {
|
|
394
|
+
const text = file.analysis.metadata.text;
|
|
395
|
+
return <span>{number.format(text.lines)} lines · {number.format(text.words)} words</span>;
|
|
396
|
+
}
|
|
383
397
|
if (!compact) return <span>{formatBytes(file.size)} · {date.format(new Date(file.modifiedAt))}</span>;
|
|
384
398
|
return <span>{formatBytes(file.size)}</span>;
|
|
385
399
|
}
|
|
@@ -451,6 +465,15 @@ function Inspector({ file, onClose, onPreview, onOpen, onReveal, onCopy, onReana
|
|
|
451
465
|
{file.analysis?.text && <div className="text-preview">{file.analysis.text}</div>}
|
|
452
466
|
</InspectorSection>
|
|
453
467
|
)}
|
|
468
|
+
{metadata?.text && (
|
|
469
|
+
<InspectorSection title="Text">
|
|
470
|
+
<Property label="Lines" value={number.format(metadata.text.lines)} />
|
|
471
|
+
<Property label="Words" value={number.format(metadata.text.words)} />
|
|
472
|
+
<Property label="Characters" value={number.format(metadata.text.characters)} />
|
|
473
|
+
{metadata.text.truncated && <div className="text-limit-note">Showing the first 256 KB.</div>}
|
|
474
|
+
{file.analysis?.text && <pre className="plain-text-preview">{file.analysis.text}</pre>}
|
|
475
|
+
</InspectorSection>
|
|
476
|
+
)}
|
|
454
477
|
{file.analysis?.tree && <InspectorSection title="Model structure"><pre className="model-tree">{file.analysis.tree}</pre></InspectorSection>}
|
|
455
478
|
|
|
456
479
|
<div className="inspector-footer">
|
|
@@ -493,7 +516,7 @@ function PreviewModal({ file, onClose }) {
|
|
|
493
516
|
}
|
|
494
517
|
|
|
495
518
|
function TypeIcon({ kind }) {
|
|
496
|
-
const Icon = kind === 'pdf' ? FileText : kind === 'image' ? FileImage : kind === 'cad' ? Boxes : File;
|
|
519
|
+
const Icon = kind === 'pdf' || kind === 'text' ? FileText : kind === 'image' ? FileImage : kind === 'cad' ? Boxes : File;
|
|
497
520
|
return <span className={`type-icon kind-${kind}`}><Icon size={19} strokeWidth={1.7} /></span>;
|
|
498
521
|
}
|
|
499
522
|
function EmptyState({ query }) {
|
package/src/project-store.js
CHANGED
|
@@ -123,5 +123,5 @@ function countKinds(files) {
|
|
|
123
123
|
counts[file.kind] = (counts[file.kind] ?? 0) + 1;
|
|
124
124
|
if (file.analyzable) counts.engineering += 1;
|
|
125
125
|
return counts;
|
|
126
|
-
}, { total: 0, engineering: 0, cad: 0, pdf: 0, image: 0, file: 0 });
|
|
126
|
+
}, { total: 0, engineering: 0, cad: 0, pdf: 0, image: 0, text: 0, file: 0 });
|
|
127
127
|
}
|
package/src/styles.css
CHANGED
|
@@ -140,19 +140,41 @@ button:focus-visible, input:focus-visible, select:focus-visible, article:focus-v
|
|
|
140
140
|
height: 31px;
|
|
141
141
|
display: flex;
|
|
142
142
|
align-items: center;
|
|
143
|
-
|
|
144
|
-
padding: 0 7px 0 calc(5px + var(--depth) * 15px);
|
|
145
|
-
border: 0;
|
|
143
|
+
padding-left: calc(5px + var(--depth) * 15px);
|
|
146
144
|
border-radius: 5px;
|
|
147
|
-
background: transparent;
|
|
148
145
|
color: #54564f;
|
|
149
146
|
font-size: 12px;
|
|
150
|
-
text-align: left;
|
|
151
|
-
cursor: pointer;
|
|
152
147
|
}
|
|
153
148
|
.tree-row:hover { background: rgba(255,255,255,.65); }
|
|
154
149
|
.tree-row.active { background: #dcddd7; color: #242521; font-weight: 600; }
|
|
155
|
-
.tree-toggle {
|
|
150
|
+
.tree-toggle {
|
|
151
|
+
flex: 0 0 22px;
|
|
152
|
+
height: 27px;
|
|
153
|
+
display: grid;
|
|
154
|
+
place-items: center;
|
|
155
|
+
padding: 0;
|
|
156
|
+
border: 0;
|
|
157
|
+
border-radius: 4px;
|
|
158
|
+
background: transparent;
|
|
159
|
+
color: #898b84;
|
|
160
|
+
cursor: pointer;
|
|
161
|
+
}
|
|
162
|
+
.tree-toggle:hover { background: rgba(72,74,68,.08); color: #4f514b; }
|
|
163
|
+
.tree-folder {
|
|
164
|
+
min-width: 0;
|
|
165
|
+
flex: 1;
|
|
166
|
+
height: 31px;
|
|
167
|
+
display: flex;
|
|
168
|
+
align-items: center;
|
|
169
|
+
gap: 7px;
|
|
170
|
+
padding: 0 7px 0 1px;
|
|
171
|
+
border: 0;
|
|
172
|
+
background: transparent;
|
|
173
|
+
color: inherit;
|
|
174
|
+
font-weight: inherit;
|
|
175
|
+
text-align: left;
|
|
176
|
+
cursor: pointer;
|
|
177
|
+
}
|
|
156
178
|
.tree-label { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
157
179
|
.tree-count { color: #999b95; font-size: 10px; font-variant-numeric: tabular-nums; }
|
|
158
180
|
.sidebar-footer { display: flex; align-items: center; gap: 15px; padding: 0 15px; border-top: 1px solid var(--line); color: #8b8d87; font-size: 10px; }
|
|
@@ -306,6 +328,7 @@ button:focus-visible, input:focus-visible, select:focus-visible, article:focus-v
|
|
|
306
328
|
.placeholder span { font-family: Georgia, "Times New Roman", serif; font-size: 11px; font-style: italic; font-weight: 400; }
|
|
307
329
|
.placeholder.kind-cad { color: #557568; background: #edf2ef; }
|
|
308
330
|
.placeholder.kind-pdf { color: #9a5b49; background: #f4ece8; }
|
|
331
|
+
.placeholder.kind-text { color: #66705e; background: #eef0e9; }
|
|
309
332
|
.placeholder.kind-image { color: #636d82; background: #eceff3; }
|
|
310
333
|
.type-badge {
|
|
311
334
|
position: absolute;
|
|
@@ -362,6 +385,7 @@ button:focus-visible, input:focus-visible, select:focus-visible, article:focus-v
|
|
|
362
385
|
.type-icon { display: grid; place-items: center; width: 36px; height: 36px; border-radius: 8px; background: #e8e7e1; color: #656760; }
|
|
363
386
|
.type-icon.kind-cad { background: #e2ece7; color: #46705f; }
|
|
364
387
|
.type-icon.kind-pdf { background: #f0e3dd; color: #9a513d; }
|
|
388
|
+
.type-icon.kind-text { background: #e8ece2; color: #5e6c53; }
|
|
365
389
|
.inspector-preview { position: relative; width: calc(100% - 28px); aspect-ratio: 4 / 3; display: grid; place-items: center; margin: 14px; padding: 0; overflow: hidden; border: 1px solid var(--line); border-radius: 8px; background: #f0efea; cursor: zoom-in; }
|
|
366
390
|
.inspector-preview:disabled { cursor: default; }
|
|
367
391
|
.inspector-preview img { width: 100%; height: 100%; object-fit: contain; }
|
|
@@ -406,6 +430,21 @@ button:focus-visible, input:focus-visible, select:focus-visible, article:focus-v
|
|
|
406
430
|
.swatch small { color: var(--muted); font-size: 11px; }
|
|
407
431
|
.material-name { margin-bottom: 7px; font-size: 12px; font-weight: 600; }
|
|
408
432
|
.text-preview { max-height: 180px; overflow: auto; margin-top: 8px; padding: 11px; border-radius: 6px; background: #eeede8; color: #4e504a; font-family: Georgia, "Times New Roman", serif; font-size: 13px; line-height: 1.6; white-space: pre-wrap; }
|
|
433
|
+
.plain-text-preview {
|
|
434
|
+
max-height: 340px;
|
|
435
|
+
margin: 10px 0 0;
|
|
436
|
+
overflow: auto;
|
|
437
|
+
padding: 12px;
|
|
438
|
+
border-radius: 6px;
|
|
439
|
+
background: #ebeae5;
|
|
440
|
+
color: #41433e;
|
|
441
|
+
font-family: "Cascadia Mono", Consolas, monospace;
|
|
442
|
+
font-size: 11px;
|
|
443
|
+
line-height: 1.65;
|
|
444
|
+
white-space: pre-wrap;
|
|
445
|
+
overflow-wrap: anywhere;
|
|
446
|
+
}
|
|
447
|
+
.text-limit-note { margin-top: 8px; color: #8a6b43; font-size: 11px; }
|
|
409
448
|
.model-tree { max-height: 240px; margin: 0; overflow: auto; padding: 11px; border-radius: 6px; background: #ebeae5; color: #474944; font-size: 11px; line-height: 1.65; white-space: pre; }
|
|
410
449
|
.inspector-footer { padding: 14px 18px 20px; border-top: 1px solid var(--line-soft); }
|
|
411
450
|
.inspector-footer button { display: flex; align-items: center; gap: 7px; padding: 0; border: 0; background: transparent; color: #60635c; font-size: 11px; cursor: pointer; }
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{color:#252623;background:#f3f2ee;font-family:Segoe UI Variable Text,Segoe UI,sans-serif;font-size:14px;font-synthesis:none;--paper: #fbfaf7;--panel: #f5f4ef;--sidebar-bg: #efeee9;--ink: #252623;--muted: #71736d;--faint: #9a9c96;--line: #deddd7;--line-soft: #e9e8e2;--accent: #2f6657;--accent-soft: #e4eee9;--sidebar: 250px;--inspector: 360px}*{box-sizing:border-box}html,body,#root{min-width:1024px;min-height:100%;margin:0}body{overflow:hidden}button,input,select{font:inherit}button,select{color:inherit}button:focus-visible,input:focus-visible,select:focus-visible,article:focus-visible{outline:2px solid rgba(47,102,87,.45);outline-offset:2px}.app-shell{height:100vh;display:grid;grid-template:56px calc(100vh - 56px) / var(--sidebar) minmax(480px,1fr) var(--inspector);background:var(--panel)}.app-shell.sidebar-collapsed{grid-template-columns:0 minmax(480px,1fr) var(--inspector)}.topbar{grid-column:1 / -1;z-index:10;display:grid;grid-template-columns:minmax(270px,1fr) minmax(360px,620px) minmax(270px,1fr);align-items:center;padding:0 14px;background:#fbfaf7fa;border-bottom:1px solid var(--line)}.brand,.top-actions,.search,.status-pill{display:flex;align-items:center}.brand{gap:9px;font-size:14px;font-weight:650}.brand-mark{display:grid;place-items:center;width:30px;height:30px;color:#fff;background:#2b2d29;border-radius:7px}.project-chip{max-width:180px;padding:4px 8px;overflow:hidden;border-radius:5px;background:#efede7;color:var(--muted);font-size:12px;font-weight:500;text-overflow:ellipsis;white-space:nowrap}.search{height:36px;gap:9px;padding:0 10px;color:var(--muted);background:#efeee9;border:1px solid transparent;border-radius:7px}.search:focus-within{background:#fff;border-color:#bbbdb6;box-shadow:0 0 0 3px #2f665714}.search input{flex:1;min-width:0;border:0;outline:0;background:transparent;color:var(--ink);font-size:13px}.search input::placeholder{color:#92948e}.search button{display:grid;padding:2px;border:0;background:transparent;cursor:pointer}.search kbd,.shortcut-list kbd{padding:2px 6px;border:1px solid #d8d7d1;border-bottom-color:#c8c7c1;border-radius:4px;background:#f8f7f3;color:#777972;font-family:inherit;font-size:10px;box-shadow:0 1px #d6d5cf}.top-actions{justify-content:flex-end;gap:9px}.icon-button{display:grid;place-items:center;width:32px;height:32px;padding:0;border:1px solid var(--line);border-radius:6px;background:var(--paper);cursor:pointer}.icon-button:hover{background:#efeee9}.icon-button.quiet{border-color:transparent;background:transparent;color:var(--muted)}.status-pill{gap:7px;padding:5px 9px;border:1px solid var(--line);border-radius:999px;color:var(--muted);font-size:11px}.status-dot{width:6px;height:6px;border-radius:50%;background:#4f8968;box-shadow:0 0 0 3px #e2eee6}.sidebar{min-width:0;display:grid;grid-template-rows:46px 1fr 48px;overflow:hidden;background:var(--sidebar-bg);border-right:1px solid var(--line)}.sidebar-collapsed .sidebar{opacity:0;pointer-events:none}.sidebar-head{display:flex;align-items:center;justify-content:space-between;padding:0 10px 0 15px;color:var(--muted);font-size:11px;font-weight:700;letter-spacing:.07em;text-transform:uppercase}.tree-scroll{padding:2px 7px 14px;overflow:auto}.tree-row{width:100%;height:31px;display:flex;align-items:center;gap:7px;padding:0 7px 0 calc(5px + var(--depth) * 15px);border:0;border-radius:5px;background:transparent;color:#54564f;font-size:12px;text-align:left;cursor:pointer}.tree-row:hover{background:#ffffffa6}.tree-row.active{background:#dcddd7;color:#242521;font-weight:600}.tree-toggle{display:grid;width:14px;color:#898b84}.tree-label{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tree-count{color:#999b95;font-size:10px;font-variant-numeric:tabular-nums}.sidebar-footer{display:flex;align-items:center;gap:15px;padding:0 15px;border-top:1px solid var(--line);color:#8b8d87;font-size:10px}.sidebar-footer strong{color:#555750}.sidebar-reveal{position:fixed;z-index:15;left:12px;top:68px;display:grid;place-items:center;width:34px;height:34px;padding:0;border:1px solid var(--line);border-radius:7px;background:var(--paper);box-shadow:0 5px 18px #1e201c1f;cursor:pointer}.workspace{min-width:0;overflow:auto;background:var(--paper)}.content-head{position:sticky;z-index:4;top:0;min-height:108px;display:flex;align-items:flex-end;justify-content:space-between;gap:24px;padding:20px 26px 18px;background:#fbfaf7f5;border-bottom:1px solid var(--line-soft);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px)}.content-title{min-width:0}.breadcrumbs{display:flex;align-items:center;gap:4px;margin-bottom:8px;color:var(--faint);font-size:11px}.breadcrumbs button{max-width:170px;padding:0;overflow:hidden;border:0;background:transparent;color:inherit;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.breadcrumbs button:hover{color:var(--ink)}.content-head h1{margin:0;overflow:hidden;color:#292a26;font-family:Georgia,Times New Roman,serif;font-size:25px;font-weight:500;letter-spacing:-.035em;text-overflow:ellipsis;white-space:nowrap}.content-head p{margin:5px 0 0;color:var(--muted);font-size:12px}.browser-toolbar{display:flex;align-items:center;gap:7px}.dropdown,.sort-control,.grid-scale,.view-switcher{height:32px;display:flex;align-items:center;border:1px solid var(--line);border-radius:6px;background:#f3f2ed;color:#686a64}.dropdown{position:relative}.dropdown-trigger{height:30px;display:flex;align-items:center;gap:6px;padding:0 8px;border:0;border-radius:5px;background:transparent;color:#5c5e58;font-size:11px;cursor:pointer}.dropdown-trigger>span{white-space:nowrap}.dropdown.open .dropdown-trigger,.dropdown-trigger:hover{background:#fff;color:var(--ink)}.dropdown-menu{position:absolute;z-index:20;top:calc(100% + 5px);left:0;min-width:100%;padding:5px;border:1px solid #d6d7d1;border-radius:7px;background:#fff;box-shadow:0 10px 28px #1e201c24}.dropdown-menu button{width:100%;min-width:130px;height:30px;display:flex;align-items:center;justify-content:space-between;gap:16px;padding:0 8px;border:0;border-radius:5px;background:transparent;color:#555750;font-size:11px;text-align:left;cursor:pointer}.dropdown-menu button:hover{background:#f1f2ee;color:var(--ink)}.dropdown-menu button.selected{color:#28584b;font-weight:650}.sort-control .dropdown{border:0;background:transparent}.sort-control .dropdown-trigger{padding-left:8px}.sort-control>button,.grid-scale button,.view-switcher button{display:grid;place-items:center;height:30px;padding:0;border:0;border-radius:5px;background:transparent;color:#74766f;cursor:pointer}.sort-control>button{width:29px;border-left:1px solid var(--line);border-radius:0 5px 5px 0}.grid-scale button{width:27px}.grid-scale button:hover:not(:disabled),.view-switcher button:hover,.sort-control>button:hover{background:#fff;color:var(--ink)}.grid-scale button:disabled{opacity:.28;cursor:default}.grid-scale span{min-width:25px;color:#62645e;font-size:10px;text-align:center;font-variant-numeric:tabular-nums}.grid-scale span:after{content:" col";color:#999b95}.view-switcher{padding:1px}.view-switcher button{width:29px;height:28px}.view-switcher button.active{background:#fff;color:var(--ink);box-shadow:0 1px 3px #1e201c1f}.file-grid{display:grid;grid-template-columns:repeat(var(--grid-columns, 4),minmax(0,1fr));gap:15px;padding:20px 26px 40px}.file-card{min-width:0;overflow:hidden;border:1px solid var(--line-soft);border-radius:8px;background:#fff;cursor:default;opacity:0;transform:translateY(4px);animation:reveal .25s ease forwards;animation-delay:var(--delay);transition:border-color .14s,background-color .14s}@keyframes reveal{to{opacity:1;transform:translateY(0)}}.file-card:hover{border-color:#bfc1ba;background:#fdfdfa}.file-card.selected{border-color:#4f7b6c;background:#fbfdfc}.thumb{position:relative;aspect-ratio:4 / 3;display:grid;place-items:center;overflow:hidden;background:#f4f3ef;border-bottom:1px solid var(--line-soft)}.thumb img{width:100%;height:100%;object-fit:contain}.placeholder{width:100%;height:100%;display:grid;place-content:center;justify-items:center;gap:8px;color:#878a83}.placeholder span{font-family:Georgia,Times New Roman,serif;font-size:11px;font-style:italic;font-weight:400}.placeholder.kind-cad{color:#557568;background:#edf2ef}.placeholder.kind-pdf{color:#9a5b49;background:#f4ece8}.placeholder.kind-image{color:#636d82;background:#eceff3}.type-badge{position:absolute;left:8px;top:8px;padding:3px 6px;border:1px solid rgba(40,42,37,.12);border-radius:4px;background:#ffffffe6;color:#64665f;font-size:9px;font-weight:700;letter-spacing:.055em;text-transform:uppercase;-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.processing{position:absolute;inset:auto 8px 8px;display:flex;align-items:center;gap:6px;padding:6px 8px;border-radius:5px;background:#242621d6;color:#fff;font-size:10px}.processing svg,.analysis-note svg,.loading-screen>svg{animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.file-copy{padding:10px 11px 11px}.file-name{overflow:hidden;color:#31322e;font-size:12px;font-weight:620;text-overflow:ellipsis;white-space:nowrap}.file-copy span{display:block;margin-top:4px;overflow:hidden;color:var(--muted);font-size:10px;font-variant-numeric:tabular-nums;text-overflow:ellipsis;white-space:nowrap}.file-grid.density-compact{gap:8px;padding-inline:18px}.file-grid.density-compact .file-copy{padding:7px}.file-grid.density-compact .file-name{font-size:10px}.file-grid.density-compact .file-copy span{font-size:8px}.file-grid.density-compact .type-badge{left:5px;top:5px;padding:2px 4px;font-size:7px}.file-grid.density-micro{gap:5px;padding-inline:13px}.file-grid.density-micro .file-card{border-radius:5px}.file-grid.density-micro .file-copy{padding:5px 6px 6px}.file-grid.density-micro .file-name{font-size:8px}.file-grid.density-micro .file-copy span{display:none}.file-grid.density-micro .type-badge{left:3px;top:3px;padding:1px 3px;border-radius:3px;font-size:6px}.file-list{padding:13px 26px 36px}.file-list .file-card{display:grid;grid-template-columns:88px minmax(0,1fr);margin-bottom:7px}.file-list .thumb{height:68px;aspect-ratio:auto;border:0;border-right:1px solid var(--line-soft)}.file-list .placeholder svg{width:23px}.file-list .placeholder span,.file-list .type-badge{display:none}.file-list .file-copy{align-self:center}.inspector{min-width:0;overflow-y:auto;background:#f8f7f3;border-left:1px solid var(--line)}.empty-inspector{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:36px;color:var(--muted);text-align:center}.inspector-empty-mark{display:grid;place-items:center;width:44px;height:44px;border:1px solid var(--line);border-radius:10px;background:#fff;color:#787a74}.empty-inspector h2{margin:15px 0 5px;color:var(--ink);font-family:Georgia,Times New Roman,serif;font-size:18px;font-weight:400}.empty-inspector p{max-width:250px;margin:0;font-size:12px;line-height:1.55}.shortcut-list{width:210px;display:grid;grid-template-columns:1fr auto;align-items:center;gap:8px 12px;margin-top:22px;color:#7e807a;font-size:11px;text-align:left}.inspector-head{min-height:61px;display:grid;grid-template-columns:36px minmax(0,1fr) 32px;align-items:center;gap:10px;padding:11px 11px 11px 14px;border-bottom:1px solid var(--line-soft)}.inspector-title{min-width:0}.inspector-title strong,.inspector-title span{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.inspector-title strong{color:#2b2c28;font-size:13px;font-weight:650}.inspector-title span{margin-top:3px;color:var(--muted);font-size:11px}.type-icon{display:grid;place-items:center;width:36px;height:36px;border-radius:8px;background:#e8e7e1;color:#656760}.type-icon.kind-cad{background:#e2ece7;color:#46705f}.type-icon.kind-pdf{background:#f0e3dd;color:#9a513d}.inspector-preview{position:relative;width:calc(100% - 28px);aspect-ratio:4 / 3;display:grid;place-items:center;margin:14px;padding:0;overflow:hidden;border:1px solid var(--line);border-radius:8px;background:#f0efea;cursor:zoom-in}.inspector-preview:disabled{cursor:default}.inspector-preview img{width:100%;height:100%;object-fit:contain}.inspector-actions{display:grid;grid-template-columns:1.3fr 1fr 34px;gap:7px;margin:-1px 14px 14px}.inspector-actions button{height:34px;display:flex;align-items:center;justify-content:center;gap:7px;padding:0 9px;border:1px solid #cccec7;border-radius:6px;background:#fff;color:#4c4e48;font-size:11px;font-weight:600;cursor:pointer}.inspector-actions button:hover{border-color:#aeb1a9;background:#fdfdfb}.inspector-actions .primary-action{border-color:#315f52;background:#315f52;color:#fff}.inspector-actions .primary-action:hover{background:#274f44}.analysis-note,.error-note{margin:0 14px 13px;padding:10px 11px;border-radius:6px;font-size:12px;line-height:1.45}.analysis-note{display:flex;align-items:center;gap:8px;background:#e5eee9;color:#4e695e}.error-note{background:#f2e3df;color:#884534}.inspector-section{padding:17px 18px 18px;border-top:1px solid var(--line-soft)}.inspector-section h3{margin:0 0 12px;color:#656861;font-size:11px;font-weight:700;letter-spacing:.065em;text-transform:uppercase}.property,.vector{min-height:32px;display:flex;align-items:center;justify-content:space-between;gap:14px;font-size:13px}.property span,.vector span{color:var(--muted)}.property strong{overflow:hidden;color:#292a27;font-weight:600;text-align:right;text-overflow:ellipsis}.vector code{color:#4f514b;font-family:Cascadia Mono,Consolas,monospace;font-size:11px}.dimension-strip{display:grid;grid-template-columns:repeat(3,1fr);gap:7px;margin-bottom:9px}.dimension-strip div{min-width:0;padding:10px;border-radius:6px;background:#ebeae5}.dimension-strip span,.dimension-strip strong{display:block}.dimension-strip span{margin-bottom:4px;color:#898b85;font-size:10px;font-weight:700}.dimension-strip strong{overflow:hidden;color:#292a27;font-size:13px;font-variant-numeric:tabular-nums;text-overflow:ellipsis}.swatches{display:grid;gap:8px}.swatch{display:grid;grid-template-columns:22px 1fr auto;align-items:center;gap:9px;font-size:12px}.swatch>span{width:22px;height:22px;border:1px solid rgba(0,0,0,.14);border-radius:5px}.swatch code,.model-tree{font-family:Cascadia Mono,Consolas,monospace}.swatch code{font-size:11px}.swatch small{color:var(--muted);font-size:11px}.material-name{margin-bottom:7px;font-size:12px;font-weight:600}.text-preview{max-height:180px;overflow:auto;margin-top:8px;padding:11px;border-radius:6px;background:#eeede8;color:#4e504a;font-family:Georgia,Times New Roman,serif;font-size:13px;line-height:1.6;white-space:pre-wrap}.model-tree{max-height:240px;margin:0;overflow:auto;padding:11px;border-radius:6px;background:#ebeae5;color:#474944;font-size:11px;line-height:1.65;white-space:pre}.inspector-footer{padding:14px 18px 20px;border-top:1px solid var(--line-soft)}.inspector-footer button{display:flex;align-items:center;gap:7px;padding:0;border:0;background:transparent;color:#60635c;font-size:11px;cursor:pointer}.inspector-footer button:hover{color:var(--ink)}.modal-backdrop{position:fixed;z-index:30;top:0;right:0;bottom:0;left:0;display:grid;place-items:center;padding:38px;background:#1e201c94;-webkit-backdrop-filter:blur(7px);backdrop-filter:blur(7px);animation:fade-in .14s ease}@keyframes fade-in{0%{opacity:0}}.preview-modal{width:min(1100px,91vw);height:min(820px,89vh);display:grid;grid-template-rows:58px 1fr;overflow:hidden;border:1px solid rgba(255,255,255,.2);border-radius:10px;background:#f8f7f3;box-shadow:0 28px 80px #14151257}.modal-head{display:flex;align-items:center;justify-content:space-between;padding:0 12px 0 18px;border-bottom:1px solid var(--line)}.modal-head strong,.modal-head span{display:block}.modal-head strong{font-size:13px}.modal-head span{margin-top:3px;color:var(--muted);font-size:11px}.modal-canvas{min-height:0;display:grid;place-items:center;padding:24px;background:#e9e8e3}.modal-canvas img{max-width:100%;max-height:100%;object-fit:contain;box-shadow:0 10px 34px #23242029}.empty-state{grid-column:1 / -1;min-height:300px;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--muted);text-align:center}.empty-state h2{margin:12px 0 4px;color:var(--ink);font-family:Georgia,Times New Roman,serif;font-size:18px;font-weight:400}.empty-state p{max-width:390px;margin:0;font-size:12px;line-height:1.5}.loading-screen{height:100vh;display:flex;align-items:center;justify-content:center;gap:10px;background:var(--paper);color:var(--muted);font-size:13px}.toast{position:fixed;z-index:40;left:50%;bottom:22px;display:flex;align-items:center;gap:8px;padding:9px 12px;border:1px solid rgba(255,255,255,.13);border-radius:7px;background:#252722f0;color:#fff;font-size:12px;box-shadow:0 10px 30px #191a1738;transform:translate(-50%)}.toast.error{background:#773226f5}@media(max-width:1220px){:root{--sidebar: 220px;--inspector: 330px}.content-head,.file-grid,.file-list{padding-inline:18px}.browser-toolbar{gap:5px}}
|