Skip to content

Commit 2b21d10

Browse files
Merge pull request #56 from posecode-dev/codex/mobile-toolbar-clarity
Improve mobile toolbar clarity
2 parents 00112ac + 971bfc9 commit 2b21d10

3 files changed

Lines changed: 79 additions & 10 deletions

File tree

playground/play.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
<span class="ico-help" aria-hidden="true"></span><span class="lbl">How to use</span>
8484
</button>
8585
<a
86+
id="feedback"
8687
href="mailto:hello@posecode.org?subject=Posecode%20Feedback"
8788
class="btn ghost"
8889
aria-label="Feedback"
@@ -93,10 +94,11 @@
9394
<button
9495
id="share"
9596
class="btn ghost"
96-
aria-label="Share"
97+
aria-label="Copy link"
9798
title="Copy a shareable link that renders this exact movement"
9899
>
99-
<span class="ico-link" aria-hidden="true"></span><span class="lbl">Share</span>
100+
<span class="ico-link" aria-hidden="true"></span
101+
><span class="lbl" aria-live="polite">Share</span>
100102
</button>
101103
<button
102104
id="copy-prompt"

playground/src/main.ts

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -426,20 +426,46 @@ speed.addEventListener("change", () => viewer?.setSpeed(Number(speed.value)));
426426
// --- Button label feedback ---
427427
// Swap a button's label (the inner `.lbl` span when present, else the button
428428
// text) to a transient message, then restore it.
429-
function flash(btn: HTMLElement, message: string): void {
429+
const flashTimers = new WeakMap<HTMLElement, number>();
430+
431+
function flash(
432+
btn: HTMLElement,
433+
message: string,
434+
status: "pending" | "success" | "error",
435+
duration = 4000,
436+
): void {
430437
const el = btn.querySelector<HTMLElement>(".lbl") ?? btn;
431-
const prev = el.textContent;
438+
const previousTimer = flashTimers.get(btn);
439+
if (previousTimer !== undefined) window.clearTimeout(previousTimer);
440+
441+
const defaultLabel = el.dataset.defaultLabel ?? el.textContent ?? "";
442+
const defaultAriaLabel =
443+
btn.dataset.defaultAriaLabel ?? btn.getAttribute("aria-label") ?? "";
444+
el.dataset.defaultLabel = defaultLabel;
445+
btn.dataset.defaultAriaLabel = defaultAriaLabel;
432446
el.textContent = message;
433-
window.setTimeout(() => (el.textContent = prev), 1500);
447+
btn.dataset.status = status;
448+
btn.setAttribute("aria-label", message);
449+
if (duration === 0) return;
450+
451+
const timer = window.setTimeout(() => {
452+
el.textContent = defaultLabel;
453+
delete btn.dataset.status;
454+
if (defaultAriaLabel === "") btn.removeAttribute("aria-label");
455+
else btn.setAttribute("aria-label", defaultAriaLabel);
456+
flashTimers.delete(btn);
457+
}, duration);
458+
flashTimers.set(btn, timer);
434459
}
435460

436461
// --- Copy LLM prompt (topbar) ---
437462
async function copyPrompt(btn: HTMLButtonElement): Promise<void> {
463+
flash(btn, "Copying…", "pending", 0);
438464
try {
439465
await navigator.clipboard.writeText(llmPrompt);
440-
flash(btn, "Copied ✓");
466+
flash(btn, "Copied ✓", "success");
441467
} catch {
442-
flash(btn, "Copy failed");
468+
flash(btn, "Copy failed", "error");
443469
}
444470
}
445471
copyBtn.addEventListener("click", () => copyPrompt(copyBtn));
@@ -449,22 +475,23 @@ copyBtn.addEventListener("click", () => copyPrompt(copyBtn));
449475
// (so it's bookmarkable), and copy the full link to the clipboard.
450476
async function shareLink(): Promise<void> {
451477
if (!editorApi) return; // editor still loading; nothing to snapshot yet
478+
flash(shareBtn, "Copying…", "pending", 0);
452479
try {
453480
const source = editorApi.getValue();
454481
const path = buildNicePlayPath(source);
455482
const hash = path === "/play" ? buildNiceShareHash(source) : "";
456483
const url = `${location.origin}${path}${hash}`;
457484
history.replaceState(null, "", `${path}${hash}`);
458485
await navigator.clipboard.writeText(url);
459-
flash(shareBtn, "Link copied ✓");
486+
flash(shareBtn, "Link copied ✓", "success");
460487
} catch (err) {
461488
const message =
462489
err instanceof TypeError
463490
? "Nothing to share"
464491
: err instanceof RangeError
465492
? "Too long to link"
466493
: "Copy failed";
467-
flash(shareBtn, message);
494+
flash(shareBtn, message, "error");
468495
}
469496
}
470497
shareBtn.addEventListener("click", shareLink);

playground/src/style.css

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1421,12 +1421,52 @@ select:hover {
14211421

14221422
@media (max-width: 860px) {
14231423
.topbar { padding: 12px; }
1424-
.topbar-actions { display: grid; grid-template-columns: 1fr auto auto; gap: 6px; }
1424+
.topbar-actions {
1425+
display: grid;
1426+
grid-template-columns: minmax(0, 1fr) auto auto;
1427+
align-items: stretch;
1428+
gap: 6px;
1429+
}
14251430
.lib-btn { min-height: 42px; }
1431+
#new-doc, #how-to, #feedback, #share {
1432+
min-height: 42px;
1433+
}
1434+
#feedback, #share {
1435+
justify-content: center;
1436+
}
1437+
#share {
1438+
grid-column: 2 / -1;
1439+
}
1440+
#share .lbl {
1441+
display: inline;
1442+
}
1443+
#share:not([data-status]) .lbl {
1444+
font-size: 0;
1445+
}
1446+
#share:not([data-status]) .lbl::after {
1447+
content: "Copy link";
1448+
font-size: 13px;
1449+
}
14261450
#copy-prompt { grid-column: 1 / -1; min-height: 42px; }
14271451
.intro { padding-left: 12px; padding-right: 12px; }
14281452
.layout { grid-template-columns: 1fr; }
14291453
.transport { gap: 8px; padding-left: 12px; padding-right: 12px; }
14301454
.speed > span, .loop > span { display: none; }
14311455
.clock { padding-left: 8px; min-width: 40px; }
14321456
}
1457+
1458+
#share[data-status="success"] {
1459+
color: var(--bg);
1460+
background: var(--accent);
1461+
border-color: var(--accent);
1462+
}
1463+
1464+
#share[data-status="pending"] {
1465+
color: var(--accent);
1466+
border-color: var(--accent);
1467+
}
1468+
1469+
#share[data-status="error"] {
1470+
color: #ffb4a9;
1471+
border-color: #a94b41;
1472+
}

0 commit comments

Comments
 (0)