← back to Model Arena

data/artifacts/258516d076e6/grok.html

962 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Shop by Color — Designer Wallcoverings</title>
<style>
:root {
  --bg: #f4efe6;
  --surface: #fbf8f2;
  --ink: #1c1a17;
  --muted: #8a8578;
  --accent: #c9a961;
  --accent-deep: #8a7340;
  --line: rgba(28, 26, 23, 0.12);
  --line-strong: rgba(28, 26, 23, 0.22);
  --shadow: 0 12px 40px rgba(28, 26, 23, 0.08), 0 2px 8px rgba(28, 26, 23, 0.04);
  --shadow-lg: 0 24px 64px rgba(28, 26, 23, 0.12), 0 4px 16px rgba(28, 26, 23, 0.06);
  --ease: cubic-bezier(.22, .9, .24, 1);
  --ease-out: cubic-bezier(.22, 1, .36, 1);
  --ease-scene: cubic-bezier(.77, 0, .18, 1);
  --serif: 'Didot', 'Bodoni MT', 'Playfair Display', 'EB Garamond', Georgia, 'Times New Roman', serif;
  --sans: 'Avenir Next', 'Century Gothic', Futura, 'Segoe UI', 'Helvetica Neue', sans-serif;
  --radius: 2px;
  --header-h: 72px;
  --hue: 32;
  --sat: 42%;
  --lit: 48%;
}

*, *::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 header / wordmark —— */
header.brand-bar {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  height: var(--header-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 28px;
  background: rgba(244, 239, 230, 0.92);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
}

.wordmark {
  font-family: var(--serif);
  font-size: clamp(18px, 3.6vw, 42px);
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink);
  line-height: 1.15;
  max-width: min(72vw, 520px);
  white-space: normal;
}

.header-meta {
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

/* —— Layout —— */
main {
  max-width: 720px;
  margin: 0 auto;
  padding: calc(var(--header-h) + 36px) 24px 120px;
}

.hero-copy {
  text-align: center;
  margin-bottom: 36px;
}

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

.hero-copy h1 {
  font-family: var(--serif);
  font-size: clamp(28px, 5vw, 50px);
  font-weight: 400;
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--ink);
  margin-bottom: 12px;
}

.hero-copy p {
  font-size: 15px;
  color: var(--muted);
  max-width: 38ch;
  margin: 0 auto;
  line-height: 1.65;
}

/* —— Wheel stage —— */
.wheel-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 28px;
  margin-bottom: 48px;
}

.wheel-wrap {
  position: relative;
  width: min(300px, 78vw);
  height: min(300px, 78vw);
  touch-action: none;
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
}

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

.wheel-svg {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  box-shadow: var(--shadow-lg), inset 0 0 0 1px var(--line);
  filter: drop-shadow(0 8px 24px rgba(28,26,23,0.1));
}

.wheel-center {
  position: absolute;
  inset: 28%;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow), inset 0 1px 0 rgba(255,255,255,0.7);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  transition: background 0.5s var(--ease);
}

.swatch-live {
  width: 42%;
  height: 42%;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  box-shadow: inset 0 2px 6px rgba(0,0,0,0.08);
  transition: background 0.35s var(--ease);
  background: hsl(var(--hue), var(--sat), var(--lit));
}

.center-label {
  margin-top: 8px;
  font-size: 9px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}

.pointer-notch {
  position: absolute;
  top: 6px;
  left: 50%;
  transform: translateX(-50%);
  width: 0; height: 0;
  border-left: 7px solid transparent;
  border-right: 7px solid transparent;
  border-top: 10px solid var(--ink);
  z-index: 3;
  filter: drop-shadow(0 1px 2px rgba(0,0,0,0.15));
  pointer-events: none;
}

.hue-readout {
  text-align: center;
}

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

.hue-meta {
  margin-top: 6px;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}

/* —— Filmstrip —— */
.film-section {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.6s var(--ease-out), transform 0.6s var(--ease-out);
  pointer-events: none;
}

.film-section.visible {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.film-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 16px;
  padding: 0 2px;
  border-bottom: 1px solid var(--line);
  padding-bottom: 12px;
}

.film-head h2 {
  font-family: var(--serif);
  font-size: 24px;
  font-weight: 400;
  letter-spacing: -0.01em;
}

.film-head span {
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}

.filmstrip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
}

@media (max-width: 520px) {
  .filmstrip { grid-template-columns: repeat(2, 1fr); gap: 14px; }
}

.tile {
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
  overflow: hidden;
  transition: transform 0.4s var(--ease), box-shadow 0.4s var(--ease);
  cursor: pointer;
  position: relative;
}

.tile:hover, .tile:focus-visible {
  transform: translateY(-3px);
  box-shadow: var(--shadow-lg);
  outline: none;
}

.tile:focus-visible { border-color: var(--accent); }

.tile-pattern {
  aspect-ratio: 3 / 4;
  width: 100%;
  position: relative;
  overflow: hidden;
}

.tile-caption {
  padding: 12px 10px 14px;
  border-top: 1px solid var(--line);
  background: var(--surface);
}

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

.tile-family {
  margin-top: 4px;
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Pattern motifs — visually distinct */
.pat-damask {
  background:
    radial-gradient(ellipse 40% 30% at 50% 30%, hsla(var(--h), var(--s), calc(var(--l) + 18%), 0.55) 0%, transparent 70%),
    radial-gradient(ellipse 28% 40% at 50% 70%, hsla(var(--h), var(--s), calc(var(--l) + 12%), 0.4) 0%, transparent 70%),
    radial-gradient(circle at 20% 20%, hsla(var(--h), var(--s), calc(var(--l) + 8%), 0.35) 0 8%, transparent 9%),
    radial-gradient(circle at 80% 20%, hsla(var(--h), var(--s), calc(var(--l) + 8%), 0.35) 0 8%, transparent 9%),
    radial-gradient(circle at 20% 80%, hsla(var(--h), var(--s), calc(var(--l) + 8%), 0.35) 0 8%, transparent 9%),
    radial-gradient(circle at 80% 80%, hsla(var(--h), var(--s), calc(var(--l) + 8%), 0.35) 0 8%, transparent 9%),
    radial-gradient(circle at 50% 50%, hsla(var(--h), var(--s), calc(var(--l) + 6%), 0.25) 0 18%, transparent 19%),
    hsl(var(--h), var(--s), var(--l));
}

.pat-damask::after {
  content: '';
  position: absolute;
  inset: 18%;
  border: 1px solid hsla(var(--h), var(--s), calc(var(--l) + 22%), 0.45);
  border-radius: 50% 50% 45% 45%;
  box-shadow: inset 0 0 0 6px hsla(var(--h), var(--s), calc(var(--l) - 6%), 0.15);
}

.pat-stripe {
  background:
    repeating-linear-gradient(
      90deg,
      hsl(var(--h), var(--s), calc(var(--l) - 6%)) 0px,
      hsl(var(--h), var(--s), calc(var(--l) - 6%)) 7px,
      hsl(var(--h), var(--s), calc(var(--l) + 10%)) 7px,
      hsl(var(--h), var(--s), calc(var(--l) + 10%)) 10px,
      hsl(var(--h), var(--s), var(--l)) 10px,
      hsl(var(--h), var(--s), var(--l)) 22px
    );
}

.pat-geo {
  background-color: hsl(var(--h), var(--s), var(--l));
  background-image:
    linear-gradient(30deg, hsla(var(--h), var(--s), calc(var(--l) - 12%), 0.55) 12%, transparent 12.5%, transparent 87%, hsla(var(--h), var(--s), calc(var(--l) - 12%), 0.55) 87.5%, hsla(var(--h), var(--s), calc(var(--l) - 12%), 0.55)),
    linear-gradient(150deg, hsla(var(--h), var(--s), calc(var(--l) - 12%), 0.55) 12%, transparent 12.5%, transparent 87%, hsla(var(--h), var(--s), calc(var(--l) - 12%), 0.55) 87.5%, hsla(var(--h), var(--s), calc(var(--l) - 12%), 0.55)),
    linear-gradient(30deg, hsla(var(--h), var(--s), calc(var(--l) - 12%), 0.55) 12%, transparent 12.5%, transparent 87%, hsla(var(--h), var(--s), calc(var(--l) - 12%), 0.55) 87.5%, hsla(var(--h), var(--s), calc(var(--l) - 12%), 0.55)),
    linear-gradient(150deg, hsla(var(--h), var(--s), calc(var(--l) - 12%), 0.55) 12%, transparent 12.5%, transparent 87%, hsla(var(--h), var(--s), calc(var(--l) - 12%), 0.55) 87.5%, hsla(var(--h), var(--s), calc(var(--l) - 12%), 0.55)),
    linear-gradient(60deg, hsla(var(--h), calc(var(--s) * 0.6), calc(var(--l) + 16%), 0.5) 25%, transparent 25.5%, transparent 75%, hsla(var(--h), calc(var(--s) * 0.6), calc(var(--l) + 16%), 0.5) 75%, hsla(var(--h), calc(var(--s) * 0.6), calc(var(--l) + 16%), 0.5)),
    linear-gradient(60deg, hsla(var(--h), calc(var(--s) * 0.6), calc(var(--l) + 16%), 0.5) 25%, transparent 25.5%, transparent 75%, hsla(var(--h), calc(var(--s) * 0.6), calc(var(--l) + 16%), 0.5) 75%, hsla(var(--h), calc(var(--s) * 0.6), calc(var(--l) + 16%), 0.5));
  background-size: 40px 70px;
  background-position: 0 0, 0 0, 20px 35px, 20px 35px, 0 0, 20px 35px;
}

.pat-botanical {
  background-color: hsl(var(--h), var(--s), var(--l));
  background-image:
    radial-gradient(ellipse 18px 28px at 25% 30%, hsla(var(--h), var(--s), calc(var(--l) + 16%), 0.55) 0 40%, transparent 42%),
    radial-gradient(ellipse 14px 22px at 70% 55%, hsla(var(--h), var(--s), calc(var(--l) + 12%), 0.5) 0 40%, transparent 42%),
    radial-gradient(ellipse 20px 14px at 55% 22%, hsla(var(--h), var(--s), calc(var(--l) - 10%), 0.4) 0 45%, transparent 47%),
    radial-gradient(ellipse 12px 20px at 35% 72%, hsla(var(--h), var(--s), calc(var(--l) + 14%), 0.45) 0 40%, transparent 42%),
    radial-gradient(ellipse 16px 12px at 78% 28%, hsla(var(--h), var(--s), calc(var(--l) - 8%), 0.35) 0 45%, transparent 47%),
    radial-gradient(circle 3px at 50% 50%, hsla(var(--h), var(--s), calc(var(--l) + 20%), 0.6) 0 100%, transparent 100%),
    radial-gradient(circle 2px at 30% 48%, hsla(var(--h), var(--s), calc(var(--l) + 18%), 0.5) 0 100%, transparent 100%),
    radial-gradient(circle 2px at 68% 40%, hsla(var(--h), var(--s), calc(var(--l) + 18%), 0.5) 0 100%, transparent 100%);
  background-size: 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 48px 64px, 48px 64px, 48px 64px;
}

.pat-botanical::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    repeating-linear-gradient(
      0deg,
      transparent 0 31px,
      hsla(var(--h), var(--s), calc(var(--l) - 14%), 0.12) 31px 32px
    );
  pointer-events: none;
}

/* —— Share bar —— */
.share-bar {
  position: fixed;
  bottom: 0; left: 0; right: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 14px 20px calc(14px + env(safe-area-inset-bottom));
  background: rgba(251, 248, 242, 0.94);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-top: 1px solid var(--line);
}

.share-btn {
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--line-strong);
  padding: 11px 18px;
  cursor: pointer;
  transition: background 0.3s var(--ease), border-color 0.3s var(--ease), color 0.3s var(--ease);
  border-radius: var(--radius);
}

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

.share-btn.accent {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--ink);
}

.share-btn.accent:hover, .share-btn.accent:focus-visible {
  background: var(--accent-deep);
  border-color: var(--accent-deep);
  color: var(--surface);
}

/* —— In-page toast overlay —— */
.toast {
  position: fixed;
  bottom: 88px;
  left: 50%;
  transform: translateX(-50%) translateY(12px);
  background: var(--ink);
  color: var(--bg);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 12px 22px;
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease);
  z-index: 200;
  white-space: nowrap;
}

.toast.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* Detail overlay */
.overlay {
  position: fixed;
  inset: 0;
  z-index: 150;
  background: rgba(28, 26, 23, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s var(--ease);
}

.overlay.open {
  opacity: 1;
  pointer-events: auto;
}

.overlay-card {
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-lg);
  max-width: 360px;
  width: 100%;
  overflow: hidden;
  transform: translateY(16px) scale(0.98);
  transition: transform 0.45s var(--ease-out);
}

.overlay.open .overlay-card {
  transform: translateY(0) scale(1);
}

.overlay-pat {
  height: 220px;
  position: relative;
}

.overlay-body {
  padding: 22px 24px 26px;
}

.overlay-body h3 {
  font-family: var(--serif);
  font-size: 28px;
  font-weight: 400;
  margin-bottom: 6px;
}

.overlay-body .sub {
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 14px;
}

.overlay-body p {
  font-size: 14px;
  color: var(--muted);
  line-height: 1.6;
  margin-bottom: 20px;
}

.overlay-close {
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  background: transparent;
  border: 1px solid var(--line-strong);
  color: var(--ink);
  padding: 10px 16px;
  cursor: pointer;
  transition: background 0.3s var(--ease), color 0.3s var(--ease);
}

.overlay-close:hover {
  background: var(--ink);
  color: var(--bg);
}

.hint {
  margin-top: 8px;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: center;
  opacity: 0.85;
}
</style>
</head>
<body>

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

<main>
  <section class="hero-copy">
    <div class="eyebrow">Curated Atelier Collection</div>
    <h1>Find your hue</h1>
    <p>Turn the wheel to explore four hand-drawn motifs in each color family — named by our studio.</p>
  </section>

  <section class="wheel-stage">
    <div class="wheel-wrap" id="wheelWrap" aria-label="Color wheel — drag or tap to select a hue">
      <div class="pointer-notch" aria-hidden="true"></div>
      <svg class="wheel-svg" id="wheelSvg" viewBox="0 0 200 200" role="img" aria-label="Hue spectrum wheel">
        <defs>
          <filter id="soft" x="-10%" y="-10%" width="120%" height="120%">
            <feGaussianBlur in="SourceGraphic" stdDeviation="0.3"/>
          </filter>
        </defs>
        <g id="segments"></g>
        <circle cx="100" cy="100" r="99" fill="none" stroke="rgba(28,26,23,0.1)" stroke-width="1"/>
      </svg>
      <div class="wheel-center">
        <div class="swatch-live" id="liveSwatch"></div>
        <div class="center-label">Selected</div>
      </div>
    </div>

    <div class="hue-readout">
      <div class="hue-name" id="hueName">Champagne Linen</div>
      <div class="hue-meta" id="hueMeta">Warm · Neutral family</div>
      <div class="hint">Drag the wheel · Tap a pattern</div>
    </div>
  </section>

  <section class="film-section visible" id="filmSection">
    <div class="film-head">
      <h2 id="filmTitle">In this family</h2>
      <span>4 motifs</span>
    </div>
    <div class="filmstrip" id="filmstrip"></div>
  </section>
</main>

<div class="share-bar">
  <button type="button" class="share-btn" id="btnCopy">Copy link</button>
  <button type="button" class="share-btn" id="btnSave">Save look</button>
  <button type="button" class="share-btn accent" id="btnShare">Share palette</button>
</div>

<div class="toast" id="toast" role="status" aria-live="polite"></div>

<div class="overlay" id="overlay" role="dialog" aria-modal="true" aria-labelledby="ovTitle">
  <div class="overlay-card">
    <div class="overlay-pat" id="ovPat"></div>
    <div class="overlay-body">
      <h3 id="ovTitle">Pattern</h3>
      <div class="sub" id="ovSub">Family</div>
      <p id="ovDesc">A refined wallcovering motif from the atelier archive.</p>
      <button type="button" class="overlay-close" id="ovClose">Close</button>
    </div>
  </div>
</div>

<script>
(function () {
  const SEGMENTS = 72;
  const families = [
    { h0: 8,   h1: 28,  name: 'Terracotta Clay',   tone: 'Earthen · Warm' },
    { h0: 28,  h1: 48,  name: 'Champagne Linen',   tone: 'Warm · Neutral family' },
    { h0: 48,  h1: 72,  name: 'Saffron Dusk',      tone: 'Golden · Spiced' },
    { h0: 72,  h1: 100, name: 'Olive Grove',       tone: 'Botanical · Soft' },
    { h0: 100, h1: 145, name: 'Verdant Mist',      tone: 'Garden · Cool-green' },
    { h0: 145, h1: 185, name: 'Aegean Fresco',     tone: 'Coastal · Calm' },
    { h0: 185, h1: 220, name: ' indico Smoke',      tone: 'Twilight · Deep' },
    { h0: 220, h1: 260, name: 'Wisteria Veil',     tone: 'Dusky · Romantic' },
    { h0: 260, h1: 300, name: 'Plum Damask',       tone: 'Jewel · Evening' },
    { h0: 300, h1: 340, name: 'Rosewood Blush',    tone: 'Powder · Soft' },
    { h0: 340, h1: 368, name: 'Garnet Noir',       tone: 'Crimson · Rich' }
  ];
  // fix typo
  families[6].name = 'Indico Smoke';

  const motifs = [
    {
      key: 'damask',
      cls: 'pat-damask',
      names: ['Ornella Medallion', 'Casa Scroll', 'Palatine Crest', 'Villa Arabesque', 'Atelier Rosette'],
      desc: 'A classical medallion damask — hand-scaled for grand rooms and quiet corridors alike.'
    },
    {
      key: 'stripe',
      cls: 'pat-stripe',
      names: ['Regency Ribbon', 'Column Stripe', 'Harbour Line', 'Salon Ticking', 'Gallery Band'],
      desc: 'Vertical rhythm in varied weights — tailored for panels, niches, and elongated walls.'
    },
    {
      key: 'geo',
      cls: 'pat-geo',
      names: ['Lattice No. 4', 'Prism Field', 'Facet Trellis', 'Modulo Weave', 'Tessera Path'],
      desc: 'A geometric lattice drawn from mid-century salon floors and Moroccan screens.'
    },
    {
      key: 'botanical',
      cls: 'pat-botanical',
      names: ['Willow Shadow', 'Fresco Leaf', 'Meadow Trace', 'Ivy Silhouette', 'Fern Archive'],
      desc: 'Soft botanical silhouettes, as if pressed from the studio’s garden folio.'
    }
  ];

  const root = document.documentElement;
  const segmentsG = document.getElementById('segments');
  const liveSwatch = document.getElementById('liveSwatch');
  const hueName = document.getElementById('hueName');
  const hueMeta = document.getElementById('hueMeta');
  const filmstrip = document.getElementById('filmstrip');
  const filmSection = document.getElementById('filmSection');
  const wheelWrap = document.getElementById('wheelWrap');
  const toast = document.getElementById('toast');
  const overlay = document.getElementById('overlay');

  let rotation = -20; // degrees — top of wheel is selected
  let currentHue = 32;
  let dragging = false;
  let lastAngle = 0;
  let toastTimer = null;

  // Build conical-ish wheel from wedge paths
  function buildWheel() {
    const cx = 100, cy = 100, rOuter = 98, rInner = 42;
    let html = '';
    for (let i = 0; i < SEGMENTS; i++) {
      const a0 = (i / SEGMENTS) * Math.PI * 2 - Math.PI / 2;
      const a1 = ((i + 1) / SEGMENTS) * Math.PI * 2 - Math.PI / 2;
      const h = (i / SEGMENTS) * 360;
      const p = (a) => {
        const xo = cx + rOuter * Math.cos(a);
        const yo = cy + rOuter * Math.sin(a);
        const xi = cx + rInner * Math.cos(a);
        const yi = cy + rInner * Math.sin(a);
        return { xo, yo, xi, yi };
      };
      const s = p(a0), e = p(a1);
      const d = [
        `M ${s.xo} ${s.yo}`,
        `A ${rOuter} ${rOuter} 0 0 1 ${e.xo} ${e.yo}`,
        `L ${e.xi} ${e.yi}`,
        `A ${rInner} ${rInner} 0 0 0 ${s.xi} ${s.yi}`,
        'Z'
      ].join(' ');
      const sat = 48 + Math.sin((h / 360) * Math.PI * 2) * 8;
      const lit = 52;
      html += `<path d="${d}" fill="hsl(${h}, ${sat}%, ${lit}%)" stroke="none"/>`;
    }
    // inner ring hairline
    html += `<circle cx="100" cy="100" r="42" fill="none" stroke="rgba(28,26,23,0.08)" stroke-width="1"/>`;
    segmentsG.innerHTML = html;
    applyRotation();
  }

  function applyRotation() {
    segmentsG.setAttribute('transform', `rotate(${rotation} 100 100)`);
  }

  function normHue(h) {
    h = h % 360;
    if (h < 0) h += 360;
    return h;
  }

  function hueFromRotation(rot) {
    // pointer is at top; segment 0 (hue 0) starts at top when rot=0
    // rotating wheel clockwise (positive CSS/SVG rotate) moves lower hues under pointer? 
    // SVG rotate positive is clockwise. Hue increases clockwise from top.
    // When we rotate the group by `rotation`, the hue at top = -rotation
    return normHue(-rot);
  }

  function familyFor(h) {
    for (const f of families) {
      if (h >= f.h0 && h < f.h1) return f;
      // wrap for last
      if (f.h1 > 360 && (h >= f.h0 || h < f.h1 - 360)) return f;
    }
    return families[0];
  }

  function satLitFor(h) {
    // gentle variation so jewel hues read richer
    const s = 38 + Math.round(12 * Math.abs(Math.sin((h * Math.PI) / 180)));
    const l = 44 + Math.round(6 * Math.cos((h * Math.PI) / 90));
    return { s, l };
  }

  function updateSelection(animateFilm) {
    currentHue = hueFromRotation(rotation);
    const { s, l } = satLitFor(currentHue);
    root.style.setProperty('--hue', currentHue.toFixed(1));
    root.style.setProperty('--sat', s + '%');
    root.style.setProperty('--lit', l + '%');
    liveSwatch.style.background = `hsl(${currentHue}, ${s}%, ${l}%)`;

    const fam = familyFor(currentHue);
    hueName.textContent = fam.name;
    hueMeta.textContent = fam.tone;

    renderFilm(currentHue, s, l, fam, animateFilm);
  }

  function pickName(list, hue, idx) {
    const n = list.length;
    return list[Math.abs(Math.round(hue / 17) + idx * 3) % n];
  }

  function renderFilm(h, s, l, fam, animate) {
    if (animate) {
      filmSection.classList.remove('visible');
    }

    const build = () => {
      filmstrip.innerHTML = '';
      // four distinct lightness/sat shifts per motif
      const variants = [
        { dh: -6,  ds: 4,  dl: -8 },
        { dh: 4,   ds: -6, dl: 6 },
        { dh: -2,  ds: 10, dl: -2 },
        { dh: 8,   ds: 0,  dl: 10 }
      ];

      motifs.forEach((m, i) => {
        const v = variants[i];
        const hh = normHue(h + v.dh);
        const ss = Math.max(18, Math.min(72, s + v.ds));
        const ll = Math.max(22, Math.min(72, l + v.dl));
        const name = pickName(m.names, h, i);

        const tile = document.createElement('article');
        tile.className = 'tile';
        tile.tabIndex = 0;
        tile.setAttribute('role', 'button');
        tile.dataset.name = name;
        tile.dataset.family = fam.name;
        tile.dataset.desc = m.desc;
        tile.dataset.cls = m.cls;
        tile.dataset.h = hh;
        tile.dataset.s = ss;
        tile.dataset.l = ll;

        tile.innerHTML = `
          <div class="tile-pattern ${m.cls}" style="--h:${hh};--s:${ss}%;--l:${ll}%;"></div>
          <div class="tile-caption">
            <div class="tile-name">${name}</div>
            <div class="tile-family">${fam.name}</div>
          </div>
        `;

        const open = () => openOverlay(tile.dataset);
        tile.addEventListener('click', open);
        tile.addEventListener('keydown', (e) => {
          if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); open(); }
        });

        filmstrip.appendChild(tile);
      });

      requestAnimationFrame(() => filmSection.classList.add('visible'));
    };

    if (animate) {
      setTimeout(build, 180);
    } else {
      build();
    }
  }

  function openOverlay(data) {
    const ovPat = document.getElementById('ovPat');
    ovPat.className = 'overlay-pat ' + data.cls;
    ovPat.style.setProperty('--h', data.h);
    ovPat.style.setProperty('--s', data.s + '%');
    ovPat.style.setProperty('--l', data.l + '%');
    document.getElementById('ovTitle').textContent = data.name;
    document.getElementById('ovSub').textContent = data.family + ' · Atelier';
    document.getElementById('ovDesc').textContent = data.desc;
    overlay.classList.add('open');
  }

  function closeOverlay() {
    overlay.classList.remove('open');
  }

  document.getElementById('ovClose').addEventListener('click', closeOverlay);
  overlay.addEventListener('click', (e) => { if (e.target === overlay) closeOverlay(); });
  document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeOverlay(); });

  function showToast(msg) {
    toast.textContent = msg;
    toast.classList.add('show');
    clearTimeout(toastTimer);
    toastTimer = setTimeout(() => toast.classList.remove('show'), 2200);
  }

  // Pointer interaction on wheel
  function angleAt(clientX, clientY) {
    const rect = wheelWrap.getBoundingClientRect();
    const cx = rect.left + rect.width / 2;
    const cy = rect.top + rect.height / 2;
    return Math.atan2(clientY - cy, clientX - cx) * 180 / Math.PI;
  }

  function onDown(e) {
    dragging = true;
    const pt = e.touches ? e.touches[0] : e;
    lastAngle = angleAt(pt.clientX, pt.clientY);
    e.preventDefault();
  }

  function onMove(e) {
    if (!dragging) return;
    const pt = e.touches ? e.touches[0] : e;
    const a = angleAt(pt.clientX, pt.clientY);
    let delta = a - lastAngle;
    // normalize delta
    if (delta > 180) delta -= 360;
    if (delta < -180) delta += 360;
    rotation += delta;
    lastAngle = a;
    applyRotation();
    updateSelection(false);
    e.preventDefault();
  }

  function onUp() {
    if (!dragging) return;
    dragging = false;
    // snap feel — gentle settle already continuous; refresh film with animation
    updateSelection(true);
  }

  wheelWrap.addEventListener('mousedown', onDown);
  window.addEventListener('mousemove', onMove);
  window.addEventListener('mouseup', onUp);
  wheelWrap.addEventListener('touchstart', onDown, { passive: false });
  window.addEventListener('touchmove', onMove, { passive: false });
  window.addEventListener('touchend', onUp);

  // Click on wheel (no drag) still selects via small move; also allow click-to-jump
  wheelWrap.addEventListener('click', (e) => {
    if (e.detail === 0) return;
    // jump so clicked hue goes to top
    const a = angleAt(e.clientX, e.clientY);
    // angle from center: 0 = east; top = -90
    // hue under that point currently:
    // position angle from top, clockwise:
    const fromTop = normHue(a + 90);
    const hueThere = normHue(fromTop - rotation); 
    // we want hueThere at top → rotation = -hueThere
    // Actually simpler: rotate so the clicked angle moves to top (-90)
    // delta rotation = -90 - a  (in screen space)
    // But our rotation is of the paint group...
    // Desired: hue at top becomes hue currently at click.
    const hueAtClick = normHue(fromTop + (-rotation - fromTop) + fromTop);
    // hue at angle-from-top θ is: normHue(θ - rotation) with θ=fromTop
    const hClick = normHue(fromTop - rotation);
    rotation = -hClick;
    applyRotation();
    updateSelection(true);
  });

  // Share actions
  document.getElementById('btnCopy').addEventListener('click', () => {
    const text = `Designer Wallcoverings — ${hueName.textContent} palette`;
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(text).then(() => showToast('Link copied')).catch(() => showToast('Copied · ' + hueName.textContent));
    } else {
      showToast('Saved · ' + hueName.textContent);
    }
  });

  document.getElementById('btnSave').addEventListener('click', () => {
    showToast('Look saved to atelier');
  });

  document.getElementById('btnShare').addEventListener('click', () => {
    const fam = familyFor(currentHue);
    const payload = `Shop by Color · ${fam.name} — Designer Wallcoverings`;
    if (navigator.share) {
      navigator.share({ title: 'Designer Wallcoverings', text: payload }).catch(() => showToast('Palette ready to share'));
    } else if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(payload).then(() => showToast('Palette copied')).catch(() => showToast('Palette ready'));
    } else {
      showToast('Palette · ' + fam.name);
    }
  });

  // Init
  buildWheel();
  updateSelection(false);
})();
</script>
</body>
</html>