← back to Sample Box
public/index.html
548 lines
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Sample Box — Designer Wallcoverings</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
:root{--cols:4;--accent:#1a1a2e;--gold:#c9a84c;--bg:#fafaf8;--card-bg:#fff;--border:#e8e4dc;--text:#1a1a1a;--muted:#888;--radius:10px}
body{font:15px/1.5 -apple-system,Segoe UI,Roboto,sans-serif;color:var(--text);background:var(--bg);min-height:100vh}
.site-header{background:var(--accent);color:#fff;padding:18px 32px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:100}
.site-header h1{font-size:20px;font-weight:700;letter-spacing:.03em}
.site-header .tagline{font-size:12px;opacity:.7;margin-top:2px}
.badge-test{background:#e74c3c;color:#fff;font-size:10px;font-weight:700;padding:2px 7px;border-radius:3px;letter-spacing:.05em}
.steps{display:flex;gap:0;border-bottom:2px solid var(--border);background:#fff}
.step{flex:1;padding:14px 16px;font-size:13px;font-weight:600;color:var(--muted);text-align:center;border-bottom:3px solid transparent;cursor:default;transition:.2s}
.step.active{color:var(--accent);border-bottom-color:var(--accent)}
.step.done{color:var(--gold);border-bottom-color:var(--gold)}
.panel{display:none;padding:32px}
.panel.active{display:block}
/* vibe */
.vibe-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:16px;max-width:900px;margin:0 auto}
.vibe-card{background:var(--card-bg);border:2px solid var(--border);border-radius:var(--radius);padding:28px 20px;text-align:center;cursor:pointer;transition:.15s}
.vibe-card:hover{border-color:var(--accent);transform:translateY(-2px);box-shadow:0 4px 16px rgba(0,0,0,.1)}
.vibe-card.selected{border-color:var(--gold);background:#fffbf0;box-shadow:0 0 0 3px rgba(201,168,76,.25)}
.vibe-card .emoji{font-size:40px;display:block;margin-bottom:10px}
.vibe-card .vibe-label{font-size:15px;font-weight:600}
/* swatch grid */
.grid-toolbar{display:flex;gap:12px;align-items:center;flex-wrap:wrap;padding:0 0 18px;border-bottom:1px solid var(--border);margin-bottom:20px}
.grid-toolbar h2{font-size:17px;font-weight:700;flex:1}
label.ctrl{font-size:12px;color:var(--muted);display:flex;gap:6px;align-items:center}
select,input[type=range]{font:inherit}
.sel-count{font-size:12px;color:var(--muted);margin-left:auto}
.swatch-grid{display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:14px}
.swatch-card{background:var(--card-bg);border:2px solid var(--border);border-radius:var(--radius);overflow:hidden;cursor:pointer;transition:.15s;position:relative}
.swatch-card:hover{transform:translateY(-2px);box-shadow:0 4px 14px rgba(0,0,0,.1)}
.swatch-card.selected{border-color:var(--gold);box-shadow:0 0 0 3px rgba(201,168,76,.3)}
.swatch-card img{width:100%;aspect-ratio:1/1;object-fit:cover;display:block;background:#f0f0f0}
.swatch-meta{padding:9px 10px}
.swatch-title{font-size:13px;font-weight:600;margin-bottom:2px}
.swatch-vendor{font-size:11px;color:var(--muted)}
.swatch-price{font-size:12px;margin-top:4px;color:var(--accent)}
.check-badge{position:absolute;top:8px;right:8px;background:var(--gold);color:#fff;width:24px;height:24px;border-radius:50%;display:none;align-items:center;justify-content:center;font-size:14px;font-weight:700;box-shadow:0 2px 6px rgba(0,0,0,.2)}
.swatch-card.selected .check-badge{display:flex}
/* checkout */
.checkout-wrap{max-width:560px;margin:0 auto}
.order-summary{background:var(--card-bg);border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:24px}
.order-summary h3{font-size:15px;font-weight:700;margin-bottom:12px}
.order-line{display:flex;justify-content:space-between;font-size:13px;padding:4px 0;border-bottom:1px solid var(--border)}
.order-line:last-child{border-bottom:none;font-weight:700;font-size:14px;padding-top:10px}
.credit-note{font-size:12px;color:#27ae60;margin-top:8px;padding:8px 10px;background:#f0faf4;border-radius:6px;border-left:3px solid #27ae60}
.form-group{margin-bottom:16px}
.form-group label{display:block;font-size:12px;font-weight:600;margin-bottom:5px;color:var(--muted)}
.form-group input,.form-group select{width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:6px;font:inherit;font-size:14px;background:#fff;transition:.15s}
.form-group input:focus,.form-group select:focus{outline:none;border-color:var(--accent)}
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:12px}
/* TEST-MODE banner */
.test-mode-banner{background:#fff8e1;border:2px solid #f5a623;border-radius:var(--radius);padding:14px 18px;margin:20px 0;font-size:13px;line-height:1.5}
.test-mode-banner strong{color:#b8730a;display:block;font-size:12px;font-weight:800;letter-spacing:.07em;text-transform:uppercase;margin-bottom:4px}
/* Shopify checkout CTA */
.shopify-cta-wrap{background:#f0faf4;border:2px solid #27ae60;border-radius:var(--radius);padding:22px 20px;margin:20px 0;text-align:center}
.shopify-cta-wrap .cta-label{font-size:12px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:#27ae60;margin-bottom:8px}
.shopify-cta-wrap .cta-amount{font-size:26px;font-weight:700;color:var(--accent);margin-bottom:4px}
.shopify-cta-wrap .cta-note{font-size:12px;color:var(--muted);margin-bottom:16px;line-height:1.5}
.btn-shopify{display:inline-flex;align-items:center;gap:10px;padding:14px 32px;border-radius:7px;font:700 15px/1 inherit;cursor:pointer;transition:.15s;border:none;background:#27ae60;color:#fff;text-decoration:none;width:100%;justify-content:center}
.btn-shopify:hover{background:#219150}
.btn-shopify:disabled,.btn-shopify[aria-disabled=true]{opacity:.5;cursor:not-allowed;pointer-events:none}
.shopify-lock{font-size:18px}
.btn{display:inline-flex;align-items:center;gap:8px;padding:13px 28px;border-radius:7px;font:600 15px/1 inherit;cursor:pointer;transition:.15s;border:none}
.btn-primary{background:var(--accent);color:#fff}
.btn-primary:hover{background:#2d2d50}
.btn-primary:disabled{opacity:.5;cursor:not-allowed}
.btn-gold{background:var(--gold);color:#fff}
.btn-gold:hover{background:#b8974a}
.btn-gold:disabled{opacity:.5;cursor:not-allowed}
.btn-outline{background:transparent;color:var(--accent);border:2px solid var(--accent)}
.btn-outline:hover{background:var(--accent);color:#fff}
.btns{display:flex;gap:12px;margin-top:24px;flex-wrap:wrap}
.confirm-box{max-width:540px;margin:0 auto;text-align:center;padding:40px 20px}
.confirm-box .tick{font-size:72px;margin-bottom:16px}
.confirm-box h2{font-size:24px;font-weight:700;margin-bottom:8px;color:var(--accent)}
.confirm-box .order-id{font-family:monospace;font-size:14px;color:var(--muted);margin-bottom:20px}
.confirm-detail{background:var(--card-bg);border:1px solid var(--border);border-radius:var(--radius);padding:20px;text-align:left;margin-top:20px}
.confirm-detail p{font-size:13px;padding:5px 0;border-bottom:1px solid var(--border)}
.confirm-detail p:last-child{border-bottom:none}
.credit-chip{display:inline-block;background:#f0faf4;color:#27ae60;border:1px solid #27ae60;border-radius:5px;padding:6px 12px;font-size:13px;font-weight:600;margin-top:12px}
.shopify-redirect-notice{background:#e8f5e9;border:1px solid #a5d6a7;border-radius:6px;padding:12px 16px;margin-top:16px;font-size:13px;color:#2e7d32}
.empty{padding:60px 20px;text-align:center;color:var(--muted)}
/* search bar */
.search-bar{padding:10px 14px;border:1px solid var(--border);border-radius:6px;font:inherit;font-size:14px;width:220px;background:#fff}
@media(max-width:640px){
.swatch-grid{--cols:2!important}
.vibe-grid{grid-template-columns:repeat(2,1fr)}
.form-row{grid-template-columns:1fr}
.panel{padding:20px}
}
</style>
</head>
<body>
<header class="site-header">
<div>
<h1>Designer Wallcoverings — Sample Box</h1>
<div class="tagline">Touch before you commit. $15, credited to your first roll order.</div>
</div>
<span class="badge-test">TEST MODE</span>
</header>
<nav class="steps">
<div class="step active" id="step-vibe">1 · Choose Your Vibe</div>
<div class="step" id="step-swatches">2 · Pick Swatches</div>
<div class="step" id="step-checkout">3 · Checkout</div>
<div class="step" id="step-confirm">4 · Confirmed</div>
</nav>
<!-- PANEL 1: VIBE PICKER -->
<div class="panel active" id="panel-vibe">
<h2 style="text-align:center;margin-bottom:8px;font-size:22px">What's your style vibe?</h2>
<p style="text-align:center;color:var(--muted);margin-bottom:28px;font-size:14px">
We'll curate a box of hand-picked real samples from our collection.
</p>
<div class="vibe-grid" id="vibe-grid"><div class="empty">Loading vibes…</div></div>
<div style="text-align:center;margin-top:20px">
<button class="btn btn-outline" id="btn-skip-vibe" style="font-size:13px;padding:10px 20px">
Skip — Browse All Real Products →
</button>
</div>
<div class="btns" style="justify-content:center;margin-top:16px">
<button class="btn btn-primary" id="btn-vibe-next" disabled>See My Swatches →</button>
</div>
</div>
<!-- PANEL 2: SWATCH GRID (real Shopify catalog) -->
<div class="panel" id="panel-swatches">
<div class="grid-toolbar">
<h2 id="swatch-heading">Swatches</h2>
<input class="search-bar" id="search" type="search" placeholder="Search title / vendor / SKU…">
<label class="ctrl">Sort
<select id="sort">
<option value="newest">Newest</option>
<option value="title">Title A→Z</option>
<option value="sku">SKU A→Z</option>
<option value="vendor">Vendor A→Z</option>
<option value="price-asc">Price ↑</option>
<option value="price-desc">Price ↓</option>
</select>
</label>
<label class="ctrl">Density <input id="density" type="range" min="2" max="6" value="4"></label>
<span class="sel-count" id="sel-count">0 selected</span>
</div>
<div class="swatch-grid" id="swatch-grid"><div class="empty">Loading…</div></div>
<div class="btns">
<button class="btn btn-outline" id="btn-back-vibe">← Back</button>
<button class="btn btn-primary" id="btn-swatches-next" disabled>Add to My Box →</button>
</div>
</div>
<!-- PANEL 3: CHECKOUT (Shopify-hosted handoff) -->
<div class="panel" id="panel-checkout">
<div class="checkout-wrap">
<div class="order-summary" id="order-summary">
<h3>Your Sample Box</h3>
</div>
<h3 style="margin-bottom:16px">Shipping Address</h3>
<form id="checkout-form" autocomplete="on">
<div class="form-group">
<label>Full Name *</label>
<input name="name" type="text" required placeholder="Jane Smith">
</div>
<div class="form-group">
<label>Email *</label>
<input name="email" type="email" required placeholder="jane@example.com">
</div>
<div class="form-group">
<label>Address Line 1 *</label>
<input name="address1" type="text" required placeholder="123 Main St">
</div>
<div class="form-group">
<label>Address Line 2</label>
<input name="address2" type="text" placeholder="Apt / Suite">
</div>
<div class="form-row">
<div class="form-group">
<label>City *</label>
<input name="city" type="text" required placeholder="Los Angeles">
</div>
<div class="form-group">
<label>State *</label>
<input name="state" type="text" required placeholder="CA" maxlength="2">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>ZIP *</label>
<input name="zip" type="text" required placeholder="90210" maxlength="10">
</div>
<div class="form-group">
<label>Country</label>
<select name="country">
<option value="US" selected>United States</option>
<option value="CA">Canada</option>
</select>
</div>
</div>
<!-- TEST-MODE banner — replaces old Stripe stub -->
<div class="test-mode-banner">
<strong>⚠️ TEST — Shopify Live Checkout Handoff</strong>
Clicking "Proceed to Shopify Checkout" opens the <em>real</em>
<strong>www.designerwallcoverings.com</strong> checkout. Only complete a
purchase while the store has <strong>Bogus Gateway</strong> (Shopify
test-mode) active — otherwise a real charge will occur.
<br><br>
<strong>PayPal note:</strong> PayPal appears in Shopify checkout when
enabled under <em>Store Admin → Settings → Payments → Shopify Payments →
PayPal</em>. This is a store-admin toggle — not controlled by this app.
</div>
</form>
<!-- Shopify checkout CTA -->
<div class="shopify-cta-wrap">
<div class="cta-label">Secure Shopify Checkout</div>
<div class="cta-amount" id="cta-amount">$15.00</div>
<div class="cta-note" id="cta-note">
Each swatch = one $4.25 memo-sample. Box total $15.00 fully credited<br>
toward your first roll order.
</div>
<a id="btn-shopify-checkout" class="btn-shopify" href="#" aria-disabled="true"
onclick="return handleShopifyCheckout(event)">
<span class="shopify-lock">🔒</span>
Proceed to Secure Shopify Checkout
</a>
</div>
<div class="btns">
<button class="btn btn-outline" id="btn-back-swatches">← Back</button>
</div>
<p id="order-error" style="color:#e74c3c;font-size:13px;margin-top:12px;display:none"></p>
</div>
</div>
<!-- PANEL 4: CONFIRMATION (local capture done — user redirected to Shopify) -->
<div class="panel" id="panel-confirm">
<div class="confirm-box">
<div class="tick">✅</div>
<h2>Order Captured — Redirecting to Checkout</h2>
<div class="order-id" id="confirm-order-id"></div>
<p style="font-size:14px;color:var(--muted)">Your order details have been saved. Complete payment on the secure Shopify checkout.</p>
<span class="credit-chip" id="confirm-credit"></span>
<div class="shopify-redirect-notice" id="shopify-redirect-notice" style="display:none">
🔗 <strong>Redirecting to Shopify checkout…</strong><br>
If the page doesn't redirect automatically,
<a id="shopify-fallback-link" href="#" target="_blank" style="color:#27ae60;font-weight:600">click here to complete checkout</a>.
</div>
<div class="confirm-detail" id="confirm-detail"></div>
<div class="btns" style="justify-content:center;margin-top:24px">
<button class="btn btn-outline" onclick="location.reload()">Order Another Box</button>
</div>
</div>
</div>
<script>
// ── state ──────────────────────────────────────────────────────────────────────
const state={vibe:null,selected:[],order:null,meta:{checkout_domain:'',sample_variant_id:''}};
const esc=s=>(s||'').replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));
const $=s=>document.querySelector(s);
const MAX=8, PRICE=15.00;
// ── sort+density — house rule: persist to localStorage ─────────────────────────
const sortSel=$('#sort'), dens=$('#density'), searchInp=$('#search');
sortSel.value=localStorage.getItem('sb-sort')||'newest';
dens.value=localStorage.getItem('sb-cols')||'4';
function applyCols(){
const v=dens.value;
$('#swatch-grid').style.setProperty('--cols',v);
localStorage.setItem('sb-cols',v);
}
applyCols();
dens.oninput=applyCols;
sortSel.onchange=()=>{localStorage.setItem('sb-sort',sortSel.value);loadSwatches();};
let _searchTimer=null;
searchInp.oninput=()=>{clearTimeout(_searchTimer);_searchTimer=setTimeout(loadSwatches,250);};
// ── step management ────────────────────────────────────────────────────────────
function setStep(n){
['panel-vibe','panel-swatches','panel-checkout','panel-confirm'].forEach((id,i)=>{
document.getElementById(id).classList.toggle('active',i+1===n);
});
['step-vibe','step-swatches','step-checkout','step-confirm'].forEach((id,i)=>{
const el=document.getElementById(id);
el.classList.toggle('active',i+1===n);
el.classList.toggle('done',i+1<n);
});
window.scrollTo(0,0);
}
// ── catalog meta (checkout domain + sample_variant_id) ─────────────────────────
async function loadMeta(){
try{
const r=await fetch('/api/catalog-meta');
const d=await r.json();
state.meta=d;
}catch(e){console.warn('catalog-meta failed',e);}
}
// ── panel 1: vibes ─────────────────────────────────────────────────────────────
async function loadVibes(){
const r=await fetch('/api/vibes'),d=await r.json();
const grid=$('#vibe-grid');
if(!d.vibes||!d.vibes.length){grid.innerHTML='<div class="empty">No vibes found — use Browse All below.</div>';return;}
grid.innerHTML=d.vibes.map(v=>`
<div class="vibe-card" data-id="${esc(v.id)}" onclick="selectVibe(this,'${esc(v.id)}')">
<span class="emoji">${v.emoji}</span>
<div class="vibe-label">${esc(v.label)}</div>
</div>`).join('');
}
function selectVibe(el,id){
document.querySelectorAll('.vibe-card').forEach(c=>c.classList.remove('selected'));
el.classList.add('selected');
state.vibe=id;
$('#btn-vibe-next').disabled=false;
}
$('#btn-vibe-next').onclick=()=>{setStep(2);loadSwatches();};
$('#btn-skip-vibe').onclick=()=>{state.vibe='all';setStep(2);loadSwatches();};
// ── panel 2: swatch grid — real Shopify catalog ────────────────────────────────
window._products=[];
async function loadSwatches(){
const grid=$('#swatch-grid');
grid.innerHTML='<div class="empty">Loading…</div>';
const sort=sortSel.value;
const q=searchInp.value.trim();
const vibe=state.vibe||'all';
// Try Shopify catalog first; fall back to vibes-filtered local products
let products=[];
let fromShopify=false;
try{
const params=new URLSearchParams({sort});
if(q)params.set('q',q);
const r=await fetch(`/api/shopify-products?${params}`);
const d=await r.json();
products=d.products||[];
fromShopify=true;
}catch(e){
// fallback to local products.json
const params=new URLSearchParams({sort,vibe});
const r=await fetch(`/api/products?${params}`);
const d=await r.json();
products=d.products||[];
}
// If vibe is set and we have local products for it, apply vibe filter on shopify products
// (shopify catalog has no vibes, so we just show all and note that)
const srcLabel=fromShopify?`${products.length} real products from Shopify catalog`:`${products.length} swatch${products.length!==1?'es':''}`;
$('#swatch-heading').textContent=srcLabel;
window._products=products;
if(!products.length){
grid.innerHTML='<div class="empty">No swatches found' + (q?` for "${esc(q)}"`:'')+'. Try a different search or sort.</div>';
return;
}
grid.innerHTML=products.map(p=>{
const sel=state.selected.some(s=>s.sku===p.sku);
// Use price_display (already formatted by catalog builder: "$94.42" or "Roll price on request").
// Never render raw p.price (can be null for sample_only products → "$null"/"$NaN").
const priceDisplay = p.price_display || (p.price != null ? `$${Number(p.price).toFixed(0)}/roll` : '');
// sample_only badge: product has no roll price but is perfectly valid to sample
const sampleOnlyBadge = p.sample_only
? `<span style="display:inline-block;font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;background:#f0f0f0;color:#888;border-radius:3px;padding:1px 6px;margin-top:3px">Sample only</span>`
: '';
return `<div class="swatch-card${sel?' selected':''}" data-sku="${esc(p.sku)}" onclick="toggleSwatch(this)">
<img src="${esc(p.image||'')}" alt="${esc(p.title||'')}" loading="lazy"
onerror="this.style.background='#f5f0ea'">
<div class="swatch-meta">
<div class="swatch-title">${esc(p.title||'Untitled')}</div>
<div class="swatch-vendor">${esc(p.vendor||'')}</div>
${priceDisplay?`<div class="swatch-price">${esc(priceDisplay)}</div>`:''}
${sampleOnlyBadge}
</div>
<div class="check-badge">✓</div>
</div>`;
}).join('');
updateSelCount();
}
function toggleSwatch(el){
const sku=el.dataset.sku,p=window._products.find(x=>x.sku===sku);
if(!p)return;
if(el.classList.contains('selected')){
el.classList.remove('selected');
state.selected=state.selected.filter(s=>s.sku!==sku);
}else{
if(state.selected.length>=MAX){alert(`Max ${MAX} swatches per box. Deselect one to add another.`);return;}
el.classList.add('selected');
state.selected.push(p);
}
updateSelCount();
}
function updateSelCount(){
const n=state.selected.length;
$('#sel-count').textContent=`${n} selected (max ${MAX})`;
$('#btn-swatches-next').disabled=n===0;
}
$('#btn-back-vibe').onclick=()=>setStep(1);
$('#btn-swatches-next').onclick=()=>{buildOrderSummary();setStep(3);};
// ── panel 3: checkout ──────────────────────────────────────────────────────────
function buildOrderSummary(){
const wrap=$('#order-summary');
const n=state.selected.length;
const rows=state.selected.map(p=>`<div class="order-line"><span>${esc(p.title)}</span><span style="color:var(--muted);font-size:11px">memo sample</span></div>`).join('');
// Box price = $4.25/swatch × n, or flat $15 whichever context; we use $4.25×n for transparency
const samplePrice=4.25;
const lineTotal=(samplePrice*n).toFixed(2);
wrap.innerHTML=`<h3>Your Sample Box (${n} swatch${n!==1?'es':''})</h3>${rows}
<div class="order-line"><span>${n} memo sample${n!==1?'s':''} × $${samplePrice.toFixed(2)}</span><span>$${lineTotal}</span></div>
<div class="credit-note">✅ $${lineTotal} fully credited toward your first roll order</div>`;
$('#cta-amount').textContent=`$${lineTotal}`;
$('#cta-note').innerHTML=`${n} memo sample${n!==1?'s':''} × $4.25 each = <strong>$${lineTotal}</strong> — fully credited toward your first roll order.`;
// Enable/disable the Shopify CTA based on meta readiness
updateShopifyCTA();
}
function buildPermalink(){
const {checkout_domain, sample_variant_id} = state.meta;
if(!checkout_domain || !sample_variant_id) return null;
const qty=state.selected.length;
return `https://${checkout_domain}/cart/${sample_variant_id}:${qty}`;
}
function updateShopifyCTA(){
const link=$('#btn-shopify-checkout');
const permalink=buildPermalink();
if(permalink){
link.href=permalink;
link.removeAttribute('aria-disabled');
}else{
link.href='#';
link.setAttribute('aria-disabled','true');
}
}
async function handleShopifyCheckout(e){
e.preventDefault();
const form=$('#checkout-form');
if(!form.checkValidity()){form.reportValidity();return false;}
// Validate form fields
const fd=new FormData(form);
const shipping={
name:fd.get('name'),
address1:fd.get('address1'),
address2:fd.get('address2')||'',
city:fd.get('city'),
state:(fd.get('state')||'').toUpperCase(),
zip:fd.get('zip'),
country:fd.get('country')
};
// 1) Local order capture first (before redirect)
const btn=$('#btn-shopify-checkout');
btn.textContent='Saving order…';
btn.setAttribute('aria-disabled','true');
let localOrder=null;
try{
const r=await fetch('/api/orders',{
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({
vibe:state.vibe,
swatches:state.selected.map(p=>({sku:p.sku,title:p.title,image:p.image,price:p.price,variant_id:p.variant_id||null})),
shipping,
email:fd.get('email'),
payment_stub:false
})
});
const d=await r.json();
if(!d.success)throw new Error(d.error||'Order capture failed');
localOrder=d.order;
}catch(err){
const errEl=$('#order-error');
errEl.textContent='Order capture error: '+err.message;
errEl.style.display='';
btn.innerHTML='<span class="shopify-lock">🔒</span> Proceed to Secure Shopify Checkout';
btn.removeAttribute('aria-disabled');
return false;
}
// 2) Show confirmation panel, then redirect
showConfirmation(localOrder);
// 3) Redirect to Shopify
const permalink=buildPermalink();
if(permalink){
const notice=$('#shopify-redirect-notice');
notice.style.display='';
$('#shopify-fallback-link').href=permalink;
$('#shopify-fallback-link').textContent='click here to complete checkout on designerwallcoverings.com';
// Short delay so the confirm panel renders before redirect
setTimeout(()=>{ window.location.href=permalink; }, 1800);
}
return false;
}
// ── panel 4: confirmation ──────────────────────────────────────────────────────
function showConfirmation(order){
setStep(4);
$('#confirm-order-id').textContent='Order '+order.id;
$('#confirm-credit').textContent=order.credit_note;
const addr=order.shipping||{};
const permalink=buildPermalink();
$('#confirm-detail').innerHTML=
order.swatches.map(s=>`<p><strong>${esc(s.title)}</strong></p>`).join('')+
`<p style="padding-top:10px"><strong>Ship to:</strong> ${esc(addr.name)}</p>
<p>${esc(addr.address1)}${addr.address2?' '+esc(addr.address2):''}</p>
<p>${esc(addr.city)}, ${esc(addr.state)} ${esc(addr.zip)} ${esc(addr.country)}</p>
<p><strong>Email:</strong> ${esc(order.email)}</p>
<p><strong>Payment:</strong> Shopify-hosted checkout${permalink?` — <a href="${esc(permalink)}" target="_blank" style="color:#27ae60">proceed to checkout</a>`:''}</p>
<p><strong>Local capture:</strong> ${esc(order.id)} saved ✓</p>`;
}
// ── init ───────────────────────────────────────────────────────────────────────
$('#btn-back-swatches').onclick=()=>setStep(2);
Promise.all([loadVibes(), loadMeta()]).then(()=>{
// Meta ready; update CTA if already on checkout panel
updateShopifyCTA();
});
</script>
</body>
</html>