afterimage-cli 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +2 -3
- package/agents/cursor/afterimage.mdc +5 -7
- package/agents/skill/SKILL.md +5 -7
- package/package.json +1 -1
- package/src/cli/commands/init.js +51 -3
- package/src/runtime/overlay.js +46 -17
- package/viewer-dist/assets/index-BIqvOv5V.css +1 -0
- package/viewer-dist/assets/index-otlBd-9Q.js +9 -0
- package/viewer-dist/index.html +2 -2
- package/viewer-dist/assets/index-DSNYkIru.css +0 -1
- package/viewer-dist/assets/index-IhpNLW4o.js +0 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.4
|
|
4
|
+
|
|
5
|
+
- **One command to install.** `npx afterimage-cli init` now does the whole setup: init installs afterimage-cli in the project as a dev dependency, at its own version, with the package manager your lockfile names (npm, pnpm, Yarn or Bun). A plain HTML folder no longer needs `npm init -y` first, since init creates `package.json` before the install. A project that already lists afterimage-cli installs nothing, and a failed install leaves the rest of the setup in place and prints the command to finish it. The setup prompt and the skill use the one command too.
|
|
6
|
+
|
|
7
|
+
## 0.2.3
|
|
8
|
+
|
|
9
|
+
- **Scroll sync covers panels.** In an app that scrolls a panel instead of the page, Scroll synced in side by side, the wipe and holding B only moved the page, which never scrolls, so the two versions drifted apart. Now each panel follows the same panel in the other version, even when the page around it changed between versions.
|
|
10
|
+
- **Switching version crossfades in place.** The frame used to go dark, then blank, then draw the next version, and it jumped back to the top. Now the next version loads out of sight, lines up with where you were scrolled, page and panels, and fades in over the old one.
|
|
11
|
+
- **Wipe and side by side glide into each other.** Each half of the wipe slides into its own column and back, without frames covering each other or going blank on the way. The change check that used to start mid-glide and freeze it now waits until the frames land.
|
|
12
|
+
- **The pill hands over smoothly both ways.** Highlight and Compare now start on screen, after the viewer has taken the page's place, so the outlines fade in and the wipe sweeps in from the edge. Turning them off plays out before the page comes back, on the same route and scroll, panels included. The page's pill and the viewer's blend in place instead of swapping.
|
|
13
|
+
- **The pill changes shape instead of jumping.** Entering or leaving Compare, its segments glide to their new width and their content fades in.
|
|
14
|
+
|
|
3
15
|
## 0.2.2
|
|
4
16
|
|
|
5
17
|
- **Apps that scroll a panel instead of the page.** Many app shells keep the page still and scroll the content inside it. There, outlines stayed where they were drawn while the content scrolled away under them, and stepping to a change scrolled nothing. Now outlines follow their elements as the panel scrolls and stop at its edges, and stepping scrolls each panel that holds the change, so it lands in the middle. Outlines also no longer make a page like that scroll on its own.
|
package/README.md
CHANGED
|
@@ -11,11 +11,10 @@ Afterimage keeps every version of your prototype you save and serves them all fr
|
|
|
11
11
|
Afterimage needs Node 22 or later. From your prototype's project root:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
|
|
15
|
-
npx afterimage init
|
|
14
|
+
npx afterimage-cli init
|
|
16
15
|
```
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
npx asks once to fetch the package. `init` installs it in the project as a dev dependency, with the package manager your lockfile names, and creates `package.json` first for a plain HTML folder that has none.
|
|
19
18
|
|
|
20
19
|
`init` works out what your prototype is: a plain HTML folder, a Vite app, a Next.js static export, an Astro site, or SvelteKit with `@sveltejs/adapter-static`. It puts the Afterimage pill on your prototype and makes `npm run dev` start Afterimage beside your app. It also writes `afterimage.config.json`, lines in `.gitattributes` and `.gitignore`, and host config for Vercel and Netlify, then lists every file it changed. For Next.js, Astro and SvelteKit it prints the one import that adds the pill. When it can't tell which folder holds the prototype, it asks. You can also tell it:
|
|
21
20
|
|
|
@@ -13,13 +13,11 @@ The user works on the prototype, not in a terminal. Do the steps yourself and re
|
|
|
13
13
|
|
|
14
14
|
When the user asks to add Afterimage, or to track the prototype's versions, do all of this:
|
|
15
15
|
|
|
16
|
-
1.
|
|
17
|
-
2.
|
|
18
|
-
3.
|
|
19
|
-
4.
|
|
20
|
-
5.
|
|
21
|
-
6. Start `npm run dev` as a background process. If you started the project's dev server earlier in this session, stop that one first: this is the same server, with Afterimage beside it. Wait for the line that starts with `Afterimage is running at`.
|
|
22
|
-
7. Tell the user where the prototype is running and that the pill at its bottom saves a version and shows them all. The address is the app dev server's own (such as Vite's `Local:` line), or the one after `Your prototype is at` for plain HTML.
|
|
16
|
+
1. Run `npx --yes afterimage-cli init --agent claude --no-input` if you are Claude Code, or `npx --yes afterimage-cli init --agent cursor --no-input` if you are Cursor. It installs afterimage-cli in the project unless `package.json` already lists it, and creates `package.json` when there is none. Handle what it prints as "Set up" below says.
|
|
17
|
+
2. Init puts the pill on plain HTML pages and in a Vite app's `index.html` itself. For Next.js, Astro and SvelteKit it prints a note that starts with `Add the Afterimage pill to your app`. Make that change yourself, exactly as the note says.
|
|
18
|
+
3. Unless `afterimage/versions.json` already lists a version, run `npx afterimage save "First version"`.
|
|
19
|
+
4. Start `npm run dev` as a background process. If you started the project's dev server earlier in this session, stop that one first: this is the same server, with Afterimage beside it. Wait for the line that starts with `Afterimage is running at`.
|
|
20
|
+
5. Tell the user where the prototype is running and that the pill at its bottom saves a version and shows them all. The address is the app dev server's own (such as Vite's `Local:` line), or the one after `Your prototype is at` for plain HTML.
|
|
23
21
|
|
|
24
22
|
## Commands
|
|
25
23
|
|
package/agents/skill/SKILL.md
CHANGED
|
@@ -13,13 +13,11 @@ The user works on the prototype, not in a terminal. Do the steps yourself and re
|
|
|
13
13
|
|
|
14
14
|
When the user asks to add Afterimage, or to track the prototype's versions, do all of this:
|
|
15
15
|
|
|
16
|
-
1.
|
|
17
|
-
2.
|
|
18
|
-
3.
|
|
19
|
-
4.
|
|
20
|
-
5.
|
|
21
|
-
6. Start `npm run dev` as a background process. If you started the project's dev server earlier in this session, stop that one first: this is the same server, with Afterimage beside it. Wait for the line that starts with `Afterimage is running at`.
|
|
22
|
-
7. Tell the user where the prototype is running and that the pill at its bottom saves a version and shows them all. The address is the app dev server's own (such as Vite's `Local:` line), or the one after `Your prototype is at` for plain HTML.
|
|
16
|
+
1. Run `npx --yes afterimage-cli init --agent claude --no-input` if you are Claude Code, or `npx --yes afterimage-cli init --agent cursor --no-input` if you are Cursor. It installs afterimage-cli in the project unless `package.json` already lists it, and creates `package.json` when there is none. Handle what it prints as "Set up" below says.
|
|
17
|
+
2. Init puts the pill on plain HTML pages and in a Vite app's `index.html` itself. For Next.js, Astro and SvelteKit it prints a note that starts with `Add the Afterimage pill to your app`. Make that change yourself, exactly as the note says.
|
|
18
|
+
3. Unless `afterimage/versions.json` already lists a version, run `npx afterimage save "First version"`.
|
|
19
|
+
4. Start `npm run dev` as a background process. If you started the project's dev server earlier in this session, stop that one first: this is the same server, with Afterimage beside it. Wait for the line that starts with `Afterimage is running at`.
|
|
20
|
+
5. Tell the user where the prototype is running and that the pill at its bottom saves a version and shows them all. The address is the app dev server's own (such as Vite's `Local:` line), or the one after `Your prototype is at` for plain HTML.
|
|
23
21
|
|
|
24
22
|
## Commands
|
|
25
23
|
|
package/package.json
CHANGED
package/src/cli/commands/init.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
1
2
|
import fs from 'node:fs/promises';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import readline from 'node:readline/promises';
|
|
@@ -349,6 +350,52 @@ async function planAgent(cwd, name, { isTTY, ask }) {
|
|
|
349
350
|
};
|
|
350
351
|
}
|
|
351
352
|
|
|
353
|
+
// "npx afterimage-cli init" runs from npx's cache, so the project gets the package too, at the version that is
|
|
354
|
+
// running: npm run dev, save and build all run it from node_modules. It goes in with the package manager the project's
|
|
355
|
+
// lockfile names, after every other step, since the install writes package.json too.
|
|
356
|
+
const { version } = JSON.parse(await fs.readFile(new URL('../../../package.json', import.meta.url), 'utf8'));
|
|
357
|
+
const installers = [['pnpm-lock.yaml', 'pnpm add -D'], ['yarn.lock', 'yarn add -D'], ['bun.lock', 'bun add -d'], ['bun.lockb', 'bun add -d']];
|
|
358
|
+
|
|
359
|
+
// The package manager's own output only shows when the install fails, as its last lines.
|
|
360
|
+
function installInTerminal(command, cwd) {
|
|
361
|
+
return new Promise((resolve, reject) => {
|
|
362
|
+
const child = spawn(command, { shell: true, cwd, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
363
|
+
const output = [];
|
|
364
|
+
child.stdout.on('data', chunk => output.push(chunk));
|
|
365
|
+
child.stderr.on('data', chunk => output.push(chunk));
|
|
366
|
+
child.on('error', error => reject(new Error(`The install could not start: ${error.message}.`)));
|
|
367
|
+
child.on('close', code => {
|
|
368
|
+
if (code === 0) return resolve();
|
|
369
|
+
const said = Buffer.concat(output).toString().trim().split(/\r?\n/).slice(-12).join('\n');
|
|
370
|
+
reject(new Error(`${said ? `${said}\n` : ''}The install failed (exit code ${code}).`));
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async function planInstall(cwd, { manifest }, { stdout, install }) {
|
|
376
|
+
if ('afterimage-cli' in { ...manifest.dependencies, ...manifest.devDependencies }) return { change: null, notes: [], apply: null };
|
|
377
|
+
let installer = 'npm install -D';
|
|
378
|
+
for (const [lockfile, command] of installers) {
|
|
379
|
+
if (await readText(path.join(cwd, lockfile)) !== null) {
|
|
380
|
+
installer = command;
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const command = `${installer} afterimage-cli@${version}`;
|
|
385
|
+
return {
|
|
386
|
+
change: `Installed afterimage-cli ${version} as a dev dependency`,
|
|
387
|
+
notes: [],
|
|
388
|
+
apply: async () => {
|
|
389
|
+
stdout.write(`Installing afterimage-cli ${version}...\n`);
|
|
390
|
+
try {
|
|
391
|
+
await install(command, cwd);
|
|
392
|
+
} catch (error) {
|
|
393
|
+
throw new Error(`${error.message}\nEverything else is set up. Install Afterimage yourself to finish: ${command}`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
352
399
|
export async function run(args, ctx) {
|
|
353
400
|
const { cwd, stdout, stderr } = ctx;
|
|
354
401
|
|
|
@@ -381,7 +428,7 @@ export async function run(args, ctx) {
|
|
|
381
428
|
|
|
382
429
|
// Plan first, write after. package.json is read before the config step so a bad one stops init before any question,
|
|
383
430
|
// and detection (which reads package.json) still sees the project as it was.
|
|
384
|
-
let plans;
|
|
431
|
+
let plans, agentPlan;
|
|
385
432
|
try {
|
|
386
433
|
const packageFile = await readPackage(cwd);
|
|
387
434
|
const configPlan = await planConfig(cwd, flags, prompt);
|
|
@@ -394,7 +441,8 @@ export async function run(args, ctx) {
|
|
|
394
441
|
await planLines(cwd, '.gitignore', ['.afterimage/', `${configPlan.config.history}/.afterimage-*`, 'node_modules/'], hasIgnore),
|
|
395
442
|
await planVercel(cwd, prompt),
|
|
396
443
|
await planNetlify(cwd),
|
|
397
|
-
await planAgent(cwd, agent, prompt)
|
|
444
|
+
agentPlan = await planAgent(cwd, agent, prompt),
|
|
445
|
+
await planInstall(cwd, packageFile, { stdout, install: ctx.install ?? installInTerminal })
|
|
398
446
|
];
|
|
399
447
|
} catch (error) {
|
|
400
448
|
// Ctrl-C at a prompt. Nothing has been written yet, so stopping here is clean. 130 is the shell's code for SIGINT.
|
|
@@ -411,7 +459,7 @@ export async function run(args, ctx) {
|
|
|
411
459
|
else stdout.write(notes.length ? 'No files changed. Still left for you:\n' : 'Everything is already set up. Nothing changed.\n');
|
|
412
460
|
if (notes.length) stdout.write(`${changes.length ? '\n' : ''}${notes.join('\n\n')}\n`);
|
|
413
461
|
// With --agent the copy is in place, and a fresh one needs loading; without it, say how to add it.
|
|
414
|
-
const agentLine = agent ?
|
|
462
|
+
const agentLine = agent ? agentPlan.change && agents[agent].loads : skillHint;
|
|
415
463
|
const dev = (await readPackage(cwd)).manifest.scripts?.dev;
|
|
416
464
|
stdout.write(`\n${agentLine ? `${agentLine}\n` : ''}${nextStep(typeof dev === 'string' && dev.trim().startsWith(devWrap))}\n`);
|
|
417
465
|
return 0;
|
package/src/runtime/overlay.js
CHANGED
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
const inDev = local && (scriptTag ? servedByDev : mode !== 'production');
|
|
40
40
|
const helperPorts = Array.from({ length: 10 }, (_, i) => 4321 + i);
|
|
41
41
|
const OPEN_TIMEOUT_MS = 12000;
|
|
42
|
+
const LAYER_FADE_MS = 140; // .layer's fade in; the page's pill stays up under it until it's done
|
|
42
43
|
|
|
43
44
|
const getJson = async (url, timeout = 1500) => {
|
|
44
45
|
try {
|
|
@@ -115,11 +116,12 @@
|
|
|
115
116
|
vertical-align: 1px; animation: pulse 1s ease-in-out infinite alternate; }
|
|
116
117
|
@keyframes pulse { from { opacity: 0.35; } to { opacity: 1; } }
|
|
117
118
|
/* The viewer waits here, laid out but unseen, until it shows what was asked for. Not display:none, so it loads. */
|
|
118
|
-
|
|
119
|
-
.layer
|
|
119
|
+
/* Under the pill the two are the same picture, so the short fade only blends the page's pill into the viewer's. */
|
|
120
|
+
.layer { position: fixed; inset: 0; z-index: 2147483647; opacity: 0; pointer-events: none; transition: opacity 110ms cubic-bezier(0.22, 1, 0.36, 1); }
|
|
121
|
+
.layer[data-shown] { opacity: 1; pointer-events: auto; transition-duration: 140ms; }
|
|
120
122
|
.layer iframe { display: block; width: 100%; height: 100%; border: 0; background: transparent; }
|
|
121
123
|
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
122
|
-
@media (prefers-reduced-motion: reduce) { button { transition: none; } .busy, button[aria-busy='true']::before { animation: none; } }
|
|
124
|
+
@media (prefers-reduced-motion: reduce) { button, .layer { transition: none; } .busy, button[aria-busy='true']::before { animation: none; } }
|
|
123
125
|
`;
|
|
124
126
|
|
|
125
127
|
const host = document.createElement('afterimage-overlay');
|
|
@@ -249,10 +251,21 @@
|
|
|
249
251
|
viewer = { el, frame, origin: new URL(state.base).origin, versions: state.versions, listening: false, asked: null, shown: false };
|
|
250
252
|
}
|
|
251
253
|
|
|
254
|
+
// The panels that scroll on this page (an app shell's content), in page order, and where each is. The viewer reads
|
|
255
|
+
// its frame the same way (stage/scrollSync.js), so the two open and close on the same rows.
|
|
256
|
+
const scrollingPanels = () => [...document.querySelectorAll('*')].filter(element => element.scrollHeight > element.clientHeight
|
|
257
|
+
&& /^(auto|scroll|overlay)$/.test(getComputedStyle(element).overflowY));
|
|
258
|
+
const panelTops = () => scrollingPanels().map(panel => Math.round(panel.scrollTop));
|
|
259
|
+
function setPanelTops(tops) {
|
|
260
|
+
if (!Array.isArray(tops)) return;
|
|
261
|
+
scrollingPanels().forEach((panel, index) => { if (Number.isFinite(tops[index])) panel.scrollTop = tops[index]; });
|
|
262
|
+
}
|
|
263
|
+
const afterFrames = (count, fn) => requestAnimationFrame(() => (count > 1 ? afterFrames(count - 1, fn) : fn()));
|
|
264
|
+
|
|
252
265
|
// A click can come before the viewer has started; then it is asked as soon as it says it is listening.
|
|
253
266
|
function ask() {
|
|
254
267
|
if (!viewer?.listening || !viewer.asked) return;
|
|
255
|
-
viewer.frame.contentWindow.postMessage({ type: 'afterimage:open', view: viewer.asked.view, route: location.hash, scrollY: window.scrollY }, viewer.origin);
|
|
268
|
+
viewer.frame.contentWindow.postMessage({ type: 'afterimage:open', view: viewer.asked.view, route: location.hash, scrollY: window.scrollY, panels: panelTops() }, viewer.origin);
|
|
256
269
|
}
|
|
257
270
|
|
|
258
271
|
function open(name, event) {
|
|
@@ -289,28 +302,44 @@
|
|
|
289
302
|
el.setAttribute('aria-modal', 'true');
|
|
290
303
|
el.setAttribute('aria-label', 'Version history');
|
|
291
304
|
el.setAttribute('data-shown', '');
|
|
292
|
-
|
|
293
|
-
document.documentElement.style.setProperty('overflow', 'hidden');
|
|
305
|
+
const fade = matchMedia('(prefers-reduced-motion: reduce)').matches ? 0 : LAYER_FADE_MS;
|
|
294
306
|
viewer.frame.focus();
|
|
307
|
+
// Once the swap is done, the viewer moves into what was asked for, where it can be seen doing it. One thing at
|
|
308
|
+
// a time: the two pills blend in place first, then the viewer's changes shape.
|
|
309
|
+
const { frame, origin } = viewer;
|
|
310
|
+
afterFrames(1, () => setTimeout(() => {
|
|
311
|
+
if (!viewer?.shown) return;
|
|
312
|
+
dock.hidden = true;
|
|
313
|
+
// Only now, under an opaque viewer: dropping the page's scrollbar moves its layout.
|
|
314
|
+
document.documentElement.style.setProperty('overflow', 'hidden');
|
|
315
|
+
frame.contentWindow?.postMessage({ type: 'afterimage:shown' }, origin);
|
|
316
|
+
}, fade));
|
|
295
317
|
}
|
|
296
318
|
|
|
297
|
-
// The viewer stays loaded for next time. The page takes the route and scroll the visitor left the viewer on
|
|
298
|
-
|
|
319
|
+
// The viewer stays loaded for next time. The page takes the route and scroll the visitor left the viewer on, and
|
|
320
|
+
// settles there under the viewer; then the viewer goes. The two look the same by then, so the swap doesn't show.
|
|
321
|
+
function hide({ route, scrollY, panels } = {}) {
|
|
299
322
|
if (!viewer?.shown) return;
|
|
300
323
|
viewer.shown = false;
|
|
301
324
|
const { el } = viewer;
|
|
302
|
-
el.removeAttribute('data-shown');
|
|
303
|
-
el.removeAttribute('role');
|
|
304
|
-
el.removeAttribute('aria-modal');
|
|
305
|
-
el.setAttribute('aria-hidden', 'true');
|
|
306
325
|
el.inert = true;
|
|
307
|
-
dock.hidden = false;
|
|
308
326
|
document.documentElement.style.removeProperty('overflow');
|
|
309
327
|
if (typeof route === 'string' && route !== location.hash && (route || location.hash)) location.hash = route;
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
328
|
+
afterFrames(2, () => {
|
|
329
|
+
if (typeof scrollY === 'number') window.scrollTo(0, scrollY);
|
|
330
|
+
setPanelTops(panels);
|
|
331
|
+
afterFrames(1, () => {
|
|
332
|
+
if (viewer?.shown) return; // opened again meanwhile
|
|
333
|
+
el.removeAttribute('data-shown');
|
|
334
|
+
el.removeAttribute('role');
|
|
335
|
+
el.removeAttribute('aria-modal');
|
|
336
|
+
el.setAttribute('aria-hidden', 'true');
|
|
337
|
+
dock.hidden = false;
|
|
338
|
+
returnFocus?.focus?.({ preventScroll: true });
|
|
339
|
+
// A save the agent made from the terminal meanwhile shows up in the count.
|
|
340
|
+
refresh();
|
|
341
|
+
});
|
|
342
|
+
});
|
|
314
343
|
}
|
|
315
344
|
|
|
316
345
|
window.addEventListener('keydown', event => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.stage{--label-lift:0px;background:var(--surface);width:100%;height:100%;position:relative;overflow:hidden}.frame-col{pointer-events:none;width:100%;height:100%;position:absolute;top:0;left:0;overflow:hidden}.frame-col[data-role=base]{z-index:1}.frame-box{transform-origin:0 0;pointer-events:none;width:100%;height:100%;position:absolute;top:0;left:0}.stage .frame{pointer-events:auto;position:absolute;inset:0}.stage .frame[data-visible=false]{visibility:hidden}.stage .frame-cover{opacity:0;pointer-events:none}.frame-box[data-gliding]{will-change:transform}.stage[data-dragging]{cursor:ew-resize}.stage[data-dragging] .frame{pointer-events:none}.wipe-divider{z-index:3;cursor:ew-resize;touch-action:none;width:9px;margin-left:-4.5px;position:absolute;top:0;bottom:0}.wipe-line{background:var(--surface);width:1px;position:absolute;top:0;bottom:0;left:4px;box-shadow:0 0 0 .5px #ffffff1f}.wipe-grip{background:var(--surface);width:26px;height:40px;color:var(--muted);box-shadow:0 0 0 1px var(--hairline), 0 2px 8px #0000004d;border-radius:8px;outline:none;place-items:center;transition:color .15s,background-color .15s;display:grid;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.wipe-grip:focus-visible{box-shadow:0 0 0 2px var(--ring), 0 2px 8px #0000004d}.wipe-divider[data-dragging] .wipe-grip{background:var(--raised);color:var(--text)}@media (hover:hover){.wipe-divider:hover .wipe-grip{color:var(--text)}}.compare-label{z-index:2;background:var(--surface);height:28px;color:var(--text);box-shadow:0 0 0 1px var(--hairline), 0 1px 2px #0003, 0 8px 24px #0004;pointer-events:none;white-space:nowrap;border-radius:8px;align-items:center;gap:6px;padding:0 10px;font-size:12px;display:inline-flex;position:absolute}.compare-label .num{font-size:12px}.compare-label-kind{color:var(--muted)}.side-tools{z-index:4;gap:6px;display:flex;position:absolute;top:5px;left:50%;translate:-50%}.zoom-toggle{background:var(--surface);box-shadow:0 0 0 1px var(--hairline), 0 1px 2px #0003, 0 8px 24px #0004;border-radius:999px;padding:2px;display:flex}.zoom-opt{appearance:none;height:24px;color:var(--muted);font:inherit;font-variant-numeric:tabular-nums;white-space:nowrap;cursor:pointer;touch-action:manipulation;background:0 0;border:0;border-radius:999px;align-items:center;margin:0;padding:0 9px;font-size:12px;line-height:1;transition:color .15s;display:inline-flex;position:relative}.zoom-opt[data-on]{color:var(--text);cursor:default}.zoom-opt:focus-visible{outline:2px solid var(--ring);outline-offset:-2px}@media (hover:hover){.zoom-opt:not([data-on]):hover{color:var(--text)}}.zoom-indicator{background:var(--raised);border-radius:999px;position:absolute;inset:0}.zoom-opt-label{position:relative}.sync-chip{appearance:none;background:var(--surface);height:28px;color:var(--muted);font:inherit;white-space:nowrap;cursor:pointer;box-shadow:0 0 0 1px var(--hairline), 0 1px 2px #0003, 0 8px 24px #0004;touch-action:manipulation;border:0;border-radius:999px;align-items:center;gap:6px;margin:0;padding:0 11px 0 10px;font-size:12px;transition:color .15s,background-color .15s;display:inline-flex}.sync-chip[data-on]{color:var(--text)}.sync-chip:focus-visible{outline:2px solid var(--ring);outline-offset:2px}@media (hover:hover){.sync-chip:hover{background:var(--raised)}}.sync-dot{width:6px;height:6px;box-shadow:inset 0 0 0 1.5px var(--muted);border-radius:50%;transition:background-color .15s,box-shadow .15s}.sync-chip[data-on] .sync-dot{background:var(--green);box-shadow:inset 0 0 0 1.5px var(--green)}.pan-bar{z-index:3;touch-action:none;outline:none;height:9px;position:absolute}.pan-track,.pan-thumb{border-radius:999px;height:3px;position:absolute;bottom:2px}.pan-track{background:#ffffff0f;left:0;right:0}.pan-thumb{background:var(--faint);transition:background-color .15s}.pan-bar[data-dragging] .pan-thumb{background:var(--text)}.pan-bar:focus-visible .pan-thumb{background:var(--text);box-shadow:0 0 0 2px var(--ring)}@media (hover:hover){.pan-bar:hover .pan-thumb{background:var(--muted)}}.stage[data-layout=side]{--label-lift:60px}.stage-note{z-index:2;background:var(--surface);height:28px;color:var(--muted);box-shadow:0 0 0 1px var(--hairline), 0 1px 2px #0003, 0 8px 24px #0004;pointer-events:none;white-space:nowrap;border-radius:8px;align-items:center;padding:0 12px;font-size:12px;display:inline-flex;position:absolute;bottom:12px;left:12px}.stage-note .num{color:var(--text)}@media (width<=1100px){.stage-note{bottom:72px}.stage{--label-lift:60px}}.pill-dock{z-index:10;pointer-events:none;justify-content:center;padding:0 16px;display:flex;position:fixed;bottom:16px;left:0;right:0}.pill-dock[data-open]:not([data-phone]){justify-content:flex-end}.pill-dock[data-phone]{bottom:calc(12px + env(safe-area-inset-bottom,0px));padding:0 6px}.dock-body{pointer-events:none;justify-content:center;max-width:100%;display:flex;position:relative}.pill-dock[data-phone] .dock-body{width:100%}.pill{background:var(--surface);max-width:100%;color:var(--text);white-space:nowrap;box-shadow:0 0 0 1px var(--hairline), 0 1px 2px #0000004d, 0 10px 28px #00000040;pointer-events:auto;-webkit-user-select:none;user-select:none;border-radius:999px;align-items:center;gap:2px;padding:5px;font-size:13px;line-height:1;display:flex;position:relative}.pill-seg{border-radius:999px;align-items:center;height:34px;display:flex;position:relative}.pill-sep{background:var(--hairline);flex:none;width:1px;height:18px;margin:0 4px}.pill .muted{color:var(--muted)}.pill-icon{flex:none;display:block}.pill-btn{appearance:none;height:34px;color:inherit;font:inherit;white-space:nowrap;cursor:pointer;touch-action:manipulation;-webkit-tap-highlight-color:transparent;background:0 0;border:0;border-radius:999px;align-items:center;gap:8px;margin:0;padding:0 12px;line-height:1;transition:background-color .15s,color .15s,transform .12s cubic-bezier(.22,1,.36,1);display:inline-flex}.pill-btn:disabled{cursor:default}.pill-btn:active:not(:disabled){transform:scale(.97)}.pill-btn:focus-visible,.pill-step:focus-visible{outline:2px solid var(--ring);outline-offset:-2px}@media (hover:hover){.pill-btn:hover:not(:disabled){background:var(--raised)}}.version-seg{gap:2px}.version-btn{padding:0 8px 0 12px}.pill-dot{width:6px;height:6px;box-shadow:inset 0 0 0 1.5px var(--muted);background:0 0;border-radius:50%;flex:none;transition:background-color .15s,box-shadow .15s}.pill-dot[data-latest]{background:var(--green);box-shadow:inset 0 0 0 1.5px var(--green)}.pill-dock[data-phone] .version-btn{padding:0 12px}.version-field-wrap{display:inline-flex}.version-field{height:24px;color:var(--text);caret-color:var(--text);text-align:center;cursor:text;background:#ffffff0d;border:0;border-radius:6px;outline:none;margin:0;padding:0 6px;font-size:12px;transition:background-color .15s,box-shadow .15s,color .15s}.version-field::selection{background:#ededed40}@media (hover:hover){.version-field:hover{background:var(--raised)}}.version-field:focus{background:#0f0f0f;box-shadow:inset 0 0 0 1px #ffffff40}.version-field[data-invalid]{color:var(--red);background:#0f0f0f;box-shadow:inset 0 0 0 1px #e0705fb3}.version-field[data-invalid]::selection{background:#e0705f40}.version-of{height:34px;color:var(--muted);cursor:text;align-items:center;gap:4px;padding:0 12px 0 6px;display:inline-flex}.version-of .num{font-size:12px}.pill-hint{background:var(--surface);height:28px;color:var(--muted);white-space:nowrap;box-shadow:0 0 0 1px var(--hairline), 0 6px 18px #00000040;pointer-events:none;border-radius:8px;align-items:center;gap:6px;padding:0 10px;font-size:12px;display:flex;position:absolute;bottom:calc(100% + 13px);left:0}.pill-hint[data-error]{color:var(--text)}.pill-hint kbd{background:var(--raised);min-width:18px;height:18px;color:var(--text);font:12px/1 var(--font-mono);border-radius:4px;place-items:center;padding:0 4px;display:inline-grid}.pill-hint kbd+kbd{margin-left:-2px}.tick-strip{cursor:pointer;touch-action:none;border-radius:999px;justify-content:center;align-items:center;min-width:32px;height:34px;padding:0 10px;display:flex;position:relative}.tick-strip[data-dragging]{cursor:grabbing}.tick-strip:has(.tick-input:focus-visible){outline:2px solid var(--ring);outline-offset:-2px}.tick-input{opacity:0;pointer-events:none;width:100%;height:100%;margin:0;position:absolute;inset:0}.tick-track{align-items:flex-end;gap:3px;height:14px;display:flex;position:relative}.tick-track[data-fade=start]{-webkit-mask-image:linear-gradient(90deg,#0000,#000 18px);mask-image:linear-gradient(90deg,#0000,#000 18px)}.tick-track[data-fade=end]{-webkit-mask-image:linear-gradient(270deg,#0000,#000 18px);mask-image:linear-gradient(270deg,#0000,#000 18px)}.tick-track[data-fade=both]{-webkit-mask-image:linear-gradient(90deg,#0000,#000 16px calc(100% - 16px),#0000);mask-image:linear-gradient(90deg,#0000,#000 16px calc(100% - 16px),#0000)}.tick-slot{flex:none;width:2px;height:14px;display:block}.tick{transform-origin:50% 100%;background:#ffffff38;border-radius:1px;width:2px;height:14px;transition:transform .14s cubic-bezier(.22,1,.36,1),background-color .14s;display:block;transform:scaleY(.643)}.tick[data-hover]{background:#ffffff80;transform:scaleY(.857)}.tick[data-role=range]{background:#ffffff80}.tick[data-role=base]{width:4px;box-shadow:inset 0 0 0 1px var(--text);background:0 0;margin-left:-1px;transform:scaleY(1)}.tick-strip[data-range] .tick[data-role=base]{cursor:grab}.tick-strip[data-dragging] .tick[data-role=base]{cursor:grabbing}.tick[data-role=selected]{background:var(--text);transform:scaleY(1)}.tick-tip-anchor{pointer-events:none;position:absolute;bottom:calc(100% + 13px);left:10px}.tick-tip{background:var(--surface);white-space:nowrap;max-width:280px;height:28px;box-shadow:0 0 0 1px var(--hairline), 0 6px 18px #00000040;border-radius:8px;align-items:center;gap:6px;padding:0 10px;font-size:12px;display:flex;transform:translate(-50%)}.tick-tip .num{color:var(--muted)}.tick-tip-sep{color:var(--faint)}.tick-tip-name{text-overflow:ellipsis;min-width:0;color:var(--text);overflow:hidden}.highlight-seg{background-color:#0000;transition:background-color .15s;overflow:hidden}.highlight-seg[data-on]{background-color:var(--raised)}.pill-morph{border-radius:999px;flex:none}.morph-content{align-items:center;display:flex}.auto-width-inner{align-items:center;width:max-content;height:34px;display:flex;position:relative}.seg-content{align-items:center;height:34px;display:flex}.pill-count{min-width:20px;height:20px;color:var(--amber);background:#e5a83b26;border-radius:999px;place-items:center;padding:0 6px;font-size:12px;line-height:1;display:inline-grid}.pill-count[data-zero]{color:var(--muted);background:#ffffff10}.pill-dock[data-phone] .highlight-btn{gap:6px}.highlight-seg[data-on] .pill-btn:hover:not(:disabled){background:#ffffff0a}.stepper-label{padding:0 8px 0 12px}.stepper-label .pill-icon{color:var(--amber)}.stepper-text{align-items:baseline;gap:5px;display:inline-flex}.stepper-n{color:var(--amber);text-align:right;font-size:12px}.stepper-n[data-empty]{color:var(--faint)}.stepper-total{font-size:12px}.stepper-filter-dot{background:var(--amber);border-radius:50%;align-self:center;width:6px;height:6px}.stepper-filter-dot[data-kind=added]{background:var(--green)}.stepper-filter-dot[data-kind=removed]{background:var(--red)}@media (width<=760px){.stepper-text[data-filter] .stepper-prefix{display:none}}.stepper-arrows{padding-right:4px;display:flex}.pill-step{appearance:none;width:26px;height:34px;color:var(--muted);cursor:pointer;touch-action:manipulation;-webkit-tap-highlight-color:transparent;background:0 0;border:0;border-radius:7px;place-items:center;margin:0;padding:0;transition:color .15s;display:grid;position:relative}.pill-step:before{content:"";background:0 0;border-radius:6px;transition:background-color .15s,transform .12s cubic-bezier(.22,1,.36,1);position:absolute;inset:5px 1px}.pill-step svg{position:relative}.pill-step:disabled{color:var(--faint);cursor:default}.pill-step:active:not(:disabled):before{background:#ffffff14;transform:scale(.9)}@media (hover:hover){.pill-step:hover:not(:disabled){color:var(--text)}.pill-step:hover:not(:disabled):before{background:#ffffff14}}.swap-seg{display:grid}.swap-seg>.pill-btn{grid-area:1/1;justify-content:center}.swap-seg>[inert]{pointer-events:none}.compare-btn{color:var(--muted)}@media (hover:hover){.compare-btn:hover:not(:disabled){color:var(--text)}}.latest-btn{background:var(--raised);color:var(--text)}@media (hover:hover){.latest-btn:hover{background:#323232}}.compare-versions{gap:0;padding:0 2px 0 8px}.version-field[data-baseline]:not(:focus):not([data-invalid]){box-shadow:inset 0 0 0 1px #ffffff30}.arrow-btn{color:var(--muted);gap:0;padding:0 6px}@media (hover:hover){.arrow-btn:hover:not(:disabled){color:var(--text)}}.pill-dock[data-phone] .version-btn .muted{color:var(--muted)}.pill-dock[data-phone] .version-btn{gap:5px}.compare-seg{gap:2px}.layout-toggle{background:#0f0f0f;border-radius:999px;padding:2px;display:flex}.layout-opt{appearance:none;height:28px;color:var(--muted);font:inherit;white-space:nowrap;cursor:pointer;touch-action:manipulation;-webkit-tap-highlight-color:transparent;background:0 0;border:0;border-radius:999px;align-items:center;margin:0;padding:0 10px;line-height:1;transition:color .15s;display:inline-flex;position:relative}.layout-opt[data-on]{color:var(--text);cursor:default}.layout-opt:focus-visible{outline:2px solid var(--ring);outline-offset:-2px}@media (hover:hover){.layout-opt:not([data-on]):hover{color:var(--text)}}.layout-indicator{background:var(--raised);border-radius:999px;position:absolute;inset:0}.layout-opt-label{position:relative}.close-btn{width:34px;color:var(--muted);justify-content:center;padding:0}@media (hover:hover){.close-btn:hover{color:var(--text)}}.pill-dock[data-retracted]{justify-content:flex-end}.pill-dock[data-phone][data-retracted] .dock-body{width:auto}.retract-btn{color:var(--muted);padding:0 9px}@media (hover:hover){.retract-btn:hover{color:var(--text)}}.chip{appearance:none;height:36px;font:inherit;cursor:pointer;touch-action:manipulation;-webkit-tap-highlight-color:transparent;border:0;gap:8px;padding:0 14px;font-size:13px;transition:background-color .15s}.chip:focus-visible{outline:2px solid var(--ring);outline-offset:2px}@media (hover:hover){.chip:hover{background:var(--raised)}}.chip .num{font-size:12px}.chip-hint{opacity:0;transition:opacity .15s;left:auto;right:0}@media (hover:hover){.chip:hover .chip-hint{opacity:1;transition-delay:.4s}}.chip:focus-visible .chip-hint{opacity:1}@media (prefers-reduced-motion:reduce){.chip-hint{transition:none}}.panel{background:var(--surface);width:300px;max-height:calc(100dvh - 84px);color:var(--text);text-align:left;white-space:normal;box-shadow:0 0 0 1px var(--hairline), 0 1px 2px #0000004d, 0 16px 40px #00000059;transform-origin:100% 100%;pointer-events:auto;border-radius:14px;flex-direction:column;font-size:13px;line-height:1.4;display:flex;position:absolute;bottom:calc(100% + 8px);right:0;overflow:hidden}.panel-scroll{overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:#ffffff26 transparent;flex:auto;min-height:0;overflow-y:auto}.panel .num{font-size:12px}.panel .muted{color:var(--muted)}.panel .amber{color:var(--amber)}:where(.panel) button{appearance:none;color:inherit;font:inherit;cursor:pointer;touch-action:manipulation;-webkit-tap-highlight-color:transparent;background:0 0;border:0;margin:0}:where(.panel) button:focus-visible{outline:2px solid var(--ring);outline-offset:-2px}.sr-only{clip:rect(0 0 0 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.panel-head{padding:10px 8px 12px 14px}.head-top{align-items:flex-start;gap:8px;display:flex}.panel-name{text-wrap:pretty;overflow-wrap:anywhere;-webkit-line-clamp:2;-webkit-box-orient:vertical;flex:1;min-width:0;margin:0;padding-top:5px;font-size:14px;font-weight:600;line-height:1.3;display:-webkit-box;overflow:hidden}.head-icons{flex:none;gap:2px;display:flex}.icon-btn{width:30px;height:30px;color:var(--muted);border-radius:8px;place-items:center;padding:0;transition:background-color .15s,color .15s,transform .12s cubic-bezier(.22,1,.36,1);display:grid;position:relative}.icon-btn[data-done]{color:var(--green)}.icon-btn:active{transform:scale(.94)}@media (hover:hover){.icon-btn:hover{background:var(--raised);color:var(--text)}.icon-btn[data-done]:hover{color:var(--green)}}.icon-swap{place-items:center;display:grid}.panel-meta{color:var(--muted);margin:3px 6px 0 0;font-size:12px;line-height:20px}.tag{height:20px;color:var(--muted);vertical-align:top;background:#ffffff10;border-radius:999px;align-items:center;margin-left:8px;padding:0 7px;font-size:12px;line-height:1;display:inline-flex}.tag[data-latest]{color:var(--green);background:#6fbf7326}.notes{margin:8px 6px 0 0}.note{color:var(--text);text-wrap:pretty;overflow-wrap:anywhere;margin:0;font-size:13px;line-height:1.45}.note+.note{margin-top:6px}.note[data-clamped]{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.text-btn{height:24px;color:var(--muted);align-items:center;gap:4px;margin-top:2px;padding:0;font-size:12px;transition:color .15s;display:inline-flex}@media (hover:hover){.text-btn:hover{color:var(--text)}}.rows,.rows>*+*{border-top:1px solid var(--hairline)}.row{align-items:center;min-height:44px;display:flex}.row-static{padding:12px 14px}.row-main{text-align:left;flex:1;justify-content:space-between;align-items:center;gap:8px;min-width:0;min-height:44px;padding:0 14px;transition:background-color .15s;display:flex}.row-static-main{cursor:default}@media (hover:hover){button.row-main:hover{background:#ffffff08}}.row-split>.row-main{padding-right:6px}.row-text{flex:1;min-width:0;padding:0 14px}.row-chevron{color:var(--muted);flex:none}.small-btn{background:var(--raised);height:28px;color:var(--text);border-radius:8px;flex:none;margin-right:8px;padding:0 10px;transition:background-color .15s,transform .12s cubic-bezier(.22,1,.36,1)}.small-btn:active{transform:scale(.97)}@media (hover:hover){.small-btn:hover{background:#323232}}.switch{flex:none;place-items:center;width:50px;height:44px;padding:0;display:grid}.switch-track{background:#ffffff26;border-radius:999px;width:30px;height:18px;transition:background-color .16s,opacity .16s,transform .12s cubic-bezier(.22,1,.36,1);display:block;position:relative}.switch-thumb{background:var(--text);border-radius:50%;width:14px;height:14px;transition:transform .2s cubic-bezier(.22,1,.36,1);position:absolute;top:2px;left:2px;box-shadow:0 1px 2px #0000004d}.switch[aria-checked=true] .switch-track{background:var(--amber)}.switch[aria-checked=true] .switch-thumb{transform:translate(12px)}.switch[data-dim] .switch-track{opacity:.45}.switch:active .switch-track{transform:scale(.94)}.switch:focus-visible{outline:none}.switch:focus-visible .switch-track{outline:2px solid var(--ring);outline-offset:2px}.folder{background:#161616;overflow:hidden}.folder-inner{padding:6px}.list-note{color:var(--muted);margin:0;padding:10px 8px;font-size:12px}.filters{gap:2px;margin-bottom:6px;display:flex}.filter{height:28px;color:var(--muted);border-radius:7px;flex:auto;padding:0 8px;font-size:12px;transition:color .15s;position:relative}.filter[aria-pressed=true]{color:var(--text)}@media (hover:hover){.filter:hover{color:var(--text)}}.filter-fill{background:var(--raised);border-radius:7px;position:absolute;inset:0}.filter-label{position:relative}.change-list,.version-list{overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:#ffffff26 transparent;position:relative;overflow-y:auto}.change-list{max-height:260px}.change-list:not([data-end]),.version-list:not([data-end]){-webkit-mask-image:linear-gradient(#000 calc(100% - 32px),#0000);mask-image:linear-gradient(#000 calc(100% - 32px),#0000)}.crow{text-align:left;border-radius:8px;grid-template-columns:20px minmax(0,1fr);align-items:start;gap:10px;width:100%;padding:7px 8px;transition:background-color .15s;display:grid}@media (hover:hover){.crow:hover{background:#ffffff08}}.crow[aria-current]{background:var(--raised)}.crow-badge{width:20px;height:20px;color:var(--amber);background:#e5a83b26;border-radius:6px;place-items:center;margin-top:1px;line-height:1;display:grid}.crow[data-kind=added] .crow-badge{color:var(--green);background:#6fbf7326}.crow[data-kind=removed] .crow-badge{color:var(--red);background:#e0705f26}.crow-text{flex-direction:column;gap:1px;min-width:0;display:flex}.crow-what{color:var(--muted);white-space:nowrap;text-overflow:ellipsis;font-size:12px;overflow:hidden}.crow-diff{white-space:nowrap;align-items:baseline;gap:6px;min-width:0;display:flex}.crow-value{text-overflow:ellipsis;flex:0 auto;min-width:2em;overflow:hidden}.crow-value.is-before{color:var(--muted);flex-shrink:3}.crow-arrow{color:var(--faint);flex:none}.crow-swatch{vertical-align:-1px;border-radius:3px;width:10px;height:10px;margin-right:5px;display:inline-block;box-shadow:inset 0 0 0 1px #ffffff2e}.list-more{width:100%;height:32px;color:var(--muted);border-radius:8px;justify-content:center;align-items:center;gap:4px;margin-top:2px;font-size:12px;transition:background-color .15s,color .15s;display:flex}@media (hover:hover){.list-more:hover{color:var(--text);background:#ffffff08}}.search{background:var(--raised);height:32px;color:var(--faint);cursor:text;border-radius:8px;align-items:center;gap:8px;margin-bottom:4px;padding:0 10px;transition:box-shadow .15s,color .15s;display:flex}.search:focus-within{color:var(--muted);box-shadow:inset 0 0 0 1px #ffffff40}.search-input{min-width:0;height:100%;color:var(--text);caret-color:var(--text);font:inherit;background:0 0;border:0;outline:none;flex:1;margin:0;padding:0;font-size:13px}.search-input::placeholder{color:var(--faint)}.search-input::-webkit-search-cancel-button{display:none}.version-list{max-height:286px}.vgroup-label{z-index:1;height:26px;color:var(--faint);background:#161616;margin:0;padding:8px 8px 0;font-size:12px;font-weight:500;line-height:18px;position:sticky;top:0}.vgroup-label.num{font-weight:400}.vrow{text-align:left;border-radius:8px;grid-template-columns:24px minmax(0,1fr) auto 3ch;align-items:center;gap:8px;width:100%;height:32px;padding:0 8px;transition:background-color .15s;display:grid}@media (hover:hover){.vrow:hover{background:#ffffff08}}.vrow[aria-current]{background:var(--raised)}.vrow-n{color:var(--faint)}.vrow[aria-current] .vrow-n{color:var(--text)}.vrow-name{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.vrow-time{color:var(--faint)}.vrow-count{color:var(--amber);text-align:right}.vrow-count[data-zero]{color:var(--faint)}.vrow-count-in{animation:.22s cubic-bezier(.22,1,.36,1) count-in}@keyframes count-in{0%{opacity:0}}.panel[data-phone]{width:auto;max-height:calc(100dvh - 120px - env(safe-area-inset-bottom,0px));transform-origin:50% 100%;border-radius:18px;bottom:calc(100% + 6px);left:0;right:0}.grab{cursor:grab;touch-action:none;flex:none;place-items:center;height:20px;margin-bottom:-6px;display:grid}.grab-bar{background:#fff3;border-radius:2px;width:36px;height:4px}.panel[data-phone] .panel-head{padding-top:6px}.panel[data-phone] .row,.panel[data-phone] .row-main{min-height:48px}.panel[data-phone] .vrow{height:44px}.panel[data-phone] .crow{padding-block:10px}.panel[data-phone] .filter{height:36px}.panel[data-phone] .search{height:40px}.panel[data-phone] .search-input{font-size:16px}.panel[data-phone] .list-more{height:44px}.panel-tip{border-top:1px solid var(--hairline);color:var(--muted);text-wrap:balance;flex:none;margin:0;padding:9px 14px 10px;font-size:12px;line-height:20px}.panel-tip kbd{background:var(--raised);min-width:18px;height:18px;color:var(--text);font:12px/1 var(--font-mono);vertical-align:1px;border-radius:4px;place-items:center;padding:0 4px;display:inline-grid}.panel-tip kbd+kbd{margin-left:2px}.tip-item{white-space:nowrap}.help-scrim{z-index:20;background:#0006;place-items:center;padding:16px;display:grid;position:fixed;inset:0}.help-sheet{background:var(--surface);width:340px;max-width:100%;max-height:100%;color:var(--text);box-shadow:0 0 0 1px var(--hairline), 0 1px 2px #0000004d, 0 16px 40px #00000059;border-radius:14px;flex-direction:column;font-size:13px;line-height:1.4;display:flex;overflow:hidden}.help-head{justify-content:space-between;align-items:center;gap:8px;padding:8px 8px 4px 14px;display:flex}.help-title{margin:0;font-size:14px;font-weight:600;line-height:30px}.help-close{appearance:none;width:30px;height:30px;color:var(--muted);cursor:pointer;touch-action:manipulation;background:0 0;border:0;border-radius:8px;place-items:center;margin:0;padding:0;transition:background-color .15s,color .15s,transform .12s cubic-bezier(.22,1,.36,1);display:grid}.help-close:active{transform:scale(.94)}.help-close:focus-visible{outline:2px solid var(--ring);outline-offset:-2px}@media (hover:hover){.help-close:hover{background:var(--raised);color:var(--text)}}.help-body{overscroll-behavior:contain;padding:4px 14px 14px;overflow-y:auto}.help-group+.help-group{margin-top:12px}.help-group-name{color:var(--faint);margin:0 0 4px;font-size:12px;font-weight:500;line-height:18px}.help-rows{margin:0}.help-row{grid-template-columns:56px 1fr;align-items:baseline;column-gap:8px;padding:4px 0;display:grid}.help-keys{gap:3px;margin:0;display:flex}.help-text{color:var(--text);text-wrap:pretty;margin:0}.help-sheet kbd{background:var(--raised);min-width:20px;height:20px;color:var(--text);font:12px/1 var(--font-mono);border-radius:5px;place-items:center;padding:0 5px;display:inline-grid}:root{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--surface:#1b1b1b;--raised:#2a2a2a;--hairline:#ffffff14;--text:#ededed;--muted:#8e8e8e;--faint:#5c5c5c;--amber:#e5a83b;--green:#6fbf73;--red:#e0705f;--ring:#ededed8c;--font-ui:system-ui, -apple-system, "Segoe UI", sans-serif;--font-mono:ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace}*{box-sizing:border-box}html,body,#root{height:100%}body{background:var(--surface);color:var(--text);font:14px/1.4 var(--font-ui);-webkit-font-smoothing:antialiased;margin:0}.num{font-family:var(--font-mono);font-variant-numeric:tabular-nums}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:0s!important;animation-duration:0s!important}}.frame{background:var(--surface);border:0;width:100%;height:100%;display:block}.load-error{text-align:center;place-content:center;justify-items:center;gap:8px;height:100%;display:grid}.load-error h1{margin:0;font-size:16px;font-weight:600}.load-error p{color:var(--muted);margin:0}.load-error button{border:1px solid var(--hairline);background:var(--raised);height:32px;color:var(--text);font:inherit;cursor:pointer;border-radius:8px;margin-top:8px;padding:0 14px}.load-error button:hover{background:#323232}.load-error button:focus-visible{outline:2px solid var(--amber);outline-offset:2px}
|