@ozsarman/clarityjs 0.6.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 +178 -0
- package/package.json +168 -0
- package/src/analyze.js +534 -0
- package/src/async-state.js +555 -0
- package/src/bundle-runtime.js +35 -0
- package/src/clarity-bundle.js +332 -0
- package/src/clarity-test.js +622 -0
- package/src/cli.js +453 -0
- package/src/codegen.js +1934 -0
- package/src/dev-server.js +362 -0
- package/src/devtools.js +765 -0
- package/src/edge.js +606 -0
- package/src/error-overlay.js +535 -0
- package/src/file-conventions.js +472 -0
- package/src/font.js +513 -0
- package/src/game-loop.js +106 -0
- package/src/head.js +393 -0
- package/src/hydrate.js +292 -0
- package/src/i18n.js +403 -0
- package/src/image.js +352 -0
- package/src/index.js +193 -0
- package/src/islands.js +284 -0
- package/src/isr.js +306 -0
- package/src/layout.js +342 -0
- package/src/lexer.js +572 -0
- package/src/linter.js +547 -0
- package/src/pages-router.js +229 -0
- package/src/parser.js +1108 -0
- package/src/router.js +732 -0
- package/src/runtime.js +1465 -0
- package/src/scoped-css.js +641 -0
- package/src/server-actions.js +439 -0
- package/src/server-data.js +225 -0
- package/src/sourcemap.js +130 -0
- package/src/ssg.js +310 -0
- package/src/ssr.js +621 -0
- package/src/store.js +276 -0
- package/src/transitions.js +438 -0
- package/src/ts-plugin.js +613 -0
- package/src/typegen.js +240 -0
- package/src/vite-plugin.js +447 -0
- package/types/index.d.ts +366 -0
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clarity.js — Developer Error Overlay
|
|
3
|
+
*
|
|
4
|
+
* A fullscreen Shadow DOM overlay that surfaces:
|
|
5
|
+
* - Compiler errors (from the Clarity Vite plugin)
|
|
6
|
+
* - Runtime errors (window.onerror / unhandledrejection)
|
|
7
|
+
* - Manually reported errors (_overlayShowError)
|
|
8
|
+
*
|
|
9
|
+
* The overlay is completely isolated from page styles via Shadow DOM and
|
|
10
|
+
* renders zero bytes in production builds (guard with import.meta.env.DEV).
|
|
11
|
+
*
|
|
12
|
+
* Integration:
|
|
13
|
+
* import { initErrorOverlay } from '@ozsarman/clarityjs/error-overlay';
|
|
14
|
+
* if (import.meta.env.DEV) initErrorOverlay();
|
|
15
|
+
*
|
|
16
|
+
* Vite plugin usage:
|
|
17
|
+
* The clarity Vite plugin (vite-plugin.js) calls
|
|
18
|
+
* `_overlayShowError({ message, file, line, col, frame, type })` over the
|
|
19
|
+
* Vite HMR WebSocket when a .clarity file fails to compile.
|
|
20
|
+
*
|
|
21
|
+
* Author: Claude (Anthropic) + Özdemir Sarman
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
// ─── Public surface ───────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
let _overlay = null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Mount the error overlay. Safe to call multiple times — only one instance
|
|
30
|
+
* is created.
|
|
31
|
+
*
|
|
32
|
+
* @param {object} [options]
|
|
33
|
+
* @param {boolean} [options.catchRuntime=true] Listen for window.onerror / unhandledrejection
|
|
34
|
+
* @param {boolean} [options.catchHMR=true] Listen for Vite HMR error events
|
|
35
|
+
* @returns {{ dismiss: () => void, show: _overlayShowError }}
|
|
36
|
+
*/
|
|
37
|
+
export function initErrorOverlay({ catchRuntime = true, catchHMR = true } = {}) {
|
|
38
|
+
if (typeof document === 'undefined') return { dismiss: () => {}, show: () => {} };
|
|
39
|
+
if (_overlay) return { dismiss: () => _overlay?.dismiss(), show: _overlayShowError };
|
|
40
|
+
|
|
41
|
+
_overlay = new ErrorOverlay();
|
|
42
|
+
|
|
43
|
+
// ── Runtime error listeners ───────────────────────────────────────────────
|
|
44
|
+
if (catchRuntime) {
|
|
45
|
+
_overlay._stopRuntime = _listenRuntime();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── Vite HMR listeners ────────────────────────────────────────────────────
|
|
49
|
+
if (catchHMR) {
|
|
50
|
+
_overlay._stopHMR = _listenHMR();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
dismiss: () => _overlay?.dismiss(),
|
|
55
|
+
show: _overlayShowError,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Programmatically show an error in the overlay.
|
|
61
|
+
*
|
|
62
|
+
* @param {object} err
|
|
63
|
+
* @param {string} err.message - Human-readable error message
|
|
64
|
+
* @param {'compiler'|'runtime'|'network'} [err.type='runtime']
|
|
65
|
+
* @param {string} [err.file] - Source file path
|
|
66
|
+
* @param {number} [err.line] - 1-based line number
|
|
67
|
+
* @param {number} [err.col] - 1-based column number
|
|
68
|
+
* @param {string} [err.frame] - Code frame snippet (pre-formatted)
|
|
69
|
+
* @param {string} [err.stack] - Error stack trace string
|
|
70
|
+
* @param {string} [err.plugin] - Plugin/module name that produced the error
|
|
71
|
+
*/
|
|
72
|
+
export function _overlayShowError(err) {
|
|
73
|
+
if (!_overlay) return;
|
|
74
|
+
_overlay.push(err);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Dismiss the overlay (same as clicking the close button).
|
|
79
|
+
*/
|
|
80
|
+
export function _overlayDismiss() {
|
|
81
|
+
_overlay?.dismiss();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ─── Runtime listener ─────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
function _listenRuntime() {
|
|
87
|
+
function onError(event) {
|
|
88
|
+
const e = event.error ?? event;
|
|
89
|
+
_overlayShowError({
|
|
90
|
+
type: 'runtime',
|
|
91
|
+
message: e?.message ?? String(event.message ?? event),
|
|
92
|
+
file: event.filename ?? e?.fileName,
|
|
93
|
+
line: event.lineno ?? e?.lineNumber,
|
|
94
|
+
col: event.colno ?? e?.columnNumber,
|
|
95
|
+
stack: e?.stack,
|
|
96
|
+
});
|
|
97
|
+
// Don't swallow — let the browser console also log it
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function onUnhandled(event) {
|
|
101
|
+
const reason = event.reason;
|
|
102
|
+
_overlayShowError({
|
|
103
|
+
type: 'runtime',
|
|
104
|
+
message: reason instanceof Error ? reason.message : String(reason ?? 'Unhandled Promise rejection'),
|
|
105
|
+
stack: reason instanceof Error ? reason.stack : undefined,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
window.addEventListener('error', onError, true);
|
|
110
|
+
window.addEventListener('unhandledrejection', onUnhandled, true);
|
|
111
|
+
|
|
112
|
+
return () => {
|
|
113
|
+
window.removeEventListener('error', onError, true);
|
|
114
|
+
window.removeEventListener('unhandledrejection', onUnhandled, true);
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ─── Vite HMR listener ────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
function _listenHMR() {
|
|
121
|
+
// Vite exposes import.meta.hot in dev mode
|
|
122
|
+
if (typeof import.meta === 'undefined' || !import.meta.hot) return () => {};
|
|
123
|
+
|
|
124
|
+
// Vite's built-in error event — fires for plugin transform errors
|
|
125
|
+
import.meta.hot.on('vite:error', (data) => {
|
|
126
|
+
const err = data.err ?? data;
|
|
127
|
+
_overlayShowError({
|
|
128
|
+
type: 'compiler',
|
|
129
|
+
message: err.message ?? String(err),
|
|
130
|
+
file: err.id ?? err.file,
|
|
131
|
+
line: err.loc?.line,
|
|
132
|
+
col: err.loc?.column,
|
|
133
|
+
frame: err.frame,
|
|
134
|
+
plugin: err.plugin,
|
|
135
|
+
stack: err.stack,
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Custom Clarity compiler error channel
|
|
140
|
+
import.meta.hot.on('clarity:error', (data) => {
|
|
141
|
+
_overlayShowError({ type: 'compiler', ...data });
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Clear overlay when HMR replaces the module successfully
|
|
145
|
+
import.meta.hot.on('vite:afterUpdate', () => {
|
|
146
|
+
_overlay?.clearAll();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return () => {}; // Vite HMR listeners can't be removed individually
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ─── ErrorOverlay class ───────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
class ErrorOverlay {
|
|
155
|
+
constructor() {
|
|
156
|
+
/** @type {Array<object>} */
|
|
157
|
+
this._errors = [];
|
|
158
|
+
this._currentIndex = 0;
|
|
159
|
+
this._stopRuntime = null;
|
|
160
|
+
this._stopHMR = null;
|
|
161
|
+
|
|
162
|
+
this.el = document.createElement('div');
|
|
163
|
+
this.el.setAttribute('data-clarity-error-overlay', '');
|
|
164
|
+
Object.assign(this.el.style, {
|
|
165
|
+
position: 'fixed',
|
|
166
|
+
inset: '0',
|
|
167
|
+
zIndex: '2147483647',
|
|
168
|
+
display: 'none',
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const shadow = this.el.attachShadow({ mode: 'open' });
|
|
172
|
+
const style = document.createElement('style');
|
|
173
|
+
style.textContent = _CSS;
|
|
174
|
+
shadow.appendChild(style);
|
|
175
|
+
|
|
176
|
+
this._root = document.createElement('div');
|
|
177
|
+
this._root.className = 'overlay';
|
|
178
|
+
shadow.appendChild(this._root);
|
|
179
|
+
|
|
180
|
+
document.body.appendChild(this.el);
|
|
181
|
+
this._buildShell();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ── Public ─────────────────────────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
push(err) {
|
|
187
|
+
this._errors.push(_normalizeError(err));
|
|
188
|
+
// Jump to the newest error
|
|
189
|
+
this._currentIndex = this._errors.length - 1;
|
|
190
|
+
this.el.style.display = '';
|
|
191
|
+
this._render();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
dismiss() {
|
|
195
|
+
this.el.style.display = 'none';
|
|
196
|
+
this._errors = [];
|
|
197
|
+
this._currentIndex = 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
clearAll() {
|
|
201
|
+
this.dismiss();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
dispose() {
|
|
205
|
+
this._stopRuntime?.();
|
|
206
|
+
this._stopHMR?.();
|
|
207
|
+
this.el.remove();
|
|
208
|
+
_overlay = null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ── Private ────────────────────────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
_buildShell() {
|
|
214
|
+
this._root.innerHTML = `
|
|
215
|
+
<div class="backdrop"></div>
|
|
216
|
+
<div class="card">
|
|
217
|
+
<div class="card-header">
|
|
218
|
+
<span class="badge badge--compiler hidden" id="badge-compiler">Compiler</span>
|
|
219
|
+
<span class="badge badge--runtime hidden" id="badge-runtime">Runtime</span>
|
|
220
|
+
<span class="badge badge--network hidden" id="badge-network">Network</span>
|
|
221
|
+
<span class="title" id="err-title">Error</span>
|
|
222
|
+
<div class="nav hidden" id="nav">
|
|
223
|
+
<button id="btn-prev" title="Previous error">‹</button>
|
|
224
|
+
<span id="nav-counter"></span>
|
|
225
|
+
<button id="btn-next" title="Next error">›</button>
|
|
226
|
+
</div>
|
|
227
|
+
<button class="close-btn" id="btn-close" title="Dismiss (Escape)">✕</button>
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<div class="card-body">
|
|
231
|
+
<pre class="message" id="err-message"></pre>
|
|
232
|
+
<div class="location hidden" id="err-location"></div>
|
|
233
|
+
<pre class="frame hidden" id="err-frame"></pre>
|
|
234
|
+
<details class="stack-details hidden" id="err-stack-wrap">
|
|
235
|
+
<summary>Stack trace</summary>
|
|
236
|
+
<pre class="stack" id="err-stack"></pre>
|
|
237
|
+
</details>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<div class="card-footer">
|
|
241
|
+
<span class="hint">Press <kbd>Escape</kbd> to dismiss · errors also in the browser console</span>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
`;
|
|
245
|
+
|
|
246
|
+
// Event bindings
|
|
247
|
+
const $ = id => this._root.querySelector(`#${id}`);
|
|
248
|
+
|
|
249
|
+
$('btn-close').addEventListener('click', () => this.dismiss());
|
|
250
|
+
$('btn-prev').addEventListener('click', () => { this._currentIndex = Math.max(0, this._currentIndex - 1); this._render(); });
|
|
251
|
+
$('btn-next').addEventListener('click', () => { this._currentIndex = Math.min(this._errors.length - 1, this._currentIndex + 1); this._render(); });
|
|
252
|
+
this._root.querySelector('.backdrop').addEventListener('click', () => this.dismiss());
|
|
253
|
+
|
|
254
|
+
// Keyboard: Escape to dismiss, arrow keys to navigate
|
|
255
|
+
this._onKey = (e) => {
|
|
256
|
+
if (e.key === 'Escape') this.dismiss();
|
|
257
|
+
if (e.key === 'ArrowLeft') { this._currentIndex = Math.max(0, this._currentIndex - 1); this._render(); }
|
|
258
|
+
if (e.key === 'ArrowRight') { this._currentIndex = Math.min(this._errors.length - 1, this._currentIndex + 1); this._render(); }
|
|
259
|
+
};
|
|
260
|
+
window.addEventListener('keydown', this._onKey);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
_render() {
|
|
264
|
+
if (this._errors.length === 0) { this.dismiss(); return; }
|
|
265
|
+
|
|
266
|
+
const err = this._errors[this._currentIndex];
|
|
267
|
+
const $ = id => this._root.querySelector(`#${id}`);
|
|
268
|
+
|
|
269
|
+
// Badges
|
|
270
|
+
['compiler', 'runtime', 'network'].forEach(t => {
|
|
271
|
+
$(`badge-${t}`).classList.toggle('hidden', err.type !== t);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Title
|
|
275
|
+
$('err-title').textContent = err.title ?? _titleForType(err.type);
|
|
276
|
+
|
|
277
|
+
// Message
|
|
278
|
+
$('err-message').textContent = err.message;
|
|
279
|
+
|
|
280
|
+
// Location
|
|
281
|
+
const locEl = $('err-location');
|
|
282
|
+
if (err.file || err.line) {
|
|
283
|
+
locEl.classList.remove('hidden');
|
|
284
|
+
locEl.innerHTML = _buildLocation(err);
|
|
285
|
+
} else {
|
|
286
|
+
locEl.classList.add('hidden');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Code frame
|
|
290
|
+
const frameEl = $('err-frame');
|
|
291
|
+
if (err.frame) {
|
|
292
|
+
frameEl.classList.remove('hidden');
|
|
293
|
+
frameEl.innerHTML = _highlightFrame(err.frame);
|
|
294
|
+
} else {
|
|
295
|
+
frameEl.classList.add('hidden');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Stack
|
|
299
|
+
const stackWrap = $('err-stack-wrap');
|
|
300
|
+
if (err.stack) {
|
|
301
|
+
stackWrap.classList.remove('hidden');
|
|
302
|
+
$('err-stack').textContent = _cleanStack(err.stack);
|
|
303
|
+
} else {
|
|
304
|
+
stackWrap.classList.add('hidden');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Nav
|
|
308
|
+
const navEl = $('nav');
|
|
309
|
+
if (this._errors.length > 1) {
|
|
310
|
+
navEl.classList.remove('hidden');
|
|
311
|
+
$('nav-counter').textContent = `${this._currentIndex + 1} / ${this._errors.length}`;
|
|
312
|
+
$('btn-prev').disabled = this._currentIndex === 0;
|
|
313
|
+
$('btn-next').disabled = this._currentIndex === this._errors.length - 1;
|
|
314
|
+
} else {
|
|
315
|
+
navEl.classList.add('hidden');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
321
|
+
|
|
322
|
+
function _normalizeError(err) {
|
|
323
|
+
if (err instanceof Error) {
|
|
324
|
+
return { type: 'runtime', message: err.message, stack: err.stack };
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
type: err.type ?? 'runtime',
|
|
328
|
+
message: err.message ?? String(err),
|
|
329
|
+
file: err.file,
|
|
330
|
+
line: err.line,
|
|
331
|
+
col: err.col,
|
|
332
|
+
frame: err.frame,
|
|
333
|
+
stack: err.stack,
|
|
334
|
+
plugin: err.plugin,
|
|
335
|
+
title: err.title,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function _titleForType(type) {
|
|
340
|
+
switch (type) {
|
|
341
|
+
case 'compiler': return 'Compiler Error';
|
|
342
|
+
case 'network': return 'Network Error';
|
|
343
|
+
default: return 'Runtime Error';
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function _buildLocation({ file, line, col, plugin }) {
|
|
348
|
+
let parts = [];
|
|
349
|
+
if (file) parts.push(`<span class="loc-file">${_esc(file)}</span>`);
|
|
350
|
+
if (line) {
|
|
351
|
+
let pos = `:${line}`;
|
|
352
|
+
if (col) pos += `:${col}`;
|
|
353
|
+
parts.push(`<span class="loc-pos">${_esc(pos)}</span>`);
|
|
354
|
+
}
|
|
355
|
+
if (plugin) parts.push(`<span class="loc-plugin">via ${_esc(plugin)}</span>`);
|
|
356
|
+
return parts.join('');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Highlight the error pointer line (the one with `^`) in a code frame.
|
|
361
|
+
* Lines containing only `^` characters are wrapped in a <mark> span.
|
|
362
|
+
*/
|
|
363
|
+
function _highlightFrame(frame) {
|
|
364
|
+
return frame
|
|
365
|
+
.split('\n')
|
|
366
|
+
.map(line => {
|
|
367
|
+
const safe = _esc(line);
|
|
368
|
+
if (/^\s*\^+\s*$/.test(line)) return `<mark>${safe}</mark>`;
|
|
369
|
+
if (/^\s*\|\s*$/.test(line)) return `<span class="frame-pipe">${safe}</span>`;
|
|
370
|
+
return safe;
|
|
371
|
+
})
|
|
372
|
+
.join('\n');
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** Remove internal Clarity frames from a stack trace. */
|
|
376
|
+
function _cleanStack(stack) {
|
|
377
|
+
return stack
|
|
378
|
+
.split('\n')
|
|
379
|
+
.filter(l => !l.includes('@ozsarman/clarityjs/src/devtools'))
|
|
380
|
+
.join('\n');
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function _esc(str) {
|
|
384
|
+
return String(str ?? '')
|
|
385
|
+
.replace(/&/g, '&')
|
|
386
|
+
.replace(/</g, '<')
|
|
387
|
+
.replace(/>/g, '>')
|
|
388
|
+
.replace(/"/g, '"');
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// ─── Embedded CSS ─────────────────────────────────────────────────────────────
|
|
392
|
+
|
|
393
|
+
const _CSS = `
|
|
394
|
+
:host { all: initial; font-family: monospace; }
|
|
395
|
+
|
|
396
|
+
.overlay {
|
|
397
|
+
position: fixed; inset: 0;
|
|
398
|
+
display: flex; align-items: center; justify-content: center;
|
|
399
|
+
padding: 24px;
|
|
400
|
+
box-sizing: border-box;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.backdrop {
|
|
404
|
+
position: absolute; inset: 0;
|
|
405
|
+
background: rgba(0,0,0,.65);
|
|
406
|
+
backdrop-filter: blur(2px);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.card {
|
|
410
|
+
position: relative;
|
|
411
|
+
width: 100%; max-width: 780px;
|
|
412
|
+
max-height: 90vh;
|
|
413
|
+
background: #1e1e2e;
|
|
414
|
+
border: 1px solid #f38ba8;
|
|
415
|
+
border-radius: 12px;
|
|
416
|
+
box-shadow: 0 20px 60px rgba(0,0,0,.6);
|
|
417
|
+
overflow: hidden;
|
|
418
|
+
display: flex;
|
|
419
|
+
flex-direction: column;
|
|
420
|
+
color: #cdd6f4;
|
|
421
|
+
font-size: 13px;
|
|
422
|
+
line-height: 1.5;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/* ── Header ── */
|
|
426
|
+
.card-header {
|
|
427
|
+
display: flex; align-items: center; gap: 8px;
|
|
428
|
+
padding: 12px 16px;
|
|
429
|
+
background: #181825;
|
|
430
|
+
border-bottom: 1px solid #313244;
|
|
431
|
+
flex-shrink: 0;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.badge {
|
|
435
|
+
padding: 2px 8px; border-radius: 20px;
|
|
436
|
+
font-size: 10px; font-weight: 700; text-transform: uppercase;
|
|
437
|
+
letter-spacing: .05em;
|
|
438
|
+
}
|
|
439
|
+
.badge--compiler { background: #f38ba8; color: #1e1e2e; }
|
|
440
|
+
.badge--runtime { background: #fab387; color: #1e1e2e; }
|
|
441
|
+
.badge--network { background: #89b4fa; color: #1e1e2e; }
|
|
442
|
+
.hidden { display: none !important; }
|
|
443
|
+
|
|
444
|
+
.title { font-weight: 700; color: #f38ba8; font-size: 14px; flex: 1; }
|
|
445
|
+
|
|
446
|
+
.nav { display: flex; align-items: center; gap: 4px; color: #a6adc8; font-size: 12px; }
|
|
447
|
+
.nav button {
|
|
448
|
+
background: #313244; border: none; color: #cdd6f4;
|
|
449
|
+
width: 22px; height: 22px; border-radius: 4px; cursor: pointer;
|
|
450
|
+
font-size: 14px; display: flex; align-items: center; justify-content: center;
|
|
451
|
+
}
|
|
452
|
+
.nav button:disabled { opacity: .4; cursor: default; }
|
|
453
|
+
.nav button:hover:not(:disabled) { background: #45475a; }
|
|
454
|
+
|
|
455
|
+
.close-btn {
|
|
456
|
+
background: none; border: none; color: #6c7086;
|
|
457
|
+
cursor: pointer; font-size: 16px; padding: 2px 6px;
|
|
458
|
+
border-radius: 4px; line-height: 1;
|
|
459
|
+
}
|
|
460
|
+
.close-btn:hover { background: #313244; color: #f38ba8; }
|
|
461
|
+
|
|
462
|
+
/* ── Body ── */
|
|
463
|
+
.card-body {
|
|
464
|
+
overflow-y: auto; flex: 1; padding: 16px;
|
|
465
|
+
display: flex; flex-direction: column; gap: 12px;
|
|
466
|
+
}
|
|
467
|
+
.card-body::-webkit-scrollbar { width: 4px; }
|
|
468
|
+
.card-body::-webkit-scrollbar-thumb { background: #45475a; border-radius: 2px; }
|
|
469
|
+
|
|
470
|
+
.message {
|
|
471
|
+
margin: 0;
|
|
472
|
+
color: #f38ba8;
|
|
473
|
+
font-size: 14px; font-weight: 600;
|
|
474
|
+
white-space: pre-wrap; word-break: break-word;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.location {
|
|
478
|
+
display: flex; gap: 8px; flex-wrap: wrap;
|
|
479
|
+
font-size: 11px;
|
|
480
|
+
}
|
|
481
|
+
.loc-file { color: #89b4fa; }
|
|
482
|
+
.loc-pos { color: #a6e3a1; }
|
|
483
|
+
.loc-plugin { color: #cba6f7; }
|
|
484
|
+
|
|
485
|
+
.frame {
|
|
486
|
+
margin: 0;
|
|
487
|
+
padding: 12px 14px;
|
|
488
|
+
background: #181825;
|
|
489
|
+
border: 1px solid #313244;
|
|
490
|
+
border-left: 3px solid #f38ba8;
|
|
491
|
+
border-radius: 6px;
|
|
492
|
+
font-size: 12px;
|
|
493
|
+
overflow-x: auto;
|
|
494
|
+
white-space: pre;
|
|
495
|
+
color: #cdd6f4;
|
|
496
|
+
tab-size: 2;
|
|
497
|
+
}
|
|
498
|
+
.frame mark {
|
|
499
|
+
background: none; color: #f38ba8; font-weight: 700;
|
|
500
|
+
}
|
|
501
|
+
.frame .frame-pipe { color: #585b70; }
|
|
502
|
+
|
|
503
|
+
.stack-details { }
|
|
504
|
+
.stack-details summary {
|
|
505
|
+
cursor: pointer; color: #6c7086; font-size: 11px;
|
|
506
|
+
padding: 2px 0; user-select: none;
|
|
507
|
+
}
|
|
508
|
+
.stack-details summary:hover { color: #a6adc8; }
|
|
509
|
+
.stack-details[open] summary { margin-bottom: 8px; }
|
|
510
|
+
|
|
511
|
+
.stack {
|
|
512
|
+
margin: 0;
|
|
513
|
+
padding: 10px 12px;
|
|
514
|
+
background: #181825;
|
|
515
|
+
border-radius: 6px;
|
|
516
|
+
font-size: 11px;
|
|
517
|
+
color: #6c7086;
|
|
518
|
+
overflow-x: auto;
|
|
519
|
+
white-space: pre;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/* ── Footer ── */
|
|
523
|
+
.card-footer {
|
|
524
|
+
padding: 8px 16px;
|
|
525
|
+
background: #181825;
|
|
526
|
+
border-top: 1px solid #313244;
|
|
527
|
+
flex-shrink: 0;
|
|
528
|
+
}
|
|
529
|
+
.hint { color: #585b70; font-size: 10px; }
|
|
530
|
+
kbd {
|
|
531
|
+
background: #313244; border: 1px solid #45475a;
|
|
532
|
+
border-radius: 3px; padding: 1px 5px; font-family: inherit;
|
|
533
|
+
font-size: 10px;
|
|
534
|
+
}
|
|
535
|
+
`;
|