describe-me 0.1.1 → 0.2.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/package.json +2 -2
- package/src/content-box.ts +51 -0
- package/src/fit-stage.ts +20 -0
- package/src/header.ts +11 -11
- package/src/inspector.ts +1 -1
- package/src/main-panel.ts +1 -1
- package/src/sidebar.ts +73 -30
- package/src/stage.ts +8 -0
- package/src/states-gallery.ts +1 -52
- package/src/style.css +406 -178
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "describe-me",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Living component documentation generated from your Vitest tests: viewer and CLI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"vitest",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"rrweb-snapshot": "^2.1.6",
|
|
45
45
|
"vite": "^8.3.0",
|
|
46
|
-
"@describe-me/core": "^0.
|
|
46
|
+
"@describe-me/core": "^0.2.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/node": "^22"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const SKIPPED_TAGS = new Set(['SCRIPT', 'NOSCRIPT', 'STYLE', 'LINK', 'META', 'TITLE'])
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether an element draws something itself. Layout wrappers (the test
|
|
5
|
+
* container, a full-width flex row) stretch across the page and would make
|
|
6
|
+
* every measurement fall back to the whole viewport.
|
|
7
|
+
*/
|
|
8
|
+
function isVisual(element: Element, view: Window): boolean {
|
|
9
|
+
if (element.children.length === 0) {
|
|
10
|
+
return true
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const style = view.getComputedStyle(element)
|
|
14
|
+
const hasBackground =
|
|
15
|
+
style.backgroundColor !== 'rgba(0, 0, 0, 0)' && style.backgroundColor !== 'transparent'
|
|
16
|
+
|
|
17
|
+
const hasBorder = parseFloat(style.borderTopWidth) > 0 || parseFloat(style.borderLeftWidth) > 0
|
|
18
|
+
const hasShadow = style.boxShadow !== 'none'
|
|
19
|
+
|
|
20
|
+
return hasBackground || hasBorder || hasShadow
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The box around everything that actually paints in a snapshot, in the iframe's own pixels. */
|
|
24
|
+
export function contentBox(body: HTMLElement, view: Window): DOMRect | null {
|
|
25
|
+
let box: DOMRect | null = null
|
|
26
|
+
|
|
27
|
+
for (const element of Array.from(body.querySelectorAll('*'))) {
|
|
28
|
+
if (SKIPPED_TAGS.has(element.tagName) || !isVisual(element, view)) {
|
|
29
|
+
continue
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const rect = element.getBoundingClientRect()
|
|
33
|
+
if (rect.width === 0 || rect.height === 0) {
|
|
34
|
+
continue
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!box) {
|
|
38
|
+
box = DOMRect.fromRect(rect)
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const right = Math.max(box.right, rect.right)
|
|
43
|
+
const bottom = Math.max(box.bottom, rect.bottom)
|
|
44
|
+
box.x = Math.min(box.x, rect.x)
|
|
45
|
+
box.y = Math.min(box.y, rect.y)
|
|
46
|
+
box.width = right - box.x
|
|
47
|
+
box.height = bottom - box.y
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return box
|
|
51
|
+
}
|
package/src/fit-stage.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { contentBox } from './content-box.js'
|
|
2
|
+
|
|
3
|
+
const BOTTOM_MARGIN = 20
|
|
4
|
+
const MIN_HEIGHT = 120
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Shrink the replay frame to what the snapshot actually paints. A snapshot is
|
|
8
|
+
* a whole document, so at its natural size the frame is a tall empty sheet
|
|
9
|
+
* with the component alone in the top corner.
|
|
10
|
+
*/
|
|
11
|
+
export function fitStage(iframe: HTMLIFrameElement): void {
|
|
12
|
+
const body = iframe.contentDocument?.body
|
|
13
|
+
const view = iframe.contentWindow
|
|
14
|
+
const box = body && view ? contentBox(body, view) : null
|
|
15
|
+
if (!box) {
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
iframe.style.height = `${Math.max(MIN_HEIGHT, Math.round(box.bottom + BOTTOM_MARGIN))}px`
|
|
20
|
+
}
|
package/src/header.ts
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
import { el } from './el.js'
|
|
2
2
|
import { allTests, state } from './state.js'
|
|
3
3
|
|
|
4
|
+
function stat(count: number, label: string, tone?: 'pass' | 'fail'): HTMLElement {
|
|
5
|
+
return el('span', { class: tone ? `stat ${tone}` : 'stat' }, el('b', {}, String(count)), label)
|
|
6
|
+
}
|
|
7
|
+
|
|
4
8
|
/** Wordmark, pass/fail counts and the time of the last run. */
|
|
5
9
|
export function renderHeader(): HTMLElement {
|
|
6
10
|
const manifest = state.manifest
|
|
7
11
|
const tests = manifest ? allTests(manifest) : []
|
|
8
12
|
const passed = tests.filter((test) => test.state === 'passed').length
|
|
9
13
|
const failed = tests.filter((test) => test.state === 'failed').length
|
|
10
|
-
const summary = el('span', { class: 'summary' })
|
|
11
|
-
summary.append(
|
|
12
|
-
el('b', {}, String(tests.length)),
|
|
13
|
-
' tests · ',
|
|
14
|
-
el('b', {}, String(passed)),
|
|
15
|
-
' passed',
|
|
16
|
-
)
|
|
17
14
|
|
|
15
|
+
const summary = el('span', { class: 'summary' }, stat(tests.length, 'tests'))
|
|
16
|
+
summary.append(stat(passed, 'passed', 'pass'))
|
|
18
17
|
if (failed) {
|
|
19
|
-
summary.append(
|
|
18
|
+
summary.append(stat(failed, 'failed', 'fail'))
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
const
|
|
21
|
+
const wordmark = el('span', { class: 'wordmark' }, 'describe', el('span', { class: 'me' }, '-me'))
|
|
23
22
|
const when = manifest ? new Date(manifest.generatedAt).toLocaleTimeString() : '…'
|
|
23
|
+
|
|
24
24
|
return el(
|
|
25
25
|
'header',
|
|
26
26
|
{ class: 'header' },
|
|
27
|
-
|
|
27
|
+
wordmark,
|
|
28
28
|
summary,
|
|
29
|
-
el('span', { class: 'right live' },
|
|
29
|
+
el('span', { class: 'right live' }, 'updated ', el('span', { class: 'mono' }, when)),
|
|
30
30
|
)
|
|
31
31
|
}
|
package/src/inspector.ts
CHANGED
|
@@ -48,7 +48,7 @@ export function renderInspector(): HTMLElement {
|
|
|
48
48
|
'section',
|
|
49
49
|
{},
|
|
50
50
|
el('h3', {}, `frame ${state.frame + 1} / ${test.frames.length}`),
|
|
51
|
-
el('div', {}, frame.label),
|
|
51
|
+
el('div', { class: 'mono' }, frame.label),
|
|
52
52
|
el('div', { class: 'hint' }, `${frame.kind} · ${frame.at}ms into the test`),
|
|
53
53
|
),
|
|
54
54
|
)
|
package/src/main-panel.ts
CHANGED
|
@@ -59,7 +59,7 @@ export function renderMain(): HTMLElement {
|
|
|
59
59
|
},
|
|
60
60
|
},
|
|
61
61
|
el('span', { class: 'k' }, timelineFrame.kind),
|
|
62
|
-
el('span', {}, timelineFrame.label),
|
|
62
|
+
el('span', { class: 'label' }, timelineFrame.label),
|
|
63
63
|
el('span', { class: 't' }, `${timelineFrame.at}ms`),
|
|
64
64
|
),
|
|
65
65
|
)
|
package/src/sidebar.ts
CHANGED
|
@@ -1,13 +1,42 @@
|
|
|
1
|
+
import type { ManifestModule, ManifestTest } from '@describe-me/core/types'
|
|
1
2
|
import { el } from './el.js'
|
|
2
3
|
import { select, selectSuite, state } from './state.js'
|
|
3
4
|
import { suiteKey } from './suite-key.js'
|
|
4
5
|
import { buildTree, type SuiteNode } from './tree.js'
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
/**
|
|
8
|
+
* The one describe every test of a module sits under, if there is one. Its
|
|
9
|
+
* overview lists exactly the tests the module's overview would, so the two
|
|
10
|
+
* rows collapse into a single header instead of leading to near-identical
|
|
11
|
+
* pages.
|
|
12
|
+
*/
|
|
13
|
+
function soleSuite(root: SuiteNode): SuiteNode | null {
|
|
14
|
+
if (root.tests.length > 0 || root.suites.size !== 1) {
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return [...root.suites.values()][0] ?? null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The header of a module block: the describe it holds, over the file it came from. */
|
|
22
|
+
function moduleButton(name: string | null, file: string, key: string): HTMLElement {
|
|
7
23
|
return el(
|
|
8
24
|
'button',
|
|
9
25
|
{
|
|
10
|
-
class:
|
|
26
|
+
class: `module-button${key === state.suiteKey ? ' active' : ''}`,
|
|
27
|
+
click: () => selectSuite(key),
|
|
28
|
+
title: `overview of ${name ? `${name} — ${file}` : file}`,
|
|
29
|
+
},
|
|
30
|
+
name ? el('span', { class: 'module-name' }, name) : null,
|
|
31
|
+
el('span', { class: 'module-path' }, file),
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function suiteButton(label: string, key: string): HTMLElement {
|
|
36
|
+
return el(
|
|
37
|
+
'button',
|
|
38
|
+
{
|
|
39
|
+
class: `suite-button${key === state.suiteKey ? ' active' : ''}`,
|
|
11
40
|
click: () => selectSuite(key),
|
|
12
41
|
title: `overview of ${label}`,
|
|
13
42
|
},
|
|
@@ -15,47 +44,61 @@ function scopeButton(label: string, key: string, kind: 'module' | 'suite'): HTML
|
|
|
15
44
|
)
|
|
16
45
|
}
|
|
17
46
|
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
47
|
+
function testButton(test: ManifestTest): HTMLElement {
|
|
48
|
+
return el(
|
|
49
|
+
'button',
|
|
50
|
+
{
|
|
51
|
+
class: `test${!state.suiteKey && test.id === state.testId ? ' active' : ''}`,
|
|
52
|
+
click: () => select(test.id),
|
|
53
|
+
title: test.fullName,
|
|
54
|
+
},
|
|
55
|
+
el('span', { class: `dot ${test.state}` }),
|
|
56
|
+
el('span', { class: 'test-name' }, test.name),
|
|
57
|
+
el('span', { class: 'n' }, String(test.frames.length)),
|
|
58
|
+
)
|
|
59
|
+
}
|
|
23
60
|
|
|
61
|
+
/** The rows under one suite: its own tests, then the suites nested in it. */
|
|
62
|
+
function renderRows(node: SuiteNode, moduleId: string, path: string[]): DocumentFragment {
|
|
63
|
+
const rows = document.createDocumentFragment()
|
|
24
64
|
for (const test of node.tests) {
|
|
25
|
-
|
|
26
|
-
el(
|
|
27
|
-
'button',
|
|
28
|
-
{
|
|
29
|
-
class: `test${!state.suiteKey && test.id === state.testId ? ' active' : ''}`,
|
|
30
|
-
click: () => select(test.id),
|
|
31
|
-
title: test.fullName,
|
|
32
|
-
},
|
|
33
|
-
el('span', { class: `dot ${test.state}` }),
|
|
34
|
-
el('span', {}, test.name),
|
|
35
|
-
el('span', { class: 'n' }, String(test.frames.length)),
|
|
36
|
-
),
|
|
37
|
-
)
|
|
65
|
+
rows.append(testButton(test))
|
|
38
66
|
}
|
|
39
67
|
|
|
40
68
|
for (const child of node.suites.values()) {
|
|
41
|
-
|
|
69
|
+
rows.append(renderSuite(child, moduleId, [...path, child.name]))
|
|
42
70
|
}
|
|
43
71
|
|
|
44
|
-
return
|
|
72
|
+
return rows
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function renderSuite(node: SuiteNode, moduleId: string, path: string[]): HTMLElement {
|
|
76
|
+
return el(
|
|
77
|
+
'div',
|
|
78
|
+
{ class: 'suite' },
|
|
79
|
+
suiteButton(node.name, suiteKey(moduleId, path)),
|
|
80
|
+
renderRows(node, moduleId, path),
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function renderModule(mod: ManifestModule): HTMLElement {
|
|
85
|
+
const root = buildTree(mod)
|
|
86
|
+
const only = soleSuite(root)
|
|
87
|
+
const path = only ? [only.name] : []
|
|
88
|
+
|
|
89
|
+
return el(
|
|
90
|
+
'div',
|
|
91
|
+
{ class: 'module' },
|
|
92
|
+
moduleButton(only?.name ?? null, mod.id, suiteKey(mod.id, path)),
|
|
93
|
+
el('div', { class: 'suite root' }, renderRows(only ?? root, mod.id, path)),
|
|
94
|
+
)
|
|
45
95
|
}
|
|
46
96
|
|
|
47
97
|
/** One block per test module, each holding its suite tree. */
|
|
48
98
|
export function renderSidebar(): HTMLElement {
|
|
49
99
|
const aside = el('aside', { class: 'sidebar' })
|
|
50
100
|
for (const mod of state.manifest?.modules ?? []) {
|
|
51
|
-
aside.append(
|
|
52
|
-
el(
|
|
53
|
-
'div',
|
|
54
|
-
{ class: 'module' },
|
|
55
|
-
scopeButton(mod.id, suiteKey(mod.id, []), 'module'),
|
|
56
|
-
renderSuite(buildTree(mod), mod.id, []),
|
|
57
|
-
),
|
|
58
|
-
)
|
|
101
|
+
aside.append(renderModule(mod))
|
|
59
102
|
}
|
|
60
103
|
|
|
61
104
|
return aside
|
package/src/stage.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createCache, createMirror, rebuildIntoSandboxedIframe } from 'rrweb-snapshot'
|
|
2
2
|
import type { ManifestFrame } from '@describe-me/core/types'
|
|
3
3
|
import { loadSnapshot } from './data.js'
|
|
4
|
+
import { fitStage } from './fit-stage.js'
|
|
4
5
|
import { state } from './state.js'
|
|
5
6
|
|
|
6
7
|
let paintToken = 0
|
|
@@ -25,4 +26,11 @@ export async function paintFrame(stage: HTMLElement, frame: ManifestFrame): Prom
|
|
|
25
26
|
if (state.width !== 'auto') {
|
|
26
27
|
iframe.style.width = `${state.width}px`
|
|
27
28
|
}
|
|
29
|
+
|
|
30
|
+
// Styles are in place after a frame; only then do the boxes have their final size.
|
|
31
|
+
requestAnimationFrame(() => {
|
|
32
|
+
if (token === paintToken) {
|
|
33
|
+
fitStage(iframe)
|
|
34
|
+
}
|
|
35
|
+
})
|
|
28
36
|
}
|
package/src/states-gallery.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createCache, createMirror, rebuildIntoSandboxedIframe } from 'rrweb-snapshot'
|
|
2
2
|
import type { ManifestFrame, ManifestTest } from '@describe-me/core/types'
|
|
3
|
+
import { contentBox } from './content-box.js'
|
|
3
4
|
import { loadSnapshot } from './data.js'
|
|
4
5
|
import { el } from './el.js'
|
|
5
6
|
import { select } from './state.js'
|
|
@@ -25,58 +26,6 @@ async function paintThumb(thumb: HTMLElement, frame: ManifestFrame, token: numbe
|
|
|
25
26
|
requestAnimationFrame(() => fitThumb(thumb, iframe))
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
const SKIPPED_TAGS = new Set(['SCRIPT', 'NOSCRIPT', 'STYLE', 'LINK', 'META', 'TITLE'])
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Whether an element draws something itself. Layout wrappers (the test
|
|
32
|
-
* container, a full-width flex row) stretch across the page and would make
|
|
33
|
-
* every thumbnail zoom out to the whole viewport.
|
|
34
|
-
*/
|
|
35
|
-
function isVisual(element: Element, view: Window): boolean {
|
|
36
|
-
if (element.children.length === 0) {
|
|
37
|
-
return true
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const style = view.getComputedStyle(element)
|
|
41
|
-
const hasBackground =
|
|
42
|
-
style.backgroundColor !== 'rgba(0, 0, 0, 0)' && style.backgroundColor !== 'transparent'
|
|
43
|
-
|
|
44
|
-
const hasBorder = parseFloat(style.borderTopWidth) > 0 || parseFloat(style.borderLeftWidth) > 0
|
|
45
|
-
const hasShadow = style.boxShadow !== 'none'
|
|
46
|
-
|
|
47
|
-
return hasBackground || hasBorder || hasShadow
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** The box around everything that actually paints in the snapshot, in the iframe's own pixels. */
|
|
51
|
-
function contentBox(body: HTMLElement, view: Window): DOMRect | null {
|
|
52
|
-
let box: DOMRect | null = null
|
|
53
|
-
|
|
54
|
-
for (const element of Array.from(body.querySelectorAll('*'))) {
|
|
55
|
-
if (SKIPPED_TAGS.has(element.tagName) || !isVisual(element, view)) {
|
|
56
|
-
continue
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const rect = element.getBoundingClientRect()
|
|
60
|
-
if (rect.width === 0 || rect.height === 0) {
|
|
61
|
-
continue
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (!box) {
|
|
65
|
-
box = DOMRect.fromRect(rect)
|
|
66
|
-
continue
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const right = Math.max(box.right, rect.right)
|
|
70
|
-
const bottom = Math.max(box.bottom, rect.bottom)
|
|
71
|
-
box.x = Math.min(box.x, rect.x)
|
|
72
|
-
box.y = Math.min(box.y, rect.y)
|
|
73
|
-
box.width = right - box.x
|
|
74
|
-
box.height = bottom - box.y
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return box
|
|
78
|
-
}
|
|
79
|
-
|
|
80
29
|
const THUMB_PADDING = 16
|
|
81
30
|
|
|
82
31
|
/**
|
package/src/style.css
CHANGED
|
@@ -1,29 +1,80 @@
|
|
|
1
|
+
/* --------------------------------------------------------------- tokens -- */
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Chrome is set in the UI sans; anything that came out of the codebase —
|
|
5
|
+
* props, types, values, file paths, frame labels — is set in the mono face.
|
|
6
|
+
* That split is what keeps a documentation tool from reading like a terminal.
|
|
7
|
+
*/
|
|
1
8
|
:root {
|
|
2
|
-
--
|
|
9
|
+
--font-ui: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
10
|
+
--font-mono: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
|
|
11
|
+
/*--mono: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;*/
|
|
12
|
+
--bg: #f5f6f8;
|
|
3
13
|
--panel: #ffffff;
|
|
4
|
-
--
|
|
5
|
-
--
|
|
6
|
-
--line: #e6e6e6;
|
|
7
|
-
--hover: #f0f0f0;
|
|
8
|
-
--accent: #2b5cff;
|
|
9
|
-
--pass: #1f9d55;
|
|
10
|
-
--fail: #d43f3f;
|
|
11
|
-
--skip: #b5b5b5;
|
|
14
|
+
--raised: #ffffff;
|
|
15
|
+
--stage-bg: #eef0f3;
|
|
12
16
|
--stage: #ffffff;
|
|
13
|
-
|
|
17
|
+
|
|
18
|
+
--ink: #1c2024;
|
|
19
|
+
--ink-2: #4f5762;
|
|
20
|
+
--ink-3: #6b7381;
|
|
21
|
+
|
|
22
|
+
--line: #e6e9ee;
|
|
23
|
+
--line-soft: #eff1f5;
|
|
24
|
+
--hover: #f1f3f6;
|
|
25
|
+
|
|
26
|
+
--accent: #4a63d8;
|
|
27
|
+
--pass: #2f9e5f;
|
|
28
|
+
--fail: #d04747;
|
|
29
|
+
--warn: #b0760c;
|
|
30
|
+
--skip: #a8b0bb;
|
|
31
|
+
|
|
32
|
+
--tint: color-mix(in srgb, var(--accent) 7%, transparent);
|
|
33
|
+
--tint-strong: color-mix(in srgb, var(--accent) 13%, transparent);
|
|
34
|
+
--ring: color-mix(in srgb, var(--accent) 45%, transparent);
|
|
35
|
+
|
|
36
|
+
--r-sm: 6px;
|
|
37
|
+
--r-md: 8px;
|
|
38
|
+
--r-lg: 12px;
|
|
39
|
+
--r-pill: 999px;
|
|
40
|
+
|
|
41
|
+
--shadow-sm: 0 1px 2px rgba(20, 24, 32, 0.05);
|
|
42
|
+
--shadow-md: 0 1px 2px rgba(20, 24, 32, 0.05), 0 6px 16px -6px rgba(20, 24, 32, 0.14);
|
|
43
|
+
|
|
14
44
|
color-scheme: light dark;
|
|
15
45
|
}
|
|
16
46
|
@media (prefers-color-scheme: dark) {
|
|
17
47
|
:root {
|
|
18
|
-
--bg: #
|
|
19
|
-
--panel: #
|
|
20
|
-
--
|
|
21
|
-
--
|
|
22
|
-
--line: #2a2a2a;
|
|
23
|
-
--hover: #202020;
|
|
48
|
+
--bg: #111316;
|
|
49
|
+
--panel: #171a1f;
|
|
50
|
+
--raised: #1c2026;
|
|
51
|
+
--stage-bg: #0b0d10;
|
|
24
52
|
--stage: #ffffff;
|
|
53
|
+
|
|
54
|
+
--ink: #e2e6ea;
|
|
55
|
+
--ink-2: #b0b9c5;
|
|
56
|
+
--ink-3: #8a939f;
|
|
57
|
+
|
|
58
|
+
--line: #252a31;
|
|
59
|
+
--line-soft: #1e2228;
|
|
60
|
+
--hover: #21262d;
|
|
61
|
+
|
|
62
|
+
--accent: #8ea2ff;
|
|
63
|
+
--pass: #4fbb7d;
|
|
64
|
+
--fail: #e8756f;
|
|
65
|
+
--warn: #d3a24a;
|
|
66
|
+
--skip: #5c646f;
|
|
67
|
+
|
|
68
|
+
--tint: color-mix(in srgb, var(--accent) 10%, transparent);
|
|
69
|
+
--tint-strong: color-mix(in srgb, var(--accent) 18%, transparent);
|
|
70
|
+
|
|
71
|
+
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
72
|
+
--shadow-md: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 20px -8px rgba(0, 0, 0, 0.6);
|
|
25
73
|
}
|
|
26
74
|
}
|
|
75
|
+
|
|
76
|
+
/* ----------------------------------------------------------------- base -- */
|
|
77
|
+
|
|
27
78
|
* {
|
|
28
79
|
box-sizing: border-box;
|
|
29
80
|
}
|
|
@@ -35,7 +86,8 @@ body {
|
|
|
35
86
|
body {
|
|
36
87
|
background: var(--bg);
|
|
37
88
|
color: var(--ink);
|
|
38
|
-
font: 13px/1.
|
|
89
|
+
font: 400 13px/1.5 var(--font-ui);
|
|
90
|
+
-webkit-font-smoothing: antialiased;
|
|
39
91
|
}
|
|
40
92
|
button {
|
|
41
93
|
font: inherit;
|
|
@@ -48,200 +100,326 @@ button {
|
|
|
48
100
|
a {
|
|
49
101
|
color: inherit;
|
|
50
102
|
}
|
|
103
|
+
::selection {
|
|
104
|
+
background: var(--tint-strong);
|
|
105
|
+
}
|
|
106
|
+
:focus-visible {
|
|
107
|
+
outline: 2px solid var(--ring);
|
|
108
|
+
outline-offset: 1px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Panels scroll on their own; the thin bars keep the chrome quiet. */
|
|
112
|
+
.sidebar,
|
|
113
|
+
.inspector,
|
|
114
|
+
.overview,
|
|
115
|
+
.stage,
|
|
116
|
+
.timeline {
|
|
117
|
+
scrollbar-width: thin;
|
|
118
|
+
scrollbar-color: var(--line) transparent;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Anything quoted from the codebase. */
|
|
122
|
+
.mono {
|
|
123
|
+
font-family: var(--font-mono);
|
|
124
|
+
}
|
|
51
125
|
|
|
52
126
|
#app {
|
|
53
127
|
height: 100%;
|
|
54
128
|
display: grid;
|
|
55
|
-
grid-template-rows:
|
|
129
|
+
grid-template-rows: 52px 1fr;
|
|
56
130
|
}
|
|
131
|
+
.body {
|
|
132
|
+
display: grid;
|
|
133
|
+
grid-template-columns: 296px 1fr 320px;
|
|
134
|
+
min-height: 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* --------------------------------------------------------------- header -- */
|
|
138
|
+
|
|
57
139
|
.header {
|
|
58
140
|
display: flex;
|
|
59
141
|
align-items: center;
|
|
60
|
-
gap:
|
|
61
|
-
padding: 0
|
|
142
|
+
gap: 18px;
|
|
143
|
+
padding: 0 18px;
|
|
62
144
|
border-bottom: 1px solid var(--line);
|
|
63
145
|
background: var(--panel);
|
|
64
146
|
}
|
|
65
147
|
.wordmark {
|
|
148
|
+
font-size: 14px;
|
|
66
149
|
font-weight: 600;
|
|
67
|
-
letter-spacing: -0.
|
|
150
|
+
letter-spacing: -0.015em;
|
|
68
151
|
}
|
|
69
152
|
.wordmark .me {
|
|
70
153
|
color: var(--accent);
|
|
71
154
|
}
|
|
72
155
|
.summary {
|
|
73
|
-
|
|
156
|
+
display: flex;
|
|
157
|
+
align-items: center;
|
|
158
|
+
gap: 14px;
|
|
159
|
+
color: var(--ink-2);
|
|
160
|
+
font-size: 12px;
|
|
74
161
|
}
|
|
75
|
-
.
|
|
162
|
+
.stat {
|
|
163
|
+
display: inline-flex;
|
|
164
|
+
align-items: baseline;
|
|
165
|
+
gap: 5px;
|
|
166
|
+
}
|
|
167
|
+
.summary .stat + .stat::before {
|
|
168
|
+
content: '';
|
|
169
|
+
align-self: center;
|
|
170
|
+
width: 1px;
|
|
171
|
+
height: 11px;
|
|
172
|
+
margin-right: 14px;
|
|
173
|
+
background: var(--line);
|
|
174
|
+
}
|
|
175
|
+
.stat b {
|
|
76
176
|
color: var(--ink);
|
|
77
|
-
font-weight:
|
|
177
|
+
font-weight: 600;
|
|
178
|
+
font-variant-numeric: tabular-nums;
|
|
78
179
|
}
|
|
79
|
-
.
|
|
180
|
+
.stat.pass b {
|
|
181
|
+
color: var(--pass);
|
|
182
|
+
}
|
|
183
|
+
.stat.fail {
|
|
184
|
+
padding: 2px 9px;
|
|
185
|
+
border-radius: var(--r-pill);
|
|
186
|
+
background: color-mix(in srgb, var(--fail) 12%, transparent);
|
|
80
187
|
color: var(--fail);
|
|
81
188
|
}
|
|
189
|
+
.stat.fail b {
|
|
190
|
+
color: inherit;
|
|
191
|
+
}
|
|
82
192
|
.header .right {
|
|
83
193
|
margin-left: auto;
|
|
84
|
-
color: var(--
|
|
194
|
+
color: var(--ink-3);
|
|
195
|
+
font-size: 12px;
|
|
85
196
|
}
|
|
86
197
|
.live {
|
|
87
198
|
display: inline-flex;
|
|
88
199
|
align-items: center;
|
|
89
|
-
gap:
|
|
200
|
+
gap: 7px;
|
|
90
201
|
}
|
|
91
202
|
.live::before {
|
|
92
203
|
content: '';
|
|
93
|
-
width:
|
|
94
|
-
height:
|
|
204
|
+
width: 6px;
|
|
205
|
+
height: 6px;
|
|
95
206
|
border-radius: 50%;
|
|
96
207
|
background: var(--pass);
|
|
208
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--pass) 18%, transparent);
|
|
97
209
|
}
|
|
98
210
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
grid-template-columns: 300px 1fr 320px;
|
|
102
|
-
min-height: 0;
|
|
103
|
-
}
|
|
211
|
+
/* -------------------------------------------------------------- sidebar -- */
|
|
212
|
+
|
|
104
213
|
.sidebar {
|
|
105
214
|
border-right: 1px solid var(--line);
|
|
106
215
|
overflow: auto;
|
|
107
|
-
padding:
|
|
216
|
+
padding: 14px 10px 32px;
|
|
108
217
|
background: var(--panel);
|
|
109
218
|
}
|
|
110
|
-
.module {
|
|
111
|
-
margin-
|
|
219
|
+
.module + .module {
|
|
220
|
+
margin-top: 18px;
|
|
221
|
+
padding-top: 18px;
|
|
222
|
+
border-top: 1px solid var(--line-soft);
|
|
112
223
|
}
|
|
113
224
|
.module-button,
|
|
114
225
|
.suite-button {
|
|
226
|
+
position: relative;
|
|
115
227
|
display: block;
|
|
116
228
|
width: 100%;
|
|
117
229
|
text-align: left;
|
|
118
|
-
border-radius:
|
|
230
|
+
border-radius: var(--r-sm);
|
|
119
231
|
overflow: hidden;
|
|
120
232
|
text-overflow: ellipsis;
|
|
121
233
|
white-space: nowrap;
|
|
122
234
|
}
|
|
123
235
|
.module-button {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
236
|
+
display: flex;
|
|
237
|
+
flex-direction: column;
|
|
238
|
+
align-items: flex-start;
|
|
239
|
+
gap: 1px;
|
|
240
|
+
padding: 5px 9px;
|
|
241
|
+
margin-bottom: 4px;
|
|
127
242
|
}
|
|
128
|
-
.
|
|
129
|
-
|
|
243
|
+
.module-name,
|
|
244
|
+
.module-path {
|
|
245
|
+
max-width: 100%;
|
|
246
|
+
overflow: hidden;
|
|
247
|
+
text-overflow: ellipsis;
|
|
248
|
+
white-space: nowrap;
|
|
130
249
|
}
|
|
131
|
-
.
|
|
132
|
-
padding: 3px 8px;
|
|
250
|
+
.module-name {
|
|
133
251
|
color: var(--ink);
|
|
252
|
+
font-size: 13px;
|
|
253
|
+
font-weight: 600;
|
|
254
|
+
letter-spacing: -0.01em;
|
|
134
255
|
}
|
|
135
|
-
.module-
|
|
136
|
-
|
|
137
|
-
|
|
256
|
+
.module-path {
|
|
257
|
+
color: var(--ink-3);
|
|
258
|
+
font-family: var(--font-mono);
|
|
259
|
+
font-size: 10.5px;
|
|
138
260
|
}
|
|
139
|
-
.
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
261
|
+
.suite-button {
|
|
262
|
+
padding: 4px 9px;
|
|
263
|
+
color: var(--ink);
|
|
264
|
+
font-size: 12.5px;
|
|
265
|
+
font-weight: 500;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/* One hairline per nesting level: the tree stays readable without deep indents. */
|
|
269
|
+
.suite:not(.root) {
|
|
270
|
+
margin-left: 9px;
|
|
271
|
+
padding-left: 9px;
|
|
272
|
+
border-left: 1px solid var(--line-soft);
|
|
143
273
|
}
|
|
144
274
|
.test {
|
|
275
|
+
position: relative;
|
|
145
276
|
display: flex;
|
|
146
277
|
align-items: center;
|
|
147
278
|
gap: 8px;
|
|
148
279
|
width: 100%;
|
|
280
|
+
min-width: 0;
|
|
149
281
|
text-align: left;
|
|
150
|
-
padding: 4px
|
|
151
|
-
border-radius:
|
|
152
|
-
|
|
282
|
+
padding: 4px 9px;
|
|
283
|
+
border-radius: var(--r-sm);
|
|
284
|
+
color: var(--ink-2);
|
|
285
|
+
}
|
|
286
|
+
.test-name {
|
|
287
|
+
overflow: hidden;
|
|
288
|
+
text-overflow: ellipsis;
|
|
289
|
+
white-space: nowrap;
|
|
153
290
|
}
|
|
291
|
+
.module-button:hover,
|
|
292
|
+
.suite-button:hover,
|
|
154
293
|
.test:hover {
|
|
155
294
|
background: var(--hover);
|
|
295
|
+
color: var(--ink);
|
|
156
296
|
}
|
|
297
|
+
|
|
298
|
+
/* Selection is a tinted row with an accent edge — visible while scanning, quiet at rest. */
|
|
299
|
+
.module-button.active,
|
|
300
|
+
.suite-button.active,
|
|
157
301
|
.test.active {
|
|
158
|
-
background: var(--
|
|
159
|
-
color: var(--
|
|
302
|
+
background: var(--tint);
|
|
303
|
+
color: var(--ink);
|
|
304
|
+
font-weight: 500;
|
|
305
|
+
}
|
|
306
|
+
.module-button.active::before,
|
|
307
|
+
.suite-button.active::before,
|
|
308
|
+
.test.active::before {
|
|
309
|
+
content: '';
|
|
310
|
+
position: absolute;
|
|
311
|
+
left: 0;
|
|
312
|
+
top: 3px;
|
|
313
|
+
bottom: 3px;
|
|
314
|
+
width: 2px;
|
|
315
|
+
border-radius: 0 2px 2px 0;
|
|
316
|
+
background: var(--accent);
|
|
160
317
|
}
|
|
318
|
+
.test .n {
|
|
319
|
+
margin-left: auto;
|
|
320
|
+
color: var(--ink-3);
|
|
321
|
+
font-family: var(--font-mono);
|
|
322
|
+
font-size: 10.5px;
|
|
323
|
+
font-variant-numeric: tabular-nums;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/* Passing is the norm; only a failure earns full contrast. */
|
|
161
327
|
.dot {
|
|
162
|
-
width:
|
|
163
|
-
height:
|
|
328
|
+
width: 6px;
|
|
329
|
+
height: 6px;
|
|
164
330
|
border-radius: 50%;
|
|
165
331
|
flex: none;
|
|
166
332
|
}
|
|
167
333
|
.dot.passed {
|
|
168
|
-
background: var(--pass);
|
|
334
|
+
background: color-mix(in srgb, var(--pass) 55%, transparent);
|
|
169
335
|
}
|
|
170
336
|
.dot.failed {
|
|
171
337
|
background: var(--fail);
|
|
338
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--fail) 16%, transparent);
|
|
172
339
|
}
|
|
173
340
|
.dot.skipped,
|
|
174
341
|
.dot.pending {
|
|
175
|
-
background:
|
|
176
|
-
|
|
177
|
-
.test .n {
|
|
178
|
-
color: var(--muted);
|
|
179
|
-
margin-left: auto;
|
|
180
|
-
font-size: 11px;
|
|
181
|
-
}
|
|
182
|
-
.test.active .n {
|
|
183
|
-
color: inherit;
|
|
184
|
-
opacity: 0.7;
|
|
342
|
+
background: transparent;
|
|
343
|
+
box-shadow: inset 0 0 0 1.5px var(--skip);
|
|
185
344
|
}
|
|
186
345
|
|
|
346
|
+
/* ----------------------------------------------------------------- main -- */
|
|
347
|
+
|
|
187
348
|
.main {
|
|
188
349
|
display: grid;
|
|
189
|
-
grid-template-rows:
|
|
350
|
+
grid-template-rows: 46px 1fr auto;
|
|
190
351
|
min-width: 0;
|
|
191
352
|
min-height: 0;
|
|
192
353
|
}
|
|
193
354
|
.crumbs {
|
|
194
355
|
display: flex;
|
|
195
356
|
align-items: center;
|
|
196
|
-
gap:
|
|
357
|
+
gap: 7px;
|
|
197
358
|
padding: 0 16px;
|
|
198
359
|
border-bottom: 1px solid var(--line);
|
|
199
|
-
|
|
360
|
+
background: var(--panel);
|
|
361
|
+
color: var(--ink-3);
|
|
362
|
+
font-size: 12.5px;
|
|
200
363
|
overflow: hidden;
|
|
201
364
|
white-space: nowrap;
|
|
202
365
|
}
|
|
203
366
|
.crumbs .sep {
|
|
204
|
-
|
|
367
|
+
color: var(--ink-3);
|
|
368
|
+
opacity: 0.55;
|
|
205
369
|
}
|
|
206
370
|
.crumbs .cur {
|
|
207
371
|
color: var(--ink);
|
|
372
|
+
font-weight: 500;
|
|
208
373
|
}
|
|
374
|
+
|
|
375
|
+
/* A segmented control, not three loose buttons. */
|
|
209
376
|
.crumbs .tools {
|
|
210
377
|
margin-left: auto;
|
|
211
378
|
display: flex;
|
|
212
|
-
gap:
|
|
379
|
+
gap: 2px;
|
|
380
|
+
padding: 2px;
|
|
381
|
+
border-radius: var(--r-md);
|
|
382
|
+
background: var(--hover);
|
|
213
383
|
}
|
|
214
384
|
.crumbs .tools button {
|
|
215
|
-
padding:
|
|
216
|
-
border-radius:
|
|
217
|
-
color: var(--
|
|
385
|
+
padding: 3px 10px;
|
|
386
|
+
border-radius: var(--r-sm);
|
|
387
|
+
color: var(--ink-3);
|
|
388
|
+
font-size: 11.5px;
|
|
389
|
+
font-variant-numeric: tabular-nums;
|
|
390
|
+
}
|
|
391
|
+
.crumbs .tools button:hover {
|
|
392
|
+
color: var(--ink);
|
|
218
393
|
}
|
|
219
394
|
.crumbs .tools button.on {
|
|
220
|
-
background: var(--
|
|
395
|
+
background: var(--panel);
|
|
221
396
|
color: var(--ink);
|
|
397
|
+
box-shadow: var(--shadow-sm);
|
|
222
398
|
}
|
|
399
|
+
|
|
223
400
|
.stage {
|
|
224
401
|
position: relative;
|
|
225
402
|
overflow: auto;
|
|
226
|
-
padding:
|
|
403
|
+
padding: 28px;
|
|
227
404
|
display: flex;
|
|
228
405
|
justify-content: center;
|
|
229
406
|
align-items: flex-start;
|
|
230
|
-
background
|
|
231
|
-
background-size: 16px 16px;
|
|
407
|
+
background: var(--stage-bg);
|
|
232
408
|
}
|
|
233
409
|
.stage iframe {
|
|
234
410
|
border: 1px solid var(--line);
|
|
235
|
-
border-radius:
|
|
411
|
+
border-radius: var(--r-lg);
|
|
236
412
|
background: var(--stage);
|
|
237
413
|
width: 100%;
|
|
238
|
-
height:
|
|
239
|
-
min-height:
|
|
240
|
-
box-shadow:
|
|
241
|
-
transition:
|
|
414
|
+
height: 180px;
|
|
415
|
+
min-height: 120px;
|
|
416
|
+
box-shadow: var(--shadow-md);
|
|
417
|
+
transition:
|
|
418
|
+
width 0.2s ease,
|
|
419
|
+
height 0.2s ease;
|
|
242
420
|
}
|
|
243
421
|
.stage .empty {
|
|
244
|
-
color: var(--
|
|
422
|
+
color: var(--ink-3);
|
|
245
423
|
align-self: center;
|
|
246
424
|
}
|
|
247
425
|
|
|
@@ -256,88 +434,107 @@ a {
|
|
|
256
434
|
.frame {
|
|
257
435
|
display: inline-flex;
|
|
258
436
|
align-items: center;
|
|
259
|
-
gap:
|
|
260
|
-
padding: 6px
|
|
261
|
-
border: 1px solid
|
|
262
|
-
border-radius:
|
|
437
|
+
gap: 9px;
|
|
438
|
+
padding: 6px 12px;
|
|
439
|
+
border: 1px solid transparent;
|
|
440
|
+
border-radius: var(--r-md);
|
|
263
441
|
white-space: nowrap;
|
|
264
|
-
color: var(--
|
|
442
|
+
color: var(--ink-2);
|
|
265
443
|
}
|
|
266
444
|
.frame:hover {
|
|
267
445
|
background: var(--hover);
|
|
268
446
|
}
|
|
269
447
|
.frame.active {
|
|
270
|
-
|
|
448
|
+
background: var(--tint);
|
|
449
|
+
border-color: color-mix(in srgb, var(--accent) 30%, transparent);
|
|
271
450
|
color: var(--ink);
|
|
272
451
|
}
|
|
273
452
|
.frame .k {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
453
|
+
display: inline-flex;
|
|
454
|
+
align-items: center;
|
|
455
|
+
gap: 6px;
|
|
456
|
+
color: var(--ink-3);
|
|
457
|
+
font-size: 11px;
|
|
458
|
+
}
|
|
459
|
+
.frame .k::before {
|
|
460
|
+
content: '';
|
|
461
|
+
width: 6px;
|
|
462
|
+
height: 6px;
|
|
463
|
+
border-radius: 2px;
|
|
464
|
+
background: currentColor;
|
|
465
|
+
}
|
|
466
|
+
.frame .label {
|
|
467
|
+
font-family: var(--font-mono);
|
|
468
|
+
font-size: 12px;
|
|
278
469
|
}
|
|
279
470
|
.frame .t {
|
|
280
|
-
|
|
281
|
-
font-
|
|
471
|
+
color: var(--ink-3);
|
|
472
|
+
font-family: var(--font-mono);
|
|
473
|
+
font-size: 10.5px;
|
|
474
|
+
font-variant-numeric: tabular-nums;
|
|
282
475
|
}
|
|
283
476
|
.frame.render .k {
|
|
284
477
|
color: var(--accent);
|
|
285
|
-
opacity: 1;
|
|
286
478
|
}
|
|
287
479
|
.frame.action .k {
|
|
288
480
|
color: var(--pass);
|
|
289
|
-
opacity: 1;
|
|
290
481
|
}
|
|
291
482
|
.frame.step .k {
|
|
292
|
-
color:
|
|
293
|
-
opacity: 1;
|
|
294
|
-
}
|
|
295
|
-
.frame.end .k {
|
|
296
|
-
color: var(--muted);
|
|
483
|
+
color: var(--warn);
|
|
297
484
|
}
|
|
298
485
|
|
|
486
|
+
/* ------------------------------------------------------------ inspector -- */
|
|
487
|
+
|
|
299
488
|
.inspector {
|
|
300
489
|
border-left: 1px solid var(--line);
|
|
301
490
|
overflow: auto;
|
|
302
|
-
padding: 16px;
|
|
491
|
+
padding: 16px 18px 32px;
|
|
303
492
|
background: var(--panel);
|
|
304
493
|
}
|
|
305
494
|
.inspector h3 {
|
|
306
|
-
margin: 0 0
|
|
307
|
-
font-size:
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
font-weight: 500;
|
|
495
|
+
margin: 0 0 9px;
|
|
496
|
+
font-size: 11.5px;
|
|
497
|
+
font-weight: 600;
|
|
498
|
+
color: var(--ink-3);
|
|
499
|
+
text-transform: capitalize;
|
|
312
500
|
}
|
|
313
501
|
.inspector section {
|
|
314
|
-
margin-bottom:
|
|
502
|
+
margin-bottom: 22px;
|
|
503
|
+
}
|
|
504
|
+
.inspector section + section {
|
|
505
|
+
padding-top: 18px;
|
|
506
|
+
border-top: 1px solid var(--line-soft);
|
|
315
507
|
}
|
|
316
508
|
.status {
|
|
317
509
|
display: inline-flex;
|
|
318
510
|
align-items: center;
|
|
319
511
|
gap: 8px;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
width: 8px;
|
|
323
|
-
height: 8px;
|
|
512
|
+
font-weight: 500;
|
|
513
|
+
text-transform: capitalize;
|
|
324
514
|
}
|
|
325
515
|
.kv {
|
|
326
516
|
width: 100%;
|
|
327
517
|
border-collapse: collapse;
|
|
328
518
|
}
|
|
329
519
|
.kv td {
|
|
330
|
-
padding:
|
|
520
|
+
padding: 4px 0;
|
|
331
521
|
vertical-align: top;
|
|
332
|
-
border-bottom: 1px solid var(--line);
|
|
522
|
+
border-bottom: 1px solid var(--line-soft);
|
|
523
|
+
}
|
|
524
|
+
.kv tr:last-child td {
|
|
525
|
+
border-bottom: 0;
|
|
333
526
|
}
|
|
334
527
|
.kv td:first-child {
|
|
335
|
-
color: var(--
|
|
336
|
-
padding-right:
|
|
528
|
+
color: var(--ink-3);
|
|
529
|
+
padding-right: 14px;
|
|
337
530
|
white-space: nowrap;
|
|
531
|
+
font-family: var(--font-mono);
|
|
532
|
+
font-size: 12px;
|
|
338
533
|
}
|
|
339
534
|
.kv td:last-child {
|
|
340
535
|
word-break: break-word;
|
|
536
|
+
font-family: var(--font-mono);
|
|
537
|
+
font-size: 12px;
|
|
341
538
|
}
|
|
342
539
|
.val-string {
|
|
343
540
|
color: #b0501f;
|
|
@@ -347,99 +544,139 @@ a {
|
|
|
347
544
|
color: var(--accent);
|
|
348
545
|
}
|
|
349
546
|
.val-fn {
|
|
350
|
-
color: var(--
|
|
547
|
+
color: var(--ink-3);
|
|
351
548
|
font-style: italic;
|
|
352
549
|
}
|
|
550
|
+
@media (prefers-color-scheme: dark) {
|
|
551
|
+
.val-string {
|
|
552
|
+
color: #d98a5f;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
353
555
|
.err {
|
|
556
|
+
margin: 0;
|
|
354
557
|
color: var(--fail);
|
|
355
558
|
white-space: pre-wrap;
|
|
559
|
+
font-family: var(--font-mono);
|
|
356
560
|
font-size: 12px;
|
|
357
561
|
}
|
|
358
562
|
.hint {
|
|
359
|
-
color: var(--
|
|
563
|
+
color: var(--ink-3);
|
|
360
564
|
font-size: 12px;
|
|
361
565
|
}
|
|
362
566
|
kbd {
|
|
567
|
+
display: inline-block;
|
|
568
|
+
min-width: 18px;
|
|
569
|
+
text-align: center;
|
|
363
570
|
border: 1px solid var(--line);
|
|
364
|
-
border-
|
|
571
|
+
border-bottom-width: 2px;
|
|
572
|
+
border-radius: 5px;
|
|
365
573
|
padding: 0 4px;
|
|
574
|
+
background: var(--bg);
|
|
366
575
|
font: inherit;
|
|
367
576
|
font-size: 11px;
|
|
577
|
+
color: var(--ink-2);
|
|
578
|
+
}
|
|
579
|
+
.missing-line {
|
|
580
|
+
color: var(--fail);
|
|
581
|
+
font-family: var(--font-mono);
|
|
582
|
+
font-size: 12px;
|
|
583
|
+
}
|
|
584
|
+
.component-line {
|
|
585
|
+
display: flex;
|
|
586
|
+
gap: 12px;
|
|
587
|
+
justify-content: space-between;
|
|
588
|
+
padding: 3px 0;
|
|
589
|
+
}
|
|
590
|
+
.component-line span:first-child {
|
|
591
|
+
font-family: var(--font-mono);
|
|
592
|
+
font-size: 12px;
|
|
593
|
+
}
|
|
594
|
+
.component-line .hint {
|
|
595
|
+
font-family: var(--font-mono);
|
|
596
|
+
font-size: 11px;
|
|
597
|
+
overflow: hidden;
|
|
598
|
+
text-overflow: ellipsis;
|
|
368
599
|
}
|
|
369
600
|
|
|
601
|
+
/* ------------------------------------------------------------- overview -- */
|
|
602
|
+
|
|
370
603
|
.overview {
|
|
371
604
|
overflow: auto;
|
|
372
605
|
min-width: 0;
|
|
373
606
|
min-height: 0;
|
|
374
|
-
padding:
|
|
607
|
+
padding: 24px 28px 56px;
|
|
375
608
|
}
|
|
376
609
|
.overview-head {
|
|
377
610
|
display: flex;
|
|
378
611
|
align-items: baseline;
|
|
379
612
|
gap: 16px;
|
|
380
|
-
padding-bottom:
|
|
613
|
+
padding-bottom: 14px;
|
|
381
614
|
border-bottom: 1px solid var(--line);
|
|
382
615
|
}
|
|
383
616
|
.overview-head h1 {
|
|
384
617
|
margin: 0;
|
|
385
|
-
font-size:
|
|
618
|
+
font-size: 19px;
|
|
386
619
|
font-weight: 600;
|
|
387
|
-
letter-spacing: -0.
|
|
620
|
+
letter-spacing: -0.02em;
|
|
388
621
|
}
|
|
389
622
|
.overview-meta {
|
|
390
623
|
margin-left: auto;
|
|
391
|
-
color: var(--
|
|
624
|
+
color: var(--ink-3);
|
|
625
|
+
font-family: var(--font-mono);
|
|
626
|
+
font-size: 11px;
|
|
392
627
|
}
|
|
393
628
|
.overview-section {
|
|
394
|
-
margin-top:
|
|
629
|
+
margin-top: 26px;
|
|
395
630
|
}
|
|
396
631
|
.overview-section h2 {
|
|
397
632
|
margin: 0;
|
|
398
|
-
font-size:
|
|
633
|
+
font-size: 15px;
|
|
399
634
|
font-weight: 600;
|
|
400
635
|
}
|
|
401
636
|
.overview h3 {
|
|
402
|
-
margin:
|
|
403
|
-
font-size:
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
font-weight: 500;
|
|
637
|
+
margin: 22px 0 10px;
|
|
638
|
+
font-size: 11.5px;
|
|
639
|
+
font-weight: 600;
|
|
640
|
+
color: var(--ink-3);
|
|
641
|
+
text-transform: capitalize;
|
|
408
642
|
}
|
|
409
643
|
.overview .empty {
|
|
410
|
-
color: var(--
|
|
411
|
-
padding:
|
|
644
|
+
color: var(--ink-3);
|
|
645
|
+
padding: 28px 0;
|
|
412
646
|
}
|
|
413
647
|
.head-hint {
|
|
414
648
|
margin-left: 10px;
|
|
415
|
-
color: var(--
|
|
649
|
+
color: var(--ink-3);
|
|
650
|
+
font-weight: 400;
|
|
416
651
|
text-transform: none;
|
|
417
|
-
letter-spacing: 0;
|
|
418
|
-
opacity: 0.85;
|
|
419
652
|
}
|
|
420
653
|
|
|
421
654
|
.props th {
|
|
422
655
|
text-align: left;
|
|
423
|
-
padding: 0
|
|
424
|
-
font-size:
|
|
425
|
-
font-weight:
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
color: var(--muted);
|
|
656
|
+
padding: 0 14px 7px 0;
|
|
657
|
+
font-size: 11.5px;
|
|
658
|
+
font-weight: 600;
|
|
659
|
+
color: var(--ink-3);
|
|
660
|
+
text-transform: capitalize;
|
|
429
661
|
border-bottom: 1px solid var(--line);
|
|
430
662
|
}
|
|
431
663
|
.props td {
|
|
432
|
-
padding:
|
|
664
|
+
padding: 9px 14px 9px 0;
|
|
433
665
|
}
|
|
434
666
|
.props td.prop-name {
|
|
435
667
|
color: var(--ink);
|
|
668
|
+
font-family: var(--font-mono);
|
|
669
|
+
font-size: 12px;
|
|
670
|
+
font-weight: 500;
|
|
436
671
|
}
|
|
437
672
|
.props .req {
|
|
438
673
|
color: var(--fail);
|
|
439
674
|
}
|
|
440
675
|
.props .prop-type {
|
|
441
|
-
color: var(--
|
|
676
|
+
color: var(--ink-2);
|
|
442
677
|
white-space: pre-wrap;
|
|
678
|
+
font-family: var(--font-mono);
|
|
679
|
+
font-size: 12px;
|
|
443
680
|
}
|
|
444
681
|
.props .prop-covered {
|
|
445
682
|
width: 52%;
|
|
@@ -448,44 +685,43 @@ kbd {
|
|
|
448
685
|
display: inline-flex;
|
|
449
686
|
align-items: baseline;
|
|
450
687
|
gap: 4px;
|
|
451
|
-
margin: 0 6px
|
|
452
|
-
padding:
|
|
453
|
-
border:
|
|
454
|
-
|
|
455
|
-
font-size:
|
|
688
|
+
margin: 0 6px 5px 0;
|
|
689
|
+
padding: 2px 9px;
|
|
690
|
+
border-radius: var(--r-pill);
|
|
691
|
+
font-family: var(--font-mono);
|
|
692
|
+
font-size: 11.5px;
|
|
456
693
|
white-space: nowrap;
|
|
457
694
|
}
|
|
458
695
|
.chip.covered {
|
|
459
696
|
color: var(--pass);
|
|
460
|
-
background: color-mix(in srgb, var(--pass)
|
|
461
|
-
border-color: color-mix(in srgb, var(--pass) 32%, transparent);
|
|
697
|
+
background: color-mix(in srgb, var(--pass) 11%, transparent);
|
|
462
698
|
}
|
|
463
699
|
.chip.missing {
|
|
464
700
|
color: var(--fail);
|
|
465
|
-
background: color-mix(in srgb, var(--fail)
|
|
466
|
-
border-color: color-mix(in srgb, var(--fail) 32%, transparent);
|
|
701
|
+
background: color-mix(in srgb, var(--fail) 11%, transparent);
|
|
467
702
|
}
|
|
468
703
|
.chip-default {
|
|
469
|
-
color: var(--
|
|
470
|
-
font-size:
|
|
704
|
+
color: var(--ink-3);
|
|
705
|
+
font-size: 10.5px;
|
|
471
706
|
}
|
|
472
707
|
|
|
473
708
|
.gallery {
|
|
474
709
|
display: grid;
|
|
475
710
|
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
|
476
|
-
gap:
|
|
711
|
+
gap: 16px;
|
|
477
712
|
}
|
|
478
713
|
.tile {
|
|
479
714
|
display: block;
|
|
480
715
|
width: 100%;
|
|
481
716
|
text-align: left;
|
|
482
717
|
border: 1px solid var(--line);
|
|
483
|
-
border-radius:
|
|
718
|
+
border-radius: var(--r-lg);
|
|
484
719
|
overflow: hidden;
|
|
485
|
-
background: var(--
|
|
720
|
+
background: var(--raised);
|
|
721
|
+
transition: border-color 0.15s ease;
|
|
486
722
|
}
|
|
487
723
|
.tile:hover {
|
|
488
|
-
border-color: var(--
|
|
724
|
+
border-color: var(--accent);
|
|
489
725
|
}
|
|
490
726
|
.thumb {
|
|
491
727
|
height: 150px;
|
|
@@ -502,26 +738,18 @@ kbd {
|
|
|
502
738
|
pointer-events: none;
|
|
503
739
|
}
|
|
504
740
|
.thumb .hint {
|
|
505
|
-
padding:
|
|
741
|
+
padding: 12px;
|
|
506
742
|
}
|
|
507
743
|
.cap {
|
|
508
744
|
display: flex;
|
|
509
745
|
align-items: center;
|
|
510
746
|
gap: 8px;
|
|
511
|
-
padding:
|
|
747
|
+
padding: 9px 12px;
|
|
512
748
|
min-width: 0;
|
|
749
|
+
font-size: 12.5px;
|
|
513
750
|
}
|
|
514
751
|
.cap-name {
|
|
515
752
|
overflow: hidden;
|
|
516
753
|
text-overflow: ellipsis;
|
|
517
754
|
white-space: nowrap;
|
|
518
755
|
}
|
|
519
|
-
|
|
520
|
-
.missing-line {
|
|
521
|
-
color: var(--fail);
|
|
522
|
-
}
|
|
523
|
-
.component-line {
|
|
524
|
-
display: flex;
|
|
525
|
-
gap: 12px;
|
|
526
|
-
justify-content: space-between;
|
|
527
|
-
}
|