@motion-proto/live-tokens 0.74.0 → 0.76.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/.claude/skills/live-tokens-check-compliance/SKILL.md +33 -38
- package/.claude/skills/live-tokens-create-component/SKILL copy.md +196 -0
- package/.claude/skills/live-tokens-create-component/SKILL.md +198 -146
- package/.claude/skills/live-tokens-create-component/references/contract-tests.md +95 -52
- package/.claude/skills/live-tokens-create-component/references/intrinsics.md +2 -2
- package/.claude/skills/live-tokens-create-component/references/linked-siblings.md +2 -2
- package/.claude/skills/live-tokens-create-component/references/sketch-mode.md +12 -12
- package/.claude/skills/live-tokens-create-component/references/token-naming.md +2 -1
- package/.claude/skills/live-tokens-create-page/SKILL.md +187 -0
- package/.claude/skills/live-tokens-create-page/references/interaction-sources.md +66 -0
- package/.claude/skills/live-tokens-create-page/references/layout-sources.md +87 -0
- package/.claude/skills/live-tokens-create-theme/SKILL.md +52 -48
- package/.claude/skills/live-tokens-create-theme/references/design-directions.md +1 -1
- package/.claude/skills/live-tokens-fix-findings/SKILL.md +69 -60
- package/.claude/skills/live-tokens-pick-component/SKILL.md +64 -79
- package/.claude/skills/live-tokens-set-colors/SKILL.md +47 -38
- package/.claude/skills/live-tokens-set-geometry/SKILL.md +58 -35
- package/.claude/skills/live-tokens-set-geometry/references/geometry-anchors.md +5 -3
- package/.claude/skills/live-tokens-set-type/SKILL.md +30 -30
- package/CHANGELOG.md +138 -0
- package/README.md +17 -7
- package/bin/check-component.mjs +91 -19
- package/bin/check-page.mjs +102 -21
- package/bin/cli.mjs +87 -113
- package/bin/contractRunner.mjs +945 -0
- package/bin/create.mjs +1 -1
- package/bin/lib/catalogue.mjs +37 -30
- package/bin/lib/findings.mjs +46 -15
- package/bin/lib/report.mjs +3 -3
- package/bin/lib/tokenVocabulary.mjs +4 -4
- package/bin/migrate-routes.mjs +5 -5
- package/bin/migrate.mjs +4 -4
- package/bin/save-theme.mjs +8 -9
- package/bin/set-colors.mjs +9 -11
- package/bin/set-geometry.mjs +7 -7
- package/bin/set-type.mjs +5 -7
- package/bin/setup-claude.mjs +110 -0
- package/dist-plugin/{chunk-W6Y4BWFB.js → chunk-6WGFOJXO.js} +22 -0
- package/dist-plugin/{chunk-7VRTBGJT.js → chunk-PDNL4NC5.js} +9 -2
- package/dist-plugin/{chunk-V3YF6CGT.js → chunk-SWXRVZKT.js} +43 -1
- package/dist-plugin/{dataPaths-BhWzd5cL.d.cts → dataPaths-BpIK_Obx.d.cts} +1 -0
- package/dist-plugin/{dataPaths-BhWzd5cL.d.ts → dataPaths-BpIK_Obx.d.ts} +1 -0
- package/dist-plugin/index.cjs +310 -147
- package/dist-plugin/index.d.cts +1 -1
- package/dist-plugin/index.d.ts +1 -1
- package/dist-plugin/index.js +218 -122
- package/dist-plugin/migrateData/index.cjs +66 -2
- package/dist-plugin/migrateData/index.d.cts +1 -1
- package/dist-plugin/migrateData/index.d.ts +1 -1
- package/dist-plugin/migrateData/index.js +3 -3
- package/dist-plugin/setColors/index.cjs +61 -4
- package/dist-plugin/setColors/index.d.cts +1 -1
- package/dist-plugin/setColors/index.d.ts +1 -1
- package/dist-plugin/setColors/index.js +5 -5
- package/dist-plugin/setGeometry/index.cjs +107 -44
- package/dist-plugin/setGeometry/index.d.cts +5 -5
- package/dist-plugin/setGeometry/index.d.ts +5 -5
- package/dist-plugin/setGeometry/index.js +51 -45
- package/dist-plugin/setType/index.cjs +15 -0
- package/dist-plugin/setType/index.d.cts +1 -1
- package/dist-plugin/setType/index.d.ts +1 -1
- package/dist-plugin/setType/index.js +1 -1
- package/dist-plugin/tokensCssMigrations/index.d.cts +1 -1
- package/dist-plugin/tokensCssMigrations/index.d.ts +1 -1
- package/dist-plugin/tokensCssMigrations/index.js +1 -1
- package/package.json +35 -8
- package/src/app/site.css +19 -9
- package/src/editor/bootstrap.ts +2 -0
- package/src/editor/component-editor/CollapsibleSectionEditor.svelte +4 -4
- package/src/editor/component-editor/DialogEditor.svelte +4 -4
- package/src/editor/component-editor/MenuSelectEditor.svelte +4 -1
- package/src/editor/component-editor/SegmentedControlEditor.svelte +6 -1
- package/src/editor/component-editor/TabBarEditor.svelte +1 -1
- package/src/editor/component-editor/TableEditor.svelte +2 -2
- package/src/editor/component-editor/scaffolding/TokenLayout.svelte +6 -3
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +41 -20
- package/src/editor/core/components/adjustAliases.ts +59 -45
- package/src/editor/core/components/aliasKinds.ts +9 -5
- package/src/editor/core/preview/themePreview.ts +9 -2
- package/src/editor/core/sketch/sketchLayer.ts +22 -0
- package/src/editor/core/store/editorStore.ts +10 -1
- package/src/editor/core/themes/buildColors.ts +3 -3
- package/src/editor/core/themes/liveStateStream.ts +26 -0
- package/src/editor/core/themes/migrations/2026-09-07-stroke-role-renames.ts +52 -0
- package/src/editor/core/themes/migrations/index.ts +2 -0
- package/src/editor/core/themes/themeDocumentSync.ts +22 -11
- package/src/editor/core/themes/themeService.ts +9 -2
- package/src/editor/pages/ComponentEditorPage.svelte +17 -1
- package/src/editor/pages/liveTokensEditorHandle.ts +23 -0
- package/src/editor/skill-atlas/SkillAtlas.svelte +75 -573
- package/src/editor/skill-atlas/TreeCanvas.svelte +263 -0
- package/src/editor/skill-atlas/TreeNodeCard.svelte +136 -53
- package/src/editor/skill-atlas/edges.ts +31 -0
- package/src/editor/skill-atlas/skillSources.generated.ts +20 -19
- package/src/editor/skill-atlas/skillTrees.ts +19 -3840
- package/src/editor/skill-atlas/trees/check-compliance.ts +183 -0
- package/src/editor/skill-atlas/trees/create-component.ts +275 -0
- package/src/editor/skill-atlas/trees/create-page.ts +320 -0
- package/src/editor/skill-atlas/trees/create-theme.ts +255 -0
- package/src/editor/skill-atlas/trees/fix-findings.ts +469 -0
- package/src/editor/skill-atlas/trees/pick-component.ts +300 -0
- package/src/editor/skill-atlas/trees/set-colors.ts +148 -0
- package/src/editor/skill-atlas/trees/set-geometry.ts +136 -0
- package/src/editor/skill-atlas/trees/set-type.ts +142 -0
- package/src/editor/skill-atlas/types.ts +3 -4
- package/src/editor/skill-atlas/wireLayout.ts +287 -0
- package/src/live-tokens/data/themes/autumn.json +15 -15
- package/src/live-tokens/data/themes/halloween.json +15 -15
- package/src/live-tokens/data/themes/midnight-study.json +15 -15
- package/src/live-tokens/data/themes/ocean.json +15 -15
- package/src/live-tokens/data/themes/royal-velvet.json +15 -15
- package/src/live-tokens/data/themes/sketchy.json +15 -15
- package/src/live-tokens/data/themes/spring-meadow.json +15 -15
- package/src/live-tokens/data/themes/sunset.json +15 -15
- package/src/system/components/Badge.svelte +7 -0
- package/src/system/components/Button.svelte +7 -0
- package/src/system/components/Callout.svelte +10 -6
- package/src/system/components/Card.svelte +23 -4
- package/src/system/components/CodeSnippet.svelte +4 -3
- package/src/system/components/CollapsibleSection.svelte +23 -8
- package/src/system/components/CornerBadge.svelte +6 -0
- package/src/system/components/Dialog.svelte +13 -6
- package/src/system/components/IconButton.svelte +9 -0
- package/src/system/components/Image.svelte +8 -0
- package/src/system/components/ImageLightbox.svelte +6 -0
- package/src/system/components/InlineEditActions.svelte +7 -0
- package/src/system/components/Input.svelte +7 -0
- package/src/system/components/MenuSelect.svelte +7 -0
- package/src/system/components/Notification.svelte +7 -0
- package/src/system/components/Panel.svelte +6 -0
- package/src/system/components/ProgressBar.svelte +5 -0
- package/src/system/components/RadioButton.svelte +11 -5
- package/src/system/components/SectionDivider.svelte +8 -0
- package/src/system/components/SegmentedControl.svelte +6 -0
- package/src/system/components/SideNavigation.svelte +6 -0
- package/src/system/components/Slider.svelte +7 -4
- package/src/system/components/TabBar.svelte +15 -9
- package/src/system/components/Table.svelte +8 -3
- package/src/system/components/Toggle.svelte +4 -4
- package/src/system/components/Tooltip.svelte +6 -0
- package/src/testing-js/chunk-AO7EZHYV.js +776 -0
- package/src/testing-js/chunk-AO7EZHYV.js.map +1 -0
- package/src/testing-js/chunk-FAFOAWYL.js +39 -0
- package/src/testing-js/chunk-FAFOAWYL.js.map +1 -0
- package/src/testing-js/chunk-L73N4NSO.js +23 -0
- package/src/testing-js/chunk-L73N4NSO.js.map +1 -0
- package/src/testing-js/chunk-LXR3MN6N.js +3063 -0
- package/src/testing-js/chunk-LXR3MN6N.js.map +1 -0
- package/src/testing-js/chunk-ZMSX6CXR.js +53 -0
- package/src/testing-js/chunk-ZMSX6CXR.js.map +1 -0
- package/src/testing-js/component-alias.contract.js +81 -0
- package/src/testing-js/component-alias.contract.js.map +1 -0
- package/src/testing-js/component-editor.contract.js +62 -0
- package/src/testing-js/component-editor.contract.js.map +1 -0
- package/src/testing-js/component-render.contract.js +568 -0
- package/src/testing-js/component-render.contract.js.map +1 -0
- package/src/testing-js/index.d.ts +293 -0
- package/src/testing-js/index.js +222 -0
- package/src/testing-js/index.js.map +1 -0
- package/src/testing-js/registry.contract.js +39 -0
- package/src/testing-js/registry.contract.js.map +1 -0
- package/src/testing-js/vitest-BE6uGF31.d.ts +73 -0
- package/src/testing-js/vitest.d.ts +3 -0
- package/src/testing-js/vitest.js +13 -0
- package/src/testing-js/vitest.js.map +1 -0
- package/template/README.md +13 -0
- package/template/_gitignore +6 -0
- package/template/package.json +3 -1
- package/template/src/pages/Home.svelte +4 -18
- package/.claude/skills/live-tokens-build-page/SKILL.md +0 -103
- package/.claude/skills/live-tokens-build-page/references/layout-sources.md +0 -48
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
import { tick } from 'svelte';
|
|
3
3
|
import { navigate } from '../core/routing/router';
|
|
4
4
|
import Button from '../../system/components/Button.svelte';
|
|
5
|
-
import Callout from '../../system/components/Callout.svelte';
|
|
6
5
|
import TabBar from '../../system/components/TabBar.svelte';
|
|
7
6
|
import SourcePane from './SourcePane.svelte';
|
|
8
|
-
import
|
|
7
|
+
import TreeCanvas from './TreeCanvas.svelte';
|
|
9
8
|
import { SKILL_DOC, skillDocs } from './skillSources';
|
|
10
9
|
import { skillTrees } from './skillTrees';
|
|
11
|
-
import type {
|
|
10
|
+
import type { LineRange, Selection } from './types';
|
|
12
11
|
|
|
13
12
|
// `/skills#set-type` opens that skill, so a link can hand someone one tree
|
|
14
13
|
// rather than the atlas front door.
|
|
@@ -42,16 +41,6 @@
|
|
|
42
41
|
sourcePane?.scrollTo({ top: 0 });
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
let rows = $derived.by(() => {
|
|
46
|
-
const byRow = new Map<number, TreeNode[]>();
|
|
47
|
-
for (const node of tree.nodes) {
|
|
48
|
-
const group = byRow.get(node.row);
|
|
49
|
-
if (group) group.push(node);
|
|
50
|
-
else byRow.set(node.row, [node]);
|
|
51
|
-
}
|
|
52
|
-
return [...byRow.entries()].sort((a, b) => a[0] - b[0]).map(([, group]) => group);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
44
|
function selectTarget(key: string, label: string, range: LineRange) {
|
|
56
45
|
selection = { key, label, lines: range };
|
|
57
46
|
// Every range cites SKILL.md, so a step selected while a reference is open
|
|
@@ -104,339 +93,11 @@
|
|
|
104
93
|
sourcePane?.scrollTo({ top: 0 });
|
|
105
94
|
}
|
|
106
95
|
|
|
107
|
-
// ---- wires -------------------------------------------------------------
|
|
108
|
-
// Connectors are measured from the laid-out cards, so they survive wrapping,
|
|
109
|
-
// theme changes, and the geometry skill moving every radius and padding.
|
|
110
|
-
let canvas: HTMLElement | undefined = $state();
|
|
111
96
|
let treePane: HTMLElement | undefined = $state();
|
|
112
97
|
let sourcePane: HTMLElement | undefined = $state();
|
|
113
|
-
|
|
114
|
-
interface Wire {
|
|
115
|
-
d: string;
|
|
116
|
-
back: boolean;
|
|
117
|
-
lit: boolean;
|
|
118
|
-
/** Trunks and buses carry no arrowhead; only the segment that arrives does. */
|
|
119
|
-
arrow: boolean;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
interface Label {
|
|
123
|
-
x: number;
|
|
124
|
-
y: number;
|
|
125
|
-
text: string;
|
|
126
|
-
lit: boolean;
|
|
127
|
-
/** Lane labels run along the lane; branch labels sit across the drop. */
|
|
128
|
-
rotate: boolean;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
interface Box {
|
|
132
|
-
left: number;
|
|
133
|
-
right: number;
|
|
134
|
-
top: number;
|
|
135
|
-
bottom: number;
|
|
136
|
-
midX: number;
|
|
137
|
-
midY: number;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
interface Drawing {
|
|
141
|
-
wires: Wire[];
|
|
142
|
-
labels: Label[];
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
let wires: Wire[] = $state([]);
|
|
146
|
-
let labels: Label[] = $state([]);
|
|
147
|
-
let canvasSize = $state({ w: 0, h: 0 });
|
|
148
|
-
|
|
149
|
-
/** Clearance between a card border and the wire, so the arrowhead reads as
|
|
150
|
-
* an arrival rather than as part of the card outline. */
|
|
151
|
-
const STANDOFF = 6;
|
|
152
|
-
const CORNER = 12;
|
|
153
|
-
/** How far outside the cards a lane sits, and how far one lane nests
|
|
154
|
-
* inside the next when two share a stretch of gutter. */
|
|
155
|
-
const LANE_INSET = 40;
|
|
156
|
-
const LANE_STEP = 16;
|
|
157
|
-
|
|
158
|
-
/** Cards laid out in one row share a top to within a pixel; cards pushed onto
|
|
159
|
-
* a second wrapped line do not, and a single bus cannot serve both. */
|
|
160
|
-
function aligned(values: number[]): boolean {
|
|
161
|
-
return Math.max(...values) - Math.min(...values) < 4;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function push<T>(map: Map<string, T[]>, key: string, value: T) {
|
|
165
|
-
const list = map.get(key);
|
|
166
|
-
if (list) list.push(value);
|
|
167
|
-
else map.set(key, [value]);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function label(out: Drawing, text: string | undefined, x: number, y: number, lit: boolean, rotate = false) {
|
|
171
|
-
if (text) out.labels.push({ x, y, text, lit, rotate });
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/** One trunk down from the parent, one bus across, one drop into each child —
|
|
175
|
-
* so a split reads as a single decision rather than as N crossing curves.
|
|
176
|
-
* Each answer sits on its own drop. */
|
|
177
|
-
function fanOut(out: Drawing, a: Box, kids: Box[], edges: Edge[], lit: (i: number) => boolean) {
|
|
178
|
-
const top = kids[0].top;
|
|
179
|
-
const busY = (a.bottom + top) / 2;
|
|
180
|
-
const xs = kids.map((k) => k.midX);
|
|
181
|
-
const r = Math.max(
|
|
182
|
-
0,
|
|
183
|
-
Math.min(CORNER, (Math.max(...xs) - Math.min(...xs)) / 2, (busY - a.bottom) / 2, (top - busY) / 2),
|
|
184
|
-
);
|
|
185
|
-
const turns = xs.map((x) => (x < a.midX ? x + r : x > a.midX ? x - r : x));
|
|
186
|
-
const busLeft = Math.min(...turns, a.midX);
|
|
187
|
-
const busRight = Math.max(...turns, a.midX);
|
|
188
|
-
const anyLit = kids.some((_, i) => lit(i));
|
|
189
|
-
|
|
190
|
-
out.wires.push({
|
|
191
|
-
back: false,
|
|
192
|
-
arrow: false,
|
|
193
|
-
lit: anyLit,
|
|
194
|
-
d: `M ${a.midX} ${a.bottom + STANDOFF} L ${a.midX} ${busY} M ${busLeft} ${busY} L ${busRight} ${busY}`,
|
|
195
|
-
});
|
|
196
|
-
kids.forEach((k, i) => {
|
|
197
|
-
const x = k.midX;
|
|
198
|
-
const turn = turns[i];
|
|
199
|
-
out.wires.push({
|
|
200
|
-
back: false,
|
|
201
|
-
arrow: true,
|
|
202
|
-
lit: lit(i),
|
|
203
|
-
d:
|
|
204
|
-
turn === x
|
|
205
|
-
? `M ${x} ${busY} L ${x} ${top - STANDOFF}`
|
|
206
|
-
: `M ${turn} ${busY} Q ${x} ${busY}, ${x} ${busY + r} L ${x} ${top - STANDOFF}`,
|
|
207
|
-
});
|
|
208
|
-
label(out, edges[i].label, x, (busY + r + top - STANDOFF) / 2, lit(i));
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/** The mirror of `fanOut`: risers up to a shared bus, one trunk into the child,
|
|
213
|
-
* so several outcomes converging land as one arrow instead of a pile. */
|
|
214
|
-
function fanIn(out: Drawing, parents: Box[], b: Box, edges: Edge[], lit: (i: number) => boolean) {
|
|
215
|
-
const bottom = parents[0].bottom;
|
|
216
|
-
const busY = (bottom + b.top) / 2;
|
|
217
|
-
const xs = parents.map((p) => p.midX);
|
|
218
|
-
const r = Math.max(
|
|
219
|
-
0,
|
|
220
|
-
Math.min(CORNER, (Math.max(...xs) - Math.min(...xs)) / 2, (busY - bottom) / 2, (b.top - busY) / 2),
|
|
221
|
-
);
|
|
222
|
-
const turns = xs.map((x) => (x < b.midX ? x + r : x > b.midX ? x - r : x));
|
|
223
|
-
const anyLit = parents.some((_, i) => lit(i));
|
|
224
|
-
|
|
225
|
-
parents.forEach((p, i) => {
|
|
226
|
-
const x = p.midX;
|
|
227
|
-
const turn = turns[i];
|
|
228
|
-
out.wires.push({
|
|
229
|
-
back: false,
|
|
230
|
-
arrow: false,
|
|
231
|
-
lit: lit(i),
|
|
232
|
-
d:
|
|
233
|
-
turn === x
|
|
234
|
-
? `M ${x} ${bottom + STANDOFF} L ${x} ${busY}`
|
|
235
|
-
: `M ${x} ${bottom + STANDOFF} L ${x} ${busY - r} Q ${x} ${busY}, ${turn} ${busY}`,
|
|
236
|
-
});
|
|
237
|
-
label(out, edges[i].label, x, (bottom + STANDOFF + busY - r) / 2, lit(i));
|
|
238
|
-
});
|
|
239
|
-
out.wires.push({
|
|
240
|
-
back: false,
|
|
241
|
-
arrow: true,
|
|
242
|
-
lit: anyLit,
|
|
243
|
-
d:
|
|
244
|
-
`M ${Math.min(...turns, b.midX)} ${busY} L ${Math.max(...turns, b.midX)} ${busY}` +
|
|
245
|
-
` M ${b.midX} ${busY} L ${b.midX} ${b.top - STANDOFF}`,
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/** A plain parent-to-child curve, and the fallback wherever a bus would have
|
|
250
|
-
* to cross a card: children wrapped onto a second line, or parents staggered
|
|
251
|
-
* across rows. */
|
|
252
|
-
function curve(out: Drawing, a: Box, b: Box, edge: Edge, lit: boolean) {
|
|
253
|
-
const y1 = a.bottom + STANDOFF;
|
|
254
|
-
const y2 = b.top - STANDOFF;
|
|
255
|
-
const bend = Math.max(16, (y2 - y1) * 0.55);
|
|
256
|
-
out.wires.push({
|
|
257
|
-
back: false,
|
|
258
|
-
arrow: true,
|
|
259
|
-
lit,
|
|
260
|
-
d: `M ${a.midX} ${y1} C ${a.midX} ${y1 + bend}, ${b.midX} ${y2 - bend}, ${b.midX} ${y2}`,
|
|
261
|
-
});
|
|
262
|
-
label(out, edge.label, (a.midX + b.midX) / 2, (y1 + y2) / 2, lit);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/** Every card the span passes between, not just the two it joins. */
|
|
266
|
-
function crossed(a: Box, b: Box, all: Box[]): Box[] {
|
|
267
|
-
const top = Math.min(a.midY, b.midY);
|
|
268
|
-
const bottom = Math.max(a.midY, b.midY);
|
|
269
|
-
return all.filter((x) => x !== a && x !== b && x.bottom > top && x.top < bottom);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/** On the spine: no other card shares its row. A wrapped branch row also
|
|
273
|
-
* "crosses" cards on its way down, and those stay curves. */
|
|
274
|
-
function alone(box: Box, all: Box[]): boolean {
|
|
275
|
-
return all.every((x) => x === box || !aligned([x.top, box.top]));
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/** Re-run loops ride a lane in the left gutter the centred spine opens up,
|
|
279
|
-
* rather than bulging around the card they leave. */
|
|
280
|
-
function backEdge(out: Drawing, a: Box, b: Box, lane: number, edge: Edge, lit: boolean) {
|
|
281
|
-
const dir = Math.sign(b.midY - a.midY) || -1;
|
|
282
|
-
const r = Math.max(
|
|
283
|
-
0,
|
|
284
|
-
Math.min(CORNER, (a.left - STANDOFF - lane) / 2, Math.abs(a.midY - b.midY) / 2),
|
|
285
|
-
);
|
|
286
|
-
out.wires.push({
|
|
287
|
-
back: true,
|
|
288
|
-
arrow: true,
|
|
289
|
-
lit,
|
|
290
|
-
d:
|
|
291
|
-
`M ${a.left - STANDOFF} ${a.midY} L ${lane + r} ${a.midY}` +
|
|
292
|
-
` Q ${lane} ${a.midY}, ${lane} ${a.midY + dir * r}` +
|
|
293
|
-
` L ${lane} ${b.midY - dir * r} Q ${lane} ${b.midY}, ${lane + r} ${b.midY}` +
|
|
294
|
-
` L ${b.left - STANDOFF} ${b.midY}`,
|
|
295
|
-
});
|
|
296
|
-
label(out, edge.label ?? 're-run', lane, (a.midY + b.midY) / 2, lit, true);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/** A branch that skips a row rides the right gutter, the mirror of a loop,
|
|
300
|
-
* so a "no" never has to be drawn through the card the "yes" leads to. */
|
|
301
|
-
function skipEdge(out: Drawing, a: Box, b: Box, lane: number, edge: Edge, lit: boolean) {
|
|
302
|
-
const r = Math.max(
|
|
303
|
-
0,
|
|
304
|
-
Math.min(CORNER, (lane - a.right - STANDOFF) / 2, Math.abs(a.midY - b.midY) / 2),
|
|
305
|
-
);
|
|
306
|
-
out.wires.push({
|
|
307
|
-
back: false,
|
|
308
|
-
arrow: true,
|
|
309
|
-
lit,
|
|
310
|
-
d:
|
|
311
|
-
`M ${a.right + STANDOFF} ${a.midY} L ${lane - r} ${a.midY}` +
|
|
312
|
-
` Q ${lane} ${a.midY}, ${lane} ${a.midY + r}` +
|
|
313
|
-
` L ${lane} ${b.midY - r} Q ${lane} ${b.midY}, ${lane - r} ${b.midY}` +
|
|
314
|
-
` L ${b.right + STANDOFF} ${b.midY}`,
|
|
315
|
-
});
|
|
316
|
-
label(out, edge.label, lane, (a.midY + b.midY) / 2, lit, true);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
function drawWires() {
|
|
320
|
-
if (!canvas) return;
|
|
321
|
-
const base = canvas.getBoundingClientRect();
|
|
322
|
-
canvasSize = { w: base.width, h: base.height };
|
|
323
|
-
const selectedNode = selection?.key.split(':')[0] ?? null;
|
|
324
|
-
const lit = (...ids: string[]) => ids.includes(selectedNode ?? '');
|
|
325
|
-
|
|
326
|
-
const box = (id: string): Box | null => {
|
|
327
|
-
const el = canvas?.querySelector(`[data-node="${id}"]`);
|
|
328
|
-
if (!el) return null;
|
|
329
|
-
const r = el.getBoundingClientRect();
|
|
330
|
-
return {
|
|
331
|
-
left: r.left - base.left,
|
|
332
|
-
right: r.right - base.left,
|
|
333
|
-
top: r.top - base.top,
|
|
334
|
-
bottom: r.bottom - base.top,
|
|
335
|
-
midX: r.left + r.width / 2 - base.left,
|
|
336
|
-
midY: r.top + r.height / 2 - base.top,
|
|
337
|
-
};
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
const boxes = new Map<string, Box>();
|
|
341
|
-
for (const node of tree.nodes) {
|
|
342
|
-
const b = box(node.id);
|
|
343
|
-
if (b) boxes.set(node.id, b);
|
|
344
|
-
}
|
|
345
|
-
const all = [...boxes.values()];
|
|
346
|
-
|
|
347
|
-
const edges = tree.edges as Edge[];
|
|
348
|
-
const children = new Map<string, Edge[]>();
|
|
349
|
-
const parents = new Map<string, Edge[]>();
|
|
350
|
-
for (const edge of edges) {
|
|
351
|
-
if (edge.back) continue;
|
|
352
|
-
push(children, edge.from, edge);
|
|
353
|
-
push(parents, edge.to, edge);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const out: Drawing = { wires: [], labels: [] };
|
|
357
|
-
const claimed = new Set<Edge>();
|
|
358
|
-
|
|
359
|
-
for (const [from, kidEdges] of children) {
|
|
360
|
-
const a = boxes.get(from);
|
|
361
|
-
const kids = kidEdges.map((e) => boxes.get(e.to));
|
|
362
|
-
if (!a || kidEdges.length < 2 || kids.some((k) => !k)) continue;
|
|
363
|
-
const kept = kids as Box[];
|
|
364
|
-
if (!aligned(kept.map((k) => k.top))) continue;
|
|
365
|
-
kidEdges.forEach((e) => claimed.add(e));
|
|
366
|
-
fanOut(out, a, kept, kidEdges, (i) => lit(from, kidEdges[i].to));
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
for (const [to, parentEdges] of parents) {
|
|
370
|
-
const open = parentEdges.filter((e) => !claimed.has(e));
|
|
371
|
-
const b = boxes.get(to);
|
|
372
|
-
const ups = open.map((e) => boxes.get(e.from));
|
|
373
|
-
if (!b || open.length < 2 || ups.some((u) => !u)) continue;
|
|
374
|
-
const kept = ups as Box[];
|
|
375
|
-
if (!aligned(kept.map((u) => u.bottom))) continue;
|
|
376
|
-
open.forEach((e) => claimed.add(e));
|
|
377
|
-
fanIn(out, kept, b, open, (i) => lit(open[i].from, to));
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Longest skip first, so a long one always rides outside the skips it spans.
|
|
381
|
-
const skips: { edge: Edge; a: Box; b: Box; over: Box[] }[] = [];
|
|
382
|
-
for (const edge of edges) {
|
|
383
|
-
if (edge.back || claimed.has(edge)) continue;
|
|
384
|
-
const a = boxes.get(edge.from);
|
|
385
|
-
const b = boxes.get(edge.to);
|
|
386
|
-
if (!a || !b) continue;
|
|
387
|
-
const over = alone(a, all) && alone(b, all) ? crossed(a, b, all) : [];
|
|
388
|
-
if (over.length > 0) skips.push({ edge, a, b, over });
|
|
389
|
-
else curve(out, a, b, edge, lit(edge.from, edge.to));
|
|
390
|
-
}
|
|
391
|
-
skips.sort((x, y) => Math.abs(y.a.midY - y.b.midY) - Math.abs(x.a.midY - x.b.midY));
|
|
392
|
-
let outer = -Infinity;
|
|
393
|
-
for (const { edge, a, b, over } of skips) {
|
|
394
|
-
const lane = Math.max(
|
|
395
|
-
...over.map((x) => x.right),
|
|
396
|
-
a.right,
|
|
397
|
-
b.right,
|
|
398
|
-
) + LANE_INSET;
|
|
399
|
-
const placed = Math.max(lane, outer + LANE_STEP);
|
|
400
|
-
outer = placed;
|
|
401
|
-
skipEdge(out, a, b, placed, edge, lit(edge.from, edge.to));
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Shortest loop first, so a long one always nests outside the loops it spans.
|
|
405
|
-
const loops = edges
|
|
406
|
-
.filter((e) => e.back)
|
|
407
|
-
.map((edge) => ({ edge, a: boxes.get(edge.from), b: boxes.get(edge.to) }))
|
|
408
|
-
.filter((e): e is { edge: Edge; a: Box; b: Box } => !!e.a && !!e.b)
|
|
409
|
-
.sort((x, y) => Math.abs(x.a.midY - x.b.midY) - Math.abs(y.a.midY - y.b.midY));
|
|
410
|
-
|
|
411
|
-
let inner = Infinity;
|
|
412
|
-
for (const { edge, a, b } of loops) {
|
|
413
|
-
const clear = Math.min(...crossed(a, b, all).map((x) => x.left), a.left, b.left) - LANE_INSET;
|
|
414
|
-
const lane = Math.max(4, Math.min(clear, inner - LANE_STEP));
|
|
415
|
-
inner = lane;
|
|
416
|
-
backEdge(out, a, b, lane, edge, lit(edge.from, edge.to));
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
wires = out.wires;
|
|
420
|
-
labels = out.labels;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
$effect(() => {
|
|
424
|
-
// Re-read on tab change and on selection (which only relights existing wires).
|
|
425
|
-
void tree;
|
|
426
|
-
void selection;
|
|
427
|
-
const frame = requestAnimationFrame(drawWires);
|
|
428
|
-
return () => cancelAnimationFrame(frame);
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
$effect(() => {
|
|
432
|
-
if (!canvas) return;
|
|
433
|
-
const observer = new ResizeObserver(() => drawWires());
|
|
434
|
-
observer.observe(canvas);
|
|
435
|
-
return () => observer.disconnect();
|
|
436
|
-
});
|
|
437
98
|
</script>
|
|
438
99
|
|
|
439
|
-
<div class="atlas">
|
|
100
|
+
<div class="skill-atlas">
|
|
440
101
|
<header class="masthead">
|
|
441
102
|
<div class="masthead-top">
|
|
442
103
|
<h1>Skill Atlas</h1>
|
|
@@ -444,15 +105,12 @@
|
|
|
444
105
|
Back to Demo
|
|
445
106
|
</Button>
|
|
446
107
|
</div>
|
|
447
|
-
<p class="standfirst">
|
|
448
|
-
The eight live-tokens authoring skills as decision trees. Select a step and its lines light up in
|
|
449
|
-
the skill on the right; select a line number and the step that owns it lights up on the left.
|
|
450
|
-
</p>
|
|
451
|
-
<div class="tabs">
|
|
452
|
-
<TabBar {tabs} selectedTab={active} ontabChange={changeTab} />
|
|
453
|
-
</div>
|
|
454
108
|
</header>
|
|
455
109
|
|
|
110
|
+
<div class="tabs">
|
|
111
|
+
<TabBar {tabs} selectedTab={active} ontabChange={changeTab} />
|
|
112
|
+
</div>
|
|
113
|
+
|
|
456
114
|
<div class="split">
|
|
457
115
|
<section class="pane" aria-label="{tree.id} decision tree">
|
|
458
116
|
<div class="pane-head">
|
|
@@ -460,72 +118,9 @@
|
|
|
460
118
|
<span class="pane-note">{tree.nodes.length} steps</span>
|
|
461
119
|
</div>
|
|
462
120
|
<div class="pane-body" bind:this={treePane}>
|
|
463
|
-
<
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
<div class="canvas" bind:this={canvas}>
|
|
468
|
-
<svg
|
|
469
|
-
class="wires"
|
|
470
|
-
viewBox="0 0 {canvasSize.w} {canvasSize.h}"
|
|
471
|
-
width={canvasSize.w}
|
|
472
|
-
height={canvasSize.h}
|
|
473
|
-
aria-hidden="true"
|
|
474
|
-
>
|
|
475
|
-
<defs>
|
|
476
|
-
<marker
|
|
477
|
-
id="atlas-arrow"
|
|
478
|
-
viewBox="0 0 10 10"
|
|
479
|
-
refX="9"
|
|
480
|
-
refY="5"
|
|
481
|
-
markerWidth="9"
|
|
482
|
-
markerHeight="9"
|
|
483
|
-
markerUnits="userSpaceOnUse"
|
|
484
|
-
orient="auto"
|
|
485
|
-
>
|
|
486
|
-
<path d="M 0 1 L 9 5 L 0 9 z" />
|
|
487
|
-
</marker>
|
|
488
|
-
</defs>
|
|
489
|
-
|
|
490
|
-
{#each wires as wire, i (i)}
|
|
491
|
-
<path
|
|
492
|
-
d={wire.d}
|
|
493
|
-
class:back={wire.back}
|
|
494
|
-
class:lit={wire.lit}
|
|
495
|
-
marker-end={wire.arrow ? 'url(#atlas-arrow)' : undefined}
|
|
496
|
-
/>
|
|
497
|
-
{/each}
|
|
498
|
-
|
|
499
|
-
{#each labels as label, i (i)}
|
|
500
|
-
<text
|
|
501
|
-
class="wire-label"
|
|
502
|
-
class:lit={label.lit}
|
|
503
|
-
x={label.x}
|
|
504
|
-
y={label.y}
|
|
505
|
-
transform={label.rotate ? `rotate(-90 ${label.x} ${label.y})` : undefined}
|
|
506
|
-
text-anchor="middle"
|
|
507
|
-
dominant-baseline="central"
|
|
508
|
-
>
|
|
509
|
-
{label.text}
|
|
510
|
-
</text>
|
|
511
|
-
{/each}
|
|
512
|
-
</svg>
|
|
513
|
-
|
|
514
|
-
<div class="flow">
|
|
515
|
-
{#each rows as group, i (i)}
|
|
516
|
-
<div class="row" data-count={group.length} style="--n: {group.length}">
|
|
517
|
-
{#each group as node (node.id)}
|
|
518
|
-
<TreeNodeCard
|
|
519
|
-
{node}
|
|
520
|
-
selected={selection?.key ?? null}
|
|
521
|
-
onselect={selectTarget}
|
|
522
|
-
onopen={openDoc}
|
|
523
|
-
/>
|
|
524
|
-
{/each}
|
|
525
|
-
</div>
|
|
526
|
-
{/each}
|
|
527
|
-
</div>
|
|
528
|
-
</div>
|
|
121
|
+
<h2 class="tagline">{tree.tagline}</h2>
|
|
122
|
+
|
|
123
|
+
<TreeCanvas {tree} selected={selection?.key ?? null} onselect={selectTarget} onopen={openDoc} />
|
|
529
124
|
</div>
|
|
530
125
|
</section>
|
|
531
126
|
|
|
@@ -548,33 +143,22 @@
|
|
|
548
143
|
</div>
|
|
549
144
|
</section>
|
|
550
145
|
</div>
|
|
551
|
-
|
|
552
|
-
<footer class="statusbar">
|
|
553
|
-
{#if selection}
|
|
554
|
-
<span class="status-label">{selection.label}</span>
|
|
555
|
-
<span class="status-range">
|
|
556
|
-
SKILL.md {selection.lines[0] === selection.lines[1]
|
|
557
|
-
? `line ${selection.lines[0]}`
|
|
558
|
-
: `lines ${selection.lines[0]}–${selection.lines[1]}`}
|
|
559
|
-
</span>
|
|
560
|
-
{:else}
|
|
561
|
-
<span class="status-range">Select a step to map it onto the skill.</span>
|
|
562
|
-
{/if}
|
|
563
|
-
</footer>
|
|
564
146
|
</div>
|
|
565
147
|
|
|
566
148
|
<style>
|
|
567
149
|
/* A two-pane tool rather than a reading column. Nothing here sits at a page
|
|
568
|
-
column position, so the grid would only be capping the working surface
|
|
569
|
-
|
|
570
|
-
.atlas {
|
|
150
|
+
column position, so the grid would only be capping the working surface. */
|
|
151
|
+
.skill-atlas {
|
|
571
152
|
display: flex;
|
|
572
153
|
flex-direction: column;
|
|
573
154
|
padding: var(--space-48) var(--space-32) var(--space-32);
|
|
574
155
|
}
|
|
575
156
|
|
|
576
157
|
.masthead {
|
|
577
|
-
|
|
158
|
+
/* Replaces the 0.67em the user agent put on the h1, which the row's own
|
|
159
|
+
spacing now carries so the heading can sit at margin zero. */
|
|
160
|
+
margin-top: var(--space-24);
|
|
161
|
+
margin-bottom: var(--space-32);
|
|
578
162
|
}
|
|
579
163
|
|
|
580
164
|
.masthead-top {
|
|
@@ -584,9 +168,9 @@
|
|
|
584
168
|
and would cover a button parked there. */
|
|
585
169
|
gap: var(--space-24);
|
|
586
170
|
flex-wrap: wrap;
|
|
587
|
-
/* The row carries the heading's own
|
|
588
|
-
|
|
589
|
-
|
|
171
|
+
/* The row carries the heading's own margins; align-items centres the margin
|
|
172
|
+
box, so a margin left on the h1 offsets the title against the button by
|
|
173
|
+
half of it. */
|
|
590
174
|
}
|
|
591
175
|
|
|
592
176
|
.masthead-top h1 {
|
|
@@ -596,21 +180,20 @@
|
|
|
596
180
|
line-height: var(--heading-xl-line-height);
|
|
597
181
|
letter-spacing: var(--heading-xl-letter-spacing);
|
|
598
182
|
color: var(--text-primary);
|
|
599
|
-
margin
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
.standfirst {
|
|
603
|
-
max-width: 68ch;
|
|
604
|
-
margin: 0 0 var(--space-32);
|
|
605
|
-
font-family: var(--body-md-font-family);
|
|
606
|
-
font-size: var(--body-md-font-size);
|
|
607
|
-
font-weight: var(--body-md-font-weight);
|
|
608
|
-
line-height: var(--body-md-line-height);
|
|
609
|
-
letter-spacing: var(--body-md-letter-spacing);
|
|
610
|
-
color: var(--text-secondary);
|
|
183
|
+
margin: 0;
|
|
611
184
|
}
|
|
612
185
|
|
|
186
|
+
/* Below the desktop lock the page scrolls, and the strip pins at the top;
|
|
187
|
+
it sits outside the masthead so the title can scroll away without it. The
|
|
188
|
+
gap below is padding: a margin would leave a slit the panes show through
|
|
189
|
+
once the strip is pinned. */
|
|
613
190
|
.tabs {
|
|
191
|
+
position: sticky;
|
|
192
|
+
top: 0;
|
|
193
|
+
z-index: 1;
|
|
194
|
+
padding-bottom: var(--space-40);
|
|
195
|
+
background: var(--page-bg);
|
|
196
|
+
background-attachment: var(--page-bg-attachment, fixed);
|
|
614
197
|
overflow-x: auto;
|
|
615
198
|
}
|
|
616
199
|
|
|
@@ -627,13 +210,6 @@
|
|
|
627
210
|
gap: var(--space-32);
|
|
628
211
|
}
|
|
629
212
|
|
|
630
|
-
@media (min-width: 64rem) {
|
|
631
|
-
.split {
|
|
632
|
-
grid-template-columns: 3fr 2fr;
|
|
633
|
-
align-items: start;
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
|
|
637
213
|
/* The reference tabs only exist on the source pane, so a body of its own
|
|
638
214
|
fixed height would leave the two panes ending at different places. */
|
|
639
215
|
.pane {
|
|
@@ -700,132 +276,58 @@
|
|
|
700
276
|
scroll-behavior: smooth;
|
|
701
277
|
}
|
|
702
278
|
|
|
703
|
-
.
|
|
279
|
+
.tagline {
|
|
280
|
+
text-align: center;
|
|
281
|
+
margin: 0;
|
|
704
282
|
padding: var(--space-24) var(--space-24) 0;
|
|
283
|
+
font-family: var(--heading-xl-font-family);
|
|
284
|
+
font-size: var(--heading-xl-font-size);
|
|
285
|
+
font-weight: var(--heading-xl-font-weight);
|
|
286
|
+
line-height: var(--heading-xl-line-height);
|
|
287
|
+
letter-spacing: var(--heading-xl-letter-spacing);
|
|
288
|
+
color: var(--text-primary);
|
|
705
289
|
}
|
|
706
290
|
|
|
707
|
-
/*
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
.wires > path {
|
|
723
|
-
fill: none;
|
|
724
|
-
stroke: var(--border-neutral-medium);
|
|
725
|
-
stroke-width: var(--border-width-2);
|
|
726
|
-
stroke-linecap: round;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
.wires > path.lit {
|
|
730
|
-
stroke: var(--text-accent);
|
|
731
|
-
stroke-width: var(--border-width-3);
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
/* A re-run loop is a route, not a failure — danger red overstated it. */
|
|
735
|
-
.wires > path.back {
|
|
736
|
-
stroke: var(--border-accent-medium);
|
|
737
|
-
stroke-dasharray: 6 8;
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
.wires > path.back.lit {
|
|
741
|
-
stroke: var(--text-accent);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
.wires marker path {
|
|
745
|
-
fill: context-stroke;
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
/* The halo breaks the wire so the answer reads on its own ground. */
|
|
749
|
-
.wire-label {
|
|
750
|
-
font-family: var(--code-font-family);
|
|
751
|
-
font-size: var(--font-size-sm);
|
|
752
|
-
fill: var(--text-tertiary);
|
|
753
|
-
stroke: var(--surface-neutral-lowest);
|
|
754
|
-
stroke-width: calc(var(--border-width-2) * 3);
|
|
755
|
-
stroke-linejoin: round;
|
|
756
|
-
paint-order: stroke;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
.wire-label.lit {
|
|
760
|
-
fill: var(--text-accent);
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
.flow {
|
|
764
|
-
/* Nearly every row holds one node, so the pane's full width would only
|
|
765
|
-
stretch a card that has nothing to say at its right edge. Capping the
|
|
766
|
-
track turns the column into a spine and gives the wires somewhere to
|
|
767
|
-
travel. */
|
|
768
|
-
--node-w: 30rem;
|
|
769
|
-
|
|
770
|
-
position: relative;
|
|
771
|
-
display: flex;
|
|
772
|
-
flex-direction: column;
|
|
773
|
-
gap: var(--space-40);
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
/* Sibling branch rows share a column count so a family and its follow-up
|
|
777
|
-
question stay in the same column when they wrap. */
|
|
778
|
-
.row {
|
|
779
|
-
/* A fan only earns a trunk-and-bus while it fits on one line, so branches
|
|
780
|
-
narrow enough to stay unwrapped are worth more than uniform columns. */
|
|
781
|
-
--track: 9rem;
|
|
782
|
-
|
|
783
|
-
display: grid;
|
|
784
|
-
grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--track)), 1fr));
|
|
785
|
-
gap: var(--space-24);
|
|
786
|
-
align-items: start;
|
|
787
|
-
/* The auto margins that centre the row also switch off the flex stretch,
|
|
788
|
-
so the width has to be asked for rather than inherited. */
|
|
789
|
-
width: 100%;
|
|
790
|
-
max-width: calc(var(--n) * var(--node-w) + (var(--n) - 1) * var(--space-24));
|
|
791
|
-
margin-inline: auto;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
/* A flat step would spend the same gap on "next" as on "the tree splits
|
|
795
|
-
here". The extra margin lets a branch announce itself before it is read. */
|
|
796
|
-
.row:not([data-count='1']) {
|
|
797
|
-
margin-block: var(--space-48);
|
|
798
|
-
}
|
|
291
|
+
/* Desktop: the surface locks to the viewport and each pane scrolls on its
|
|
292
|
+
own, so nothing ever slides under the tab strip. Placed after the base
|
|
293
|
+
.pane rule: its 82vh would otherwise win on source order. The router wrapper
|
|
294
|
+
assumes window-scroll pages (min-height + a 12rem bottom pad); the doubled
|
|
295
|
+
.lt-app outranks its scoped rule on a specificity tie. The child
|
|
296
|
+
combinator keeps the demo home out: it has an .atlas of its own. */
|
|
297
|
+
@media (min-width: 64rem) {
|
|
298
|
+
:global(.lt-app.lt-app:has(> .skill-atlas)) {
|
|
299
|
+
height: 100vh;
|
|
300
|
+
min-height: 0;
|
|
301
|
+
padding-bottom: 0;
|
|
302
|
+
overflow: hidden;
|
|
303
|
+
}
|
|
799
304
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
305
|
+
/* border-box: with no global reset the frame is content-box, so its own
|
|
306
|
+
padding would push the panes' bottom edge past the wrapper's clip. */
|
|
307
|
+
.skill-atlas {
|
|
308
|
+
box-sizing: border-box;
|
|
309
|
+
height: 100%;
|
|
310
|
+
overflow: hidden;
|
|
311
|
+
}
|
|
807
312
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
flex-wrap: wrap;
|
|
813
|
-
margin-top: var(--space-24);
|
|
814
|
-
padding-top: var(--space-16);
|
|
815
|
-
border-top: var(--border-width-1) solid var(--border-neutral-faint);
|
|
816
|
-
}
|
|
313
|
+
.masthead,
|
|
314
|
+
.tabs {
|
|
315
|
+
flex: 0 0 auto;
|
|
316
|
+
}
|
|
817
317
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
318
|
+
/* minmax(0, 1fr) pins the row to the remaining height; an auto row would
|
|
319
|
+
grow to the panes' content and break their scroll. */
|
|
320
|
+
.split {
|
|
321
|
+
flex: 1 1 auto;
|
|
322
|
+
min-height: 0;
|
|
323
|
+
grid-template-columns: 3fr 2fr;
|
|
324
|
+
grid-template-rows: minmax(0, 1fr);
|
|
325
|
+
}
|
|
824
326
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
327
|
+
.pane {
|
|
328
|
+
box-sizing: border-box;
|
|
329
|
+
height: 100%;
|
|
330
|
+
}
|
|
829
331
|
}
|
|
830
332
|
|
|
831
333
|
@media (prefers-reduced-motion: reduce) {
|