-
-
-
-
-
-
{tool.name}
-
{tool.description}
+ {tools.map((tool) => {
+ const LinkComponent = tool.static ? 'a' : Link
+ return (
+
+
+
+
+
+
+
+
+
{tool.name}
+
{tool.description}
+
+
-
-
-
-
- ))}
+
+
+ )
+ })}
From ea0f55f38b0df662dd991c154e20c69ee509624e Mon Sep 17 00:00:00 2001
From: 0xkkonrad
Date: Wed, 22 Jul 2026 13:48:02 +0000
Subject: [PATCH 2/3] fix: share-card robustness + idle canvas guard (review
findings)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Safari drops transient user activation across the awaited toBlob, so
clipboard.write always rejected and COPY silently became a download —
construct the ClipboardItem synchronously around the Promise.
Guard the null-blob path (createObjectURL(null) threw uncaught and
killed both buttons), defer revokeObjectURL past the download fetch,
skip petal-rain repaints while the tab is hidden, and derive the boot
banner count from QUESTIONS.length.
---
public/onboarding-quiz/index.html | 25 +++++++++++++++++--------
1 file changed, 17 insertions(+), 8 deletions(-)
diff --git a/public/onboarding-quiz/index.html b/public/onboarding-quiz/index.html
index 726444bdf3..2c58ceac11 100644
--- a/public/onboarding-quiz/index.html
+++ b/public/onboarding-quiz/index.html
@@ -368,6 +368,7 @@ PEANUT WELCOME CLUB
}
sizeRain(); addEventListener('resize', sizeRain);
setInterval(() => {
+ if (document.hidden) return;
rctx.fillStyle = 'rgba(255,246,251,0.16)'; rctx.fillRect(0, 0, rain.width, rain.height);
rctx.font = fs + 'px monospace';
cols.forEach((y, i) => {
@@ -569,20 +570,28 @@ PEANUT WELCOME CLUB
x.fillText('the handbook loves you ♡ close ur threads', 540, 840);
x.fillStyle = '#B268C8'; x.font = '26px monospace';
x.fillText('peanut.me/onboarding-quiz — join the club ♡', 540, 990);
- const blob = await new Promise((res) => c.toBlob(res, 'image/png'));
+ // ClipboardItem must be constructed synchronously in the tap's task, wrapping the
+ // Promise — awaiting toBlob first expires Safari's transient user activation.
+ const blobPromise = new Promise((res, rej) => c.toBlob((b) => (b ? res(b) : rej(new Error('toBlob failed'))), 'image/png'));
try {
if (mode === 'copy' && navigator.clipboard && window.ClipboardItem) {
- await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);
+ await navigator.clipboard.write([new ClipboardItem({ 'image/png': blobPromise })]);
$('btnCopy').textContent = '📋 COPIED ♡'; setTimeout(() => ($('btnCopy').textContent = '📋 COPY SHARE CARD'), 1600);
return;
}
throw new Error('fallback');
} catch {
- const a = document.createElement('a');
- a.href = URL.createObjectURL(blob);
- a.download = `peanut-welcome-club-${correctCount}-${order.length}.png`;
- a.click(); URL.revokeObjectURL(a.href);
- $('btnDl').textContent = '💾 SAVED ♡'; setTimeout(() => ($('btnDl').textContent = '💾 DOWNLOAD CARD'), 1600);
+ try {
+ const blob = await blobPromise;
+ const a = document.createElement('a');
+ a.href = URL.createObjectURL(blob);
+ a.download = `peanut-welcome-club-${correctCount}-${order.length}.png`;
+ a.click();
+ setTimeout(() => URL.revokeObjectURL(a.href), 2000);
+ $('btnDl').textContent = '💾 SAVED ♡'; setTimeout(() => ($('btnDl').textContent = '💾 DOWNLOAD CARD'), 1600);
+ } catch {
+ $('btnDl').textContent = '💾 hmm, that didn\'t work — try again ♡';
+ }
}
}
@@ -615,7 +624,7 @@ PEANUT WELCOME CLUB
[null, '> brewing welcome tea .................... done ♡'],
[null, '> fluffing your onboarding pillow ........ done ♡'],
['warn', '> scanning for bad vibes ................. NONE FOUND!!'],
- [null, '> 23 questions · 3 levels · pts + streaks · infinite emotional support'],
+ [null, `> ${QUESTIONS.length} questions · 3 levels · pts + streaks · infinite emotional support`],
['ok', '> WELCOME HOME, ANON ♡ the club has been expecting you'],
], () => $('btnStart').classList.remove('hidden'));
From 3208af584ec21ec7429b5e8f42c0ee5cea468bc1 Mon Sep 17 00:00:00 2001
From: 0xkkonrad
Date: Wed, 22 Jul 2026 13:53:39 +0000
Subject: [PATCH 3/3] fix: route share-card fallback feedback to the clicked
button (CodeRabbit)
---
public/onboarding-quiz/index.html | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/public/onboarding-quiz/index.html b/public/onboarding-quiz/index.html
index 2c58ceac11..6d75f0ede5 100644
--- a/public/onboarding-quiz/index.html
+++ b/public/onboarding-quiz/index.html
@@ -581,6 +581,9 @@ PEANUT WELCOME CLUB
}
throw new Error('fallback');
} catch {
+ // feedback goes on the button the user actually clicked, even when copy falls back to download
+ const btn = mode === 'copy' ? $('btnCopy') : $('btnDl');
+ const idle = mode === 'copy' ? '📋 COPY SHARE CARD' : '💾 DOWNLOAD CARD';
try {
const blob = await blobPromise;
const a = document.createElement('a');
@@ -588,9 +591,9 @@ PEANUT WELCOME CLUB
a.download = `peanut-welcome-club-${correctCount}-${order.length}.png`;
a.click();
setTimeout(() => URL.revokeObjectURL(a.href), 2000);
- $('btnDl').textContent = '💾 SAVED ♡'; setTimeout(() => ($('btnDl').textContent = '💾 DOWNLOAD CARD'), 1600);
+ btn.textContent = '💾 SAVED ♡'; setTimeout(() => (btn.textContent = idle), 1600);
} catch {
- $('btnDl').textContent = '💾 hmm, that didn\'t work — try again ♡';
+ btn.textContent = 'hmm, that didn\'t work — try again ♡'; setTimeout(() => (btn.textContent = idle), 2400);
}
}
}