← 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&mdash;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>