Confetti and a share card on finish
Goal: the moment the quiz finishes, confetti falls over the page and the shopper gets a “My result” image they can share or save.
Uses: Storefront JS API, quiz.finished (terminal_page, points, quiz_name). Nothing to install; no API key. Events fire on the Plus and Enterprise plans.
Confetti
Section titled “Confetti”quiz.finished fires once per session, after the result page landed. A burst of a few dozen coloured squares is enough; they remove themselves.
<script> window.octaneai = window.octaneai || []; window.octaneai.push(function (octaneai) { octaneai.on('quiz.finished', function () { var colors = ['#f43f5e', '#f59e0b', '#10b981', '#3b82f6', '#a855f7']; for (var i = 0; i < 60; i++) { var piece = document.createElement('span'); piece.className = 'octane-confetti'; piece.style.left = Math.random() * 100 + 'vw'; piece.style.background = colors[i % colors.length]; piece.style.animationDelay = Math.random() * 600 + 'ms'; document.body.appendChild(piece); piece.addEventListener('animationend', function () { this.remove(); }); } }); });</script><style> .octane-confetti { position: fixed; top: -12px; width: 10px; height: 14px; z-index: 2147483646; pointer-events: none; animation: octane-confetti-fall 1800ms ease-in forwards; } @keyframes octane-confetti-fall { to { transform: translateY(105vh) rotate(720deg); opacity: 0; } } @media (prefers-reduced-motion: reduce) { .octane-confetti { display: none; } }</style>The share card
Section titled “The share card”points maps each dimension id to its total, so the winning dimension is the largest entry; terminal_page is the key of the result page. Both are ids from the editor, not words a shopper should read, so keep a small map from id to label on the page. The card is drawn on a canvas and handed to the browser’s share sheet when it can take a file, else offered as a download.
<button id="share-result" hidden>Share my result</button><script> window.octaneai = window.octaneai || []; window.octaneai.push(function (octaneai) { var LABELS = { dry: 'Dry skin', oily: 'Oily skin', combination: 'Combination skin' };
function winner(points) { var ids = Object.keys(points); ids.sort(function (a, b) { return points[b] - points[a]; }); return ids[0] || null; }
function draw(quizName, headline) { var canvas = document.createElement('canvas'); canvas.width = 1080; canvas.height = 1080; var ctx = canvas.getContext('2d'); ctx.fillStyle = '#111827'; ctx.fillRect(0, 0, 1080, 1080); ctx.fillStyle = '#f9fafb'; ctx.textAlign = 'center'; ctx.font = '48px system-ui, sans-serif'; ctx.fillText(quizName, 540, 420); ctx.font = 'bold 96px system-ui, sans-serif'; ctx.fillText(headline, 540, 560); ctx.font = '36px system-ui, sans-serif'; ctx.fillText(location.hostname, 540, 980); return canvas; }
octaneai.on('quiz.finished', function (event) { var result = event.data.object; var id = winner(result.points) || result.terminal_page; var headline = LABELS[id] || 'My result'; var canvas = draw(result.quiz_name || 'Quiz', headline); var button = document.getElementById('share-result'); button.hidden = false; button.onclick = function () { canvas.toBlob(function (blob) { var file = new File([blob], 'my-result.png', { type: 'image/png' }); if (navigator.canShare && navigator.canShare({ files: [file] })) { navigator.share({ files: [file], title: headline }).catch(function () {}); return; } var link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'my-result.png'; link.click(); }, 'image/png'); }; }); });</script>The card carries the result label and the store’s hostname, nothing about the shopper: no answers, no email, no session id.
Errors to handle
Section titled “Errors to handle”navigator.sharerejects when the shopper dismisses the sheet; the.catchkeeps that silent. It also needs a user gesture, which is why the share happens on the button’s click and not inside the event listener.pointsis{}on a quiz without dimensions; the card then falls back toterminal_page, and to “My result” when that key has no label.
Things to know
Section titled “Things to know”- Sharing a file needs
navigator.canShare({ files }): Safari on iOS and macOS, Chrome on Android and desktop; Firefox on desktop has no share sheet, so it takes the download path. - A shopper who retakes the quiz starts a new session and
quiz.finishedfires again; the button’s handler is replaced with the new card. prefers-reduced-motionturns the confetti off; keep the share card, it does not move.- To see what the finish carries, set
localStorage['octaneai:debug'] = '1'in the console.
