← back to Patterndesignlab
public/design.html
160 lines
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Pattern · Pattern Design Lab</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<header class="top"><div class="wrap">
<a class="brand" href="/">Pattern Design<span class="dot">.</span>Lab</a>
<form class="searchbox" onsubmit="event.preventDefault();var v=this.querySelector('input').value.trim();location.href='/'+(v?('?q='+encodeURIComponent(v)):'');return false;">
<input type="search" placeholder="Search patterns, styles, colorways…" autocomplete="off">
<button class="go" type="submit" aria-label="search">→</button>
</form>
<nav class="navlinks"><a href="/">Explore</a><a href="/trends">Trends</a><a href="/designer-signup">Sell your designs</a></nav>
</div></header>
<div class="wrap" id="root"><p style="padding:40px 0;color:#6b6b6b">Loading…</p></div>
<footer><div class="wrap">Pattern Design Lab · owned, settlement-passed originals only</div></footer>
<script>
const id = decodeURIComponent(location.pathname.split('/').pop());
let sel = null, design = null;
function esc(s){ return (s==null?'':String(s)).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c])); }
async function load(){
const r = await fetch('/api/designs/'+encodeURIComponent(id));
if(!r.ok){ document.getElementById('root').innerHTML='<p style="padding:40px 0">Pattern not found. <a href="/">Back to explore</a></p>'; return; }
design = await r.json();
const tiers = design.license_tiers||[];
sel = tiers[0] ? tiers[0].tier : null;
const tags = (design.tags||[]).slice(0,12);
document.title = design.title + ' · Pattern Design Lab';
document.getElementById('root').innerHTML = `
<div class="detail">
<div class="preview">
<div class="big" style="background-image:url('${design.img}')"></div>
<div class="wallwrap">
<div class="wall">
<div class="paper" id="paper" style="background-image:url('${design.img}');background-size:22% 22%"></div>
<div class="floor"></div><div class="sofa"></div><div class="lamp"></div><div class="shade"></div>
</div>
<div class="scalebar"><span style="font-size:12px;color:#6b6b6b">Repeat scale</span>
<input type="range" min="8" max="45" value="22" oninput="document.getElementById('paper').style.backgroundSize=this.value+'% '+this.value+'%'">
</div>
<p class="note">Live seamless tile-on-wall preview — the pattern repeats edge-to-edge. Drag to resize the repeat.</p>
</div>
</div>
<div class="info">
<h1 class="dtitle">${esc(design.title)}</h1>
<p class="dsub">${esc(design.style||'')} · by <a href="/designer/${design.designer_slug}" style="color:var(--accent-ink)">${esc(design.designer_name)}</a>${design.designer_country?' · '+esc(design.designer_country):''}</p>
${design.style_line?`<p style="color:#4a4a4a">${esc(design.style_line)}</p>`:''}
<div class="specs">
<div><div class="k">Colorway</div><div class="v"><span class="swatch" style="display:inline-block;background:${design.dominant_hex}"></span> ${esc(design.colorway||'—')}</div></div>
<div><div class="k">Motif</div><div class="v">${esc(design.motif||'—')}</div></div>
<div><div class="k">Technique</div><div class="v">${esc(design.technique||'—')}</div></div>
<div><div class="k">Seamless</div><div class="v">${design.seamless?'Yes — tiles edge-to-edge':'No — mural panel'}</div></div>
</div>
<h3 style="margin:22px 0 6px;font-size:15px">Choose a license</h3>
<div class="tiers" id="tiers">
${tiers.map(t=>`<div class="tier ${t.tier===sel?'sel':''}" data-tier="${t.tier}" onclick="pick('${t.tier}')">
<span class="tp">${esc(t.label||t.tier)}</span><span>$${t.priceUsd}</span></div>`).join('') || '<p class="note">Licensing tiers coming soon.</p>'}
</div>
<input id="email" type="email" placeholder="your@email.com (optional)" style="width:100%;height:42px;border:1px solid var(--line);border-radius:10px;padding:0 12px;margin-bottom:10px;font-size:14px">
<button class="buy" onclick="buy()" ${tiers.length?'':'disabled'}>License this pattern</button>
<p class="note" id="buynote"></p>
${tiers.length?`<h3 style="margin:22px 0 4px;font-size:15px">What each license grants</h3>
<div class="terms">${tiers.map(t=>`
<div class="titem">
<div class="th"><b>${esc(t.label||t.tier)}</b><span class="p">$${esc(t.priceUsd)}</span></div>
<div class="td">${esc(t.terms||'Contact us for full usage terms.')}</div>
</div>`).join('')}</div>`:''}
<div class="tagrow">${tags.map(t=>`<span class="tag">${esc(t)}</span>`).join('')}</div>
<div id="adminbar"></div>
</div>
</div>`;
renderAdminBar();
fetch('/api/config').then(r=>r.json()).then(c=>{
document.getElementById('buynote').textContent = c.checkoutReady
? 'Secure checkout — Stripe TEST mode (no real charge).'
: 'Checkout opens when payment is enabled; for now this records a licensing inquiry.';
});
}
function pick(t){ sel=t; document.querySelectorAll('.tier').forEach(e=>e.classList.toggle('sel', e.dataset.tier===t)); }
async function buy(){
const note=document.getElementById('buynote');
const r = await fetch('/api/license/checkout',{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({designId:design.id,tier:sel,email:document.getElementById('email').value})});
const j = await r.json();
if(j.url){ location.href=j.url; return; }
if(j.ok){ note.textContent='✓ '+(j.note||'Recorded — we\'ll be in touch.'); note.style.color='var(--accent-ink)'; }
else { note.textContent='Could not start checkout: '+(j.error||'error'); }
}
// ---- Admin bar: "add this design to the DW Shopify store as a DIG- item" ----
// Only renders when /api/admin/me confirms an admin session (cookie via /admin-login,
// or basic auth while the site is gated). Product is created DRAFT by default.
async function renderAdminBar(){
const el = document.getElementById('adminbar');
if(!el || !design) return;
let admin = false;
try { admin = (await (await fetch('/api/admin/me')).json()).admin; } catch {}
if(!admin){
el.innerHTML = `<p class="note" style="margin-top:26px"><a href="/admin-login?next=${encodeURIComponent(location.pathname)}" style="color:#9a9a9a">Admin</a></p>`;
return;
}
if(design.shopify_product_id){
el.innerHTML = `<div style="margin-top:26px;padding:14px;border:1px solid var(--line);border-radius:12px;background:#f7f5f8">
<b style="font-size:14px">Admin</b>
<p class="note" style="margin:6px 0 0">✓ In Shopify as <b>${esc(design.shopify_sku||'')}</b> ·
<a href="https://admin.shopify.com/store/designer-laboratory-sandbox/products/${esc(design.shopify_product_id)}" target="_blank" style="color:var(--accent-ink)">open in Shopify admin</a></p>
</div>`;
return;
}
const num = String(design.id).replace(/[^0-9]/g,'') || design.id;
el.innerHTML = `<div style="margin-top:26px;padding:14px;border:1px solid var(--line);border-radius:12px;background:#f7f5f8">
<b style="font-size:14px">Admin · add to Shopify (DW Bespoke Studio)</b>
<div style="display:flex;gap:8px;margin-top:10px;flex-wrap:wrap">
<input id="adm-sku" value="DIG-${esc(num)}" style="flex:1;min-width:130px;height:38px;border:1px solid var(--line);border-radius:8px;padding:0 10px;font-size:13px">
<input id="adm-price" type="number" step="0.01" value="${Number(design.price_min||149)}" title="price USD" style="width:90px;height:38px;border:1px solid var(--line);border-radius:8px;padding:0 10px;font-size:13px">
<select id="adm-status" style="height:38px;border:1px solid var(--line);border-radius:8px;font-size:13px">
<option value="draft" selected>Draft</option><option value="active">Active</option>
</select>
</div>
<button class="buy" style="margin-top:10px" onclick="pushShopify()">Add SKU to Shopify</button>
<p class="note" id="adm-note" style="margin-top:8px">Creates the product on the live DW store (Draft = not customer-visible until activated).</p>
</div>`;
}
async function pushShopify(){
const note = document.getElementById('adm-note');
const btn = event?.target; if(btn){ if(btn.disabled) return; btn.disabled = true; } // double-click = duplicate product
note.textContent = 'Pushing to Shopify…'; note.style.color = '';
try{
const r = await fetch('/api/admin/designs/'+encodeURIComponent(design.id)+'/shopify', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({ sku: document.getElementById('adm-sku').value.trim(),
price: Number(document.getElementById('adm-price').value),
status: document.getElementById('adm-status').value }) });
const j = await r.json();
if(j.ok){
design.shopify_product_id = j.productId; design.shopify_sku = j.sku;
renderAdminBar();
} else {
note.textContent = '✗ ' + (j.error||'push failed') + (j.detail?(' — '+JSON.stringify(j.detail).slice(0,200)):'');
note.style.color = '#b3261e';
if(btn) btn.disabled = false;
}
}catch(e){ note.textContent = '✗ '+e.message; note.style.color = '#b3261e'; if(btn) btn.disabled = false; }
}
load();
</script>
</body>
</html>