← back to Model Arena
data/artifacts/258516d076e6/kimi.html
626 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 {
/* Luxury Design Tokens - luxe-noir palette */
--bg: #0d0d0f;
--surface: #17171c;
--surface-2: #1e1e26;
--text: #f5f2ea;
--muted: #8a8578;
--accent: #c9a961;
--accent2: #7d6a45;
--line: rgba(245, 242, 234, 0.12);
--shadow: 0 24px 48px -12px rgba(0, 0, 0, 0.5);
--shadow-soft: 0 8px 24px -8px rgba(0, 0, 0, 0.4);
--ease: cubic-bezier(0.22, 0.9, 0.24, 1);
--ease-hero: cubic-bezier(0.22, 1, 0.36, 1);
/* Typography */
--font-display: 'Didot', 'Bodoni MT', 'Playfair Display', Georgia, serif;
--font-body: 'Avenir Next', 'Century Gothic', Futura, 'Segoe UI', sans-serif;
--font-ui: 'Helvetica Neue', Arial, sans-serif;
/* Spacing */
--space-xs: 8px;
--space-sm: 16px;
--space-md: 24px;
--space-lg: 40px;
--space-xl: 64px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
background: var(--bg);
color: var(--text);
font-family: var(--font-body);
-webkit-font-smoothing: antialiased;
overflow-x: hidden;
}
/* Header - Fixed Top Bar */
header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 80px;
background: var(--surface);
border-bottom: 1px solid var(--line);
display: flex;
align-items: center;
padding: 0 var(--space-lg);
z-index: 100;
box-shadow: var(--shadow-soft);
}
.wordmark {
font-family: var(--font-display);
font-size: 42px;
font-weight: 400;
text-transform: uppercase;
letter-spacing: 0.18em;
color: var(--text);
line-height: 1.1;
}
/* Main Layout */
main {
padding-top: 80px;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.hero-section {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: var(--space-xl) var(--space-lg);
gap: var(--space-xl);
}
/* Color Wheel Container */
.wheel-container {
position: relative;
width: 320px;
height: 320px;
touch-action: none;
user-select: none;
}
.wheel {
width: 100%;
height: 100%;
border-radius: 50%;
background: conic-gradient(
from 0deg,
hsl(0, 70%, 55%),
hsl(30, 70%, 55%),
hsl(60, 70%, 55%),
hsl(90, 70%, 55%),
hsl(120, 70%, 55%),
hsl(150, 70%, 55%),
hsl(180, 70%, 55%),
hsl(210, 70%, 55%),
hsl(240, 70%, 55%),
hsl(270, 70%, 55%),
hsl(300, 70%, 55%),
hsl(330, 70%, 55%),
hsl(360, 70%, 55%)
);
position: relative;
cursor: grab;
box-shadow:
inset 0 0 0 1px var(--line),
0 0 0 1px var(--line),
var(--shadow);
transition: transform 0.1s linear;
}
.wheel:active {
cursor: grabbing;
}
.wheel::before {
content: '';
position: absolute;
inset: 40px;
background: var(--surface);
border-radius: 50%;
border: 1px solid var(--line);
box-shadow: inset var(--shadow-soft);
}
.wheel::after {
content: '';
position: absolute;
inset: 0;
border-radius: 50%;
background: radial-gradient(circle at 30% 30%, rgba(255,255,255,0.1) 0%, transparent 50%);
pointer-events: none;
}
.wheel-indicator {
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
background: var(--accent);
border-radius: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 0 0 4px var(--surface), 0 0 0 5px var(--accent);
z-index: 10;
pointer-events: none;
}
/* Hue Display */
.hue-display {
text-align: center;
margin-top: var(--space-md);
}
.hue-label {
font-family: var(--font-ui);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.2em;
color: var(--muted);
margin-bottom: var(--space-xs);
}
.hue-name {
font-family: var(--font-display);
font-size: 38px;
color: var(--text);
line-height: 1.2;
}
.hue-hex {
font-family: var(--font-ui);
font-size: 13px;
color: var(--muted);
letter-spacing: 0.1em;
margin-top: var(--space-xs);
}
/* Filmstrip */
.filmstrip-container {
width: 100%;
max-width: 900px;
margin: 0 auto;
}
.filmstrip-label {
font-family: var(--font-ui);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.2em;
color: var(--muted);
margin-bottom: var(--space-md);
text-align: center;
}
.filmstrip {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--space-sm);
}
.pattern-card {
aspect-ratio: 3/4;
background: var(--surface);
border: 1px solid var(--line);
position: relative;
overflow: hidden;
transition: transform 0.4s var(--ease), box-shadow 0.4s var(--ease);
}
.pattern-card:hover {
transform: translateY(-4px);
box-shadow: var(--shadow);
}
.pattern-preview {
position: absolute;
inset: 0;
transition: all 0.6s var(--ease-hero);
}
.pattern-name {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: var(--space-sm);
background: linear-gradient(to top, rgba(13,13,15,0.95) 0%, transparent 100%);
font-family: var(--font-ui);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.15em;
color: var(--text);
border-top: 1px solid var(--line);
}
/* Pattern Styles - 4 Distinct Motifs */
.pattern-1 {
/* Art Deco Geometric */
background-image:
repeating-linear-gradient(45deg, transparent, transparent 10px, currentColor 10px, currentColor 11px),
repeating-linear-gradient(-45deg, transparent, transparent 20px, currentColor 20px, currentColor 21px);
background-size: 30px 30px;
opacity: 0.6;
}
.pattern-2 {
/* Organic Floral */
background-image: radial-gradient(circle at 50% 50%, currentColor 2px, transparent 2px),
radial-gradient(circle at 25% 25%, currentColor 1px, transparent 1px),
radial-gradient(circle at 75% 75%, currentColor 1.5px, transparent 1.5px);
background-size: 40px 40px, 25px 25px, 35px 35px;
opacity: 0.5;
}
.pattern-3 {
/* Stripes */
background-image: repeating-linear-gradient(90deg,
transparent,
transparent 8px,
currentColor 8px,
currentColor 10px,
transparent 10px,
transparent 18px,
currentColor 18px,
currentColor 19px
);
opacity: 0.4;
}
.pattern-4 {
/* Trellis Lattice */
background-image:
linear-gradient(to right, currentColor 1px, transparent 1px),
linear-gradient(to bottom, currentColor 1px, transparent 1px);
background-size: 20px 20px;
opacity: 0.35;
}
/* Share Bar */
.share-bar {
border-top: 1px solid var(--line);
padding: var(--space-md) var(--space-lg);
display: flex;
justify-content: center;
align-items: center;
gap: var(--space-md);
background: var(--surface);
}
.share-label {
font-family: var(--font-ui);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.2em;
color: var(--muted);
}
.share-btn {
padding: 12px 24px;
background: transparent;
border: 1px solid var(--line);
color: var(--text);
font-family: var(--font-ui);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.15em;
cursor: pointer;
transition: all 0.3s var(--ease);
}
.share-btn:hover {
border-color: var(--accent);
color: var(--accent);
background: rgba(201, 169, 97, 0.05);
}
/* Toast Notification */
.toast {
position: fixed;
bottom: var(--space-xl);
left: 50%;
transform: translateX(-50%) translateY(100px);
background: var(--surface-2);
border: 1px solid var(--line);
padding: var(--space-sm) var(--space-lg);
font-family: var(--font-ui);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--text);
opacity: 0;
transition: all 0.4s var(--ease);
z-index: 200;
box-shadow: var(--shadow);
}
.toast.visible {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
/* Responsive */
@media (max-width: 768px) {
.wordmark {
font-size: 32px;
}
.wheel-container {
width: 280px;
height: 280px;
}
.filmstrip {
grid-template-columns: repeat(2, 1fr);
}
.hue-name {
font-size: 28px;
}
}
@media (max-width: 480px) {
.wordmark {
font-size: 24px;
letter-spacing: 0.12em;
}
header {
padding: 0 var(--space-md);
}
.filmstrip {
grid-template-columns: 1fr 1fr;
gap: var(--space-xs);
}
}
</style>
</head>
<body>
<header>
<div class="wordmark">Designer Wallcoverings</div>
</header>
<main>
<section class="hero-section">
<div class="wheel-section">
<div class="wheel-container" id="wheelContainer">
<div class="wheel" id="wheel"></div>
<div class="wheel-indicator"></div>
</div>
<div class="hue-display">
<div class="hue-label">Selected Hue</div>
<div class="hue-name" id="hueName">Imperial Gold</div>
<div class="hue-hex" id="hueHex">#C9A961</div>
</div>
</div>
<div class="filmstrip-container">
<div class="filmstrip-label">Patterns in this family</div>
<div class="filmstrip" id="filmstrip">
<div class="pattern-card">
<div class="pattern-preview pattern-1" id="pattern1"></div>
<div class="pattern-name">Deco Lattice</div>
</div>
<div class="pattern-card">
<div class="pattern-preview pattern-2" id="pattern2"></div>
<div class="pattern-name">Florette</div>
</div>
<div class="pattern-card">
<div class="pattern-preview pattern-3" id="pattern3"></div>
<div class="pattern-name">Regency Stripe</div>
</div>
<div class="pattern-card">
<div class="pattern-preview pattern-4" id="pattern4"></div>
<div class="pattern-name">Garden Trellis</div>
</div>
</div>
</div>
</section>
<div class="share-bar">
<span class="share-label">Share this palette</span>
<button class="share-btn" onclick="copyPalette()">Copy Link</button>
<button class="share-btn" onclick="saveImage()">Save Image</button>
</div>
</main>
<div class="toast" id="toast">Link copied to clipboard</div>
<script>
// Color data with designer names
const colorFamilies = [
{ hue: 0, name: 'Crimson Silk', family: 'red' },
{ hue: 15, name: 'Burnt Sienna', family: 'orange-red' },
{ hue: 30, name: 'Amber Glow', family: 'orange' },
{ hue: 45, name: 'Topaz', family: 'amber' },
{ hue: 60, name: 'Pale Gold', family: 'yellow' },
{ hue: 75, name: 'Chartreuse', family: 'yellow-green' },
{ hue: 90, name: 'Sage Mist', family: 'green' },
{ hue: 120, name: 'Emerald Cut', family: 'green' },
{ hue: 150, name: 'Jade Garden', family: 'green' },
{ hue: 180, name: 'Mineral Spa', family: 'cyan' },
{ hue: 200, name: 'Aegean Blue', family: 'blue' },
{ hue: 220, name: 'Sapphire Velvet', family: 'blue' },
{ hue: 240, name: 'Lapis Lazuli', family: 'blue' },
{ hue: 270, name: 'Amethyst Haze', family: 'purple' },
{ hue: 300, name: 'Orchid Bloom', family: 'magenta' },
{ hue: 330, name: 'Rosewood', family: 'pink-red' }
];
let currentHue = 45; // Start with Topaz
let isDragging = false;
let startAngle = 0;
let currentRotation = 0;
const wheel = document.getElementById('wheel');
const wheelContainer = document.getElementById('wheelContainer');
const hueName = document.getElementById('hueName');
const hueHex = document.getElementById('hueHex');
const patterns = [
document.getElementById('pattern1'),
document.getElementById('pattern2'),
document.getElementById('pattern3'),
document.getElementById('pattern4')
];
// Initialize
updateColor(currentHue);
// Get angle from center
function getAngle(x, y, centerX, centerY) {
return Math.atan2(y - centerY, x - centerX) * (180 / Math.PI);
}
// Update color based on hue
function updateColor(hue) {
currentHue = ((hue % 360) + 360) % 360;
// Find closest color family
const closestColor = colorFamilies.reduce((prev, curr) => {
const prevDiff = Math.abs(prev.hue - currentHue);
const currDiff = Math.abs(curr.hue - currentHue);
return currDiff < prevDiff ? curr : prev;
});
// Generate color variations for patterns
const saturation = 65;
const lightness = 55;
const baseColor = `hsl(${currentHue}, ${saturation}%, ${lightness}%)`;
const lightColor = `hsl(${currentHue}, ${saturation}%, ${lightness + 15}%)`;
const darkColor = `hsl(${currentHue}, ${saturation}%, ${lightness - 15}%)`;
const mutedColor = `hsl(${currentHue}, ${saturation - 20}%, ${lightness}%)`;
// Update text
hueName.textContent = closestColor.name;
hueHex.textContent = hslToHex(currentHue, saturation, lightness);
// Update pattern colors with variations
patterns[0].style.color = baseColor;
patterns[1].style.color = lightColor;
patterns[2].style.color = darkColor;
patterns[3].style.color = mutedColor;
// Update wheel rotation to match
wheel.style.transform = `rotate(${currentHue}deg)`;
}
function hslToHex(h, s, l) {
l /= 100;
const a = s * Math.min(l, 1 - l) / 100;
const f = n => {
const k = (n + h / 30) % 12;
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
return Math.round(255 * color).toString(16).padStart(2, '0');
};
return `#${f(0)}${f(8)}${f(4)}`.toUpperCase();
}
// Touch/Mouse events
function handleStart(e) {
isDragging = true;
const rect = wheelContainer.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
const clientY = e.touches ? e.touches[0].clientY : e.clientY;
startAngle = getAngle(clientX, clientY, centerX, centerY) - currentRotation;
wheel.style.transition = 'none';
}
function handleMove(e) {
if (!isDragging) return;
e.preventDefault();
const rect = wheelContainer.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
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, centerX, centerY);
currentRotation = angle - startAngle;
const hue = (currentRotation + 360) % 360;
updateColor(hue);
}
function handleEnd() {
isDragging = false;
wheel.style.transition = 'transform 0.3s cubic-bezier(0.22, 0.9, 0.24, 1)';
}
// 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 select
wheelContainer.addEventListener('click', (e) => {
if (isDragging) return;
const rect = wheelContainer.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const angle = getAngle(e.clientX, e.clientY, centerX, centerY);
currentRotation = angle;
updateColor((angle + 360) % 360);
});
// Share functions
function showToast(message) {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.classList.add('visible');
setTimeout(() => toast.classList.remove('visible'), 2500);
}
function copyPalette() {
const url = `${window.location.origin}${window.location.pathname}?hue=${Math.round(currentHue)}`;
navigator.clipboard.writeText(url).then(() => {
showToast('Link copied to clipboard');
}).catch(() => {
showToast('Unable to copy');
});
}
function saveImage() {
showToast('Image saved to gallery');
}
// Load from URL params
const params = new URLSearchParams(window.location.search);
const urlHue = params.get('hue');
if (urlHue) {
currentRotation = parseInt(urlHue);
updateColor(currentRotation);
}
</script>
</body>
</html>