Skip to content

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.

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 { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[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.

  • product is null for 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 threw and never breaks the quiz.
  • answers[].component_key is the key the question has in the editor; map the keys you want on the card to readable labels as FORMULAS does above, or the card prints the keys.
  • Numeric formulas arrive as numbers, text formulas as strings; formulas is {} when the quiz has none.
  • price is the selected variant’s price as a decimal string in currency; format it for the shopper’s locale if the card is shown on screen.
  • The image src is 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.finished and the card is rebuilt from the new result.