← back to Model Arena

data/artifacts/15fb25164101/grok.html

719 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shop by Color — Designer Wallcoverings</title>
<style>
:root {
  --bg: #0d0d0f;
  --surface: #17171c;
  --surface-2: #1e1e24;
  --ink: #f5f2ea;
  --muted: #8a8578;
  --accent: #c9a961;
  --accent-dim: #7d6a45;
  --line: rgba(245, 242, 234, 0.12);
  --line-strong: rgba(245, 242, 234, 0.22);
  --shadow: 0 12px 40px rgba(0, 0, 0, 0.45);
  --shadow-soft: 0 4px 20px rgba(0, 0, 0, 0.3);
  --ease: cubic-bezier(.22, .9, .24, 1);
  --ease-out: cubic-bezier(.22, 1, .36, 1);
  --ease-power: cubic-bezier(.77, 0, .18, 1);
  --serif: 'Didot', 'Bodoni MT', 'Playfair Display', Georgia, 'Times New Roman', serif;
  --sans: 'Avenir Next', 'Century Gothic', Futura, 'Segoe UI', sans-serif;
  --radius: 14px;
  --radius-sm: 6px;
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;
  --hue: 30;
  --sat: 42%;
  --lit: 55%;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  height: 100%;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

/* ── Fixed top bar ── */
.topbar {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px var(--space-5);
  background: rgba(13, 13, 15, 0.85);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-bottom: 1px solid var(--line);
}

.wordmark {
  font-family: var(--serif);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink);
  max-width: 220px;
  white-space: nowrap;
  line-height: 1.3;
}

.topbar-meta {
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ── Main ── */
main {
  min-height: 100%;
  padding: 72px var(--space-5) var(--space-7);
  display: flex;
  flex-direction: column;
  align-items: center;
  max-width: 720px;
  margin: 0 auto;
}

.intro {
  text-align: center;
  margin-bottom: var(--space-6);
  animation: fadeUp 0.8s var(--ease-out) both;
}

.eyebrow {
  font-family: var(--sans);
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: var(--space-3);
}

.intro h1 {
  font-family: var(--serif);
  font-size: 38px;
  font-weight: 400;
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--ink);
  margin-bottom: var(--space-3);
}

.intro p {
  font-size: 15px;
  color: var(--muted);
  max-width: 36ch;
  margin: 0 auto;
  line-height: 1.6;
}

/* ── Wheel stage ── */
.wheel-stage {
  position: relative;
  width: min(300px, 78vw);
  aspect-ratio: 1;
  margin: 0 auto var(--space-6);
  animation: scaleIn 0.9s var(--ease-out) 0.15s both;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}

.wheel-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: conic-gradient(
    from 0deg,
    hsl(0, 55%, 52%),
    hsl(30, 58%, 52%),
    hsl(60, 52%, 48%),
    hsl(90, 48%, 44%),
    hsl(120, 45%, 42%),
    hsl(150, 48%, 42%),
    hsl(180, 50%, 44%),
    hsl(210, 55%, 50%),
    hsl(240, 52%, 54%),
    hsl(270, 50%, 52%),
    hsl(300, 48%, 50%),
    hsl(330, 52%, 52%),
    hsl(0, 55%, 52%)
  );
  box-shadow: var(--shadow), inset 0 0 0 1px var(--line);
  cursor: grab;
}

.wheel-ring:active { cursor: grabbing; }

.wheel-inner {
  position: absolute;
  inset: 18%;
  border-radius: 50%;
  background: var(--bg);
  box-shadow: inset 0 0 0 1px var(--line), 0 0 0 1px var(--line);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: 6px;
  pointer-events: none;
}

.swatch-preview {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: hsl(var(--hue), var(--sat), var(--lit));
  box-shadow: 0 0 0 1px var(--line-strong), var(--shadow-soft);
  transition: background 0.35s var(--ease);
}

.hue-label {
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
  transition: color 0.3s var(--ease);
}

.pointer {
  position: absolute;
  top: 4px;
  left: 50%;
  width: 3px;
  height: 18px;
  margin-left: -1.5px;
  background: var(--ink);
  border-radius: 2px;
  box-shadow: 0 1px 4px rgba(0,0,0,0.5);
  z-index: 2;
  pointer-events: none;
}

.pointer::after {
  content: '';
  position: absolute;
  bottom: -5px;
  left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: var(--ink);
}

/* ── Active family name ── */
.family-name {
  text-align: center;
  margin-bottom: var(--space-5);
  min-height: 2.4em;
  animation: fadeUp 0.7s var(--ease-out) 0.25s both;
}

.family-name h2 {
  font-family: var(--serif);
  font-size: 28px;
  font-weight: 400;
  letter-spacing: -0.01em;
  color: var(--ink);
  transition: opacity 0.3s var(--ease);
}

.family-name span {
  display: block;
  font-family: var(--sans);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 6px;
}

/* ── Filmstrip ── */
.filmstrip-wrap {
  width: 100%;
  overflow: hidden;
  margin-bottom: var(--space-7);
  animation: fadeUp 0.8s var(--ease-out) 0.35s both;
}

.filmstrip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-3);
  transition: opacity 0.28s var(--ease), transform 0.35s var(--ease-out);
}

.filmstrip.is-shifting {
  opacity: 0;
  transform: translateY(10px);
}

.tile {
  position: relative;
  aspect-ratio: 3 / 4;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-soft);
  cursor: pointer;
  transition: transform 0.4s var(--ease), box-shadow 0.4s var(--ease), border-color 0.3s var(--ease);
}

.tile:hover, .tile:focus-visible {
  transform: translateY(-4px);
  box-shadow: var(--shadow);
  border-color: var(--line-strong);
  outline: none;
}

.tile:active { transform: translateY(-1px); }

.tile-pattern {
  position: absolute;
  inset: 0;
  transition: background 0.4s var(--ease);
}

/* CSS pattern variants */
.pat-damask {
  background-color: var(--c1);
  background-image:
    radial-gradient(ellipse 40% 30% at 50% 30%, var(--c2) 0%, transparent 70%),
    radial-gradient(ellipse 30% 40% at 50% 70%, var(--c3) 0%, transparent 65%),
    radial-gradient(circle at 50% 50%, transparent 28%, var(--c2) 29%, var(--c2) 31%, transparent 32%);
}

.pat-herringbone {
  background-color: var(--c1);
  background-image:
    repeating-linear-gradient(
      45deg,
      var(--c2) 0 2px,
      transparent 2px 10px
    ),
    repeating-linear-gradient(
      -45deg,
      var(--c3) 0 2px,
      transparent 2px 10px
    );
}

.pat-toile {
  background-color: var(--c1);
  background-image:
    radial-gradient(circle at 25% 25%, var(--c2) 1.5px, transparent 2px),
    radial-gradient(circle at 75% 75%, var(--c2) 1.5px, transparent 2px),
    radial-gradient(circle at 50% 50%, var(--c3) 8px, transparent 9px),
    radial-gradient(circle at 20% 70%, var(--c3) 5px, transparent 6px),
    radial-gradient(circle at 80% 30%, var(--c3) 5px, transparent 6px);
  background-size: 28px 28px, 28px 28px, 56px 56px, 56px 56px, 56px 56px;
}

.pat-grasscloth {
  background-color: var(--c1);
  background-image:
    repeating-linear-gradient(
      90deg,
      transparent,
      transparent 3px,
      var(--c2) 3px,
      var(--c2) 4px
    ),
    repeating-linear-gradient(
      0deg,
      transparent,
      transparent 1px,
      var(--c3) 1px,
      var(--c3) 2px
    );
  background-size: 8px 100%, 100% 6px;
}

.tile-meta {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 28px 10px 10px;
  background: linear-gradient(to top, rgba(0,0,0,0.72) 0%, transparent 100%);
  pointer-events: none;
}

.tile-name {
  font-family: var(--serif);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.01em;
  color: var(--ink);
  line-height: 1.2;
}

.tile-tone {
  display: block;
  margin-top: 3px;
  font-family: var(--sans);
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(245, 242, 234, 0.65);
}

/* ── Share bar ── */
.share-bar {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding-top: var(--space-5);
  border-top: 1px solid var(--line);
  animation: fadeUp 0.8s var(--ease-out) 0.45s both;
}

.share-label {
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
  margin-right: var(--space-2);
}

.share-btn {
  appearance: none;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 10px 16px;
  border-radius: 999px;
  cursor: pointer;
  transition: border-color 0.3s var(--ease), background 0.3s var(--ease), color 0.3s var(--ease), transform 0.25s var(--ease);
  box-shadow: var(--shadow-soft);
}

.share-btn:hover, .share-btn:focus-visible {
  border-color: var(--accent);
  color: var(--accent);
  outline: none;
}

.share-btn:active { transform: scale(0.97); }

.share-btn.copied {
  border-color: var(--accent);
  background: rgba(201, 169, 97, 0.12);
  color: var(--accent);
}

.brand-foot {
  margin-top: var(--space-6);
  text-align: center;
  font-family: var(--serif);
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  animation: fadeUp 0.8s var(--ease-out) 0.55s both;
}

.brand-foot em {
  font-style: normal;
  color: var(--accent-dim);
}

/* ── Motion ── */
@keyframes fadeUp {
  from { opacity: 0; transform: translateY(28px); }
  to   { opacity: 1; transform: none; }
}
@keyframes scaleIn {
  from { opacity: 0; transform: scale(0.92); }
  to   { opacity: 1; transform: none; }
}

/* ── Responsive ── */
@media (max-width: 480px) {
  .intro h1 { font-size: 28px; }
  .family-name h2 { font-size: 22px; }
  .filmstrip { gap: 8px; }
  .tile-name { font-size: 11px; }
  .share-bar { flex-wrap: wrap; }
  main { padding-left: 16px; padding-right: 16px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}
</style>
</head>
<body>

<header class="topbar">
  <div class="wordmark">Designer Wallcoverings</div>
  <div class="topbar-meta">Shop by Color</div>
</header>

<main>
  <div class="intro">
    <div class="eyebrow">Curated Palettes</div>
    <h1>Shop by Color</h1>
    <p>Turn the wheel to explore pattern families. Four wallcovering designs in every hue.</p>
  </div>

  <div class="wheel-stage" id="wheelStage" aria-label="Color wheel — drag or tap to select a hue">
    <div class="pointer" aria-hidden="true"></div>
    <div class="wheel-ring" id="wheelRing"></div>
    <div class="wheel-inner">
      <div class="swatch-preview" id="swatchPreview"></div>
      <div class="hue-label" id="hueLabel">Amber</div>
    </div>
  </div>

  <div class="family-name">
    <h2 id="familyTitle">Gilded Amber</h2>
    <span id="familySub">Warm metallics · Four designs</span>
  </div>

  <div class="filmstrip-wrap">
    <div class="filmstrip" id="filmstrip" role="list"></div>
  </div>

  <div class="share-bar">
    <span class="share-label">Share</span>
    <button type="button" class="share-btn" id="btnCopy" aria-label="Copy link">Copy Link</button>
    <button type="button" class="share-btn" id="btnSave" aria-label="Save palette">Save Palette</button>
  </div>

  <div class="brand-foot">Designer Wallcoverings <em>·</em> Est. Atelier</div>
</main>

<script>
(function () {
  const FAMILIES = [
    { h: 8,   name: 'Vermilion',   title: 'Ember Rouge',      sub: 'Fiery reds · Four designs',
      colors: ['Scarlet Atelier', 'Russet Vein', 'Cinnabar Cloth', 'Garnet Loom'],
      sats: [48, 42, 38, 44], lits: [48, 38, 32, 42] },
    { h: 28,  name: 'Amber',       title: 'Gilded Amber',     sub: 'Warm metallics · Four designs',
      colors: ['Honey Leaf', 'Bronze Damask', 'Saffron Thread', 'Ochre Field'],
      sats: [52, 40, 48, 36], lits: [52, 42, 48, 38] },
    { h: 48,  name: 'Champagne',   title: 'Sunlit Straw',     sub: 'Golden neutrals · Four designs',
      colors: ['Wheat Sheaf', 'Pale Brass', 'Flax Linen', 'Marigold Dust'],
      sats: [38, 30, 28, 42], lits: [58, 50, 62, 48] },
    { h: 78,  name: 'Olive',       title: 'Botanical Olive',  sub: 'Garden greens · Four designs',
      colors: ['Moss Canopy', 'Chartreuse Silk', 'Fern Archive', 'Lichen Wall'],
      sats: [32, 40, 28, 24], lits: [38, 48, 34, 42] },
    { h: 145, name: 'Jade',        title: 'Deep Verdure',     sub: 'Forest tones · Four designs',
      colors: ['Emerald Grove', 'Tea Leaf', 'Malachite', 'Willow Shade'],
      sats: [40, 28, 36, 22], lits: [36, 44, 32, 40] },
    { h: 175, name: 'Teal',        title: 'Aegean Tide',      sub: 'Coastal blues · Four designs',
      colors: ['Seaglass', 'Petrol Damask', 'Celadon Mist', 'Harbor Ink'],
      sats: [36, 42, 28, 38], lits: [48, 34, 56, 30] },
    { h: 210, name: 'Indigo',      title: 'Midnight Indigo',  sub: 'Deep blues · Four designs',
      colors: ['Cobalt Archive', 'Slate Navy', 'Powder Blue', 'Ink Well'],
      sats: [48, 32, 36, 40], lits: [42, 36, 58, 28] },
    { h: 255, name: 'Violet',      title: 'Amethyst Hour',    sub: 'Jewel violets · Four designs',
      colors: ['Iris Veil', 'Plum Damask', 'Wisteria', 'Aubergine'],
      sats: [38, 34, 30, 36], lits: [50, 36, 58, 30] },
    { h: 310, name: 'Rose',        title: 'Blush Nocturne',   sub: 'Soft magentas · Four designs',
      colors: ['Peony Silk', 'Mauve Archive', 'Orchid Dust', 'Berry Cloth'],
      sats: [36, 28, 32, 40], lits: [56, 46, 60, 40] },
    { h: 345, name: 'Crimson',     title: 'Burgundy Salon',   sub: 'Wine reds · Four designs',
      colors: ['Bordeaux', 'Rosewood', 'Claret Weave', 'Port Velvet'],
      sats: [44, 36, 40, 42], lits: [38, 34, 42, 30] }
  ];

  const PATTERNS = ['pat-damask', 'pat-herringbone', 'pat-toile', 'pat-grasscloth'];

  const root = document.documentElement;
  const filmstrip = document.getElementById('filmstrip');
  const swatchPreview = document.getElementById('swatchPreview');
  const hueLabel = document.getElementById('hueLabel');
  const familyTitle = document.getElementById('familyTitle');
  const familySub = document.getElementById('familySub');
  const stage = document.getElementById('wheelStage');
  const ring = document.getElementById('wheelRing');

  let currentIdx = 1;
  let dragging = false;

  function nearestFamily(hue) {
    let best = 0, bestDist = 999;
    FAMILIES.forEach((f, i) => {
      let d = Math.abs(f.h - hue);
      if (d > 180) d = 360 - d;
      if (d < bestDist) { bestDist = d; best = i; }
    });
    return best;
  }

  function applyFamily(idx, animate) {
    const f = FAMILIES[idx];
    currentIdx = idx;
    root.style.setProperty('--hue', f.h);
    root.style.setProperty('--sat', '42%');
    root.style.setProperty('--lit', '52%');
    hueLabel.textContent = f.name;

    const paint = () => {
      familyTitle.textContent = f.title;
      familySub.textContent = f.sub;
      filmstrip.innerHTML = '';
      f.colors.forEach((name, i) => {
        const h = f.h;
        const s1 = f.sats[i];
        const l1 = f.lits[i];
        const c1 = `hsl(${h}, ${s1}%, ${l1}%)`;
        const c2 = `hsl(${h}, ${Math.max(10, s1 - 12)}%, ${Math.min(70, l1 + 12)}%)`;
        const c3 = `hsl(${h}, ${Math.min(60, s1 + 8)}%, ${Math.max(18, l1 - 14)}%)`;

        const tile = document.createElement('article');
        tile.className = 'tile';
        tile.setAttribute('role', 'listitem');
        tile.setAttribute('tabindex', '0');
        tile.setAttribute('aria-label', name);
        tile.style.setProperty('--i', i);

        const pat = document.createElement('div');
        pat.className = 'tile-pattern ' + PATTERNS[i];
        pat.style.setProperty('--c1', c1);
        pat.style.setProperty('--c2', c2);
        pat.style.setProperty('--c3', c3);

        const meta = document.createElement('div');
        meta.className = 'tile-meta';
        meta.innerHTML = '<div class="tile-name"></div><span class="tile-tone"></span>';
        meta.querySelector('.tile-name').textContent = name;
        meta.querySelector('.tile-tone').textContent = f.name + ' · 0' + (i + 1);

        tile.appendChild(pat);
        tile.appendChild(meta);
        filmstrip.appendChild(tile);
      });
      filmstrip.classList.remove('is-shifting');
    };

    if (animate) {
      filmstrip.classList.add('is-shifting');
      setTimeout(paint, 280);
    } else {
      paint();
    }
  }

  function hueFromEvent(e) {
    const rect = ring.getBoundingClientRect();
    const cx = rect.left + rect.width / 2;
    const cy = rect.top + rect.height / 2;
    const point = e.touches ? e.touches[0] : e;
    const dx = point.clientX - cx;
    const dy = point.clientY - cy;
    let angle = Math.atan2(dy, dx) * (180 / Math.PI) + 90;
    if (angle < 0) angle += 360;
    return angle;
  }

  function onPointerDown(e) {
    dragging = true;
    onPointerMove(e);
    e.preventDefault();
  }

  function onPointerMove(e) {
    if (!dragging && e.type !== 'click') return;
    if (!dragging && e.type === 'click') dragging = true;
    const hue = hueFromEvent(e);
    const idx = nearestFamily(hue);
    root.style.setProperty('--hue', Math.round(hue));
    const f = FAMILIES[idx];
    root.style.setProperty('--sat', '45%');
    root.style.setProperty('--lit', '52%');
    if (idx !== currentIdx) {
      applyFamily(idx, true);
    }
    if (e.type === 'click') dragging = false;
  }

  function onPointerUp() { dragging = false; }

  ring.addEventListener('mousedown', onPointerDown);
  ring.addEventListener('touchstart', onPointerDown, { passive: false });
  window.addEventListener('mousemove', onPointerMove);
  window.addEventListener('touchmove', onPointerMove, { passive: false });
  window.addEventListener('mouseup', onPointerUp);
  window.addEventListener('touchend', onPointerUp);

  // Keyboard: arrow keys cycle families
  stage.setAttribute('tabindex', '0');
  stage.addEventListener('keydown', (e) => {
    if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
      applyFamily((currentIdx + 1) % FAMILIES.length, true);
      e.preventDefault();
    } else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
      applyFamily((currentIdx - 1 + FAMILIES.length) % FAMILIES.length, true);
      e.preventDefault();
    }
  });

  // Share actions
  const btnCopy = document.getElementById('btnCopy');
  const btnSave = document.getElementById('btnSave');

  btnCopy.addEventListener('click', async () => {
    const f = FAMILIES[currentIdx];
    const text = 'Designer Wallcoverings — ' + f.title + ' palette';
    try {
      if (navigator.clipboard) await navigator.clipboard.writeText(text + ' · Shop by Color');
    } catch (_) {}
    btnCopy.textContent = 'Copied';
    btnCopy.classList.add('copied');
    setTimeout(() => {
      btnCopy.textContent = 'Copy Link';
      btnCopy.classList.remove('copied');
    }, 1800);
  });

  btnSave.addEventListener('click', () => {
    const f = FAMILIES[currentIdx];
    btnSave.textContent = 'Saved · ' + f.name;
    btnSave.classList.add('copied');
    setTimeout(() => {
      btnSave.textContent = 'Save Palette';
      btnSave.classList.remove('copied');
    }, 1800);
  });

  // Init
  applyFamily(1, false);
})();
</script>
</body>
</html>