← back to Estimate Instant
shopify: add Buy N rolls CTA + TEST banner to calculator UI
646c3eba8ac57d50d95028e32311ce4b9ea52f20 · 2026-07-16 07:39:05 -0700 · Steve Abrams
- Email capture still first (POST /api/lead with created_at — unchanged)
- After estimate: green "Buy N rolls on Designer Wallcoverings" button appears
linking to https://www.designerwallcoverings.com/cart/<variant_id>:<N>
where N = rolls-needed from the estimate math
- Real per-roll price and total shown in button pills
- TEST-MODE banner below CTA: "hands off to LIVE Shopify checkout for N rolls;
PayPal shows when enabled; roll specs are sample; production reads metafields"
- Stand-in note shown when shopify_match=false (all 8 current entries)
- Embed iframe compatibility preserved (postHeight still fires)
- All existing styles/behavior unchanged; only result block extended
Files touched
Diff
commit 646c3eba8ac57d50d95028e32311ce4b9ea52f20
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 16 07:39:05 2026 -0700
shopify: add Buy N rolls CTA + TEST banner to calculator UI
- Email capture still first (POST /api/lead with created_at — unchanged)
- After estimate: green "Buy N rolls on Designer Wallcoverings" button appears
linking to https://www.designerwallcoverings.com/cart/<variant_id>:<N>
where N = rolls-needed from the estimate math
- Real per-roll price and total shown in button pills
- TEST-MODE banner below CTA: "hands off to LIVE Shopify checkout for N rolls;
PayPal shows when enabled; roll specs are sample; production reads metafields"
- Stand-in note shown when shopify_match=false (all 8 current entries)
- Embed iframe compatibility preserved (postHeight still fires)
- All existing styles/behavior unchanged; only result block extended
---
public/index.html | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 61 insertions(+), 1 deletion(-)
diff --git a/public/index.html b/public/index.html
index cbc0961..9dbbb45 100644
--- a/public/index.html
+++ b/public/index.html
@@ -2,7 +2,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>How Many Rolls? — Instant Wallpaper Estimate</title>
<style>
- :root{--ink:#1a1a1a;--line:#e6e6e6;--accent:#2a3a4a;--gold:#a98b4a;--bg:#f7f5f2}
+ :root{--ink:#1a1a1a;--line:#e6e6e6;--accent:#2a3a4a;--gold:#a98b4a;--bg:#f7f5f2;--green:#1e6e3a}
*{box-sizing:border-box}
body{margin:0;font:16px/1.5 -apple-system,Segoe UI,Roboto,sans-serif;color:var(--ink);background:var(--bg)}
.wrap{max-width:640px;margin:0 auto;padding:32px 20px 60px}
@@ -29,11 +29,30 @@
.detail{margin:16px 0 0;font-size:14px;color:#555;display:grid;grid-template-columns:1fr 1fr;gap:6px 18px}
.detail b{color:var(--ink);font-weight:600}
.errs{margin-top:16px;background:#fff4f4;border:1px solid #f3c9c9;color:#9a2a2a;border-radius:8px;padding:12px 14px;font-size:14px}
+
+ /* Email capture — first step */
.capture{margin-top:20px;background:var(--bg);border:1px dashed #d0cabf;border-radius:10px;padding:16px}
.capture .row2{display:flex;gap:10px;flex-wrap:wrap;margin-top:8px}
.capture input{flex:1;min-width:180px}
.capture button{font:inherit;font-weight:600;padding:11px 16px;border:0;border-radius:8px;background:var(--gold);color:#fff;cursor:pointer}
.done{color:#2a6a3a;font-weight:600;font-size:14px;margin-top:8px}
+
+ /* Shopify checkout CTA — second step, shown after email capture or immediately */
+ .checkout-block{margin-top:18px;border-top:1px solid var(--line);padding-top:18px}
+ .btn-buy{display:flex;align-items:center;justify-content:center;gap:10px;width:100%;
+ font:inherit;font-weight:700;font-size:16px;padding:15px 20px;
+ border:0;border-radius:9px;background:var(--green);color:#fff;
+ cursor:pointer;text-decoration:none;transition:background .15s}
+ .btn-buy:hover{background:#155730}
+ .btn-buy .qty-pill{background:rgba(255,255,255,.22);border-radius:5px;padding:1px 8px;font-size:14px;font-weight:800}
+ .btn-buy .price-pill{background:rgba(255,255,255,.16);border-radius:5px;padding:1px 8px;font-size:14px}
+
+ /* TEST-MODE banner — hard requirement per spec */
+ .test-banner{margin-top:10px;background:#fff8e6;border:1px solid #e8d080;border-radius:8px;
+ padding:9px 13px;font-size:12px;color:#7a5800;line-height:1.5}
+ .test-banner b{font-weight:700}
+ .standin-note{margin-top:6px;font-size:11px;color:#999;font-style:italic}
+
.foot{margin-top:20px;font-size:12px;color:#aaa;text-align:center}
.foot a{color:#888}
</style></head><body>
@@ -62,6 +81,8 @@
<div class="price" id="pr">$0</div>
</div>
<div class="detail" id="detail"></div>
+
+ <!-- Step 1: Email capture (always first) -->
<div class="capture">
<label>Email me this quote</label>
<div class="row2">
@@ -70,6 +91,26 @@
</div>
<div class="done" id="done" style="display:none">✓ Saved — a DW rep will follow up with your quote and can help you order.</div>
</div>
+
+ <!-- Step 2: Shopify checkout handoff -->
+ <div class="checkout-block" id="checkoutBlock" style="display:none">
+ <a id="btnBuy" class="btn-buy" href="#" target="_blank" rel="noopener noreferrer">
+ Buy <span id="buyQty" class="qty-pill">N rolls</span> on Designer Wallcoverings
+ <span id="buyPrice" class="price-pill">$0</span>
+ </a>
+ <!-- TEST-MODE banner — required per spec -->
+ <div class="test-banner">
+ <b>TEST</b> — this button hands off to the <b>live Shopify checkout</b> for exactly
+ <span id="bannerQty">N</span> rolls at <span id="bannerPrice">$0/roll</span> each.
+ PayPal appears at checkout when enabled in Shopify Payments.
+ Roll specs (width/length/repeat) are sample data; production reads these from
+ Shopify metafields keyed by <code>variant_id</code>.
+ </div>
+ <div class="standin-note" id="standinNote" style="display:none">
+ Stand-in variant: the selected pattern's prototype SKU is not yet in the live catalog.
+ A real DW variant is used for the checkout link so the flow works end-to-end.
+ </div>
+ </div>
</div>
</div>
<p class="foot">Estimate only — includes a standard 4" trim + pattern-repeat allowance and rounds up for a safe count. Prices are indicative; confirm quantity, price, and dye lot with your DW rep before ordering.</p>
@@ -85,8 +126,26 @@ async function loadRolls(){
$('units').addEventListener('click',e=>{ const b=e.target.closest('button'); if(!b)return;
[...$('units').children].forEach(x=>x.classList.remove('on')); b.classList.add('on');
unit=b.dataset.u; $('uw').textContent=$('uh').textContent=`(${unit})`; });
+
+function renderCheckout(r) {
+ // r = estimate response with shopify fields
+ if (!r || !r.cart_url) { $('checkoutBlock').style.display='none'; return; }
+ $('checkoutBlock').style.display='block';
+ const qty = r.rollsNeeded;
+ const total = r.price;
+ const perRoll = r.pricePerRoll;
+ $('btnBuy').href = r.cart_url;
+ $('buyQty').textContent = qty + ' roll' + (qty===1?'':'s');
+ $('buyPrice').textContent = '$' + total.toLocaleString();
+ $('bannerQty').textContent = qty;
+ $('bannerPrice').textContent = '$' + perRoll.toLocaleString(undefined,{minimumFractionDigits:2,maximumFractionDigits:2}) + '/roll';
+ // Show stand-in note if this is not an exact SKU match
+ $('standinNote').style.display = r.shopify_match === false ? 'block' : 'none';
+}
+
async function calc(){
$('errs').style.display='none'; $('result').classList.remove('show');
+ $('checkoutBlock').style.display='none';
const payload={ sku:$('pat').value, wallWidthIn:inches($('w').value), wallHeightIn:inches($('h').value) };
const r=await fetch('/api/estimate',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}).then(r=>r.json());
if(!r.ok){ $('errs').innerHTML=(r.errors||['Something went wrong.']).join('<br>'); $('errs').style.display='block'; return; }
@@ -99,6 +158,7 @@ async function calc(){
['Price per roll','$'+r.pricePerRoll],['Est. waste',r.wastePct+'%']
].map(([k,v])=>`<div>${k}: <b>${v}</b></div>`).join('');
$('result').classList.add('show'); $('done').style.display='none';
+ renderCheckout(r);
postHeight();
}
// When embedded via embed.js, report our content height so the host iframe can grow.
← 4349f95 shopify: wire server to static catalog — estimate returns ca
·
back to Estimate Instant
·
security: bind server to 127.0.0.1 so the tunnel/proxy basic b38a6f6 →