← back to Reno Visualizer

public/index.html

902 lines

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>See It On Your Wall — Designer Wallcoverings Visualizer</title>
<style>
/* ── Design tokens ── */
:root {
  --brand: #2a4a3e;
  --brand-light: #3d6b5a;
  --accent: #c8a86a;
  --surface: #fafaf8;
  --card-bg: #ffffff;
  --border: #e4e0d8;
  --text: #1a1a18;
  --muted: #7a7870;
  --cols: 3;
  --card-radius: 10px;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { font: 15px/1.5 -apple-system, 'Segoe UI', Roboto, sans-serif; color: var(--text); background: var(--surface); }

/* ── Nav ── */
.nav {
  display: flex; align-items: center; gap: 16px; padding: 14px 24px;
  background: var(--brand); color: #fff; position: sticky; top: 0; z-index: 100;
  box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
.nav-logo { font-size: 17px; font-weight: 700; letter-spacing: 0.03em; color: #fff; text-decoration: none; }
.nav-tag { font-size: 11px; opacity: 0.65; font-style: italic; }
.nav-spacer { flex: 1; }
.nav-cta { font-size: 12px; background: var(--accent); color: #1a1a18; border: none; border-radius: 6px;
  padding: 6px 14px; font-weight: 600; cursor: pointer; text-decoration: none; }

/* ── Step progress ── */
.steps {
  display: flex; gap: 0; background: #fff; border-bottom: 1px solid var(--border);
  overflow-x: auto;
}
.step-btn {
  flex: 1; min-width: 120px; padding: 12px 16px; border: none; background: none; cursor: pointer;
  font: 13px/1.2 inherit; color: var(--muted); border-bottom: 3px solid transparent;
  transition: color 0.2s, border-color 0.2s; white-space: nowrap; text-align: center;
}
.step-btn .step-num { display: block; font-size: 10px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; margin-bottom: 2px; }
.step-btn.active { color: var(--brand); border-bottom-color: var(--brand); font-weight: 600; }
.step-btn.done { color: var(--brand-light); }
.step-btn.done .step-num::after { content: ' ✓'; }

/* ── Panels ── */
.panel { display: none; }
.panel.active { display: block; }

/* ── Step 1: Room source ── */
.room-source { padding: 32px 24px; max-width: 900px; margin: 0 auto; }
.section-title { font-size: 22px; font-weight: 700; color: var(--brand); margin-bottom: 8px; }
.section-sub { color: var(--muted); margin-bottom: 24px; }

.room-tabs { display: flex; gap: 10px; margin-bottom: 20px; }
.room-tab { padding: 8px 18px; border-radius: 20px; border: 1.5px solid var(--border); font: inherit;
  font-size: 13px; cursor: pointer; background: #fff; color: var(--muted); transition: all 0.15s; }
.room-tab.active { background: var(--brand); color: #fff; border-color: var(--brand); }

.stock-rooms { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 14px; }
.room-card { border: 2px solid var(--border); border-radius: var(--card-radius); overflow: hidden;
  cursor: pointer; transition: border-color 0.15s, box-shadow 0.15s; background: #fff; }
.room-card:hover { border-color: var(--brand-light); box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.room-card.selected { border-color: var(--brand); box-shadow: 0 0 0 3px rgba(42,74,62,0.15); }
.room-card img, .room-card object { width: 100%; aspect-ratio: 4/3; object-fit: cover; display: block; pointer-events: none; }
.room-card .room-label { padding: 8px 10px; font-size: 12px; font-weight: 600; color: var(--brand); }

.upload-zone {
  border: 2px dashed var(--border); border-radius: var(--card-radius); padding: 48px 24px;
  text-align: center; cursor: pointer; transition: border-color 0.15s, background 0.15s;
  background: #fff;
}
.upload-zone:hover, .upload-zone.drag { border-color: var(--brand); background: #f0f5f2; }
.upload-icon { font-size: 40px; margin-bottom: 12px; display: block; }
.upload-zone h3 { font-size: 16px; margin-bottom: 6px; color: var(--brand); }
.upload-zone p { color: var(--muted); font-size: 13px; }
.upload-zone input { display: none; }
#upload-preview { max-width: 400px; border-radius: var(--card-radius); margin: 16px auto 0; display: none; }

.step-next { margin-top: 24px; }
.btn-primary { background: var(--brand); color: #fff; border: none; border-radius: 8px;
  padding: 12px 28px; font: 600 15px inherit; cursor: pointer; transition: background 0.15s; }
.btn-primary:hover { background: var(--brand-light); }
.btn-primary:disabled { opacity: 0.4; cursor: not-allowed; }
.btn-ghost { background: none; color: var(--brand); border: 1.5px solid var(--brand); border-radius: 8px;
  padding: 11px 24px; font: 600 15px inherit; cursor: pointer; margin-right: 10px; }

/* ── Step 2: Pattern picker ── */
.pattern-picker { padding: 24px; }
.toolbar {
  display: flex; gap: 14px; align-items: center; flex-wrap: wrap;
  background: #fff; border: 1px solid var(--border); border-radius: 10px;
  padding: 12px 16px; margin-bottom: 20px;
  position: sticky; top: 50px; z-index: 10;
}
.toolbar h2 { font-size: 16px; font-weight: 700; color: var(--brand); margin-right: 4px; }
.toolbar-spacer { flex: 1; }
.toolbar label { font-size: 12px; color: var(--muted); display: flex; gap: 6px; align-items: center; }
.toolbar select { font: 13px inherit; padding: 4px 8px; border: 1px solid var(--border); border-radius: 6px; }
.toolbar input[type=range] { width: 80px; }
.count-badge { font-size: 11px; color: var(--muted); background: var(--surface); border: 1px solid var(--border);
  padding: 3px 8px; border-radius: 12px; }

.pattern-grid {
  display: grid;
  grid-template-columns: repeat(var(--cols), 1fr);
  gap: 14px;
}
.pattern-card {
  background: var(--card-bg); border: 2px solid var(--border); border-radius: var(--card-radius);
  overflow: hidden; cursor: pointer; transition: border-color 0.15s, box-shadow 0.15s;
}
.pattern-card:hover { border-color: var(--brand-light); box-shadow: 0 4px 16px rgba(0,0,0,0.1); }
.pattern-card.selected { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(200,168,106,0.25); }
.pattern-card img { width: 100%; aspect-ratio: 1/1; object-fit: cover; display: block; background: #f0ede8; }
.pattern-meta { padding: 8px 10px; }
.pattern-title { font-size: 13px; font-weight: 600; margin-bottom: 2px; }
.pattern-sub { font-size: 11px; color: var(--muted); }
.pattern-price { font-size: 13px; color: var(--brand); font-weight: 600; margin-top: 4px; }
.pattern-sample-badge { display: inline-block; font-size: 9px; background: #e8f0e8; color: #4a7a4a;
  border: 1px solid #b0d4b0; border-radius: 4px; padding: 1px 5px; margin-left: 4px; vertical-align: middle; }
.empty-state { padding: 60px 24px; text-align: center; color: var(--muted); }

/* ── Step 3: Render ── */
.render-panel { padding: 24px; max-width: 1100px; margin: 0 auto; }
.render-layout { display: grid; grid-template-columns: 1fr 320px; gap: 24px; align-items: start; }
@media (max-width: 768px) { .render-layout { grid-template-columns: 1fr; } }

.render-canvas-wrap {
  position: relative; background: #1a1a18; border-radius: 12px; overflow: hidden;
  box-shadow: 0 8px 32px rgba(0,0,0,0.25);
}
#render-canvas { display: block; width: 100%; border-radius: 12px; cursor: crosshair; }
.render-badge {
  position: absolute; bottom: 10px; left: 10px;
  background: rgba(0,0,0,0.65); color: #fff; font-size: 10px; padding: 4px 10px;
  border-radius: 20px; backdrop-filter: blur(4px); pointer-events: none;
  letter-spacing: 0.04em;
}

.render-controls { background: #fff; border: 1px solid var(--border); border-radius: 12px; padding: 20px; }
.render-controls h3 { font-size: 15px; font-weight: 700; color: var(--brand); margin-bottom: 16px; }
.ctrl-group { margin-bottom: 16px; }
.ctrl-label { font-size: 12px; color: var(--muted); font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.06em; margin-bottom: 6px; display: block; }
.ctrl-row { display: flex; gap: 8px; align-items: center; }
.ctrl-row input[type=range] { flex: 1; }
.ctrl-val { font-size: 12px; color: var(--brand); font-weight: 600; min-width: 30px; text-align: right; }
.ctrl-select { width: 100%; font: 13px inherit; padding: 6px 10px; border: 1px solid var(--border); border-radius: 6px; }

.selected-pattern-info {
  display: flex; gap: 12px; align-items: center; padding: 12px; background: var(--surface);
  border-radius: 8px; margin-bottom: 16px; border: 1px solid var(--border);
}
.selected-pattern-thumb { width: 52px; height: 52px; object-fit: cover; border-radius: 6px; flex-shrink: 0; }
.selected-pattern-meta .title { font-size: 13px; font-weight: 600; color: var(--brand); }
.selected-pattern-meta .sub { font-size: 11px; color: var(--muted); }

.wall-zone-hint { font-size: 12px; color: var(--muted); background: #f8f4ee; border: 1px solid #e4ddd0;
  border-radius: 6px; padding: 8px 12px; margin-top: 12px; line-height: 1.4; }
.wall-zone-hint strong { color: var(--brand); }

.wall-mode-btns { display: flex; gap: 8px; flex-wrap: wrap; }
.wall-mode-btn { flex: 1; min-width: 80px; padding: 8px 10px; border: 1.5px solid var(--border);
  border-radius: 6px; font: 12px inherit; cursor: pointer; background: #fff; text-align: center;
  transition: all 0.15s; }
.wall-mode-btn.active { background: var(--brand); color: #fff; border-color: var(--brand); }

/* ── Step 4: Shop the look ── */
.shop-panel { padding: 32px 24px; max-width: 680px; margin: 0 auto; }
.look-preview {
  display: flex; gap: 16px; align-items: center; padding: 20px; background: #fff;
  border: 1px solid var(--border); border-radius: 12px; margin-bottom: 28px;
}
.look-room-thumb { width: 140px; height: 105px; object-fit: cover; border-radius: 8px; flex-shrink: 0; background: #e8e4dc; }
.look-pattern-thumb { width: 70px; height: 70px; object-fit: cover; border-radius: 6px; flex-shrink: 0; }
.look-info h3 { font-size: 16px; font-weight: 700; color: var(--brand); margin-bottom: 4px; }
.look-info p { font-size: 13px; color: var(--muted); }
.look-info .price { font-size: 18px; font-weight: 700; color: var(--brand); margin-top: 6px; }

.form-group { margin-bottom: 16px; }
.form-group label { display: block; font-size: 13px; font-weight: 600; color: var(--brand); margin-bottom: 6px; }
.form-group input, .form-group textarea, .form-group select {
  width: 100%; font: 14px inherit; padding: 10px 14px; border: 1.5px solid var(--border);
  border-radius: 8px; background: #fff; color: var(--text); transition: border-color 0.15s; outline: none;
}
.form-group input:focus, .form-group textarea:focus { border-color: var(--brand); }
.form-group textarea { min-height: 80px; resize: vertical; }

.shop-privacy { font-size: 11px; color: var(--muted); margin-top: 8px; }
.lead-success { display: none; text-align: center; padding: 40px 24px; }
.lead-success .check { font-size: 48px; display: block; margin-bottom: 12px; }
.lead-success h3 { font-size: 20px; font-weight: 700; color: var(--brand); margin-bottom: 8px; }
.lead-success p { color: var(--muted); }

/* ── Utility ── */
.spinner { display: inline-block; width: 18px; height: 18px; border: 2px solid #eee;
  border-top-color: var(--brand); border-radius: 50%; animation: spin 0.6s linear infinite; vertical-align: middle; }
@keyframes spin { to { transform: rotate(360deg); } }
.esc = { font-size: 11px; }
</style>
</head>
<body>

<nav class="nav">
  <a class="nav-logo" href="/">Designer Wallcoverings</a>
  <span class="nav-tag">See It On Your Wall</span>
  <span class="nav-spacer"></span>
  <span class="nav-tag" style="font-size:10px;opacity:0.6;">Links to the live Designer Wallcoverings product page.</span>
  <a class="nav-cta" href="https://www.designerwallcoverings.com" target="_blank" rel="noopener noreferrer">Shop All Patterns</a>
</nav>

<!-- Step progress bar -->
<div class="steps" id="steps">
  <button class="step-btn active" data-step="1" onclick="goStep(1)">
    <span class="step-num">Step 1</span>Choose a Room
  </button>
  <button class="step-btn" data-step="2" onclick="goStep(2)">
    <span class="step-num">Step 2</span>Pick a Pattern
  </button>
  <button class="step-btn" data-step="3" onclick="goStep(3)">
    <span class="step-num">Step 3</span>See It On Your Wall
  </button>
  <button class="step-btn" data-step="4" onclick="goStep(4)">
    <span class="step-num">Step 4</span>Shop the Look
  </button>
</div>

<!-- ── STEP 1: Room source ── -->
<div class="panel active" id="panel-1">
  <div class="room-source">
    <h2 class="section-title">Choose your room</h2>
    <p class="section-sub">Pick a stock room or upload your own photo. We'll apply your wallcovering to the wall.</p>

    <div class="room-tabs">
      <button class="room-tab active" onclick="showRoomTab('stock', this)">Stock Rooms</button>
      <button class="room-tab" onclick="showRoomTab('upload', this)">Upload My Room</button>
    </div>

    <!-- Stock rooms -->
    <div id="tab-stock">
      <div class="stock-rooms" id="stock-rooms-grid">
        <div class="empty-state">Loading rooms…</div>
      </div>
    </div>

    <!-- Upload tab -->
    <div id="tab-upload" style="display:none;">
      <div class="upload-zone" id="upload-zone">
        <span class="upload-icon">🏠</span>
        <h3>Upload a room photo</h3>
        <p>JPG, PNG — ideally a straight-on wall view<br>Max 8 MB</p>
        <input type="file" id="file-input" accept="image/*">
        <br><br>
        <button class="btn-ghost" onclick="document.getElementById('file-input').click()">Choose File</button>
      </div>
      <img id="upload-preview" alt="Room preview">
    </div>

    <div class="step-next">
      <button class="btn-primary" id="btn-to-step2" onclick="goStep(2)" disabled>Choose a Pattern →</button>
    </div>
  </div>
</div>

<!-- ── STEP 2: Pattern picker ── -->
<div class="panel" id="panel-2">
  <div class="pattern-picker">
    <div class="toolbar">
      <h2>Patterns</h2>
      <span class="count-badge" id="count-badge">…</span>
      <span class="toolbar-spacer"></span>
      <label>Sort
        <select id="sort-sel" onchange="onSortChange()">
          <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>
          <option value="light">Light → Dark</option>
          <option value="dark">Dark → Light</option>
        </select>
      </label>
      <label>Density <input type="range" id="density-slider" min="2" max="6" value="3" oninput="onDensityChange()"></label>
    </div>

    <div class="pattern-grid" id="pattern-grid">
      <div class="empty-state">Loading patterns…</div>
    </div>

    <div class="step-next" style="padding: 24px 0 8px;">
      <button class="btn-ghost" onclick="goStep(1)">← Back</button>
      <button class="btn-primary" id="btn-to-step3" onclick="goStep(3)" disabled>Preview on Wall →</button>
    </div>
  </div>
</div>

<!-- ── STEP 3: Render ── -->
<div class="panel" id="panel-3">
  <div class="render-panel">
    <div class="render-layout">
      <!-- Canvas -->
      <div>
        <div class="render-canvas-wrap">
          <canvas id="render-canvas" width="800" height="500"></canvas>
          <div class="render-badge">⚠ Preview render — prototype quality</div>
        </div>
        <div class="wall-zone-hint" style="margin-top: 12px;">
          <strong>Draw your wall zone:</strong> click and drag on the canvas to define the exact wall area, or use "Full Wall" below. A real compositing engine (ComfyUI / Replicate) plugs in at <code>/api/render</code>.
        </div>
      </div>

      <!-- Controls -->
      <div class="render-controls">
        <h3>Render Controls</h3>

        <div class="selected-pattern-info" id="selected-pattern-info">
          <div style="color: var(--muted); font-size: 13px;">No pattern selected</div>
        </div>

        <div class="ctrl-group">
          <span class="ctrl-label">Wall Zone</span>
          <div class="wall-mode-btns">
            <button class="wall-mode-btn active" id="btn-zone-full" onclick="setZoneFull()">Full Wall</button>
            <button class="wall-mode-btn" id="btn-zone-draw" onclick="setZoneDraw()">Draw Zone</button>
          </div>
        </div>

        <div class="ctrl-group">
          <span class="ctrl-label">Pattern Scale</span>
          <div class="ctrl-row">
            <input type="range" id="ctrl-scale" min="20" max="200" value="80" oninput="rerenderCanvas()">
            <span class="ctrl-val" id="ctrl-scale-val">80px</span>
          </div>
        </div>

        <div class="ctrl-group">
          <span class="ctrl-label">Blend Opacity</span>
          <div class="ctrl-row">
            <input type="range" id="ctrl-opacity" min="30" max="100" value="85" oninput="rerenderCanvas()">
            <span class="ctrl-val" id="ctrl-opacity-val">85%</span>
          </div>
        </div>

        <div class="ctrl-group">
          <span class="ctrl-label">Blend Mode</span>
          <select class="ctrl-select" id="ctrl-blend" onchange="rerenderCanvas()">
            <option value="multiply">Multiply (natural)</option>
            <option value="overlay">Overlay (vivid)</option>
            <option value="normal">Normal (flat)</option>
            <option value="soft-light">Soft Light</option>
            <option value="hard-light">Hard Light</option>
          </select>
        </div>

        <div class="ctrl-group">
          <span class="ctrl-label">Perspective Tint</span>
          <div class="ctrl-row">
            <input type="range" id="ctrl-shadow" min="0" max="60" value="18" oninput="rerenderCanvas()">
            <span class="ctrl-val" id="ctrl-shadow-val">18%</span>
          </div>
        </div>

        <button class="btn-primary" style="width:100%; margin-bottom: 10px;" onclick="goStep(4)">Shop This Look →</button>
        <button class="btn-ghost" style="width:100%;" onclick="goStep(2)">← Change Pattern</button>
      </div>
    </div>
  </div>
</div>

<!-- ── STEP 4: Shop the look ── -->
<div class="panel" id="panel-4">
  <div class="shop-panel">
    <div id="shop-form-wrap">
      <h2 class="section-title">Shop the Look</h2>
      <p class="section-sub" style="margin-bottom: 24px;">We'll send you a link to order this pattern, a sample, or schedule a design consultation.</p>

      <div class="look-preview" id="look-preview">
        <!-- Filled by JS -->
      </div>

      <form id="lead-form" onsubmit="submitLead(event)">
        <div class="form-group">
          <label for="lead-email">Email address *</label>
          <input type="email" id="lead-email" required placeholder="you@example.com">
        </div>
        <div class="form-group">
          <label for="lead-name">Your name</label>
          <input type="text" id="lead-name" placeholder="Optional">
        </div>
        <div class="form-group">
          <label for="lead-interest">I'm interested in…</label>
          <select id="lead-interest">
            <option value="order">Ordering full rolls</option>
            <option value="sample">Requesting a sample first</option>
            <option value="consult">Design consultation</option>
            <option value="quote">Getting a quote for a project</option>
          </select>
        </div>
        <div class="form-group">
          <label for="lead-message">Notes (optional)</label>
          <textarea id="lead-message" placeholder="Room dimensions, project timeline, any details…"></textarea>
        </div>
        <button class="btn-primary" type="submit" id="submit-btn" style="width:100%;">Send My Look →</button>
        <p class="shop-privacy" style="margin-top: 10px; text-align: center;">No spam. We'll reply within 1 business day.</p>
      </form>
    </div>

    <div class="lead-success" id="lead-success">
      <span class="check">✅</span>
      <h3>We've got your look!</h3>
      <p>Check your inbox — we'll send ordering details, a sample link, and any project notes.</p>
      <br>
      <div id="lead-success-product-link"></div>
      <br>
      <button class="btn-ghost" onclick="startOver()">Visualize another pattern</button>
    </div>

    <div style="margin-top: 24px; text-align: center;">
      <button class="btn-ghost" onclick="goStep(3)">← Back to Preview</button>
    </div>
  </div>
</div>

<script>
/* ── State ── */
const state = {
  selectedRoom: null,      // { id, label, src, wall_region } or { custom: true, dataUrl, label }
  selectedPattern: null,   // product object
  uploadedDataUrl: null,
  wallZone: null,          // { x, y, w, h } in canvas px — null = full wall
  drawingZone: false,
  drawStart: null,
};

/* ── Helpers ── */
const $ = s => document.querySelector(s);
const $$ = s => document.querySelectorAll(s);
function esc(s) { return (s||'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); }
function fmtPrice(p) { return p ? '$' + Number(p).toFixed(2) : ''; }

/* ── Steps ── */
function goStep(n) {
  $$('.panel').forEach(p => p.classList.remove('active'));
  $$('.step-btn').forEach(b => b.classList.remove('active'));
  $(`#panel-${n}`).classList.add('active');
  $(`.step-btn[data-step="${n}"]`).classList.add('active');
  // Mark completed steps
  $$('.step-btn').forEach(b => {
    const s = parseInt(b.dataset.step);
    b.classList.toggle('done', s < n);
  });
  if (n === 3) initRenderStep();
  if (n === 4) initShopStep();
  window.scrollTo(0, 0);
}

/* ── Step 1: Rooms ── */
async function loadRooms() {
  try {
    const r = await fetch('/api/rooms');
    const d = await r.json();
    renderStockRooms(d.rooms);
  } catch(e) {
    $('#stock-rooms-grid').innerHTML = '<div class="empty-state">Could not load rooms.</div>';
  }
}

function renderStockRooms(rooms) {
  $('#stock-rooms-grid').innerHTML = rooms.map(r => `
    <div class="room-card" id="room-${r.id}" onclick="selectRoom(${JSON.stringify(JSON.stringify(r))})">
      <img src="${esc(r.src)}" alt="${esc(r.label)}" loading="lazy" onerror="this.style.background='#e0ddd8';this.alt='Room image'">
      <div class="room-label">${esc(r.label)}</div>
    </div>`).join('');
}

function selectRoom(jsonStr) {
  const room = JSON.parse(jsonStr);
  state.selectedRoom = room;
  $$('.room-card').forEach(c => c.classList.remove('selected'));
  $(`#room-${room.id}`)?.classList.add('selected');
  state.uploadedDataUrl = null;
  $('#btn-to-step2').disabled = false;
}

function showRoomTab(tab, btn) {
  $$('.room-tab').forEach(b => b.classList.remove('active'));
  btn.classList.add('active');
  $('#tab-stock').style.display = tab === 'stock' ? '' : 'none';
  $('#tab-upload').style.display = tab === 'upload' ? '' : 'none';
}

// Upload handling
$('#upload-zone').addEventListener('dragover', e => { e.preventDefault(); e.currentTarget.classList.add('drag'); });
$('#upload-zone').addEventListener('dragleave', e => e.currentTarget.classList.remove('drag'));
$('#upload-zone').addEventListener('drop', e => { e.preventDefault(); e.currentTarget.classList.remove('drag'); handleFiles(e.dataTransfer.files); });
$('#file-input').addEventListener('change', e => handleFiles(e.target.files));

function handleFiles(files) {
  const file = files[0];
  if (!file || !file.type.startsWith('image/')) return;
  if (file.size > 8 * 1024 * 1024) { alert('Image too large (max 8 MB)'); return; }
  const reader = new FileReader();
  reader.onload = e => {
    const dataUrl = e.target.result;
    state.uploadedDataUrl = dataUrl;
    state.selectedRoom = { custom: true, dataUrl, label: 'My Room', wall_region: { x: 0, y: 0, w: 100, h: 65 } };
    $('#upload-preview').src = dataUrl;
    $('#upload-preview').style.display = 'block';
    $('#btn-to-step2').disabled = false;
    // Also upload to server (base64 JSON, zero-dep)
    const mime = file.type;
    fetch('/api/upload-room', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ data: dataUrl, mime })
    }).catch(() => {}); // non-blocking, client-side dataUrl is canonical
  };
  reader.readAsDataURL(file);
}

/* ── Step 2: Patterns ── */
let _products = [];

async function loadProducts() {
  const sort = localStorage.getItem('pattern-sort') || 'newest';
  $('#sort-sel').value = sort;
  applyDensity();
  try {
    const r = await fetch('/api/products?sort=' + encodeURIComponent(sort));
    const d = await r.json();
    _products = d.products || [];
    renderPatterns(_products);
    $('#count-badge').textContent = d.count + ' patterns';
  } catch(e) {
    $('#pattern-grid').innerHTML = '<div class="empty-state">Could not load patterns.</div>';
  }
}

function renderPatterns(products) {
  if (!products.length) {
    $('#pattern-grid').innerHTML = '<div class="empty-state">No patterns found.</div>';
    return;
  }
  $('#pattern-grid').innerHTML = products.map(p => {
    const imgSrc = p.image_url || p.image || '';
    const isSelected = state.selectedPattern && state.selectedPattern.sku === p.sku;
    return `<div class="pattern-card${isSelected ? ' selected' : ''}" id="pc-${esc(p.sku)}" onclick="selectPattern(${JSON.stringify(JSON.stringify(p))})">
      <img src="${esc(imgSrc)}" alt="${esc(p.title)}" loading="lazy" onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%22200%22 height=%22200%22><rect width=%22200%22 height=%22200%22 fill=%22%23e8e4dc%22/></svg>'">
      <div class="pattern-meta">
        <div class="pattern-title">${esc(p.title)}${p.sample_only ? '<span class="pattern-sample-badge">Sample only</span>' : ''}</div>
        <div class="pattern-sub">${esc(p.vendor || '')}</div>
        ${p.price_display ? `<div class="pattern-price">${esc(p.price_display)}${p.buyable ? '/roll' : ''}</div>` : ''}
      </div>
    </div>`;
  }).join('');
}

function selectPattern(jsonStr) {
  const p = JSON.parse(jsonStr);
  state.selectedPattern = p;
  $$('.pattern-card').forEach(c => c.classList.remove('selected'));
  $(`#pc-${CSS.escape(p.sku)}`)?.classList.add('selected');
  $('#btn-to-step3').disabled = false;
}

function onSortChange() {
  const v = $('#sort-sel').value;
  localStorage.setItem('pattern-sort', v);
  fetch('/api/products?sort=' + encodeURIComponent(v))
    .then(r => r.json())
    .then(d => { _products = d.products || []; renderPatterns(_products); $('#count-badge').textContent = d.count + ' patterns'; });
}

function onDensityChange() {
  const v = $('#density-slider').value;
  localStorage.setItem('pattern-cols', v);
  document.documentElement.style.setProperty('--cols', v);
}
function applyDensity() {
  const v = localStorage.getItem('pattern-cols') || '3';
  $('#density-slider').value = v;
  document.documentElement.style.setProperty('--cols', v);
}

/* ── Step 3: Canvas render ── */
const canvas = document.getElementById('render-canvas');
const ctx = canvas.getContext('2d');
let _roomImg = null;
let _patternImg = null;
let _drawingActive = false;
let _drawRect = null;

async function initRenderStep() {
  if (!state.selectedRoom || !state.selectedPattern) return;
  updateSelectedPatternInfo();
  await loadRoomImage();
  await loadPatternImage();
  // Set default wall zone from room's wall_region
  state.wallZone = null; // null = full wall from wall_region
  rerenderCanvas();
}

function updateSelectedPatternInfo() {
  const p = state.selectedPattern;
  if (!p) return;
  const imgSrc = p.image_url || p.image || '';
  $('#selected-pattern-info').innerHTML = `
    <img class="selected-pattern-thumb" src="${esc(imgSrc)}" alt="${esc(p.title)}"
      onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2252%22 height=%2252%22><rect width=%2252%22 height=%2252%22 fill=%22%23e8e4dc%22/></svg>'">
    <div class="selected-pattern-meta">
      <div class="title">${esc(p.title)}</div>
      <div class="sub">${esc(p.vendor || '')} · ${esc(p.color || '')}</div>
      <div class="sub" style="margin-top:2px;font-weight:600;color:var(--brand)">${esc(p.price_display || '')}${p.buyable ? '/roll' : ''}</div>
    </div>`;
}

function loadRoomImage() {
  return new Promise(resolve => {
    const img = new Image();
    img.crossOrigin = 'anonymous';
    const src = state.selectedRoom.custom ? state.selectedRoom.dataUrl : state.selectedRoom.src;
    img.onload = () => { _roomImg = img; resolve(); };
    img.onerror = () => { _roomImg = null; resolve(); };
    img.src = src;
  });
}

function loadPatternImage() {
  return new Promise(resolve => {
    const p = state.selectedPattern;
    const src = p.image_url || p.image || '';
    if (!src) { _patternImg = null; resolve(); return; }
    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.onload = () => { _patternImg = img; resolve(); };
    img.onerror = () => {
      // Fallback: solid color swatch from hex
      _patternImg = null;
      resolve();
    };
    img.src = src;
  });
}

function getWallRect() {
  // If user drew a zone, use that
  if (state.wallZone) return state.wallZone;
  // Otherwise use the room's default wall_region (in % → px)
  const wr = state.selectedRoom?.wall_region || { x: 0, y: 0, w: 100, h: 70 };
  return {
    x: Math.round(canvas.width * wr.x / 100),
    y: Math.round(canvas.height * wr.y / 100),
    w: Math.round(canvas.width * wr.w / 100),
    h: Math.round(canvas.height * wr.h / 100),
  };
}

function rerenderCanvas() {
  // Update control value displays
  $('#ctrl-scale-val').textContent = $('#ctrl-scale').value + 'px';
  $('#ctrl-opacity-val').textContent = $('#ctrl-opacity').value + '%';
  $('#ctrl-shadow-val').textContent = $('#ctrl-shadow').value + '%';

  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // Draw room base
  if (_roomImg) {
    ctx.drawImage(_roomImg, 0, 0, canvas.width, canvas.height);
  } else {
    // Fallback: plain room sketch
    ctx.fillStyle = '#e8e4dc';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = '#c8b89a';
    ctx.fillRect(0, 350, canvas.width, 150);
  }

  // Wall zone
  const wall = getWallRect();
  const tileSize = parseInt($('#ctrl-scale').value) || 80;
  const opacity = parseInt($('#ctrl-opacity').value) / 100;
  const blendMode = $('#ctrl-blend').value;
  const shadowPct = parseInt($('#ctrl-shadow').value) / 100;

  // Composite pattern onto wall zone
  ctx.save();
  ctx.beginPath();
  ctx.rect(wall.x, wall.y, wall.w, wall.h);
  ctx.clip();

  if (_patternImg) {
    // Create tiling pattern
    const offscreen = document.createElement('canvas');
    offscreen.width = tileSize;
    offscreen.height = tileSize;
    const oc = offscreen.getContext('2d');
    oc.drawImage(_patternImg, 0, 0, tileSize, tileSize);
    const pat = ctx.createPattern(offscreen, 'repeat');
    ctx.globalAlpha = opacity;
    ctx.globalCompositeOperation = blendMode;
    ctx.fillStyle = pat;
    ctx.fillRect(wall.x, wall.y, wall.w, wall.h);
    ctx.globalCompositeOperation = 'source-over';
    ctx.globalAlpha = 1;
  } else if (state.selectedPattern?.hex) {
    // Solid color fallback
    ctx.globalAlpha = opacity;
    ctx.globalCompositeOperation = blendMode;
    ctx.fillStyle = state.selectedPattern.hex;
    ctx.fillRect(wall.x, wall.y, wall.w, wall.h);
    ctx.globalCompositeOperation = 'source-over';
    ctx.globalAlpha = 1;
  }

  // Perspective shadow gradient (top lighter, bottom slightly darker — simulates ceiling/floor light)
  if (shadowPct > 0) {
    const grad = ctx.createLinearGradient(wall.x, wall.y, wall.x, wall.y + wall.h);
    grad.addColorStop(0, `rgba(255,255,255,${shadowPct * 0.3})`);
    grad.addColorStop(0.5, 'rgba(0,0,0,0)');
    grad.addColorStop(1, `rgba(0,0,0,${shadowPct * 0.4})`);
    ctx.globalCompositeOperation = 'source-over';
    ctx.fillStyle = grad;
    ctx.fillRect(wall.x, wall.y, wall.w, wall.h);
  }

  ctx.restore();

  // Draw zone outline if user-drawn
  if (state.wallZone) {
    ctx.save();
    ctx.strokeStyle = 'rgba(200,168,106,0.8)';
    ctx.lineWidth = 2;
    ctx.setLineDash([6, 3]);
    ctx.strokeRect(wall.x, wall.y, wall.w, wall.h);
    ctx.restore();
  }

  // Draw in-progress drag zone
  if (_drawRect) {
    ctx.save();
    ctx.strokeStyle = 'rgba(200,168,106,0.9)';
    ctx.lineWidth = 2;
    ctx.setLineDash([6, 3]);
    ctx.fillStyle = 'rgba(200,168,106,0.1)';
    ctx.fillRect(_drawRect.x, _drawRect.y, _drawRect.w, _drawRect.h);
    ctx.strokeRect(_drawRect.x, _drawRect.y, _drawRect.w, _drawRect.h);
    ctx.restore();
  }
}

// Zone drawing
function setZoneFull() {
  state.wallZone = null;
  _drawingActive = false;
  canvas.style.cursor = 'default';
  $('#btn-zone-full').classList.add('active');
  $('#btn-zone-draw').classList.remove('active');
  rerenderCanvas();
}
function setZoneDraw() {
  state.wallZone = null;
  _drawingActive = true;
  canvas.style.cursor = 'crosshair';
  $('#btn-zone-draw').classList.add('active');
  $('#btn-zone-full').classList.remove('active');
}

function canvasXY(e) {
  const rect = canvas.getBoundingClientRect();
  const scaleX = canvas.width / rect.width;
  const scaleY = canvas.height / rect.height;
  const src = e.touches ? e.touches[0] : e;
  return {
    x: (src.clientX - rect.left) * scaleX,
    y: (src.clientY - rect.top) * scaleY
  };
}

canvas.addEventListener('mousedown', e => {
  if (!_drawingActive) return;
  const p = canvasXY(e);
  state.drawStart = p;
  _drawRect = null;
});
canvas.addEventListener('mousemove', e => {
  if (!_drawingActive || !state.drawStart) return;
  const p = canvasXY(e);
  _drawRect = {
    x: Math.min(p.x, state.drawStart.x),
    y: Math.min(p.y, state.drawStart.y),
    w: Math.abs(p.x - state.drawStart.x),
    h: Math.abs(p.y - state.drawStart.y),
  };
  rerenderCanvas();
});
canvas.addEventListener('mouseup', e => {
  if (!_drawingActive || !_drawRect) return;
  if (_drawRect.w > 10 && _drawRect.h > 10) {
    state.wallZone = { ..._drawRect };
    _drawingActive = false;
    canvas.style.cursor = 'default';
    $('#btn-zone-draw').classList.remove('active');
    $('#btn-zone-full').classList.remove('active');
  }
  _drawRect = null;
  rerenderCanvas();
});

/* ── Step 4: Shop ── */
function initShopStep() {
  const p = state.selectedPattern;
  const r = state.selectedRoom;
  if (!p) return;
  const imgSrc = p.image_url || p.image || '';
  const roomSrc = r?.custom ? r.dataUrl : (r?.src || '');
  const productUrl = p.product_url || p.shopify_url || (p.handle ? `https://www.designerwallcoverings.com/products/${p.handle}` : '');
  const priceLabel = p.price_display ? (p.price_display + (p.buyable ? '/roll' : '')) : '';
  $('#look-preview').innerHTML = `
    <img class="look-room-thumb" src="${esc(roomSrc)}" alt="Room" onerror="this.style.display='none'">
    <img class="look-pattern-thumb" src="${esc(imgSrc)}" alt="${esc(p.title)}" onerror="this.style.background='${esc(p.hex||'#e8e4dc')}'">
    <div class="look-info">
      <h3>${esc(p.title)}</h3>
      <p>${esc(p.vendor || '')}${p.color ? ' · ' + esc(p.color) : ''}${p.collection ? ' · ' + esc(p.collection) : ''}</p>
      ${priceLabel ? `<p class="price">${esc(priceLabel)}</p>` : ''}
      ${productUrl ? `<p style="margin-top:6px;"><a href="${esc(productUrl)}" target="_blank" rel="noopener noreferrer" style="font-size:12px;color:var(--brand-light);text-decoration:underline;">View on Designer Wallcoverings →</a></p>` : ''}
    </div>`;
}

async function submitLead(e) {
  e.preventDefault();
  const btn = $('#submit-btn');
  btn.disabled = true;
  btn.innerHTML = '<span class="spinner"></span> Sending…';

  const p = state.selectedPattern;
  const r = state.selectedRoom;
  const productUrl = p?.product_url || p?.shopify_url || (p?.handle ? `https://www.designerwallcoverings.com/products/${p.handle}` : '');
  const body = {
    email: $('#lead-email').value.trim(),
    name: $('#lead-name').value.trim(),
    interest: $('#lead-interest').value,
    message: $('#lead-message').value.trim(),
    pattern_sku: p?.sku || null,
    pattern_title: p?.title || null,
    pattern_vendor: p?.vendor || null,
    pattern_handle: p?.handle || null,
    pattern_shopify_url: productUrl || null,
    room_label: r?.label || null,
  };

  try {
    const res = await fetch('/api/lead', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    });
    const d = await res.json();
    if (d.ok) {
      $('#shop-form-wrap').style.display = 'none';
      $('#lead-success').style.display = 'block';
      // Show direct product link after capture
      const linkEl = $('#lead-success-product-link');
      if (linkEl && productUrl) {
        linkEl.innerHTML = `<a href="${esc(productUrl)}" target="_blank" rel="noopener noreferrer" class="btn-primary" style="display:inline-block;text-decoration:none;margin-bottom:8px;">View This Pattern on Designer Wallcoverings →</a><br><small style="color:var(--muted);font-size:11px;">Links to the live Designer Wallcoverings product page.</small>`;
      }
    } else {
      btn.disabled = false;
      btn.textContent = 'Send My Look →';
      alert('Error: ' + (d.error || 'unknown'));
    }
  } catch(err) {
    btn.disabled = false;
    btn.textContent = 'Send My Look →';
    alert('Network error — please try again.');
  }
}

function startOver() {
  state.selectedRoom = null;
  state.selectedPattern = null;
  state.uploadedDataUrl = null;
  state.wallZone = null;
  $('#lead-success').style.display = 'none';
  $('#shop-form-wrap').style.display = 'block';
  $('#lead-form').reset();
  goStep(1);
}

/* ── Init ── */
loadRooms();
loadProducts();
applyDensity();
</script>
</body>
</html>