gds-lens 0.1.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/CHANGELOG.md +167 -0
- package/LICENCE.md +21 -0
- package/README.md +372 -0
- package/THIRD-PARTY-LICENSES.md +261 -0
- package/dist/esm/gds-lens.js +4731 -0
- package/dist/inline-wasm/gds-lens-engine.js +0 -0
- package/dist/inline-wasm/gds-lens-host.js +82 -0
- package/dist/inline-wasm/gds-lens-worker.js +118 -0
- package/dist/inline-wasm/gds-lens.html +29 -0
- package/dist/inline-wasm/gds-lens.js +4705 -0
- package/dist/web/gds-lens-engine.js +2 -0
- package/dist/web/gds-lens-engine.wasm +0 -0
- package/dist/web/gds-lens-host.js +82 -0
- package/dist/web/gds-lens-worker.js +118 -0
- package/dist/web/gds-lens.html +29 -0
- package/dist/web/gds-lens.js +4705 -0
- package/package.json +113 -0
- package/src/cell-search.js +88 -0
- package/src/coord-parse.js +50 -0
- package/src/engine-source.esm.js +49 -0
- package/src/engine-source.js +20 -0
- package/src/gds-lens.js +175 -0
- package/src/hosts/browser.js +156 -0
- package/src/layout-bytes.js +143 -0
- package/src/load-errors.js +64 -0
- package/src/marker-parsers.js +672 -0
- package/src/mount-target.js +23 -0
- package/src/viewer-shell.html +105 -0
- package/src/viewer.css +469 -0
- package/src/viewer.html +29 -0
- package/src/viewer.js +2688 -0
- package/src/wasm-worker.js +157 -0
- package/types/cell-search.d.ts +33 -0
- package/types/coord-parse.d.ts +11 -0
- package/types/gds-lens.d.ts +135 -0
- package/types/hosts-browser.d.ts +13 -0
- package/types/layout-bytes.d.ts +49 -0
- package/types/load-errors.d.ts +27 -0
- package/types/parsers.d.ts +84 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// The element the engine mounts into, handed over between <gds-lens>'s
|
|
2
|
+
// connectedCallback and viewer.js.
|
|
3
|
+
//
|
|
4
|
+
// It exists because viewer.js does its work as a module body rather than in a
|
|
5
|
+
// mount function: 172 top-level statements spread through the file, holding
|
|
6
|
+
// the renderer's state in module scope. Wrapping all of that in a function
|
|
7
|
+
// would mean indenting the entire file for no behavioural gain, so the element
|
|
8
|
+
// defers it with a dynamic import instead and leaves the target here for it to
|
|
9
|
+
// pick up. Passing the element rather than having viewer.js query for it also
|
|
10
|
+
// keeps this correct when the <gds-lens> is itself inside someone else's
|
|
11
|
+
// shadow root, where document.querySelector would not find it.
|
|
12
|
+
|
|
13
|
+
let target = null;
|
|
14
|
+
|
|
15
|
+
export function setMountTarget(element) {
|
|
16
|
+
target = element;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function takeMountTarget() {
|
|
20
|
+
const element = target;
|
|
21
|
+
target = null;
|
|
22
|
+
return element;
|
|
23
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<!-- lil-gui mounts its panel here rather than at document.body, so the
|
|
2
|
+
whole control surface lives inside the shadow root. -->
|
|
3
|
+
<div id="guiHost"></div>
|
|
4
|
+
<div id="loadingOverlay" role="status" aria-live="polite">
|
|
5
|
+
<div id="loadingBarTrack"><div id="loadingBarFill"></div></div>
|
|
6
|
+
<div id="loadingPhase">Loading layout...</div>
|
|
7
|
+
<div id="loadingPercent"></div>
|
|
8
|
+
</div>
|
|
9
|
+
<!-- role="alert" rather than a polite live region: this replaces the
|
|
10
|
+
view entirely, so it is the only thing left to read. -->
|
|
11
|
+
<div id="loadError" role="alert"></div>
|
|
12
|
+
<div id="reloadProgress" class="hidden" role="status" aria-live="polite">
|
|
13
|
+
<div id="reloadBarTrack"><div id="reloadBarFill"></div></div>
|
|
14
|
+
<div id="reloadLabel">Reloading...</div>
|
|
15
|
+
</div>
|
|
16
|
+
<div id="staleBanner" class="hidden" role="status">
|
|
17
|
+
<span id="staleText">A newer version of this file is on disk.</span>
|
|
18
|
+
<button id="staleReloadBtn" type="button">Reload</button>
|
|
19
|
+
<button id="staleAlwaysBtn" type="button" class="secondary"
|
|
20
|
+
title="Reload now, and automatically whenever this file changes from now on">Always</button>
|
|
21
|
+
<button id="staleDismiss" type="button" class="icon-only"
|
|
22
|
+
aria-label="Dismiss until the next change"
|
|
23
|
+
title="Dismiss until the next change">✕</button>
|
|
24
|
+
</div>
|
|
25
|
+
<!-- tabindex so the keyboard handlers in viewer.js are reachable without
|
|
26
|
+
a pointer; labelled because a bare <canvas> announces nothing. -->
|
|
27
|
+
<canvas id="glCanvas" tabindex="0" role="img"
|
|
28
|
+
aria-label="Layout view. Drag to pan, scroll to zoom."></canvas>
|
|
29
|
+
|
|
30
|
+
<!-- Cell hierarchy: rows are built by renderHierarchy in viewer.js from
|
|
31
|
+
the tree parseGdsToLayers hands back, lazily as branches open. -->
|
|
32
|
+
<div id="hierarchyPanel" class="hidden">
|
|
33
|
+
<div id="hierarchyHeader">
|
|
34
|
+
<span id="hierarchyTitle">Hierarchy</span>
|
|
35
|
+
<span id="hierarchyCount"></span>
|
|
36
|
+
<button id="hierarchyHide" type="button" class="icon-only"
|
|
37
|
+
aria-label="Hide the hierarchy tree"
|
|
38
|
+
title="Hide the hierarchy tree (H)">✕</button>
|
|
39
|
+
</div>
|
|
40
|
+
<!-- Find (see the search block in viewer.js): one box over two
|
|
41
|
+
haystacks, the design's cell names and its own TEXT labels.
|
|
42
|
+
Results take the tree's place while a query is up.
|
|
43
|
+
|
|
44
|
+
Collapsed by default, and folded away behind one row rather than
|
|
45
|
+
sitting open above the tree: the panel is here to browse the
|
|
46
|
+
hierarchy, and two more rows of chrome over it are two fewer rows
|
|
47
|
+
of the thing being browsed. "/" opens it and lands in the box, so
|
|
48
|
+
the fold costs nothing to whoever is actually searching. -->
|
|
49
|
+
<div id="hierarchyFind">
|
|
50
|
+
<button id="hierarchyFindToggle" type="button"
|
|
51
|
+
aria-expanded="false" aria-controls="hierarchySearch"
|
|
52
|
+
title="Find a cell by name, or a label by its text (/)">
|
|
53
|
+
<span id="hierarchyFindTwisty">▸</span>
|
|
54
|
+
<span id="hierarchyFindLabel">Find</span>
|
|
55
|
+
<span id="hierarchySearchCount"></span>
|
|
56
|
+
</button>
|
|
57
|
+
<div id="hierarchySearch" class="hidden">
|
|
58
|
+
<input id="hierarchySearchInput" type="text" placeholder="cell name"
|
|
59
|
+
spellcheck="false" autocomplete="off"
|
|
60
|
+
title="Find a cell by name, or a label by its text (/ focuses this box)">
|
|
61
|
+
<div id="hierarchyScope">
|
|
62
|
+
<button id="hierarchyScopeCells" type="button" aria-pressed="true"
|
|
63
|
+
title="Search the design's cell names">Cells</button>
|
|
64
|
+
<button id="hierarchyScopeLabels" type="button" aria-pressed="false"
|
|
65
|
+
title="Search the layout's own TEXT labels">Labels</button>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
<div id="hierarchyTree" role="tree" aria-label="Cell hierarchy"></div>
|
|
70
|
+
<div id="hierarchyResults" class="hidden" role="listbox" aria-label="Search results"></div>
|
|
71
|
+
</div>
|
|
72
|
+
<button id="hierarchyShowBtn" type="button" class="hidden"
|
|
73
|
+
aria-expanded="false" aria-controls="hierarchyPanel"
|
|
74
|
+
title="Show the design's cell hierarchy (H)">Hierarchy</button>
|
|
75
|
+
|
|
76
|
+
<div id="debugPanel" class="hidden">
|
|
77
|
+
<div id="debugPanelHeader">
|
|
78
|
+
<span>Debug tools</span>
|
|
79
|
+
<button id="debugCopyBtn" type="button">Copy log</button>
|
|
80
|
+
</div>
|
|
81
|
+
<div id="debugBody">
|
|
82
|
+
<!-- Engine readout, written from renderer.cpp (set_inner_html("ui", ...)). -->
|
|
83
|
+
<div id="ui"></div>
|
|
84
|
+
<div id="debugLog"></div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
<button id="debugToggleBtn" type="button"
|
|
88
|
+
aria-expanded="false" aria-controls="debugPanel">Debug tools</button>
|
|
89
|
+
|
|
90
|
+
<div id="viewReadout">
|
|
91
|
+
<div id="copyToast" class="hidden" role="status" aria-live="polite"></div>
|
|
92
|
+
<div id="scaleContainer">
|
|
93
|
+
<div id="scaleLabel">0 μm</div>
|
|
94
|
+
<div id="scaleBar" style="width: 100px;"></div>
|
|
95
|
+
</div>
|
|
96
|
+
<div id="coordReadout" class="hidden"></div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div id="measureLabels"></div>
|
|
100
|
+
|
|
101
|
+
<div id="canvasMenu" class="hidden" role="menu">
|
|
102
|
+
<button id="canvasMenuCopy" type="button" role="menuitem">
|
|
103
|
+
<span>Copy coordinate</span><span class="menu-value"></span>
|
|
104
|
+
</button>
|
|
105
|
+
</div>
|
package/src/viewer.css
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
/* ---- Theme tokens ----
|
|
2
|
+
Every color in this file comes from here, because the viewer follows
|
|
3
|
+
the embedder's light/dark theme and each one needs a counterpart.
|
|
4
|
+
Dark is the default: it's what the page paints as before any script
|
|
5
|
+
runs, and it's the far more common editor theme.
|
|
6
|
+
|
|
7
|
+
The light values ride on a single .theme-light class, which viewer.js's
|
|
8
|
+
theme block sets: it asks the host which theme is current and falls
|
|
9
|
+
back to the OS preference when the host has no opinion (see
|
|
10
|
+
applyTheme there). An embedder can also just set the class itself.
|
|
11
|
+
The canvas is not covered by any of this -- what the renderer draws is
|
|
12
|
+
themed in renderer.cpp (see setTheme). */
|
|
13
|
+
:host {
|
|
14
|
+
--bg: #0c0c0d;
|
|
15
|
+
--fg: #ffffff; /* ink over the canvas: scale bar, ruler readout */
|
|
16
|
+
--fg-dim: #a5a5a5;
|
|
17
|
+
--fg-faint: #808080;
|
|
18
|
+
--surface: #2a2a2c; /* small chrome fills: bar tracks, buttons */
|
|
19
|
+
--surface-fg: #dddddd;
|
|
20
|
+
--scrim: rgba(0, 0, 0, 0.88); /* debug panel, over the canvas */
|
|
21
|
+
--label-scrim: rgba(0, 0, 0, 0.75); /* ruler readout, over geometry */
|
|
22
|
+
--chip-scrim: rgba(12, 12, 13, 0.85); /* reload chip, over geometry */
|
|
23
|
+
--border: #333333;
|
|
24
|
+
--border-strong: #555555;
|
|
25
|
+
--accent: #4a9eff;
|
|
26
|
+
--accent-fg: #06121f; /* text on an --accent fill */
|
|
27
|
+
--accent-text: #cfe6ff; /* --accent as text, on --chip-scrim */
|
|
28
|
+
--accent-border: #2a3f57;
|
|
29
|
+
--danger: #ff4d4d;
|
|
30
|
+
--danger-dim: #ff9a9a;
|
|
31
|
+
--select-bg: #5a1f1f; /* selected marker's row */
|
|
32
|
+
--select-fg: #ffd9d9;
|
|
33
|
+
--error-bg: #2a1416;
|
|
34
|
+
--error-border: #7a2b2b;
|
|
35
|
+
--error-fg: #ffd9d9;
|
|
36
|
+
--warn-bg: #4a3410; /* "newer version on disk" banner */
|
|
37
|
+
--warn-border: #7a5a1c;
|
|
38
|
+
--warn-border-strong: #a37a26;
|
|
39
|
+
--warn-fg: #ffe9c2;
|
|
40
|
+
--warn-fg-dim: #e5cfa4;
|
|
41
|
+
--warn-btn-fg: #fff6e5;
|
|
42
|
+
--log-fg: #7fff7f;
|
|
43
|
+
--log-err: #ff7f7f;
|
|
44
|
+
/* lil-gui's own palette (mapped onto its variable names below). */
|
|
45
|
+
--panel-bg: #1f1f1f;
|
|
46
|
+
--panel-fg: #ebebeb;
|
|
47
|
+
--panel-title-bg: #111111;
|
|
48
|
+
--panel-widget: #424242;
|
|
49
|
+
--panel-widget-hover: #4f4f4f;
|
|
50
|
+
--panel-widget-focus: #595959;
|
|
51
|
+
}
|
|
52
|
+
:host(.theme-light) {
|
|
53
|
+
--bg: #fafafb;
|
|
54
|
+
--fg: #17171a;
|
|
55
|
+
--fg-dim: #56565c;
|
|
56
|
+
--fg-faint: #6b6b73; /* matches the dark side's contrast, not its lightness */
|
|
57
|
+
--surface: #e4e4e8;
|
|
58
|
+
--surface-fg: #2c2c2e;
|
|
59
|
+
--scrim: rgba(250, 250, 251, 0.93);
|
|
60
|
+
--label-scrim: rgba(255, 255, 255, 0.85);
|
|
61
|
+
--chip-scrim: rgba(255, 255, 255, 0.9);
|
|
62
|
+
--border: #d2d2d8;
|
|
63
|
+
--border-strong: #b4b4bc;
|
|
64
|
+
--accent: #1f6fd0;
|
|
65
|
+
--accent-fg: #ffffff;
|
|
66
|
+
--accent-text: #14538f;
|
|
67
|
+
--accent-border: #a8c8e8;
|
|
68
|
+
--danger: #b3261e;
|
|
69
|
+
--danger-dim: #d0574e;
|
|
70
|
+
--select-bg: #ffdcdc;
|
|
71
|
+
--select-fg: #7a1f1f;
|
|
72
|
+
--error-bg: #fdeceb;
|
|
73
|
+
--error-border: #dda3a0;
|
|
74
|
+
--error-fg: #7d201c;
|
|
75
|
+
--warn-bg: #fdf3d8;
|
|
76
|
+
--warn-border: #e0c07a;
|
|
77
|
+
--warn-border-strong: #c9a153;
|
|
78
|
+
--warn-fg: #5c431a;
|
|
79
|
+
--warn-fg-dim: #7d6231;
|
|
80
|
+
--warn-btn-fg: #3f2d0e;
|
|
81
|
+
--log-fg: #157f2a;
|
|
82
|
+
--log-err: #b3261e;
|
|
83
|
+
--panel-bg: #f4f4f6;
|
|
84
|
+
--panel-fg: #2c2c2e;
|
|
85
|
+
--panel-title-bg: #e4e4e8;
|
|
86
|
+
--panel-widget: #d8d8de;
|
|
87
|
+
--panel-widget-hover: #c8c8d0;
|
|
88
|
+
--panel-widget-focus: #babac2;
|
|
89
|
+
}
|
|
90
|
+
/* lil-gui's palette is its own set of custom properties, so the panel
|
|
91
|
+
themes by re-pointing those at the --panel-* tokens above rather
|
|
92
|
+
than by restyling any of its rows. Mapped here on .lil-gui rather
|
|
93
|
+
than on :root because lil-gui's own .lil-gui rule sets them, and a
|
|
94
|
+
rule matching the element itself beats an inherited value. (Source
|
|
95
|
+
order settles the tie: lil-gui inserts its stylesheet *before* the
|
|
96
|
+
first <style> already in <head> -- i.e. this one -- specifically so
|
|
97
|
+
page styles outrank its defaults.) */
|
|
98
|
+
.lil-gui {
|
|
99
|
+
--background-color: var(--panel-bg);
|
|
100
|
+
--text-color: var(--panel-fg);
|
|
101
|
+
--title-background-color: var(--panel-title-bg);
|
|
102
|
+
--title-text-color: var(--panel-fg);
|
|
103
|
+
--widget-color: var(--panel-widget);
|
|
104
|
+
--hover-color: var(--panel-widget-hover);
|
|
105
|
+
--focus-color: var(--panel-widget-focus);
|
|
106
|
+
--number-color: var(--accent);
|
|
107
|
+
}
|
|
108
|
+
/* A custom element is display:inline by default and would collapse to
|
|
109
|
+
zero height. The host owns the box; everything inside positions
|
|
110
|
+
against it rather than against the page. */
|
|
111
|
+
:host { display: block; position: relative; width: 100%; height: 100%; overflow: hidden;
|
|
112
|
+
background: var(--bg); user-select: none; color: var(--fg); }
|
|
113
|
+
canvas { width: 100%; height: 100%; display: block; }
|
|
114
|
+
/* The engine readout (polygon/label counts + the per-frame #renderStats
|
|
115
|
+
span, both written from renderer.cpp). It lives inside #debugPanel
|
|
116
|
+
rather than floating over the canvas on its own: it's debug-only
|
|
117
|
+
either way, and two separate debug surfaces meant the same command
|
|
118
|
+
lit up two different corners of the screen. */
|
|
119
|
+
#ui { width: 320px; flex: none; overflow-y: auto; padding: 6px 8px; border-right: 1px solid var(--border);
|
|
120
|
+
color: var(--fg-dim); font-family: monospace; font-size: 11px; line-height: 1.5; user-select: text; }
|
|
121
|
+
/* The debug entry point is hidden until the "GDSLens: Toggle Debug
|
|
122
|
+
Tools" command adds .debug to <body>. */
|
|
123
|
+
#debugToggleBtn { display: none; }
|
|
124
|
+
:host(.debug) #debugToggleBtn { display: block; }
|
|
125
|
+
/* Bottom-right stack of view readouts: the scale bar, with the
|
|
126
|
+
pointer's world coordinate under it. One anchored column rather than
|
|
127
|
+
two separately positioned boxes, so the coordinate row -- whose width
|
|
128
|
+
varies with the number in it -- can't drift into or away from the bar
|
|
129
|
+
above it. */
|
|
130
|
+
#viewReadout { position: absolute; bottom: 20px; right: 20px; z-index: 10; pointer-events: none;
|
|
131
|
+
display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }
|
|
132
|
+
#scaleContainer { font-family: monospace; font-size: 11px; color: var(--fg); text-align: center; }
|
|
133
|
+
/* Pointer coordinate, rewritten by update_coord_readout (renderer.cpp)
|
|
134
|
+
on every move and every camera change; .hidden while the pointer is
|
|
135
|
+
off the canvas. Unlike the scale bar it's a run of text over
|
|
136
|
+
arbitrary geometry, so it carries the same scrim the ruler label
|
|
137
|
+
does rather than relying on an outline. */
|
|
138
|
+
#coordReadout { font-family: monospace; font-size: 11px; color: var(--fg); background: var(--label-scrim);
|
|
139
|
+
border: 1px solid var(--border-strong); border-radius: 3px; padding: 2px 6px;
|
|
140
|
+
white-space: nowrap; }
|
|
141
|
+
#coordReadout.hidden { display: none; }
|
|
142
|
+
/* Confirmation that the canvas menu's Copy coordinate reached the
|
|
143
|
+
clipboard (see the contextmenu handler in viewer.js). Same scrim as
|
|
144
|
+
the readout it answers for. It sits first in the column on purpose:
|
|
145
|
+
#viewReadout is anchored by its bottom edge, so a line appearing at
|
|
146
|
+
the top leaves the scale bar and the coordinate exactly where they
|
|
147
|
+
were -- one added at the bottom would shove both up mid-read. */
|
|
148
|
+
#copyToast { font-family: monospace; font-size: 11px; color: var(--fg); background: var(--label-scrim);
|
|
149
|
+
border: 1px solid var(--border-strong); border-radius: 3px; padding: 2px 6px;
|
|
150
|
+
white-space: nowrap; }
|
|
151
|
+
#copyToast.hidden { display: none; }
|
|
152
|
+
/* Right-click menu on the canvas (see the contextmenu handler in
|
|
153
|
+
viewer.js). VS Code's own webview menu is suppressed for it -- the
|
|
154
|
+
preload bails out of showing one when the page has already called
|
|
155
|
+
preventDefault -- so this has to look like a menu on its own. It
|
|
156
|
+
borrows the panel's palette rather than the readouts' scrim: it sits
|
|
157
|
+
over the layout but it's chrome, not an annotation of what's under
|
|
158
|
+
it. Above the debug panel (z 41), since a menu is the most recent
|
|
159
|
+
thing the user asked for. */
|
|
160
|
+
#canvasMenu { position: absolute; z-index: 50; min-width: 190px; padding: 4px;
|
|
161
|
+
background: var(--panel-bg); color: var(--panel-fg);
|
|
162
|
+
border: 1px solid var(--border-strong); border-radius: 4px;
|
|
163
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.4); }
|
|
164
|
+
#canvasMenu.hidden { display: none; }
|
|
165
|
+
#canvasMenu button { display: flex; align-items: baseline; gap: 14px; width: 100%;
|
|
166
|
+
background: none; border: 0; border-radius: 3px; padding: 4px 8px;
|
|
167
|
+
font-family: inherit; font-size: 12px; color: inherit;
|
|
168
|
+
text-align: left; white-space: nowrap; cursor: pointer; }
|
|
169
|
+
#canvasMenu button:hover, #canvasMenu button:focus { background: var(--accent); color: var(--accent-fg); outline: none; }
|
|
170
|
+
/* The coordinate itself, shown in the readout's monospace so it reads as
|
|
171
|
+
the value being copied rather than as a menu shortcut. */
|
|
172
|
+
#canvasMenu .menu-value { margin-left: auto; font-family: monospace; font-size: 11px; color: var(--fg-dim); }
|
|
173
|
+
#canvasMenu button:hover .menu-value, #canvasMenu button:focus .menu-value { color: inherit; }
|
|
174
|
+
/* The bar is drawn in the ink color over an outline of the background,
|
|
175
|
+
so it stays readable whichever of the two it happens to sit on top of
|
|
176
|
+
(it floats over the geometry, not over a panel). */
|
|
177
|
+
#scaleBar { height: 4px; background: var(--fg); border: 1px solid var(--bg); margin-top: 4px; }
|
|
178
|
+
/* Ruler readouts: one .measure-label per ruler, created and
|
|
179
|
+
repositioned to its measurement's midpoint on every redraw by
|
|
180
|
+
update_measure_labels (renderer.cpp). The translate lifts each one
|
|
181
|
+
just above its line so it doesn't cover what's being measured.
|
|
182
|
+
#measureLabels is a plain (unpositioned) container, so the labels
|
|
183
|
+
inside it still position against the page the way the single one
|
|
184
|
+
used to. */
|
|
185
|
+
#measureLabels .measure-label { position: absolute; transform: translate(-50%, -130%); font-family: monospace; font-size: 11px; color: var(--fg); background: var(--label-scrim); border: 1px solid var(--border-strong); border-radius: 3px; padding: 3px 7px; text-align: center; line-height: 1.4; white-space: nowrap; z-index: 15; pointer-events: none; }
|
|
186
|
+
/* lil-gui floats its own panel only when it appends to document.body,
|
|
187
|
+
by adding .lil-auto-place (position:fixed, right:15px, z 1001). We
|
|
188
|
+
pass a container instead so the panel lives inside the shadow root,
|
|
189
|
+
which skips that class entirely -- so without this the panel is a
|
|
190
|
+
plain block in normal flow, spanning the full width and pushing the
|
|
191
|
+
canvas down. Positioning it here also fixes two things lil-gui's own
|
|
192
|
+
rule got wrong for a component: absolute rather than fixed, so the
|
|
193
|
+
panel stays inside the component's box instead of pinning itself to
|
|
194
|
+
the viewport when embedded in a page; and z-index below the loading
|
|
195
|
+
overlay (30) and debug panel (40) rather than lil-gui's 1001. */
|
|
196
|
+
#guiHost { position: absolute; top: 0; right: 15px; max-height: 100%; z-index: 20; display: flex; }
|
|
197
|
+
#guiHost > .lil-gui.lil-root { max-height: 100%; }
|
|
198
|
+
/* Checkbox rows (layer list + top-level toggles): lil-gui gives the
|
|
199
|
+
label a 45% min-width and no overflow handling, so a long name like
|
|
200
|
+
"250/74 – SIN_WG_S" would push into the checkbox column. Widen the
|
|
201
|
+
label (the checkbox needs nowhere near half the row) and cut it
|
|
202
|
+
character-level with an ellipsis. */
|
|
203
|
+
.lil-gui .lil-controller.lil-boolean > .lil-name { min-width: 78%; width: 78%; overflow: hidden; text-overflow: ellipsis; }
|
|
204
|
+
/* ---- Layer rows (see addLayerRow in viewer.js) ----
|
|
205
|
+
A layer row carries two more things than the plain boolean above: its
|
|
206
|
+
shape count and a solo button, both appended after lil-gui's own
|
|
207
|
+
name/widget pair. .lil-controller is already a flex row, so they only
|
|
208
|
+
need flex:none to sit at the right edge; the name gives up the width
|
|
209
|
+
they take (this rule follows the 78% one above deliberately -- same
|
|
210
|
+
specificity, so source order decides). */
|
|
211
|
+
.lil-gui .lil-controller.layer-row > .lil-name { min-width: 58%; width: 58%; }
|
|
212
|
+
.lil-gui .lil-controller.layer-row .layer-count { flex: none; padding-left: 4px;
|
|
213
|
+
color: var(--fg-faint); font-size: 10px; }
|
|
214
|
+
.lil-gui .lil-controller.layer-row .layer-solo { flex: none; width: 16px; text-align: center;
|
|
215
|
+
margin-left: 4px; border-radius: 2px;
|
|
216
|
+
color: var(--fg-faint); font-size: 10px; cursor: pointer; }
|
|
217
|
+
.lil-gui .lil-controller.layer-row .layer-solo:hover { color: var(--fg); background: var(--panel-widget-hover); }
|
|
218
|
+
/* The soloed layer's own row, so it's clear which of the hidden
|
|
219
|
+
hundred is the one still on screen. */
|
|
220
|
+
.lil-gui .lil-controller.layer-row.layer-soloed .layer-solo { color: var(--accent-fg); background: var(--accent); }
|
|
221
|
+
/* Filter box at the top of the Layers folder. lil-gui styles the inputs
|
|
222
|
+
belonging to its own controllers; this one is hand-built, so it
|
|
223
|
+
borrows the same widget fill. */
|
|
224
|
+
.lil-gui .lil-controller.layer-filter-row input { width: 100%; box-sizing: border-box;
|
|
225
|
+
font-family: inherit; font-size: var(--input-font-size);
|
|
226
|
+
background: var(--panel-widget); color: var(--panel-fg);
|
|
227
|
+
border: none; border-radius: var(--widget-border-radius);
|
|
228
|
+
height: var(--widget-height); padding: 0 6px; }
|
|
229
|
+
.lil-gui .lil-controller.layer-filter-row input:focus { background: var(--panel-widget-focus); outline: none; }
|
|
230
|
+
/* Interaction-mode row (Pan | Measure), hand-built in viewer.js since
|
|
231
|
+
lil-gui has no segmented-control type. Because the row carries
|
|
232
|
+
lil-gui's own classes, its button theming (widget color, radius,
|
|
233
|
+
height, :hover/:active) already applies -- all that's left is
|
|
234
|
+
splitting the widget in two and filling in the selected mode. The
|
|
235
|
+
name column is narrower than lil-gui's 45% default to leave both
|
|
236
|
+
buttons a readable width. */
|
|
237
|
+
.lil-gui .lil-controller.mode-row .lil-name { min-width: 30%; }
|
|
238
|
+
.lil-gui .lil-controller.mode-row .mode-widget { gap: 4px; }
|
|
239
|
+
.lil-gui .lil-controller.mode-row .mode-widget button { flex: 1; }
|
|
240
|
+
/* :hover/:active spelled out too -- lil-gui's own button:hover rule
|
|
241
|
+
outranks a bare .mode-active, which would grey out the selected
|
|
242
|
+
mode as soon as the pointer crossed it. */
|
|
243
|
+
.lil-gui .lil-controller.mode-row .mode-widget button.mode-active,
|
|
244
|
+
.lil-gui .lil-controller.mode-row .mode-widget button.mode-active:hover,
|
|
245
|
+
.lil-gui .lil-controller.mode-row .mode-widget button.mode-active:active { background: var(--accent); color: var(--accent-fg); font-weight: bold; }
|
|
246
|
+
/* Loaded-.lyp chip: the row shows the filename with an ✕ on the right
|
|
247
|
+
(injected by setLypChip in viewer.js) to unload it. */
|
|
248
|
+
.lil-gui .lil-controller.lyp-loaded { position: relative; }
|
|
249
|
+
.lil-gui .lil-controller.lyp-loaded .lil-name { padding-right: 22px; }
|
|
250
|
+
.lil-gui .lil-controller.lyp-loaded .lyp-unload { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); color: var(--danger-dim); cursor: pointer; font-size: 12px; z-index: 5; }
|
|
251
|
+
.lil-gui .lil-controller.lyp-loaded .lyp-unload:hover { color: var(--danger); }
|
|
252
|
+
/* Saved-view rows (see renderNamedViews in viewer.js): a full-width
|
|
253
|
+
button that restores the view, with an ✕ on the right that deletes
|
|
254
|
+
it -- the same shape as the loaded-file chips above, and the same
|
|
255
|
+
reason for spelling it out separately (lil-gui has no row that
|
|
256
|
+
carries a second action). */
|
|
257
|
+
.lil-gui .lil-controller.view-row { position: relative; }
|
|
258
|
+
.lil-gui .lil-controller.view-row .lil-name { padding-right: 22px; }
|
|
259
|
+
.lil-gui .lil-controller.view-row .view-delete { position: absolute; right: 8px; top: 50%;
|
|
260
|
+
transform: translateY(-50%); color: var(--danger-dim);
|
|
261
|
+
cursor: pointer; font-size: 12px; z-index: 5; }
|
|
262
|
+
.lil-gui .lil-controller.view-row .view-delete:hover { color: var(--danger); }
|
|
263
|
+
/* Marker browser (see renderMarkerBrowser in viewer.js): the selected
|
|
264
|
+
violation's row, and the disabled "… N more" overflow row. The row
|
|
265
|
+
is a full-width <button> in lil-gui, so tint the button itself
|
|
266
|
+
(!important also beats lil-gui's :hover/:active backgrounds). */
|
|
267
|
+
.lil-gui .lil-controller.marker-selected button { background: var(--select-bg) !important; }
|
|
268
|
+
.lil-gui .lil-controller.marker-selected .lil-name { color: var(--select-fg); }
|
|
269
|
+
.lil-gui .lil-controller.marker-more-row { opacity: 0.55; pointer-events: none; }
|
|
270
|
+
/* ---- Hierarchy tree (see renderHierarchy in viewer.js) ----
|
|
271
|
+
The design's cell tree, down the left edge. Like the lil-gui panel
|
|
272
|
+
on the right it floats over the canvas rather than taking width
|
|
273
|
+
from it: the canvas stays the full window, so nothing about the
|
|
274
|
+
renderer's sizing, hit-testing or camera has to know this panel
|
|
275
|
+
exists. It outranks both progress UIs (z 25/26) so a reload doesn't
|
|
276
|
+
draw its hairline bar across the tree, but stays under the
|
|
277
|
+
full-screen loading overlay (z 30) and the debug panel (z 40). */
|
|
278
|
+
#hierarchyPanel { position: absolute; top: 0; left: 0; bottom: 0; width: 260px; z-index: 27;
|
|
279
|
+
box-sizing: border-box; display: flex; flex-direction: column;
|
|
280
|
+
background: var(--scrim); border-right: 1px solid var(--border);
|
|
281
|
+
font-family: monospace; font-size: 11px; color: var(--surface-fg); }
|
|
282
|
+
#hierarchyPanel.hidden { display: none; }
|
|
283
|
+
#hierarchyHeader { display: flex; align-items: center; gap: 8px; flex: none; padding: 6px 8px;
|
|
284
|
+
color: var(--fg-dim); border-bottom: 1px solid var(--border); }
|
|
285
|
+
#hierarchyTitle { flex: 1; }
|
|
286
|
+
#hierarchyCount { color: var(--fg-faint); }
|
|
287
|
+
/* Same icon-only treatment as #staleDismiss: a real button, styled
|
|
288
|
+
back down to the bare glyph it was before. */
|
|
289
|
+
#hierarchyHide { background: none; border: 0; font: inherit; color: inherit;
|
|
290
|
+
cursor: pointer; opacity: 0.7; padding: 0 2px; }
|
|
291
|
+
#hierarchyHide:hover { opacity: 1; }
|
|
292
|
+
#hierarchyTree { flex: 1; overflow: auto; padding: 4px 0 8px; }
|
|
293
|
+
/* One row per node. The whole row frames its cell on click, so it's
|
|
294
|
+
the click target; only the left indent varies with depth (set
|
|
295
|
+
inline, since it's per-row). */
|
|
296
|
+
.hier-row { display: flex; align-items: center; gap: 4px; padding: 2px 8px 2px 0;
|
|
297
|
+
white-space: nowrap; cursor: pointer; }
|
|
298
|
+
.hier-row:hover { background: var(--surface); }
|
|
299
|
+
.hier-row.hier-selected, .hier-row.hier-selected:hover { background: var(--accent); color: var(--accent-fg); }
|
|
300
|
+
/* A cell with no geometry anywhere under it -- there's nothing to
|
|
301
|
+
frame, so the row only selects. */
|
|
302
|
+
.hier-row.hier-boxless { opacity: 0.55; cursor: default; }
|
|
303
|
+
.hier-twisty { flex: none; width: 12px; text-align: center; color: var(--fg-faint); }
|
|
304
|
+
/* flex + min-width:0 is what lets a long cell name actually truncate:
|
|
305
|
+
a flex item's default min-width is its content, and a cell name is
|
|
306
|
+
one unbreakable word, so without this the row grows and the panel
|
|
307
|
+
scrolls sideways instead. Taking the leftover width also pins the
|
|
308
|
+
placement count to the right edge. */
|
|
309
|
+
.hier-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
310
|
+
/* Placement count ("×64"), shown only when the cell is placed more
|
|
311
|
+
than once by its parent. */
|
|
312
|
+
.hier-count { flex: none; color: var(--fg-faint); }
|
|
313
|
+
.hier-row.hier-selected .hier-twisty, .hier-row.hier-selected .hier-count { color: var(--accent-fg); }
|
|
314
|
+
.hier-children.hidden { display: none; }
|
|
315
|
+
/* ---- Find (see the search block in viewer.js) ----
|
|
316
|
+
Sits between the header and the tree: it's a way into the same
|
|
317
|
+
panel, not a second panel. One row while it's closed, which is how
|
|
318
|
+
it starts -- see the markup below for why -- opening to the box and
|
|
319
|
+
the scope pair. The pair gets its own row rather than sharing one
|
|
320
|
+
with the input: at 260px a field narrowed by two buttons is too
|
|
321
|
+
small to read a cell name in. */
|
|
322
|
+
#hierarchyFind { flex: none; border-bottom: 1px solid var(--border); }
|
|
323
|
+
/* The closed row. Styled like a tree row rather than like the panel
|
|
324
|
+
header, because that's what it is: something you click to open. */
|
|
325
|
+
#hierarchyFindToggle { display: flex; align-items: center; gap: 4px; padding: 3px 8px;
|
|
326
|
+
color: var(--fg-dim); cursor: pointer; white-space: nowrap;
|
|
327
|
+
/* A <button> now, for keyboard and screen readers: reset the
|
|
328
|
+
chrome and let it fill the row as the div did. */
|
|
329
|
+
width: 100%; background: none; border: 0; font: inherit;
|
|
330
|
+
text-align: left; }
|
|
331
|
+
#hierarchyFindToggle:hover { background: var(--surface); color: var(--fg); }
|
|
332
|
+
#hierarchyFindTwisty { flex: none; width: 12px; text-align: center; color: var(--fg-faint); }
|
|
333
|
+
#hierarchyFindLabel { flex: none; }
|
|
334
|
+
#hierarchySearch { padding: 0 8px 6px; display: flex; flex-direction: column; gap: 5px; }
|
|
335
|
+
#hierarchySearch.hidden { display: none; }
|
|
336
|
+
/* user-select is off for the page as a whole (dragging the canvas would
|
|
337
|
+
otherwise select the chrome over it), and that inherits into a text
|
|
338
|
+
field -- where it costs you selecting what you typed, and makes the
|
|
339
|
+
select() behind the "/" shortcut a no-op. */
|
|
340
|
+
#hierarchySearchInput { width: 100%; box-sizing: border-box; font-family: inherit; font-size: 11px;
|
|
341
|
+
background: var(--surface); color: var(--surface-fg);
|
|
342
|
+
border: 1px solid var(--border-strong); border-radius: 3px;
|
|
343
|
+
padding: 3px 6px; user-select: text; }
|
|
344
|
+
#hierarchySearchInput:focus { outline: none; border-color: var(--accent); }
|
|
345
|
+
#hierarchyScope { display: flex; gap: 4px; }
|
|
346
|
+
#hierarchyScope button { font-family: inherit; font-size: 10px; background: var(--surface);
|
|
347
|
+
color: var(--fg-dim); border: 1px solid var(--border-strong);
|
|
348
|
+
border-radius: 3px; padding: 1px 8px; cursor: pointer; }
|
|
349
|
+
#hierarchyScope button:hover { color: var(--fg); }
|
|
350
|
+
#hierarchyScope button.scope-active, #hierarchyScope button.scope-active:hover {
|
|
351
|
+
background: var(--accent); color: var(--accent-fg); border-color: var(--accent); font-weight: bold; }
|
|
352
|
+
/* A design too large for a cell tree has no cell names on this side at
|
|
353
|
+
all (see the omitted case in renderHierarchy), so Cells has nothing
|
|
354
|
+
to search -- the button says so rather than returning nothing. */
|
|
355
|
+
#hierarchyScope button:disabled { opacity: 0.4; cursor: default; }
|
|
356
|
+
/* On the toggle row rather than beside the scope pair: it's the result
|
|
357
|
+
of the search, so it belongs next to the thing you'd click to get
|
|
358
|
+
back to it. Empty whenever the fold is closed, since closing clears
|
|
359
|
+
the query. */
|
|
360
|
+
#hierarchySearchCount { flex: 1; text-align: right; color: var(--fg-faint); overflow: hidden;
|
|
361
|
+
text-overflow: ellipsis; }
|
|
362
|
+
/* Results replace the tree rather than stacking under it, so the panel
|
|
363
|
+
shows one list at a time; clearing the box brings the tree back with
|
|
364
|
+
its open branches untouched. */
|
|
365
|
+
#hierarchyResults { flex: 1; overflow: auto; padding: 4px 0 8px; }
|
|
366
|
+
#hierarchyResults.hidden, #hierarchyTree.hidden { display: none; }
|
|
367
|
+
/* A result row is the tree's row with the indent and twisty dropped and
|
|
368
|
+
a right-hand detail added (a cell's shape count, a label's layer). */
|
|
369
|
+
.find-row { display: flex; align-items: baseline; gap: 6px; padding: 2px 8px;
|
|
370
|
+
white-space: nowrap; cursor: pointer; }
|
|
371
|
+
.find-row:hover { background: var(--surface); }
|
|
372
|
+
.find-row.find-active, .find-row.find-active:hover { background: var(--accent); color: var(--accent-fg); }
|
|
373
|
+
.find-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
374
|
+
.find-meta { flex: none; color: var(--fg-faint); font-size: 10px; }
|
|
375
|
+
.find-row.find-active .find-meta { color: var(--accent-fg); }
|
|
376
|
+
/* A cell no top cell reaches (a reference cycle, or one the tree's own
|
|
377
|
+
caps left out) has no path to open down to, so its row only says so. */
|
|
378
|
+
.find-row.find-unreachable { opacity: 0.55; cursor: default; }
|
|
379
|
+
#hierarchyResults .find-note { padding: 6px 10px; color: var(--fg-faint); white-space: normal; line-height: 1.5; }
|
|
380
|
+
/* Stand-in for a tree: no cells to show, or too many to describe. */
|
|
381
|
+
#hierarchyTree .hier-note { padding: 6px 10px; color: var(--fg-faint); white-space: normal; line-height: 1.5; }
|
|
382
|
+
/* Reopens the panel after it's been hidden; sits where the panel's own
|
|
383
|
+
header was. Hidden while the panel is open, and while there's no
|
|
384
|
+
tree to reopen (viewer.js adds .hidden for that). */
|
|
385
|
+
#hierarchyShowBtn { position: absolute; top: 8px; left: 8px; z-index: 27; font-family: monospace;
|
|
386
|
+
font-size: 10px; background: var(--surface); color: var(--fg-dim);
|
|
387
|
+
border: 1px solid var(--border-strong); border-radius: 3px; padding: 2px 8px;
|
|
388
|
+
cursor: pointer; opacity: 0.6; }
|
|
389
|
+
#hierarchyShowBtn:hover { opacity: 1; }
|
|
390
|
+
#hierarchyShowBtn.hidden, :host(.hierarchy-open) #hierarchyShowBtn { display: none; }
|
|
391
|
+
/* Everything else anchored to the left edge starts after whichever of
|
|
392
|
+
the two is on screen -- the panel, or just the button that reopens
|
|
393
|
+
it (.hierarchy-available). The open rule comes second so it wins
|
|
394
|
+
when both classes are set. */
|
|
395
|
+
:host(.hierarchy-available) #staleBanner { left: 88px; }
|
|
396
|
+
:host(.hierarchy-open) #staleBanner { left: 268px; }
|
|
397
|
+
:host(.hierarchy-open) #debugToggleBtn { left: 268px; }
|
|
398
|
+
/* "Newer version on disk" header (see the fileChanged handler in
|
|
399
|
+
viewer.js). Sits above the lil-gui panel (z 20) but below the
|
|
400
|
+
loading overlay (z 30), since it's hidden while a load runs
|
|
401
|
+
anyway. */
|
|
402
|
+
/* Stops short of the right edge so it doesn't cover the lil-gui panel
|
|
403
|
+
(260px wide, top-right) that it outranks on z-index. */
|
|
404
|
+
#staleBanner { position: absolute; top: 0; left: 0; right: 276px; box-sizing: border-box; z-index: 25;
|
|
405
|
+
display: flex; align-items: center; gap: 12px; padding: 7px 12px;
|
|
406
|
+
font-family: monospace; font-size: 12px; color: var(--warn-fg);
|
|
407
|
+
background: var(--warn-bg); border-bottom: 1px solid var(--warn-border); }
|
|
408
|
+
#staleBanner.hidden { display: none; }
|
|
409
|
+
#staleText { flex: 1; }
|
|
410
|
+
#staleBanner button { font-family: monospace; font-size: 11px; background: var(--warn-border); color: var(--warn-btn-fg);
|
|
411
|
+
border: 1px solid var(--warn-border-strong); border-radius: 3px; padding: 3px 12px; cursor: pointer; }
|
|
412
|
+
#staleBanner button:hover { background: var(--warn-border-strong); }
|
|
413
|
+
/* Secondary next to Reload: same action plus a persisted setting, so it
|
|
414
|
+
reads as the quieter of the two. */
|
|
415
|
+
#staleBanner button.secondary { background: transparent; border-color: transparent;
|
|
416
|
+
color: var(--warn-fg-dim); padding: 3px 6px; }
|
|
417
|
+
#staleBanner button.secondary:hover { background: transparent; color: var(--warn-btn-fg); text-decoration: underline; }
|
|
418
|
+
/* An icon-only control: a real <button> for keyboard and screen
|
|
419
|
+
readers, styled back down to bare text. The reset is explicit
|
|
420
|
+
because #staleBanner button above is more specific than an id on its
|
|
421
|
+
own and would otherwise give this the Reload button's chrome. */
|
|
422
|
+
#staleBanner button#staleDismiss { background: none; border: 0; border-radius: 0;
|
|
423
|
+
font: inherit; color: inherit; cursor: pointer;
|
|
424
|
+
opacity: 0.7; padding: 0 2px; }
|
|
425
|
+
#staleBanner button#staleDismiss:hover { background: none; opacity: 1; }
|
|
426
|
+
#loadingOverlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; font-family: monospace; color: var(--fg-dim); background: var(--bg); z-index: 30; }
|
|
427
|
+
#loadingOverlay.hidden { display: none; }
|
|
428
|
+
#loadingBarTrack { width: 320px; height: 8px; background: var(--surface); border-radius: 4px; overflow: hidden; }
|
|
429
|
+
#loadingBarFill { width: 0%; height: 100%; background: var(--accent); transition: width 0.15s ease-out; }
|
|
430
|
+
#loadingPhase { font-size: 12px; }
|
|
431
|
+
#loadingPercent { font-size: 11px; color: var(--fg-faint); }
|
|
432
|
+
/* The reload flavour of the same progress (see beginProgress in
|
|
433
|
+
viewer.js): a hairline bar along the top edge plus a small chip,
|
|
434
|
+
over a viewport that keeps showing the geometry already loaded.
|
|
435
|
+
Restoring the camera across a reload is pointless if the view is
|
|
436
|
+
blacked out while it happens. pointer-events stay off so a drag
|
|
437
|
+
that starts under the chip still pans the canvas. */
|
|
438
|
+
#reloadProgress { position: absolute; top: 0; left: 0; right: 0; z-index: 26; pointer-events: none; }
|
|
439
|
+
#reloadProgress.hidden { display: none; }
|
|
440
|
+
#reloadBarTrack { height: 2px; background: var(--surface); }
|
|
441
|
+
#reloadBarFill { width: 0%; height: 100%; background: var(--accent); transition: width 0.15s ease-out; }
|
|
442
|
+
#reloadLabel { display: inline-block; margin: 6px 0 0 10px; padding: 3px 8px; font-family: monospace;
|
|
443
|
+
font-size: 11px; color: var(--accent-text); background: var(--chip-scrim);
|
|
444
|
+
border: 1px solid var(--accent-border); border-radius: 3px; }
|
|
445
|
+
/* Load failures. This is deliberately NOT part of the debug tools:
|
|
446
|
+
#ui above is display:none unless the debug command is run, so an
|
|
447
|
+
error written only there is invisible to a normal user. Empty means
|
|
448
|
+
no error, so setting/clearing the text is all it takes to show or
|
|
449
|
+
hide the panel. */
|
|
450
|
+
#loadError { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); max-width: 520px; z-index: 35; padding: 16px 20px; background: var(--error-bg); border: 1px solid var(--error-border); border-radius: 5px; font-family: monospace; font-size: 12px; line-height: 1.55; color: var(--error-fg); white-space: pre-line; user-select: text; }
|
|
451
|
+
#loadError:empty { display: none; }
|
|
452
|
+
#debugPanel { position: absolute; bottom: 0; left: 0; width: 100%; max-height: 240px; z-index: 40; background: var(--scrim); border-top: 1px solid var(--border); display: flex; flex-direction: column; }
|
|
453
|
+
#debugPanel.hidden { display: none; }
|
|
454
|
+
#debugPanelHeader { display: flex; align-items: center; justify-content: space-between; padding: 4px 8px; font-family: monospace; font-size: 11px; color: var(--fg-dim); border-bottom: 1px solid var(--border); }
|
|
455
|
+
#debugPanelHeader button { font-family: monospace; font-size: 10px; background: var(--surface); color: var(--surface-fg); border: 1px solid var(--border-strong); border-radius: 3px; padding: 2px 8px; cursor: pointer; }
|
|
456
|
+
/* Readout beside the log rather than above it: the panel is capped at
|
|
457
|
+
240px tall, and stacking them would leave the log a few lines. */
|
|
458
|
+
#debugBody { display: flex; flex: 1; min-height: 0; }
|
|
459
|
+
#debugLog { flex: 1; overflow-y: auto; padding: 6px 8px; font-family: monospace; font-size: 10px; color: var(--log-fg); white-space: pre-wrap; word-break: break-all; user-select: text; }
|
|
460
|
+
#debugLog .err { color: var(--log-err); }
|
|
461
|
+
#debugToggleBtn { position: absolute; bottom: 8px; left: 8px; z-index: 41; font-family: monospace; font-size: 10px; background: var(--surface); color: var(--fg-dim); border: 1px solid var(--border-strong); border-radius: 3px; padding: 2px 8px; cursor: pointer; opacity: 0.6; }
|
|
462
|
+
#debugToggleBtn:hover { opacity: 1; }
|
|
463
|
+
|
|
464
|
+
/* The progress bars are the only motion in the viewer. Someone who has
|
|
465
|
+
asked the OS for less of it still needs the bar to show progress --
|
|
466
|
+
so it still moves, it just stops easing there. */
|
|
467
|
+
@media (prefers-reduced-motion: reduce) {
|
|
468
|
+
#loadingBarFill, #reloadBarFill { transition: none; }
|
|
469
|
+
}
|
package/src/viewer.html
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta http-equiv="Content-Security-Policy"
|
|
6
|
+
content="default-src 'none'; style-src 'unsafe-inline'; script-src 'self' 'wasm-unsafe-eval'; worker-src blob:; connect-src 'self';">
|
|
7
|
+
<title>GDS Lens</title>
|
|
8
|
+
<style>
|
|
9
|
+
/* The page harness only. Everything the viewer itself draws is styled
|
|
10
|
+
inside its shadow root (see viewer.css), which is why there is so
|
|
11
|
+
little here: a host page cannot reach in, and this cannot leak out. */
|
|
12
|
+
html, body { margin: 0; padding: 0; height: 100%; background: #1a1a1a; }
|
|
13
|
+
gds-lens { display: block; width: 100%; height: 100%; }
|
|
14
|
+
</style>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<gds-lens></gds-lens>
|
|
18
|
+
|
|
19
|
+
<!-- Emscripten's output, loaded as a classic script for the
|
|
20
|
+
createGdstkModule global it defines. -->
|
|
21
|
+
<script src="gds-lens-engine.js"></script>
|
|
22
|
+
<!-- The embedder's ViewerHost. Must precede gds-lens.js, which reads
|
|
23
|
+
window.gdsLensHost as it starts. Defaults to the plain-page host; a
|
|
24
|
+
host that needs different services replaces this file, or sets
|
|
25
|
+
window.gdsLensHost itself and omits it. -->
|
|
26
|
+
<script src="gds-lens-host.js"></script>
|
|
27
|
+
<script src="gds-lens.js"></script>
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|