← back to Patterndesignlab
public/designer.html
148 lines
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Designer · Pattern Design Lab</title>
<link rel="stylesheet" href="/styles.css">
<style>
.dprofile{padding:28px 0 8px}
.dhead{display:flex;gap:24px;align-items:flex-start;flex-wrap:wrap}
.dphoto{width:120px;height:120px;border-radius:50%;object-fit:cover;background:#eee;flex:0 0 auto;box-shadow:0 2px 10px rgba(0,0,0,.12)}
.dlogo{max-height:34px;max-width:160px;object-fit:contain;margin-bottom:8px;display:block}
.dname{font-size:30px;line-height:1.1;margin:0 0 4px}
.dtag{color:#6b6b6b;font-style:italic;margin:0 0 6px}
.dmeta{font-size:13px;color:#6b6b6b;margin:2px 0}
.dbio{max-width:620px;margin:14px 0 4px;line-height:1.5}
.dactions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0 4px}
.btn{display:inline-flex;align-items:center;gap:6px;font-size:13px;border:1px solid #ddd;background:#fff;color:#222;
padding:7px 12px;border-radius:999px;cursor:pointer;text-decoration:none}
.btn:hover{background:#f6f6f6}
.btn.pri{background:var(--accent-ink,#1c1c1c);color:#fff;border-color:transparent}
.socials{display:flex;gap:10px;flex-wrap:wrap;margin:10px 0}
.socials a{font-size:12px;letter-spacing:.03em;text-transform:uppercase;color:#444;text-decoration:none;border-bottom:1px solid #ddd;padding-bottom:1px}
.socials a:hover{color:var(--accent-ink,#1c1c1c);border-color:currentColor}
.share{position:relative}
.sharemenu{position:absolute;top:110%;left:0;background:#fff;border:1px solid #e5e5e5;border-radius:12px;padding:6px;
box-shadow:0 8px 24px rgba(0,0,0,.12);display:none;z-index:20;min-width:170px}
.sharemenu.open{display:block}
.sharemenu a,.sharemenu button{display:block;width:100%;text-align:left;background:none;border:0;padding:8px 10px;font-size:13px;
color:#222;border-radius:8px;cursor:pointer;text-decoration:none}
.sharemenu a:hover,.sharemenu button:hover{background:#f4f4f4}
</style>
</head>
<body>
<header class="top"><div class="wrap">
<a class="brand" href="/">Pattern Design<span class="dot">.</span>Lab</a>
<nav class="navlinks"><a href="/">Explore</a> <a href="/designer-login">Designer sign in</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 slug = decodeURIComponent(location.pathname.split('/').pop());
function esc(s){ return (s==null?'':String(s)).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c])); }
function fmtPrice(lo){ return lo==null?'Inquire':'$'+lo; }
let DESIGNS = [];
function socialUrl(type,h){
if(!h) return null;
h=String(h).trim(); if(/^https?:\/\//.test(h)&&type!=='website') return h;
const bare=h.replace(/^@/,'');
return ({
website:h, instagram:'https://instagram.com/'+bare, pinterest:'https://pinterest.com/'+bare,
tiktok:'https://tiktok.com/@'+bare, facebook:'https://facebook.com/'+bare, twitter:'https://twitter.com/'+bare,
youtube:'https://youtube.com/@'+bare, linkedin:'https://linkedin.com/company/'+bare
})[type]||null;
}
const SOCIAL_LABELS={website:'Website',instagram:'Instagram',pinterest:'Pinterest',tiktok:'TikTok',facebook:'Facebook',twitter:'X',youtube:'YouTube',linkedin:'LinkedIn'};
function sortDesigns(list, mode){
const a=list.slice();
const cmp={ newest:(x,y)=>new Date(y.created_at||0)-new Date(x.created_at||0),
color:(x,y)=>String(x.dominant_hex||'').localeCompare(String(y.dominant_hex||'')),
style:(x,y)=>String(x.style||'').localeCompare(String(y.style||'')),
title_az:(x,y)=>String(x.title||'').localeCompare(String(y.title||'')),
price_up:(x,y)=>(x.price_min??1e9)-(y.price_min??1e9),
price_dn:(x,y)=>(y.price_min??-1)-(x.price_min??-1) }[mode]||(()=>0);
return a.sort(cmp);
}
function renderGrid(){
const mode=document.getElementById('sort').value;
localStorage.setItem('pdl.sort',mode);
const rows=sortDesigns(DESIGNS,mode);
document.getElementById('grid').innerHTML=rows.map(x=>`
<a class="card" href="/design/${x.slug}">
<div class="thumb"><div class="tile" style="background-image:url('${x.img}')"></div>
${x.seamless?'<span class="badge">Seamless</span>':'<span class="badge">Mural</span>'}</div>
<div class="cardbody"><h3>${esc(x.title)}</h3>
<div class="meta"><span class="swatch" style="background:${x.dominant_hex||'#ccc'}"></span><span>${esc(x.style||'')}</span><span>·</span><span>${esc(x.colorway||'')}</span></div>
<div class="price">${fmtPrice(x.price_min)}</div></div>
</a>`).join('');
}
function toggleShare(){ document.getElementById('sharemenu').classList.toggle('open'); }
async function copyLink(){ try{ await navigator.clipboard.writeText(location.href); alert('Link copied'); }catch{ prompt('Copy link:',location.href);} }
(async()=>{
const r=await fetch('/api/designers/'+encodeURIComponent(slug));
if(!r.ok){ document.getElementById('root').innerHTML='<p style="padding:40px 0">Designer not found. <a href="/">Explore</a></p>'; return; }
const {designer:d, designs, count}=await r.json();
DESIGNS=designs||[];
document.title=d.name+' · Pattern Design Lab';
// owner check → show Edit
let isOwner=false;
try{ const me=await fetch('/api/designer/me'); if(me.ok){ const j=await me.json(); isOwner=j.designer&&j.designer.slug===slug; } }catch{}
const loc=[d.city,d.state_region,d.country].filter(Boolean).join(', ');
const socials=Object.keys(SOCIAL_LABELS).map(t=>{const u=socialUrl(t,d[t]);return u?`<a href="${esc(u)}" target="_blank" rel="noopener noreferrer">${SOCIAL_LABELS[t]}</a>`:''}).join('');
const shareText=encodeURIComponent((d.name||'')+' on Pattern Design Lab');
const shareUrl=encodeURIComponent(location.href);
document.getElementById('root').innerHTML=`
<div class="dprofile">
<div class="dhead">
<img class="dphoto" src="${esc(d.avatar||'')}" alt="${esc(d.name)}" onerror="this.style.background='${d.accent_hex||'#ddd'}';this.removeAttribute('src')">
<div style="flex:1;min-width:260px">
${d.logo?`<img class="dlogo" src="${esc(d.logo)}" alt="${esc(d.name)} logo">`:''}
<h1 class="dname">${esc(d.name)}</h1>
${d.tagline?`<p class="dtag">${esc(d.tagline)}</p>`:''}
<p class="dmeta">${esc(loc)}${loc?' · ':''}${count} design${count===1?'':'s'}${d.founder_name?' · '+esc(d.founder_name):''}</p>
${socials?`<div class="socials">${socials}</div>`:''}
<div class="dactions">
<div class="share">
<button class="btn" onclick="toggleShare()">Share ▾</button>
<div class="sharemenu" id="sharemenu">
<button onclick="copyLink()">Copy link</button>
<a href="https://twitter.com/intent/tweet?url=${shareUrl}&text=${shareText}" target="_blank" rel="noopener noreferrer">Share on X</a>
<a href="https://www.facebook.com/sharer/sharer.php?u=${shareUrl}" target="_blank" rel="noopener noreferrer">Share on Facebook</a>
<a href="https://pinterest.com/pin/create/button/?url=${shareUrl}&description=${shareText}" target="_blank" rel="noopener noreferrer">Pin it</a>
<a href="https://www.linkedin.com/sharing/share-offsite/?url=${shareUrl}" target="_blank" rel="noopener noreferrer">Share on LinkedIn</a>
</div>
</div>
${isOwner?`<a class="btn pri" href="/designer-dashboard">✎ Edit your page</a>`:''}
</div>
${d.bio?`<p class="dbio">${esc(d.bio)}</p>`:''}
</div>
</div>
</div>
<div class="bar"><span class="count">${count} designs</span>
<div><label for="sort">Sort</label>
<select id="sort" onchange="renderGrid()">
<option value="newest">Newest</option><option value="color">Color</option>
<option value="style">Style</option><option value="title_az">Title A→Z</option>
<option value="price_up">Price ↑</option><option value="price_dn">Price ↓</option>
</select></div>
<div><label for="density">Density</label>
<input id="density" type="range" min="2" max="6" step="1" value="4" oninput="document.documentElement.style.setProperty('--cols',this.value);localStorage.setItem('pdl.density',this.value)"></div></div>
<div class="grid" id="grid"></div>
<div style="padding:24px 0"><a href="/" style="color:var(--accent-ink)">← Back to all patterns</a></div>`;
const ss=localStorage.getItem('pdl.sort'); if(ss){const el=document.getElementById('sort'); if(el&&[...el.options].some(o=>o.value===ss)) el.value=ss;}
const dd=localStorage.getItem('pdl.density'); if(dd){document.documentElement.style.setProperty('--cols',dd);const el=document.getElementById('density');if(el)el.value=dd;}
renderGrid();
document.addEventListener('click',e=>{ const s=document.getElementById('sharemenu'); if(s&&!e.target.closest('.share')) s.classList.remove('open'); });
})();
</script>
</body>
</html>