cmdzero 0.9.0 → 0.9.1
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/overlay/overlay.js +64 -9
- package/package.json +1 -1
package/overlay/overlay.js
CHANGED
|
@@ -97,11 +97,31 @@
|
|
|
97
97
|
.cz-dot.done{background:#10b981}.cz-dot.queued,.cz-dot.running{background:#f59e0b;animation:cz-pulse 1s infinite}.cz-dot.error{background:#ef4444}.cz-dot.reverted,.cz-dot.cancelled{background:#6b7280}
|
|
98
98
|
.cz-tweak button{background:none;border:none;color:#818cf8;cursor:pointer;font-size:12.5px;padding:0}
|
|
99
99
|
.cz-meta{color:#9ca3af}
|
|
100
|
-
|
|
100
|
+
/* The hint is visible in BOTH states (out of select mode it's the ⌘0
|
|
101
|
+
affordance) — .cz-in only rides the swap between the two texts. */
|
|
102
|
+
.cz-hint{position:fixed;left:14px;bottom:14px;background:#111827;color:#9ca3af;font-size:12.5px;padding:6px 11px;border-radius:6px;pointer-events:none;opacity:0;transform:translateY(6px);transition:opacity .2s ease,transform .2s ease}
|
|
103
|
+
.cz-hint.cz-in{opacity:1;transform:translateY(0)}
|
|
101
104
|
.cz-reload-toggle{position:fixed;left:14px;bottom:46px;background:#111827;color:#6ee7b7;border:1px solid #10b981;font-size:11.5px;padding:4px 9px;border-radius:6px;cursor:pointer;pointer-events:auto}
|
|
102
105
|
.cz-reload-toggle.off{color:#9ca3af;border-color:#374151}
|
|
103
106
|
[contenteditable="plaintext-only"],[contenteditable="true"]{outline:2px dashed #10b981;outline-offset:2px}
|
|
104
|
-
@keyframes cz-pulse{50%{opacity:.4}}
|
|
107
|
+
@keyframes cz-pulse{50%{opacity:.4}}
|
|
108
|
+
/* Arming select mode is otherwise a silent text swap. A one-shot sweep says
|
|
109
|
+
"we woke up"; the ring stays lit to say "we're still listening". Both are
|
|
110
|
+
opacity/transform only so they cost nothing next to watchLayout()'s polling.
|
|
111
|
+
The ring starts below the 30px banner rather than cutting across it. */
|
|
112
|
+
.cz-ring{position:fixed;top:30px;left:0;right:0;bottom:0;pointer-events:none;opacity:0;box-shadow:inset 0 0 0 2px rgba(16,185,129,.55),inset 0 0 22px rgba(16,185,129,.14);transition:opacity .18s ease}
|
|
113
|
+
.cz-ring.cz-on{opacity:1;animation:cz-breathe 3s ease-in-out infinite}
|
|
114
|
+
.cz-shine{position:fixed;inset:0;pointer-events:none;opacity:0;background:linear-gradient(105deg,transparent 40%,rgba(110,231,183,.18) 50%,transparent 60%)}
|
|
115
|
+
/* Near-linear on purpose: a fast-out curve spends most of the run parked off
|
|
116
|
+
the right edge, so the band only actually crosses the screen for a blink. */
|
|
117
|
+
.cz-shine.cz-sweep{animation:cz-sweep .55s cubic-bezier(.45,.05,.55,.95)}
|
|
118
|
+
@keyframes cz-breathe{50%{opacity:.55}}
|
|
119
|
+
@keyframes cz-sweep{0%{opacity:0;transform:translateX(-120%)}15%{opacity:1}85%{opacity:1}100%{opacity:0;transform:translateX(120%)}}
|
|
120
|
+
@media (prefers-reduced-motion:reduce){
|
|
121
|
+
.cz-ring.cz-on{animation:none}
|
|
122
|
+
.cz-shine.cz-sweep{animation:none}
|
|
123
|
+
.cz-hint{transition:none}
|
|
124
|
+
}`;
|
|
105
125
|
|
|
106
126
|
const style = document.createElement('style');
|
|
107
127
|
style.textContent = css;
|
|
@@ -1470,18 +1490,18 @@
|
|
|
1470
1490
|
if (tries-- > 0 && Math.abs(scrollY - y) > 2) setTimeout(pin, 30);
|
|
1471
1491
|
else { try { history.scrollRestoration = 'auto'; } catch { /* ignore */ } }
|
|
1472
1492
|
})();
|
|
1473
|
-
if (saved.selectMode) setMode(true);
|
|
1493
|
+
if (saved.selectMode) setMode(true, false);
|
|
1474
1494
|
if (saved.loc) {
|
|
1475
1495
|
const again = document.querySelector(`[data-cz="${CSS.escape(saved.loc)}"]`);
|
|
1476
1496
|
if (again) select(again);
|
|
1477
1497
|
}
|
|
1478
1498
|
}
|
|
1479
|
-
if (document.readyState === 'complete') restoreAfterReload();
|
|
1480
|
-
else addEventListener('load', restoreAfterReload);
|
|
1481
1499
|
|
|
1482
1500
|
// ---------- mode + events ----------
|
|
1483
|
-
const hint = el('div', 'cz-hint', '⌘0 select mode');
|
|
1484
|
-
|
|
1501
|
+
const hint = el('div', 'cz-hint cz-in', '⌘0 select mode');
|
|
1502
|
+
const ring = el('div', 'cz-ring');
|
|
1503
|
+
const shine = el('div', 'cz-shine');
|
|
1504
|
+
root.append(hint, ring, shine);
|
|
1485
1505
|
|
|
1486
1506
|
const reloadToggle = el('button', 'cz-reload-toggle');
|
|
1487
1507
|
const syncReloadToggle = () => {
|
|
@@ -1497,13 +1517,48 @@
|
|
|
1497
1517
|
syncReloadToggle();
|
|
1498
1518
|
root.appendChild(reloadToggle);
|
|
1499
1519
|
|
|
1500
|
-
|
|
1520
|
+
// Restart the sweep even if it's mid-flight: drop the class, force a reflow,
|
|
1521
|
+
// re-add it. Without the reflow the browser coalesces the two states and the
|
|
1522
|
+
// animation never replays on a fast ⌘0 ⌘0 ⌘0.
|
|
1523
|
+
function sweep() {
|
|
1524
|
+
shine.classList.remove('cz-sweep');
|
|
1525
|
+
void shine.offsetWidth;
|
|
1526
|
+
shine.classList.add('cz-sweep');
|
|
1527
|
+
}
|
|
1528
|
+
shine.addEventListener('animationend', () => shine.classList.remove('cz-sweep'));
|
|
1529
|
+
|
|
1530
|
+
// Let the pill drop out before the words change, so the swap reads as one
|
|
1531
|
+
// motion rather than a jump cut. Compare against the text we're heading for,
|
|
1532
|
+
// not the one on screen: mid-swap those differ, and a fast ⌘0 ⌘0 would
|
|
1533
|
+
// otherwise take the early return and leave the pill showing the wrong mode.
|
|
1534
|
+
let hintTimer = null;
|
|
1535
|
+
let hintText = hint.textContent;
|
|
1536
|
+
function setHintText(text) {
|
|
1537
|
+
if (hintText === text) return;
|
|
1538
|
+
hintText = text;
|
|
1539
|
+
clearTimeout(hintTimer);
|
|
1540
|
+
hint.classList.remove('cz-in');
|
|
1541
|
+
hintTimer = setTimeout(() => { hint.textContent = hintText; hint.classList.add('cz-in'); }, 140);
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
// `announce` plays the activation sweep. It's on for a real ⌘0 press and off
|
|
1545
|
+
// when we're only restoring the mode after an auto-reload — the sweep should
|
|
1546
|
+
// read as "you just armed this", not fire again on every write.
|
|
1547
|
+
function setMode(on, announce = true) {
|
|
1501
1548
|
state.selectMode = on;
|
|
1502
|
-
|
|
1549
|
+
setHintText(on ? 'select mode — click · shift-click multi · Tab next · ⌘Z undo · Esc exit' : '⌘0 select mode');
|
|
1550
|
+
ring.classList.toggle('cz-on', on);
|
|
1551
|
+
if (on && announce) sweep();
|
|
1503
1552
|
if (on) watchLayout(); // keep the boxes glued while anything on the page moves
|
|
1504
1553
|
else { setHover(null); deselect(); clearMulti(); }
|
|
1505
1554
|
}
|
|
1506
1555
|
|
|
1556
|
+
// Run the restore only now: it calls setMode(), which touches the hint/ring/
|
|
1557
|
+
// shine consts declared above. On a document that's already `complete` when
|
|
1558
|
+
// the overlay is injected it would otherwise run during the temporal dead zone.
|
|
1559
|
+
if (document.readyState === 'complete') restoreAfterReload();
|
|
1560
|
+
else addEventListener('load', restoreAfterReload);
|
|
1561
|
+
|
|
1507
1562
|
// Visible, stamped elements in document order — the Tab cycle.
|
|
1508
1563
|
function stampedEls() {
|
|
1509
1564
|
return [...document.querySelectorAll('[data-cz]')].filter((n) => n.getClientRects().length);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cmdzero",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Tweak your UI live in the browser and write the changes straight to source. Copy and Tailwind edits cost zero tokens; everything else routes to a right-sized model via headless claude.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|