afterimage-cli 0.2.2 → 0.2.3

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.3
4
+
5
+ - **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.
6
+ - **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.
7
+ - **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.
8
+ - **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.
9
+ - **The pill changes shape instead of jumping.** Entering or leaving Compare, its segments glide to their new width and their content fades in.
10
+
3
11
  ## 0.2.2
4
12
 
5
13
  - **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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "afterimage-cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "A version history for prototypes, deployed with the prototype itself.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -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
- .layer { position: fixed; inset: 0; z-index: 2147483647; opacity: 0; pointer-events: none; }
119
- .layer[data-shown] { opacity: 1; pointer-events: auto; }
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
- dock.hidden = true;
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
- function hide({ route, scrollY } = {}) {
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
- if (typeof scrollY === 'number') requestAnimationFrame(() => requestAnimationFrame(() => window.scrollTo(0, scrollY)));
311
- returnFocus?.focus?.({ preventScroll: true });
312
- // A save the agent made from the terminal meanwhile shows up in the count.
313
- refresh();
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}