← back to Model Arena
data/artifacts/258516d076e6/gpt.html
1117 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Designer Wallcoverings · Shop by Color</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
:root {
/* Design tokens */
--bg: #0d0d0f;
--surface: #17171c;
--surface-soft: #1f2027;
--text: #f5f2ea;
--muted: #8a8578;
--accent: #c9a961; /* metallic gold */
--accent-soft: #7d6a45;
--line: rgba(201, 169, 97, 0.28);
--hairline: rgba(245, 242, 234, 0.12);
--shadow-soft: 0 18px 45px rgba(0, 0, 0, 0.65);
--shadow-subtle: 0 10px 30px rgba(0, 0, 0, 0.55);
--radius-lg: 18px;
--radius-md: 12px;
--radius-sm: 999px;
--radius-card: 16px;
--pad-xl: 32px;
--pad-lg: 24px;
--pad-md: 18px;
--pad-sm: 12px;
--gap-lg: 24px;
--gap-md: 16px;
--gap-sm: 10px;
--font-display: 'Playfair Display', 'Times New Roman', Georgia, serif;
--font-sans: 'Avenir Next', 'Century Gothic', -apple-system, 'Segoe UI', 'Helvetica Neue', sans-serif;
--fs-display: 52px;
--fs-h1: 40px;
--fs-h2: 28px;
--fs-label: 12px;
--fs-body: 16px;
--ease-swift: cubic-bezier(.22,.9,.24,1);
--ease-hero: cubic-bezier(.22,1,.36,1);
--ease-overshoot: cubic-bezier(.34,1.56,.64,1);
--ring-focus: 0 0 0 1px rgba(245, 242, 234, 0.65);
}
/* HyperFrames-inspired entrances */
@keyframes fadeUp {
from { opacity: 0; transform: translateY(28px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes scaleIn {
from { opacity: 0; transform: scale(.92); }
to { opacity: 1; transform: scale(1); }
}
@keyframes clipReveal {
from { clip-path: inset(0 100% 0 0); }
to { clip-path: inset(0); }
}
/* Global reset */
*,
*::before,
*::after {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
background: radial-gradient(circle at top, #15151b 0, var(--bg) 52%, #050507 100%);
color: var(--text);
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
text-rendering: geometricPrecision;
}
body {
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* Header / wordmark */
header.app-header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 20;
display: flex;
align-items: center;
justify-content: space-between;
padding: 18px 32px 16px;
background: linear-gradient(to bottom, rgba(13,13,15,0.96), rgba(13,13,15,0.88), rgba(13,13,15,0.72), transparent);
backdrop-filter: blur(18px);
border-bottom: 1px solid var(--hairline);
}
.brand-lockup {
display: flex;
flex-direction: column;
}
.brand-wordmark {
font-family: var(--font-display);
font-size: 44px;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--text);
white-space: nowrap;
line-height: 1.08;
}
@media (max-width: 640px) {
.brand-wordmark {
font-size: 40px;
white-space: normal;
}
}
/* Header right / utility */
.header-meta {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 8px;
font-family: var(--font-sans);
}
.header-tagline {
font-size: 11px;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--muted);
}
.header-pill {
display: inline-flex;
align-items: center;
gap: 8px;
border-radius: 999px;
padding: 6px 12px 6px 10px;
background: rgba(23,23,28,0.9);
border: 1px solid rgba(201,169,97,0.42);
box-shadow: 0 8px 18px rgba(0,0,0,0.7);
}
.header-pill-spark {
width: 6px;
height: 18px;
border-radius: 999px;
background: linear-gradient(180deg, var(--accent), rgba(201,169,97,0.1));
}
.header-pill-label {
font-size: 11px;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--accent);
}
/* Layout */
.app-main {
flex: 1;
display: flex;
justify-content: center;
padding: 112px 20px 32px;
}
.shell {
width: 100%;
max-width: 1120px;
display: grid;
grid-template-columns: minmax(0, 1.1fr) minmax(0, 1.1fr);
gap: 32px;
align-items: stretch;
animation: fadeUp 0.9s var(--ease-hero) both;
}
@media (max-width: 880px) {
.shell {
grid-template-columns: minmax(0, 1fr);
}
}
/* Left: wheel panel */
.panel {
background: radial-gradient(circle at top left, rgba(201,169,97,0.18), transparent 50%), var(--surface);
border-radius: var(--radius-lg);
padding: 26px 26px 22px;
box-shadow: var(--shadow-soft);
border: 1px solid rgba(245,242,234,0.07);
position: relative;
overflow: hidden;
}
.panel::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
border: 1px solid rgba(255,255,255,0.03);
pointer-events: none;
}
.panel-header {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-bottom: 18px;
}
.panel-title-block {
display: flex;
flex-direction: column;
gap: 4px;
}
.panel-kicker {
font-size: var(--fs-label);
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--muted);
}
.panel-title {
font-family: var(--font-display);
font-size: var(--fs-h1);
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--text);
line-height: 1.1;
}
.panel-subcopy {
font-size: 14px;
color: var(--muted);
max-width: 30ch;
}
@media (max-width: 880px) {
.panel-subcopy {
max-width: none;
}
}
/* Wheel container */
.wheel-wrap {
position: relative;
display: flex;
justify-content: center;
align-items: center;
padding: 16px 8px 14px;
}
.wheel-shadow {
position: absolute;
width: 280px;
height: 280px;
border-radius: 50%;
filter: blur(26px);
background: radial-gradient(circle, rgba(0,0,0,0.8), transparent 70%);
opacity: 0.9;
z-index: 0;
}
.color-wheel {
position: relative;
width: 260px;
height: 260px;
border-radius: 50%;
background:
radial-gradient(circle at 30% 30%, rgba(255,255,255,0.18), transparent 55%),
conic-gradient(
hsl(25,70%,55%),
hsl(40,70%,55%),
hsl(58,70%,55%),
hsl(90,55%,48%),
hsl(140,55%,46%),
hsl(190,60%,50%),
hsl(230,62%,58%),
hsl(270,60%,60%),
hsl(310,60%,56%),
hsl(350,72%,54%),
hsl(25,70%,55%)
);
box-shadow:
0 18px 35px rgba(0,0,0,0.9),
0 0 0 1px rgba(245,242,234,0.03);
display: flex;
align-items: center;
justify-content: center;
touch-action: none;
cursor: grab;
transition: box-shadow 220ms var(--ease-swift), transform 220ms var(--ease-swift);
transform: translateZ(0);
}
.color-wheel:active {
cursor: grabbing;
transform: scale(0.99);
box-shadow:
0 12px 24px rgba(0,0,0,0.9),
0 0 0 1px rgba(245,242,234,0.04);
}
/* Inner cutout */
.color-wheel::before {
content: "";
position: absolute;
width: 128px;
height: 128px;
border-radius: 50%;
background:
radial-gradient(circle at 30% 20%, rgba(255,255,255,0.16), transparent 55%),
radial-gradient(circle, #131319, #0d0d10 65%, transparent 100%);
box-shadow: inset 0 0 0 1px rgba(245,242,234,0.08);
}
/* Hue indicator needle */
.wheel-indicator {
position: absolute;
width: 54%;
height: 2px;
border-radius: 999px;
background: linear-gradient(to right, rgba(9,9,11,0), var(--accent));
transform-origin: 0% 50%;
pointer-events: none;
transition: transform 360ms var(--ease-hero);
}
/* Grip / knob */
.wheel-knob {
position: relative;
z-index: 2;
width: 94px;
height: 94px;
border-radius: 50%;
background:
radial-gradient(circle at 30% 25%, rgba(255,255,255,0.2), transparent 55%),
radial-gradient(circle, #171720, #0b0b0e 68%, #050507 100%);
box-shadow:
0 12px 22px rgba(0,0,0,0.9),
inset 0 0 0 1px rgba(245,242,234,0.1),
0 0 0 1px rgba(0,0,0,0.8);
display: flex;
align-items: center;
justify-content: center;
}
.wheel-knob::before {
content: "";
width: 60px;
height: 60px;
border-radius: 50%;
border: 1px solid rgba(245,242,234,0.14);
background:
radial-gradient(circle at 25% 20%, rgba(255,255,255,0.2), transparent 55%),
radial-gradient(circle, #111118, #050507 80%);
}
.wheel-knob::after {
content: "";
position: absolute;
width: 4px;
height: 22px;
border-radius: 999px;
background: linear-gradient(to bottom, var(--accent), rgba(201,169,97,0));
top: 18px;
left: 50%;
transform: translateX(-50%);
}
/* Hue chips strip */
.hue-meta {
margin-top: 20px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 18px;
}
.hue-name-block {
display: flex;
flex-direction: column;
gap: 4px;
}
.hue-name-label {
font-size: var(--fs-label);
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--muted);
}
.hue-name-primary {
font-family: var(--font-display);
font-size: var(--fs-h2);
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text);
}
.hue-details {
font-size: 13px;
color: var(--muted);
}
.hue-chips {
display: flex;
align-items: center;
gap: 8px;
}
.hue-chip {
width: 28px;
height: 28px;
border-radius: 10px;
border: 1px solid rgba(245,242,234,0.22);
box-shadow: 0 6px 10px rgba(0,0,0,0.75);
}
/* Right: pattern filmstrip */
.pattern-panel {
background: linear-gradient(135deg, #15151e, #101015);
border-radius: var(--radius-lg);
padding: 24px 20px 18px;
box-shadow: var(--shadow-soft);
border: 1px solid rgba(245,242,234,0.08);
display: flex;
flex-direction: column;
gap: 16px;
position: relative;
overflow: hidden;
}
.pattern-panel::before {
content: "";
position: absolute;
inset: -1px;
border-radius: inherit;
border: 1px solid rgba(255,255,255,0.03);
pointer-events: none;
}
.pattern-headline-row {
display: flex;
justify-content: space-between;
gap: 12px;
align-items: baseline;
}
.pattern-title {
font-family: var(--font-display);
font-size: 26px;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.pattern-tagline {
font-size: 13px;
color: var(--muted);
max-width: 30ch;
}
.pattern-filmstrip {
position: relative;
display: grid;
grid-auto-flow: column;
grid-auto-columns: minmax(0, 1fr);
gap: 12px;
margin-top: 4px;
}
.pattern-card {
position: relative;
border-radius: var(--radius-card);
padding: 10px;
background: radial-gradient(circle at top, rgba(201,169,97,0.18), rgba(13,13,15,0.98));
box-shadow: var(--shadow-subtle);
border: 1px solid rgba(245,242,234,0.06);
display: flex;
flex-direction: column;
gap: 8px;
min-height: 140px;
overflow: hidden;
animation: scaleIn 0.7s var(--ease-hero) both;
}
.pattern-surface {
position: relative;
flex: 1;
border-radius: 12px;
overflow: hidden;
border: 1px solid rgba(245,242,234,0.08);
background-color: #101015;
}
.pattern-meta {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
}
.pattern-name {
font-family: var(--font-display);
font-size: 15px;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--text);
}
.pattern-note {
font-size: 11px;
color: var(--muted);
letter-spacing: 0.12em;
text-transform: uppercase;
}
/* Pattern variants: pure CSS motifs */
.pattern-fill {
width: 100%;
height: 100%;
}
/* 1. Deco stripes */
.pattern-fill--stripes {
background-image:
linear-gradient(135deg, rgba(0,0,0,0.6), transparent 55%),
repeating-linear-gradient(
135deg,
var(--pattern-base) 0,
var(--pattern-base) 6px,
var(--pattern-accent) 6px,
var(--pattern-accent) 10px
);
}
/* 2. Weave */
.pattern-fill--weave {
background-image:
linear-gradient(90deg, rgba(0,0,0,0.85), transparent 50%),
repeating-linear-gradient(
0deg,
var(--pattern-base) 0,
var(--pattern-base) 16px,
var(--pattern-accent-soft) 16px,
var(--pattern-accent-soft) 18px
),
repeating-linear-gradient(
90deg,
var(--pattern-base) 0,
var(--pattern-base) 16px,
var(--pattern-accent-soft) 16px,
var(--pattern-accent-soft) 18px
);
}
/* 3. Faceted chevron */
.pattern-fill--chevron {
background-image:
linear-gradient(135deg, rgba(0,0,0,0.9), transparent 55%),
repeating-linear-gradient(
135deg,
var(--pattern-base) 0,
var(--pattern-base) 12px,
var(--pattern-accent-soft) 12px,
var(--pattern-accent-soft) 18px
),
repeating-linear-gradient(
225deg,
transparent 0,
transparent 10px,
rgba(0,0,0,0.32) 10px,
rgba(0,0,0,0.32) 20px
);
}
/* 4. Micro-mosaic */
.pattern-fill--mosaic {
background-image:
radial-gradient(circle at 0 0, rgba(255,255,255,0.18), transparent 55%),
repeating-linear-gradient(
0deg,
var(--pattern-base) 0,
var(--pattern-base) 6px,
var(--pattern-grid) 6px,
var(--pattern-grid) 7px
),
repeating-linear-gradient(
90deg,
transparent 0,
transparent 6px,
var(--pattern-grid) 6px,
var(--pattern-grid) 7px
);
}
/* Share bar */
.share-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 6px;
padding-top: 12px;
border-top: 1px solid rgba(245,242,234,0.08);
}
.share-label {
font-size: 11px;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--muted);
}
.share-actions {
display: inline-flex;
gap: 8px;
}
.share-btn {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 6px 12px;
border-radius: 999px;
border: 1px solid rgba(245,242,234,0.16);
background: radial-gradient(circle at top left, rgba(201,169,97,0.3), rgba(15,15,20,0.96));
color: var(--text);
font-size: 11px;
letter-spacing: 0.18em;
text-transform: uppercase;
font-family: var(--font-sans);
cursor: pointer;
outline: none;
transition: background 220ms var(--ease-swift), box-shadow 220ms var(--ease-swift), transform 220ms var(--ease-swift), border-color 220ms var(--ease-swift);
}
.share-btn span.icon {
display: inline-block;
margin-right: 6px;
}
.share-btn:hover {
background: radial-gradient(circle at top left, rgba(201,169,97,0.6), rgba(15,15,20,1));
box-shadow: 0 10px 24px rgba(0,0,0,0.8);
transform: translateY(-1px);
border-color: rgba(245,242,234,0.3);
}
.share-btn:active {
transform: translateY(0);
box-shadow: 0 6px 16px rgba(0,0,0,0.85);
}
.share-btn:focus-visible {
box-shadow: var(--ring-focus), 0 10px 24px rgba(0,0,0,0.8);
}
/* Overlay toast for "Shared" */
.toast {
position: fixed;
bottom: 22px;
left: 50%;
transform: translateX(-50%) translateY(40px);
padding: 10px 14px;
border-radius: 999px;
background: radial-gradient(circle at top, rgba(201,169,97,0.4), rgba(15,15,20,0.98));
border: 1px solid rgba(245,242,234,0.16);
box-shadow: 0 14px 30px rgba(0,0,0,0.9);
font-size: 11px;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--text);
opacity: 0;
pointer-events: none;
transition: opacity 260ms var(--ease-swift), transform 260ms var(--ease-swift);
z-index: 40;
}
.toast--visible {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
/* Accessibility helpers */
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
border: 0;
}
/* Responsive tweaks */
@media (max-width: 640px) {
header.app-header {
padding: 14px 18px 12px;
}
.panel,
.pattern-panel {
padding: 20px 18px 16px;
}
.color-wheel {
width: 230px;
height: 230px;
}
.wheel-shadow {
width: 250px;
height: 250px;
}
.hue-meta {
flex-direction: column;
align-items: flex-start;
}
.share-bar {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
}
</style>
</head>
<body>
<header class="app-header">
<div class="brand-lockup">
<div class="brand-wordmark">DESIGNER WALLCOVERINGS</div>
</div>
<div class="header-meta">
<div class="header-tagline">Shop by Color · Immersive Palette Stories</div>
<div class="header-pill" aria-hidden="true">
<div class="header-pill-spark"></div>
<div class="header-pill-label">Interactive Color Wheel</div>
</div>
</div>
</header>
<main class="app-main">
<section class="shell" aria-label="Shop by color wallcovering selector">
<!-- Left: color wheel -->
<section class="panel" aria-labelledby="wheel-title">
<div class="panel-header">
<div class="panel-title-block">
<div class="panel-kicker">Shop by Color</div>
<h1 id="wheel-title" class="panel-title">Hue Stories</h1>
</div>
<p class="panel-subcopy">
Drag the wheel or tap anywhere along its rim to explore curated wallcovering palettes in each color family.
</p>
</div>
<div class="wheel-wrap">
<div class="wheel-shadow" aria-hidden="true"></div>
<div
class="color-wheel"
id="colorWheel"
role="slider"
tabindex="0"
aria-valuemin="0"
aria-valuemax="359"
aria-valuenow="0"
aria-label="Select hue on color wheel"
>
<div class="wheel-indicator" id="wheelIndicator"></div>
<div class="wheel-knob" aria-hidden="true"></div>
</div>
</div>
<div class="hue-meta">
<div class="hue-name-block">
<div class="hue-name-label">Current Story</div>
<div class="hue-name-primary" id="hueName">Molten Garnet</div>
<div class="hue-details" id="hueDesc">
A deep, lacquered red with warm undertones, made for candlelit dining rooms and velvet lounges.
</div>
</div>
<div class="hue-chips" aria-hidden="true">
<div class="hue-chip" id="chipMain"></div>
<div class="hue-chip" id="chipSoft"></div>
<div class="hue-chip" id="chipInk"></div>
</div>
</div>
</section>
<!-- Right: pattern filmstrip -->
<section class="pattern-panel" aria-labelledby="pattern-title">
<div class="pattern-headline-row">
<h2 id="pattern-title" class="pattern-title">Curated Wallcoverings</h2>
<p class="pattern-tagline">
Four hand-drawn pattern moods tuned to your selected hue—each rendered in architectural detail.
</p>
</div>
<div class="pattern-filmstrip" id="patternFilmstrip" aria-live="polite">
<!-- Cards injected by JS -->
</div>
<div class="share-bar" aria-label="Share this color story">
<div class="share-label">Share this palette</div>
<div class="share-actions">
<button class="share-btn" type="button" data-share="story">
<span class="icon" aria-hidden="true">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none">
<path d="M6.3 10.9L3.2 9.1C2.6 8.8 2.3 8.5 2.3 8c0-.5.3-.8.9-1.1l3.1-1.8M9.7 11.8l3.6 2.1c.8.4 1.4.1 1.4-.7V2.8c0-.8-.6-1.1-1.4-.7L9.7 4.2" stroke="var(--accent)" stroke-width=".9" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
Story
</button>
<button class="share-btn" type="button" data-share="save">
<span class="icon" aria-hidden="true">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none">
<path d="M4.2 2.2h7.6c.6 0 1 .4 1 1v10.1c0 .3-.3.5-.6.3l-3.3-2-3.3 2c-.3.2-.6 0-.6-.3V3.2c0-.6.4-1 1-1z" stroke="var(--accent)" stroke-width=".9" stroke-linejoin="round"/>
</svg>
</span>
Save
</button>
</div>
</div>
</section>
</section>
</main>
<div class="toast" id="toast" role="status" aria-live="polite">
Palette copied to your moodboard.
</div>
<script>
(function() {
const wheel = document.getElementById('colorWheel');
const indicator = document.getElementById('wheelIndicator');
const hueNameEl = document.getElementById('hueName');
const hueDescEl = document.getElementById('hueDesc');
const chipMain = document.getElementById('chipMain');
const chipSoft = document.getElementById('chipSoft');
const chipInk = document.getElementById('chipInk');
const filmstrip = document.getElementById('patternFilmstrip');
const toast = document.getElementById('toast');
const shareButtons = document.querySelectorAll('.share-btn');
/* Color stories mapped to primary hue ranges */
const stories = [
{
name: 'Molten Garnet',
desc: 'A deep, lacquered red with warm undertones, made for candlelit dining rooms and velvet lounges.',
range: [345, 360],
chips: ['#8e1419', '#d44b3f', '#24080a'],
base: '#4b1012',
accent: '#f26b50',
accentSoft: '#c83735',
grid: 'rgba(250, 214, 192, 0.26)'
},
{
name: 'Champagne Ember',
desc: 'Effervescent blush notes with a golden gleam; perfect behind sculptural lighting and brushed brass.',
range: [15, 40],
chips: ['#b9633b', '#f2b38a', '#2a1007'],
base: '#7a3a1f',
accent: '#f3c39a',
accentSoft: '#d08f5e',
grid: 'rgba(255, 231, 205, 0.24)'
},
{
name: 'Gilded Saffron',
desc: 'A luminous saffron wash that catches the light like silk drapery in a late-afternoon sun.',
range: [40, 75],
chips: ['#b08a2b', '#f6d36a', '#221903'],
base: '#7b6220',
accent: '#f6d36a',
accentSoft: '#caa345',
grid: 'rgba(254, 240, 192, 0.24)'
},
{
name: 'Palm Noir',
desc: 'Earthy green shadows reminiscent of tropical fronds silhouetted against midnight glass.',
range: [75, 150],
chips: ['#1d5331', '#6aa86a', '#040c08'],
base: '#133825',
accent: '#78c38a',
accentSoft: '#3b7a4b',
grid: 'rgba(210, 242, 216, 0.22)'
},
{
name: 'Tideglass',
desc: 'Cool mineral blues with a faint sea-salt haze, tailored for serene bedrooms and spa suites.',
range: [150, 210],
chips: ['#1d4b5a', '#7fb3c5', '#030a0d'],
base: '#163744',
accent: '#7fb3c5',
accentSoft: '#406f82',
grid: 'rgba(211, 235, 246, 0.22)'
},
{
name: 'Inkstone Indigo',
desc: 'A saturated blue-black with gallery depth; dramatic yet quietly enveloping.',
range: [210, 260],
chips: ['#101c3e', '#44588b', '#02030c'],
base: '#101933',
accent: '#4a5f9c',
accentSoft: '#283462',
grid: 'rgba(190, 202, 230, 0.2)'
},
{
name: 'Amethyst Smoke',
desc: 'Soft plum wrapped in greyed undertones, inspired by velvet banquettes and late-night salons.',
range: [260, 305],
chips: ['#432345', '#a171a6', '#0b050c'],
base: '#321535',
accent: '#a171a6',
accentSoft: '#5f3563',
grid: 'rgba(234, 207, 243, 0.24)'
},
{
name: 'Rosewood Ember',
desc: 'Dusky rose and clove tones for intimate nooks, pairing beautifully with burnished metals.',
range: [305, 345],
chips: ['#5b2333', '#c47786', '#130408'],
base: '#3d1222',
accent: '#d58b9a',
accentSoft: '#8b455b',
grid: 'rgba(244, 214, 226, 0.24)'
}
];
let currentHue = 0;
let isDragging = false;
let wheelRect = null;
function angleFromEvent(evt) {
const rect = wheelRect || wheel.getBoundingClientRect();
wheelRect = rect;
const cx = rect.left + rect.width / 2;
const cy = rect.top + rect.height / 2;
let x, y;
if (evt.touches && evt.touches[0]) {
x = evt.touches[0].clientX - cx;
y = evt.touches[0].clientY - cy;
} else {
x = evt.clientX - cx;
y = evt.clientY - cy;
}
const rad = Math.atan2(y, x);
let deg = (rad * 180 / Math.PI) + 90;
if (deg < 0) deg += 360;
return deg;
}
function storyForHue(h) {
for (const s of stories) {
const [min, max] = s.range;
if (min <= max) {
if (h >= min && h < max) return s;
} else {
// wrap-around range
if (h >= min || h < max) return s;
}
}
return stories[0];
}
function updateUIFromHue(h) {
currentHue = h;
wheel.setAttribute('aria-valuenow', String(Math.round(h)));
indicator.style.transform = 'rotate(' + h + 'deg)';
const story = storyForHue(h);
hueNameEl.textContent = story.name;
hueDescEl.textContent = story.desc;
chipMain.style.background = story.chips[0];
chipSoft.style.background = story.chips[1];
chipInk.style.background = story.chips[2];
renderPatterns(story);
}
function renderPatterns(story) {
filmstrip.innerHTML = '';
const names = [
'Salon Chevron',
'Gallery Weave',
'Luminous Strata',
'Midnight Mosaic'
];
const motifs = [
'pattern-fill--stripes',
'pattern-fill--weave',
'pattern-fill--chevron',
'pattern-fill--mosaic'
];
const notes = [
'Hand-brushed diagonal bands',
'Basket-woven textile depth',
'Layered architectural planes',
'Fine-tiled shimmer'
];
motifs.forEach((motif, index) => {
const card = document.createElement('article');
card.className = 'pattern-card';
card.style.setProperty('--i', index);
const surface = document.createElement('div');
surface.className = 'pattern-surface';
const fill = document.createElement('div');
fill.className = 'pattern-fill ' + motif;
fill.style.setProperty('--pattern-base', story.base);
fill.style.setProperty('--pattern-accent', story.accent);
fill.style.setProperty('--pattern-accent-soft', story.accentSoft);
fill.style.setProperty('--pattern-grid', story.grid);
surface.appendChild(fill);
const meta = document.createElement('div');
meta.className = 'pattern-meta';
const nameEl = document.createElement('div');
nameEl.className = 'pattern-name';
nameEl.textContent = story.name + ' · ' + names[index];
const noteEl = document.createElement('div');
noteEl.className = 'pattern-note';
noteEl.textContent = notes[index];
meta.appendChild(nameEl);
meta.appendChild(noteEl);
card.appendChild(surface);
card.appendChild(meta);
filmstrip.appendChild(card);
});
}
function showToast(message) {
toast.textContent = message;
toast.classList.add('toast--visible');
clearTimeout(showToast._timer);
showToast._timer = setTimeout(() => {
toast.classList.remove('toast--visible');
}, 2200);
}
/* Pointer interactions */
wheel.addEventListener('mousedown', (e) => {
isDragging = true;
wheelRect = null;
const hue = angleFromEvent(e);
updateUIFromHue(hue);
e.preventDefault();
});
window.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const hue = angleFromEvent(e);
updateUIFromHue(hue);
});
window.addEventListener('mouseup', () => {
isDragging = false;
});
wheel.addEventListener('touchstart', (e) => {
isDragging = true;
wheelRect = null;
const hue = angleFromEvent(e);
updateUIFromHue(hue);
e.preventDefault();
}, { passive: false });
window.addEventListener('touchmove', (e) => {
if (!isDragging) return;
const hue = angleFromEvent(e);
updateUIFromHue(hue);
e.preventDefault();
}, { passive: false });
window.addEventListener('touchend', () => {
isDragging = false;
});
/* Keyboard control */
wheel.addEventListener('keydown', (e) => {
let delta = 0;
if (e.key === 'ArrowRight' || e.key === 'ArrowUp') delta = 10;
else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') delta = -10;
if (delta !== 0) {
let next = (currentHue + delta) % 360;
if (next < 0) next += 360;
updateUIFromHue(next);
e.preventDefault();
}
});
/* Share bar click handlers */
shareButtons.forEach(btn => {
btn.addEventListener('click', () => {
const mode = btn.getAttribute('data-share');
const msg = mode === 'save'
? 'Palette bookmarked to your design set.'
: 'Palette shared with your stylist.';
showToast(msg);
});
});
// Initial render from default hue (0 => Molten Garnet)
updateUIFromHue(0);
})();
</script>
</body>
</html>