← 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 &rarr;
    </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 &rarr;</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&rarr;Z</option>
        <option value="sku">SKU A&rarr;Z</option>
        <option value="vendor">Vendor A&rarr;Z</option>
        <option value="price-asc">Price &uarr;</option>
        <option value="price-desc">Price &darr;</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">&larr; Back</button>
    <button class="btn btn-primary" id="btn-swatches-next" disabled>Add to My Box &rarr;</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">&larr; 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">&#x2705;</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=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[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">&#x2713;</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">&#x2705; $${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>