A printable routine card
Goal: the shopper prints (or saves as PDF) a one-page card with the routine the quiz built: the products on the result page in order, the answers that shaped it, and any computed value such as a daily amount.
Uses: Storefront JS API, quiz.finished (terminal_page, products[], answers[], formulas). Nothing to install; no API key. Events fire on the Plus and Enterprise plans.
Build the card on the finish
Section titled “Build the card on the finish”quiz.finished lists every product.shown of the session in products[]; keep the ones on the result page (page_key === terminal_page). Each carries a product object with the title, the image and the selected variant’s price. answers[] holds the latest answer per question; values are the labels the shopper saw. formulas maps each formula component’s key to its value.
<section id="routine-card" hidden></section><button id="print-routine" hidden>Print my routine</button><script> window.octaneai = window.octaneai || []; window.octaneai.push(function (octaneai) { var FORMULAS = { 'formula-daily-ml': 'Daily amount (ml)' };
function text(value) { return String(value).replace(/[&<>"]/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"' }[c]; }); }
octaneai.on('quiz.finished', function (event) { var result = event.data.object; var seen = {}; var steps = result.products.filter(function (p) { if (p.page_key !== result.terminal_page || !p.product || seen[p.product_id]) return false; seen[p.product_id] = true; return true; }); var choices = result.answers.filter(function (a) { return a.option_ids.length > 0; });
var html = '<h1>' + text(result.quiz_name || 'Your routine') + '</h1>'; html += '<ol class="steps">' + steps.map(function (p, i) { var image = p.product.images[0] ? '<img src="' + text(p.product.images[0].src) + '" alt="">' : ''; return '<li>' + image + '<strong>Step ' + (i + 1) + ': ' + text(p.product.title) + '</strong>' + '<span>' + text(p.product.price) + ' ' + text(p.product.currency || '') + '</span></li>'; }).join('') + '</ol>'; html += '<dl class="facts">' + choices.map(function (a) { return '<dt>' + text(a.component_key) + '</dt><dd>' + text(a.values.join(', ')) + '</dd>'; }).join('') + Object.keys(result.formulas).map(function (key) { return '<dt>' + text(FORMULAS[key] || key) + '</dt><dd>' + text(result.formulas[key]) + '</dd>'; }).join('') + '</dl>';
var card = document.getElementById('routine-card'); card.innerHTML = html; card.hidden = false; var button = document.getElementById('print-routine'); button.hidden = false; button.onclick = function () { window.print(); }; }); });</script><style> #routine-card { max-width: 640px; margin: 24px auto; font-family: system-ui, sans-serif; } #routine-card .steps li { display: flex; gap: 12px; align-items: center; padding: 8px 0; border-bottom: 1px solid #e5e7eb; } #routine-card .steps img { width: 56px; height: 56px; object-fit: cover; } #routine-card .facts { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; margin-top: 16px; } @media print { body > :not(#routine-card) { display: none !important; } #routine-card { display: block !important; max-width: none; margin: 0; } }</style>The @media print block hides everything but the card, so the printed page is the card alone; on screen the card sits under the quiz as a preview. Choice answers only go on the card: a text, email or phone answer is the shopper’s own words, and a printed page ends up on a desk.
Errors to handle
Section titled “Errors to handle”productisnullfor a card whose product the quiz never received (a product since removed from the store); the filter above drops it rather than printing an empty step.products[]is[]when the result page has no product block; the card then lists the answers and formulas only.- A listener that throws is logged as
[OctaneAI] listener for quiz.finished threwand never breaks the quiz.
Things to know
Section titled “Things to know”answers[].component_keyis the key the question has in the editor; map the keys you want on the card to readable labels asFORMULASdoes above, or the card prints the keys.- Numeric formulas arrive as numbers, text formulas as strings;
formulasis{}when the quiz has none. priceis the selected variant’s price as a decimal string incurrency; format it for the shopper’s locale if the card is shown on screen.- The image
srcis on Shopify’s CDN; the browser fetches it for print, so print after the images loaded (the button is enabled at once, a shopper who prints within a second may get a blank image slot). - A shopper who retakes the quiz gets a new
quiz.finishedand the card is rebuilt from the new result.
