← back to Model Arena

data/artifacts/15fb25164101/claude-code.html

485 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Designer Wallcoverings — Shop by Color</title>
<style>
:root{
  /* ——— design tokens ——— */
  --bg:#0d0d0f;
  --surface:#17171c;
  --surface-2:#1e1e24;
  --ink:#f5f2ea;
  --muted:#8a8578;
  --accent:#c9a961;
  --accent-deep:#7d6a45;
  --line:rgba(201,169,97,.22);
  --hair:rgba(245,242,234,.10);
  --shadow:0 8px 24px rgba(0,0,0,.35);
  --shadow-lg:0 16px 48px rgba(0,0,0,.5);
  --glow:0 0 26px rgba(201,169,97,.28);
  --ease:cubic-bezier(.22,1,.36,1);
  --serif:'Cormorant Garamond','Didot','Bodoni MT','Playfair Display',Georgia,serif;
  --sans:-apple-system,'Segoe UI','Helvetica Neue',Arial,sans-serif;
  --s2:8px; --s3:12px; --s4:16px; --s6:24px; --s8:32px; --s12:48px; --s16:64px;
}
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
html,body{height:100%}
body{
  background:var(--bg);
  color:var(--ink);
  font-family:var(--sans);
  overflow-x:hidden;
  min-height:100vh;
  position:relative;
}
/* ambient drift */
body::before{
  content:"";
  position:fixed;inset:-30%;
  background:
    radial-gradient(40% 40% at 25% 20%, rgba(201,169,97,.08), transparent 70%),
    radial-gradient(45% 45% at 80% 75%, rgba(125,106,69,.10), transparent 72%);
  z-index:0;
  animation:drift 22s ease-in-out infinite alternate;
  pointer-events:none;
}
@keyframes drift{from{transform:translate3d(-2%,-1%,0) scale(1)}to{transform:translate3d(3%,2%,0) scale(1.08)}}

/* ——— top bar / wordmark lockup ——— */
.topbar{
  position:fixed;top:0;left:0;right:0;height:64px;z-index:20;
  display:flex;align-items:center;justify-content:space-between;
  padding:0 var(--s6);
  background:linear-gradient(180deg,rgba(13,13,15,.94),rgba(13,13,15,.72) 70%,transparent);
  backdrop-filter:blur(8px);
}
.lockup{
  max-width:220px;line-height:1;
  border-left:1px solid var(--line);
  padding-left:var(--s4);
}
.lockup .l1,.lockup .l2{
  font-family:var(--serif);
  font-weight:600;
  text-transform:uppercase;
  letter-spacing:.18em;
  color:var(--ink);
  display:block;
}
.lockup .l1{font-size:15px}
.lockup .l2{font-size:15px;color:var(--accent)}
.topbar .tag{
  font-family:var(--sans);
  font-size:10px;letter-spacing:.24em;text-transform:uppercase;
  color:var(--muted);
}

/* ——— main ——— */
main{
  position:relative;z-index:1;
  max-width:900px;margin:0 auto;
  padding:96px var(--s6) var(--s12);
  text-align:center;
}
.eyebrow{
  font-size:11px;letter-spacing:.28em;text-transform:uppercase;color:var(--accent);
  margin-bottom:var(--s4);
  animation:fadeUp .7s var(--ease) both;
}
h1{
  font-family:var(--serif);
  font-weight:500;
  font-size:clamp(38px,9vw,64px);
  line-height:1.02;
  letter-spacing:.01em;
  margin-bottom:var(--s3);
  animation:fadeUp .7s var(--ease) .06s both;
}
h1 em{font-style:italic;color:var(--accent)}
.lede{
  font-size:13px;letter-spacing:.22em;text-transform:uppercase;color:var(--muted);
  animation:fadeUp .7s var(--ease) .12s both;
}

/* ——— wheel ——— */
.wheel-stage{
  margin:var(--s12) auto var(--s8);
  width:min(80vw,340px);height:min(80vw,340px);
  position:relative;
  animation:fadeUp .8s var(--ease) .18s both;
}
.marker{
  position:absolute;top:-6px;left:50%;transform:translateX(-50%) rotate(45deg);
  width:14px;height:14px;
  background:var(--accent);
  box-shadow:var(--glow);
  z-index:5;border-radius:2px;
}
.marker::after{
  content:"";position:absolute;inset:4px;background:var(--bg);border-radius:1px;
}
.wheel{
  position:absolute;inset:0;border-radius:50%;
  cursor:grab;touch-action:none;
  box-shadow:var(--shadow-lg), inset 0 0 0 1px var(--hair);
  transition:transform .7s var(--ease);
}
.wheel.dragging{cursor:grabbing;transition:none}
.wheel::after{ /* inner ring */
  content:"";position:absolute;inset:14%;border-radius:50%;
  box-shadow:inset 0 0 0 1px rgba(245,242,234,.14), 0 0 40px rgba(0,0,0,.4) inset;
  pointer-events:none;
}
.hub{
  position:absolute;top:50%;left:50%;
  width:34%;height:34%;
  transform:translate(-50%,-50%);
  border-radius:50%;
  background:var(--surface);
  box-shadow:0 0 0 1px var(--line), 0 8px 22px rgba(0,0,0,.5), var(--glow);
  z-index:4;pointer-events:none;
  display:flex;align-items:center;justify-content:center;
  transition:background .6s var(--ease);
}
.hub-core{
  width:58%;height:58%;border-radius:50%;
  box-shadow:inset 0 2px 6px rgba(0,0,0,.4), 0 0 0 1px rgba(255,255,255,.14);
  transition:background .6s var(--ease);
}

/* ——— selection caption ——— */
.selection{
  margin-bottom:var(--s8);min-height:70px;
}
.selection .story{
  font-size:11px;letter-spacing:.28em;text-transform:uppercase;color:var(--muted);
  margin-bottom:6px;
}
.selection .fam{
  font-family:var(--serif);font-style:italic;font-weight:500;
  font-size:clamp(28px,7vw,40px);color:var(--accent);
  transition:color .5s var(--ease);
}

/* ——— filmstrip ——— */
.filmstrip{
  display:flex;gap:var(--s4);
  overflow-x:auto;scroll-snap-type:x mandatory;
  padding:var(--s2) 2px var(--s6);
  justify-content:flex-start;
  scrollbar-width:none;
}
.filmstrip::-webkit-scrollbar{display:none}
@media(min-width:760px){ .filmstrip{justify-content:center} }
.film-card{
  flex:0 0 auto;width:min(56vw,190px);
  scroll-snap-align:center;
  background:var(--surface);
  border:1px solid var(--hair);
  border-radius:14px;overflow:hidden;
  box-shadow:var(--shadow);
  transition:transform .25s var(--ease),box-shadow .25s var(--ease),border-color .25s var(--ease);
  animation:cardIn .6s var(--ease) both;
}
.film-card:hover{
  transform:translateY(-4px);
  box-shadow:var(--shadow-lg);
  border-color:var(--line);
}
.swatch{
  height:min(56vw,190px);width:100%;
  background-repeat:repeat;
  border-bottom:1px solid var(--hair);
}
.cap{padding:var(--s4) var(--s4) var(--s6);text-align:left}
.cap .pname{
  display:block;font-family:var(--serif);font-style:italic;
  font-size:15px;color:var(--muted);margin-bottom:5px;
}
.cap .cname{
  display:block;font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink);
}
.cap .rule{height:1px;background:var(--line);margin:var(--s3) 0 0;width:28px}

/* ——— share bar ——— */
.sharebar{
  margin-top:var(--s8);
  display:flex;gap:var(--s3);flex-wrap:wrap;justify-content:center;align-items:center;
  animation:fadeUp .7s var(--ease) .3s both;
}
.share-btn{
  appearance:none;background:transparent;color:var(--ink);
  border:1px solid var(--line);border-radius:999px;
  padding:11px 20px;
  font-family:var(--sans);font-size:11px;letter-spacing:.2em;text-transform:uppercase;
  cursor:pointer;
  transition:all .2s var(--ease);
  display:inline-flex;align-items:center;gap:8px;
}
.share-btn svg{width:13px;height:13px;stroke:var(--accent);fill:none;stroke-width:1.4}
.share-btn:hover{background:var(--accent);color:var(--bg);border-color:var(--accent);box-shadow:var(--glow)}
.share-btn:hover svg{stroke:var(--bg)}
.share-btn.copied{background:var(--accent-deep);color:var(--ink);border-color:var(--accent-deep)}

footer{
  margin-top:var(--s12);text-align:center;
}
footer .mark{
  font-family:var(--serif);text-transform:uppercase;letter-spacing:.28em;
  font-size:12px;color:var(--muted);
}
footer .mark b{color:var(--accent);font-weight:600}

@keyframes fadeUp{from{opacity:0;transform:translateY(28px)}to{opacity:1;transform:none}}
@keyframes cardIn{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:none}}
</style>
</head>
<body>

<header class="topbar">
  <div class="lockup">
    <span class="l1">Designer</span>
    <span class="l2">Wallcoverings</span>
  </div>
  <span class="tag">Est. Atelier</span>
</header>

<main>
  <p class="eyebrow">The Color Wheel</p>
  <h1>Shop by <em>Color</em></h1>
  <p class="lede">Turn the wheel · tap a hue</p>

  <div class="wheel-stage">
    <div class="marker"></div>
    <div class="wheel" id="wheel"></div>
    <div class="hub"><div class="hub-core" id="hubCore"></div></div>
  </div>

  <div class="selection">
    <div class="story">Color Story</div>
    <div class="fam" id="famName">Celadon</div>
  </div>

  <div class="filmstrip" id="filmstrip"></div>

  <div class="sharebar">
    <button class="share-btn" data-act="share">
      <svg viewBox="0 0 24 24"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><path d="M8.6 13.5l6.8 4M15.4 6.5l-6.8 4"/></svg>Share
    </button>
    <button class="share-btn" data-act="save">
      <svg viewBox="0 0 24 24"><path d="M5 3h14v18l-7-5-7 5z"/></svg>Save
    </button>
    <button class="share-btn" data-act="pin">
      <svg viewBox="0 0 24 24"><path d="M12 2a7 7 0 0 1 7 7c0 5-7 13-7 13S5 14 5 9a7 7 0 0 1 7-7z"/><circle cx="12" cy="9" r="2.4"/></svg>Pin
    </button>
    <button class="share-btn" data-act="copy" id="copyBtn">
      <svg viewBox="0 0 24 24"><path d="M10 13a5 5 0 0 0 7 0l2-2a5 5 0 0 0-7-7l-1 1"/><path d="M14 11a5 5 0 0 0-7 0l-2 2a5 5 0 0 0 7 7l1-1"/></svg>Copy Link
    </button>
  </div>

  <footer>
    <div class="mark">Designer <b>Wallcoverings</b></div>
  </footer>
</main>

<script>
/* ——— color families around the wheel ——— */
const FAMILIES = [
  {name:'Celadon',   h:155,s:40,l:45, names:['Celadon','Verdigris','Sage','Malachite']},
  {name:'Peacock',   h:185,s:42,l:42, names:['Peacock','Lagoon','Viridian','Juniper']},
  {name:'Cerulean',  h:210,s:45,l:44, names:['Cerulean','Prussian','Delft','Slate']},
  {name:'Iris',      h:235,s:40,l:46, names:['Midnight','Iris','Ink','Twilight']},
  {name:'Wisteria',  h:270,s:34,l:47, names:['Wisteria','Aubergine','Heather','Plum']},
  {name:'Blush',     h:335,s:42,l:53, names:['Blush','Rosewood','Coral','Dusk']},
  {name:'Carmine',   h:353,s:48,l:47, names:['Carmine','Oxblood','Vermilion','Claret']},
  {name:'Terracotta',h:25, s:52,l:49, names:['Ochre','Terracotta','Sienna','Saffron']},
  {name:'Champagne', h:43, s:50,l:53, names:['Champagne','Honey','Brass','Chamois']},
  {name:'Citron',    h:70, s:36,l:47, names:['Citron','Absinthe','Moss','Olive']},
  {name:'Oatmeal',   h:35, s:12,l:56, names:['Oatmeal','Greige','Alabaster','Taupe']},
  {name:'Pewter',    h:220,s:6, l:32, names:['Pewter','Graphite','Onyx','Ebony']},
];
const N = FAMILIES.length;
const SEG = 360 / N;

const mid = f => `hsl(${f.h} ${f.s}% ${f.l}%)`;
const tones = f => ([
  `hsl(${f.h} ${Math.round(f.s*0.7)}% ${Math.round(f.l*0.30)}%)`,
  `hsl(${f.h} ${Math.round(f.s*0.9)}% ${Math.round(f.l*0.50)}%)`,
  `hsl(${f.h} ${Math.round(f.s*0.7)}% ${Math.round(f.l*0.72)}%)`,
  `hsl(${f.h} ${Math.round(f.s*0.45)}% ${Math.round(f.l*0.90)}%)`,
]);

/* ——— CSS-drawn wallpaper patterns ——— */
const PATTERNS = [
  { name:'Damask', build:p=>({
      backgroundColor:p[0],
      backgroundImage:[
        `radial-gradient(ellipse 24% 30% at 50% 18%, ${p[3]} 0 42%, transparent 46%)`,
        `radial-gradient(ellipse 15% 22% at 50% 52%, ${p[2]} 0 44%, transparent 48%)`,
        `radial-gradient(ellipse 8% 13% at 50% 82%, ${p[3]} 0 50%, transparent 54%)`,
        `radial-gradient(circle at 50% 50%, ${p[1]} 0 7%, transparent 8%)`
      ].join(','),
      backgroundSize:'64px 96px', backgroundPosition:'center'
  })},
  { name:'Regency Stripe', build:p=>({
      backgroundImage:`repeating-linear-gradient(90deg, ${p[0]} 0 15px, ${p[2]} 15px 21px, ${p[3]} 21px 25px, ${p[2]} 25px 31px)`
  })},
  { name:'Trellis', build:p=>({
      backgroundColor:p[0],
      backgroundImage:[
        `repeating-linear-gradient(45deg, transparent 0 20px, ${p[3]} 20px 22px)`,
        `repeating-linear-gradient(-45deg, transparent 0 20px, ${p[3]} 20px 22px)`,
        `radial-gradient(circle at 50% 50%, ${p[1]} 0 12%, transparent 14%)`
      ].join(','),
      backgroundSize:'auto,auto,28px 28px'
  })},
  { name:'Sprig', build:p=>({
      backgroundColor:p[3],
      backgroundImage:[
        `radial-gradient(circle at 50% 50%, ${p[1]} 0 16%, transparent 19%)`,
        `radial-gradient(circle at 50% 50%, ${p[2]} 0 10%, transparent 13%)`
      ].join(','),
      backgroundSize:'28px 28px,28px 28px',
      backgroundPosition:'0 0,14px 14px'
  })},
];

/* ——— build the wheel conic gradient ——— */
const wheel = document.getElementById('wheel');
const stops = FAMILIES.map((f,i)=>`${mid(f)} ${i*SEG}deg ${(i+1)*SEG}deg`).join(',');
wheel.style.background = `conic-gradient(from ${-SEG/2}deg, ${stops})`;

const famName = document.getElementById('famName');
const hubCore = document.getElementById('hubCore');
const strip   = document.getElementById('filmstrip');

let rotation = 0;      // current wheel rotation (deg, clockwise +)
let selected = -1;

function idxFromRotation(r){
  return ((Math.round(-r/SEG) % N) + N) % N;
}

function render(idx){
  if(idx === selected) return;
  selected = idx;
  const f = FAMILIES[idx];
  const p = tones(f);
  famName.textContent = f.name;
  famName.style.color = `hsl(${f.h} ${Math.min(f.s+8,60)}% ${Math.max(f.l+8,55)}%)`;
  hubCore.style.background = mid(f);
  document.querySelector('.hub').style.background = `hsl(${f.h} ${Math.round(f.s*0.4)}% 14%)`;

  strip.innerHTML = '';
  PATTERNS.forEach((pat,i)=>{
    const card = document.createElement('div');
    card.className = 'film-card';
    card.style.animationDelay = (i*0.09)+'s';
    const sw = document.createElement('div');
    sw.className = 'swatch';
    Object.assign(sw.style, pat.build(p));
    const cap = document.createElement('div');
    cap.className = 'cap';
    cap.innerHTML = `<span class="pname">${pat.name}</span>
                     <span class="cname">${f.names[i]}</span>
                     <span class="rule"></span>`;
    card.append(sw,cap);
    strip.append(card);
  });
}

function applyRotation(animate){
  wheel.classList.toggle('dragging', !animate);
  wheel.style.transform = `rotate(${rotation}deg)`;
}

/* ——— pointer interaction: drag to spin, tap to select ——— */
let dragging=false, startAng=0, startRot=0, moved=0, cx=0, cy=0, downAng=0;

function angleAt(e){
  const r = wheel.getBoundingClientRect();
  cx = r.left + r.width/2; cy = r.top + r.height/2;
  const x = (e.touches?e.touches[0].clientX:e.clientX) - cx;
  const y = (e.touches?e.touches[0].clientY:e.clientY) - cy;
  return Math.atan2(x, -y) * 180/Math.PI; // clockwise from top
}

function down(e){
  dragging=true; moved=0;
  downAng = startAng = angleAt(e);
  startRot = rotation;
  wheel.classList.add('dragging');
}
function move(e){
  if(!dragging) return;
  e.preventDefault();
  let a = angleAt(e);
  let d = a - startAng;
  if(d>180) d-=360; if(d<-180) d+=360;
  moved += Math.abs(d);
  rotation = startRot + (a - downAng);
  // normalise for continuity
  let cont = a - downAng;
  if(cont>180) cont-=360; if(cont<-180) cont+=360;
  rotation = startRot + cont;
  applyRotation(false);
  render(idxFromRotation(rotation));
  startAng = a;
}
function up(e){
  if(!dragging) return;
  dragging=false;
  if(moved < 6){
    // treat as tap on a segment
    const a = downAng;                       // screen angle of tap
    const local = a - (rotation % 360);
    const j = ((Math.round(local/SEG) % N)+N)%N;
    rotation = -j*SEG;
  } else {
    // snap to nearest segment
    rotation = Math.round(rotation/SEG)*SEG;
  }
  applyRotation(true);
  render(idxFromRotation(rotation));
}

wheel.addEventListener('mousedown',down);
window.addEventListener('mousemove',move);
window.addEventListener('mouseup',up);
wheel.addEventListener('touchstart',down,{passive:true});
window.addEventListener('touchmove',move,{passive:false});
window.addEventListener('touchend',up);

/* keyboard for accessibility */
wheel.tabIndex=0;
wheel.addEventListener('keydown',e=>{
  if(e.key==='ArrowLeft'||e.key==='ArrowUp'){ rotation += SEG; applyRotation(true); render(idxFromRotation(rotation)); }
  if(e.key==='ArrowRight'||e.key==='ArrowDown'){ rotation -= SEG; applyRotation(true); render(idxFromRotation(rotation)); }
});

/* ——— share bar ——— */
document.querySelectorAll('.share-btn').forEach(btn=>{
  btn.addEventListener('click',()=>{
    const act = btn.dataset.act;
    if(act==='copy'){
      const txt = `Designer Wallcoverings — Shop by Color: ${FAMILIES[selected].name}`;
      if(navigator.clipboard) navigator.clipboard.writeText(txt).catch(()=>{});
      btn.classList.add('copied');
      const label = btn.childNodes[btn.childNodes.length-1];
      const prev = label.textContent;
      label.textContent = 'Copied';
      setTimeout(()=>{btn.classList.remove('copied');label.textContent=prev;},1400);
    } else if(act==='share' && navigator.share){
      navigator.share({title:'Designer Wallcoverings',text:`Color story: ${FAMILIES[selected].name}`}).catch(()=>{});
    }
  });
});

/* init */
applyRotation(true);
render(0);
</script>
</body>
</html>