← back to Model Arena

data/artifacts/15fb25164101/kimi.html

729 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>
/* ============================================
   DESIGN TOKENS — LUXURY SYSTEM
   ============================================ */
:root {
  /* Color Palette: luxe-noir */
  --bg: #0d0d0f;
  --surface: #17171c;
  --surface-elevated: #1e1e26;
  --ink: #f5f2ea;
  --muted: #8a8578;
  --accent: #c9a961;
  --accent-dim: #7d6a45;
  
  /* Neutrals */
  --line: rgba(245, 242, 234, 0.08);
  --line-strong: rgba(245, 242, 234, 0.2);
  --shadow: rgba(0, 0, 0, 0.4);
  --shadow-soft: rgba(0, 0, 0, 0.2);
  
  /* Typography */
  --font-display: "Playfair Display", "Didot", "Bodoni MT", "EB Garamond", Georgia, serif;
  --font-body: "Avenir Next", "Century Gothic", Futura, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  
  /* Scale */
  --text-caption: 11px;
  --text-body: 15px;
  --text-lead: 18px;
  --text-h3: 26px;
  --text-h2: 34px;
  
  /* Motion */
  --ease-out: cubic-bezier(.22, .9, .24, 1);
  --ease-in-out: cubic-bezier(.77, 0, .18, 1);
  --ease-spring: cubic-bezier(.34, 1.56, .64, 1);
  --duration-micro: 200ms;
  --duration-enter: 600ms;
  --duration-move: 400ms;
  
  /* Spacing */
  --space-xs: 8px;
  --space-sm: 16px;
  --space-md: 24px;
  --space-lg: 40px;
  --space-xl: 64px;
  
  /* Layout */
  --header-height: 72px;
  --wheel-size: min(72vw, 420px);
  --filmstrip-height: 180px;
}

/* ============================================
   RESET & BASE
   ============================================ */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}

html {
  font-size: 16px;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body {
  font-family: var(--font-body);
  font-size: var(--text-body);
  line-height: 1.6;
  color: var(--ink);
  background: var(--bg);
  min-height: 100vh;
  overflow-x: hidden;
}

/* ============================================
   HEADER — FIXED TOP BAR
   ============================================ */
.header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--header-height);
  background: rgba(13, 13, 15, 0.92);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  border-bottom: 1px solid var(--line);
  z-index: 1000;
  display: flex;
  align-items: center;
  padding: 0 var(--space-md);
}

.wordmark {
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink);
  max-width: 220px;
  line-height: 1;
}

/* ============================================
   MAIN LAYOUT
   ============================================ */
.main {
  padding-top: var(--header-height);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding-bottom: var(--space-xl);
}

.hero-zone {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: var(--space-lg) var(--space-md);
  position: relative;
}

.section-label {
  font-size: var(--text-caption);
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: var(--space-sm);
}

/* ============================================
   COLOR WHEEL
   ============================================ */
.wheel-container {
  position: relative;
  width: var(--wheel-size);
  height: var(--wheel-size);
  margin: var(--space-md) 0 var(--space-lg);
  touch-action: none;
  user-select: none;
}

.wheel {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  position: relative;
  background: conic-gradient(
    from 0deg,
    hsl(0, 65%, 45%),
    hsl(30, 65%, 45%),
    hsl(60, 55%, 50%),
    hsl(90, 45%, 45%),
    hsl(120, 40%, 40%),
    hsl(150, 45%, 38%),
    hsl(180, 50%, 40%),
    hsl(210, 55%, 45%),
    hsl(240, 60%, 50%),
    hsl(270, 55%, 48%),
    hsl(300, 50%, 45%),
    hsl(330, 60%, 48%),
    hsl(360, 65%, 45%)
  );
  box-shadow: 
    inset 0 0 0 1px var(--line-strong),
    0 24px 60px var(--shadow),
    0 0 0 12px var(--surface);
  transition: transform var(--duration-move) var(--ease-out);
}

.wheel::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 35%, rgba(255,255,255,0.12), transparent 50%);
  pointer-events: none;
}

.wheel-center {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 28%;
  height: 28%;
  background: var(--surface-elevated);
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  box-shadow: 
    0 8px 32px var(--shadow),
    inset 0 1px 0 rgba(255,255,255,0.05);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10;
}

.wheel-indicator {
  position: absolute;
  top: 8%;
  left: 50%;
  transform: translateX(-50%);
  width: 4px;
  height: 16px;
  background: var(--accent);
  border-radius: 2px;
  box-shadow: 0 0 12px var(--accent);
  transition: all var(--duration-micro) var(--ease-out);
}

.color-readout {
  text-align: center;
  padding: var(--space-xs) var(--space-sm);
}

.color-name {
  font-family: var(--font-display);
  font-size: var(--text-h3);
  font-weight: 500;
  color: var(--ink);
  line-height: 1.2;
  margin-bottom: 4px;
  transition: opacity var(--duration-micro) var(--ease-out);
}

.color-hue {
  font-size: var(--text-caption);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ============================================
   FILMSTRIP — PATTERN SHOWCASE
   ============================================ */
.filmstrip-container {
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
  padding: 0 var(--space-md);
}

.filmstrip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-sm);
  height: var(--filmstrip-height);
  opacity: 0;
  transform: translateY(20px);
  transition: 
    opacity var(--duration-enter) var(--ease-out),
    transform var(--duration-enter) var(--ease-out);
}

.filmstrip.active {
  opacity: 1;
  transform: translateY(0);
}

.pattern-tile {
  position: relative;
  background: var(--surface);
  border-radius: 4px;
  overflow: hidden;
  border: 1px solid var(--line);
  box-shadow: 0 4px 20px var(--shadow-soft);
  cursor: pointer;
  transition: 
    transform var(--duration-micro) var(--ease-out),
    box-shadow var(--duration-micro) var(--ease-out);
}

.pattern-tile:hover {
  transform: translateY(-4px);
  box-shadow: 0 12px 40px var(--shadow);
}

.pattern-tile::before {
  content: '';
  position: absolute;
  inset: 0;
  opacity: 0.9;
  transition: opacity var(--duration-micro) var(--ease-out);
}

.pattern-tile:hover::before {
  opacity: 1;
}

/* Pattern 1: Herringbone */
.pattern-herringbone::before {
  background: 
    repeating-linear-gradient(
      45deg,
      transparent,
      transparent 6px,
      currentColor 6px,
      currentColor 7px
    ),
    repeating-linear-gradient(
      -45deg,
      transparent,
      transparent 6px,
      currentColor 6px,
      currentColor 7px
    );
  color: var(--pattern-color, var(--accent));
}

/* Pattern 2: Damask-inspired radial */
.pattern-damask::before {
  background: 
    radial-gradient(circle at 30% 30%, currentColor 0%, transparent 25%),
    radial-gradient(circle at 70% 70%, currentColor 0%, transparent 25%),
    radial-gradient(circle at 50% 50%, transparent 30%, currentColor 31%, currentColor 32%, transparent 33%);
  color: var(--pattern-color, var(--accent));
  background-size: 40px 40px;
}

/* Pattern 3: Pinstripe */
.pattern-stripe::before {
  background: repeating-linear-gradient(
    90deg,
    var(--surface),
    var(--surface) 3px,
    currentColor 3px,
    currentColor 4px
  );
  color: var(--pattern-color, var(--accent));
}

/* Pattern 4: Organic texture */
.pattern-organic::before {
  background: 
    radial-gradient(ellipse 20px 30px at 20% 30%, currentColor 0%, transparent 50%),
    radial-gradient(ellipse 25px 20px at 70% 60%, currentColor 0%, transparent 45%),
    radial-gradient(ellipse 15px 25px at 50% 80%, currentColor 0%, transparent 40%);
  color: var(--pattern-color, var(--accent));
  background-size: 60px 60px;
}

.pattern-label {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  padding: var(--space-xs) var(--space-sm);
  background: linear-gradient(to top, rgba(13,13,15,0.95), transparent);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink);
  opacity: 0.8;
}

/* ============================================
   SHARE BAR
   ============================================ */
.share-zone {
  margin-top: var(--space-xl);
  padding: var(--space-md);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
}

.share-label {
  font-size: var(--text-caption);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--muted);
}

.share-bar {
  display: flex;
  gap: var(--space-sm);
}

.share-btn {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: 
    background var(--duration-micro) var(--ease-out),
    border-color var(--duration-micro) var(--ease-out),
    transform var(--duration-micro) var(--ease-out);
}

.share-btn:hover {
  background: var(--surface-elevated);
  border-color: var(--accent);
  transform: scale(1.08);
}

.share-btn svg {
  width: 18px;
  height: 18px;
  fill: var(--ink);
  transition: fill var(--duration-micro) var(--ease-out);
}

.share-btn:hover svg {
  fill: var(--accent);
}

/* ============================================
   INSTRUCTION HINT
   ============================================ */
.hint {
  margin-top: var(--space-md);
  font-size: var(--text-caption);
  letter-spacing: 0.1em;
  color: var(--muted);
  opacity: 0.7;
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

.hint::before {
  content: '◉';
  font-size: 8px;
  color: var(--accent);
}

/* ============================================
   ANIMATIONS
   ============================================ */
@keyframes pulse-glow {
  0%, 100% { box-shadow: 0 0 12px var(--accent); }
  50% { box-shadow: 0 0 20px var(--accent), 0 0 30px rgba(201, 169, 97, 0.3); }
}

.wheel-indicator.active {
  animation: pulse-glow 2s var(--ease-in-out) infinite;
}

/* ============================================
   RESPONSIVE
   ============================================ */
@media (max-width: 640px) {
  :root {
    --wheel-size: 80vw;
    --filmstrip-height: 140px;
  }
  
  .filmstrip {
    grid-template-columns: repeat(2, 1fr);
    height: auto;
    gap: var(--space-xs);
  }
  
  .pattern-tile {
    aspect-ratio: 1;
  }
  
  .color-name {
    font-size: 22px;
  }
  
  .wordmark {
    font-size: 11px;
    letter-spacing: 0.15em;
  }
}

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

<header class="header">
  <div class="wordmark">Designer Wallcoverings</div>
</header>

<main class="main">
  <div class="hero-zone">
    <span class="section-label">Shop by Color</span>
    
    <div class="wheel-container" id="wheelContainer">
      <div class="wheel" id="wheel">
        <div class="wheel-indicator" id="indicator"></div>
      </div>
      <div class="wheel-center">
        <div class="color-readout">
          <div class="color-name" id="colorName">Select Hue</div>
          <div class="color-hue" id="colorHue">Drag wheel</div>
        </div>
      </div>
    </div>
    
    <p class="hint">Drag or tap the wheel to explore</p>
  </div>
  
  <div class="filmstrip-container">
    <div class="filmstrip" id="filmstrip">
      <div class="pattern-tile pattern-herringbone" data-pattern="Herringbone">
        <span class="pattern-label">Herringbone</span>
      </div>
      <div class="pattern-tile pattern-damask" data-pattern="Damask">
        <span class="pattern-label">Damask</span>
      </div>
      <div class="pattern-tile pattern-stripe" data-pattern="Pinstripe">
        <span class="pattern-label">Pinstripe</span>
      </div>
      <div class="pattern-tile pattern-organic" data-pattern="Lacuna">
        <span class="pattern-label">Lacuna</span>
      </div>
    </div>
  </div>
  
  <div class="share-zone">
    <span class="share-label">Share This Palette</span>
    <div class="share-bar">
      <button class="share-btn" aria-label="Copy link">
        <svg viewBox="0 0 24 24"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
      </button>
      <button class="share-btn" aria-label="Share to Pinterest">
        <svg viewBox="0 0 24 24"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.08 3.16 9.42 7.62 11.16-.1-.95-.2-2.4.04-3.44l1.38-5.88s-.35-.7-.35-1.74c0-1.62.94-2.84 2.1-2.84 1 0 1.48.74 1.48 1.64 0 1-.64 2.48-.96 3.86-.28 1.14.58 2.08 1.72 2.08 2.06 0 3.66-2.18 3.66-5.32 0-2.78-2-4.72-4.84-4.72-3.3 0-5.24 2.48-5.24 5.04 0 1 .38 2.08.86 2.66.1.1.1.2.08.32l-.32 1.24c-.06.2-.18.26-.4.16-1.48-.7-2.4-2.84-2.4-4.58 0-3.72 2.7-7.14 7.78-7.14 4.08 0 7.26 2.92 7.26 6.82 0 4.06-2.56 7.32-6.12 7.32-1.2 0-2.32-.62-2.7-1.36l-.74 2.82c-.26 1.02-.1 2.28-.04 2.54C21.18 20.26 24 16.42 24 12c0-6.63-5.37-12-12-12z"/></svg>
      </button>
      <button class="share-btn" aria-label="Share to Instagram">
        <svg viewBox="0 0 24 24"><path d="M12 2.16c3.2 0 3.58.01 4.85.07 3.25.15 4.77 1.69 4.92 4.92.06 1.27.07 1.65.07 4.85 0 3.2-.01 3.58-.07 4.85-.15 3.23-1.66 4.77-4.92 4.92-1.27.06-1.64.07-4.85.07-3.2 0-3.58-.01-4.85-.07-3.26-.15-4.77-1.7-4.92-4.92-.06-1.27-.07-1.64-.07-4.85 0-3.2.01-3.58.07-4.85.15-3.23 1.66-4.77 4.92-4.92 1.27-.06 1.64-.07 4.85-.07M12 0C8.74 0 8.33.01 7.05.07 2.7.27.27 2.7.07 7.05.01 8.33 0 8.74 0 12s.01 3.67.07 4.95c.2 4.35 2.63 6.78 6.98 6.98 1.28.06 1.69.07 4.95.07s3.67-.01 4.95-.07c4.35-.2 6.78-2.63 6.98-6.98.06-1.28.07-1.69.07-4.95s-.01-3.67-.07-4.95c-.2-4.35-2.63-6.78-6.98-6.98C15.67.01 15.26 0 12 0zm0 5.84a6.16 6.16 0 1 0 0 12.32 6.16 6.16 0 0 0 0-12.32zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm7.85-10.4a1.44 1.44 0 1 0 0 2.88 1.44 1.44 0 0 0 0-2.88z"/></svg>
      </button>
    </div>
  </div>
</main>

<script>
/* ============================================
   COLOR WHEEL INTERACTION
   ============================================ */
const wheel = document.getElementById('wheel');
const wheelContainer = document.getElementById('wheelContainer');
const indicator = document.getElementById('indicator');
const colorName = document.getElementById('colorName');
const colorHue = document.getElementById('colorHue');
const filmstrip = document.getElementById('filmstrip');
const patternTiles = document.querySelectorAll('.pattern-tile');

// Designer color names mapped to hue ranges
const colorFamilies = [
  { min: 0, max: 15, name: 'Vermillion', hue: 'Red' },
  { min: 15, max: 35, name: 'Terra Cotta', hue: 'Orange' },
  { min: 35, max: 50, name: 'Amber', hue: 'Gold' },
  { min: 50, max: 70, name: 'Saffron', hue: 'Yellow' },
  { min: 70, max: 95, name: 'Olive', hue: 'Green' },
  { min: 95, max: 140, name: 'Sage', hue: 'Green' },
  { min: 140, max: 170, name: 'Verdigris', hue: 'Teal' },
  { min: 170, max: 200, name: 'Petrol', hue: 'Blue' },
  { min: 200, max: 230, name: 'Indigo', hue: 'Blue' },
  { min: 230, max: 260, name: 'Lapis', hue: 'Blue' },
  { min: 260, max: 290, name: 'Amethyst', hue: 'Purple' },
  { min: 290, max: 320, name: 'Aubergine', hue: 'Purple' },
  { min: 320, max: 340, name: 'Claret', hue: 'Red' },
  { min: 340, max: 360, name: 'Crimson', hue: 'Red' }
];

let currentRotation = 0;
let isDragging = false;
let startAngle = 0;
let startRotation = 0;
let currentHue = 0;

function getAngle(x, y) {
  const rect = wheelContainer.getBoundingClientRect();
  const centerX = rect.left + rect.width / 2;
  const centerY = rect.top + rect.height / 2;
  return Math.atan2(y - centerY, x - centerX) * 180 / Math.PI;
}

function updateColorFromRotation(rotation) {
  // Normalize rotation to 0-360
  const normalized = ((rotation % 360) + 360) % 360;
  currentHue = normalized;
  
  // Find color family
  const family = colorFamilies.find(f => normalized >= f.min && normalized < f.max) || colorFamilies[0];
  
  // Update text
  colorName.textContent = family.name;
  colorHue.textContent = `${Math.round(normalized)}° — ${family.hue}`;
  
  // Update pattern colors
  const saturation = 45 + (normalized % 30) / 30 * 20; // Vary saturation slightly
  const lightness = 40 + (normalized % 45) / 45 * 15; // Vary lightness
  const colorString = `hsl(${normalized}, ${saturation}%, ${lightness}%)`;
  
  patternTiles.forEach((tile, index) => {
    tile.style.setProperty('--pattern-color', colorString);
    // Stagger animation
    setTimeout(() => {
      tile.style.opacity = '1';
      tile.style.transform = 'translateY(0)';
    }, index * 80);
  });
  
  // Show filmstrip
  filmstrip.classList.add('active');
  indicator.classList.add('active');
}

function handleStart(e) {
  isDragging = true;
  const clientX = e.touches ? e.touches[0].clientX : e.clientX;
  const clientY = e.touches ? e.touches[0].clientY : e.clientY;
  startAngle = getAngle(clientX, clientY);
  startRotation = currentRotation;
  wheelContainer.style.cursor = 'grabbing';
  indicator.style.transform = 'translateX(-50%) scale(1.2)';
}

function handleMove(e) {
  if (!isDragging) return;
  e.preventDefault();
  const clientX = e.touches ? e.touches[0].clientX : e.clientX;
  const clientY = e.touches ? e.touches[0].clientY : e.clientY;
  const angle = getAngle(clientX, clientY);
  const delta = angle - startAngle;
  currentRotation = startRotation + delta;
  wheel.style.transform = `rotate(${currentRotation}deg)`;
  updateColorFromRotation(currentRotation);
}

function handleEnd() {
  isDragging = false;
  wheelContainer.style.cursor = 'grab';
  indicator.style.transform = 'translateX(-50%) scale(1)';
  
  // Snap to nearest 5 degrees for tactile feel
  const snap = Math.round(currentRotation / 5) * 5;
  currentRotation = snap;
  wheel.style.transition = 'transform 400ms cubic-bezier(.22, .9, .24, 1)';
  wheel.style.transform = `rotate(${currentRotation}deg)`;
  setTimeout(() => {
    wheel.style.transition = 'transform 400ms cubic-bezier(.22, .9, .24, 1)';
  }, 400);
}

// Event listeners
wheelContainer.addEventListener('mousedown', handleStart);
wheelContainer.addEventListener('touchstart', handleStart, { passive: false });

document.addEventListener('mousemove', handleMove);
document.addEventListener('touchmove', handleMove, { passive: false });

document.addEventListener('mouseup', handleEnd);
document.addEventListener('touchend', handleEnd);

// Click to jump
wheelContainer.addEventListener('click', (e) => {
  if (isDragging) return;
  const rect = wheelContainer.getBoundingClientRect();
  const clickX = e.clientX - rect.left;
  const clickY = e.clientY - rect.top;
  const centerX = rect.width / 2;
  const centerY = rect.height / 2;
  
  // Calculate angle from center
  const angle = Math.atan2(clickY - centerY, clickX - centerX) * 180 / Math.PI;
  const targetRotation = angle + 90; // Offset for indicator position
  
  currentRotation = targetRotation;
  wheel.style.transform = `rotate(${currentRotation}deg)`;
  updateColorFromRotation(currentRotation);
});

// Initialize
wheelContainer.style.cursor = 'grab';
patternTiles.forEach(tile => {
  tile.style.opacity = '0';
  tile.style.transform = 'translateY(10px)';
  tile.style.transition = 'opacity 300ms ease, transform 300ms ease';
});

// Share buttons
document.querySelectorAll('.share-btn').forEach(btn => {
  btn.addEventListener('click', () => {
    const originalTransform = btn.style.transform;
    btn.style.transform = 'scale(0.92)';
    setTimeout(() => {
      btn.style.transform = originalTransform;
    }, 150);
    
    // Copy link functionality
    if (btn.getAttribute('aria-label') === 'Copy link') {
      const url = window.location.href;
      navigator.clipboard.writeText(url).then(() => {
        const original = btn.innerHTML;
        btn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>';
        setTimeout(() => {
          btn.innerHTML = original;
        }, 1500);
      });
    }
  });
});
</script>

</body>
</html>