[object Object]

← back to Damask Kaleidoscope

v2.0.0: restore challenge spec compliance (56px top bar, Royal+carmine palette, Cormorant wordmark, 8-fold) atop the wedge-mirror engine; add bezier urn + acanthus leaves; README

abb6422127d72c499e85802999bc3d1307bf1fe7 · 2026-07-25 01:16:18 -0700 · Steve

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Files touched

Diff

commit abb6422127d72c499e85802999bc3d1307bf1fe7
Author: Steve <steve@designerwallcoverings.com>
Date:   Sat Jul 25 01:16:18 2026 -0700

    v2.0.0: restore challenge spec compliance (56px top bar, Royal+carmine palette, Cormorant wordmark, 8-fold) atop the wedge-mirror engine; add bezier urn + acanthus leaves; README
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
 README.md                       |  26 +++
 gemma3-damask-kaleidoscope.html | 359 ++++++++++++++++++++++++++++++++++++++++
 index.html                      | 330 ++++++++++++++++++++++++------------
 3 files changed, 608 insertions(+), 107 deletions(-)

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c86032a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,26 @@
+# Designer Wallcoverings — Damask Kaleidoscope
+
+A single self-contained `index.html`: a luxury **8-fold damask kaleidoscope** for the DW brand.
+Open the file in any modern browser — no build, no dependencies.
+
+## What it does
+- **True kaleidoscope reflection** — an evolving damask motif is drawn to an offscreen
+  source canvas, then a wedge is clipped and mirror-tiled 16× (8 mirrored pairs) for
+  seamless **8-fold dihedral** symmetry.
+- **Fixed 56px top bar** above the canvas: DW wordmark left (Cormorant Garamond, white,
+  uppercase, `.18em`), a `↻ Click to shift palette` hint, and a live **palette pill** on
+  the right whose dot tracks the palette's secondary bloom colour.
+- **Bezier damask motif** — the radial ornament is built from `bezierCurveTo` anchors:
+  ogee (S-curve) vines, lobed **acanthus leaf/petal** forms, and a central **urn**.
+  The kaleidoscope oscillation is applied to those bezier anchor points, so petals and
+  the urn breathe and warp.
+- **Mandatory Royal palette** (default): ground `#0d0b07`, champagne `#EDE0C4`, crisp gold
+  petals `#C8A951`, carmine secondary bloom `#B5335A` — petal-vs-ground hue contrast kept
+  60°+ so it pops (no muddy warm beige). Sapphire / Emerald / Amethyst also included.
+- Mouse-warp symmetry, click (or tap) cycles palettes with a crossfade, center gold sheen,
+  vignette overlay, time-based 60fps loop, `prefers-reduced-motion` aware.
+
+## History
+`v2.0.0` merges the strong wedge-mirror engine from the earlier rebuild with full
+compliance to the original challenge spec (56px top bar, Royal + carmine palette,
+Cormorant Garamond wordmark, 8-fold), which the interim rebuild had dropped.
diff --git a/gemma3-damask-kaleidoscope.html b/gemma3-damask-kaleidoscope.html
new file mode 100644
index 0000000..e2b5ebe
--- /dev/null
+++ b/gemma3-damask-kaleidoscope.html
@@ -0,0 +1,359 @@
+<!DOCTYPE html>
+<!-- Designer Wallcoverings — Damask Kaleidoscope · v1.0.1 -->
+<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>Designer Wallcoverings — Damask Kaleidoscope</title>
+<style>
+  :root { color-scheme: dark; }
+  html, body { margin: 0; height: 100%; overflow: hidden; background: #060505; }
+  canvas { display: block; position: fixed; inset: 0; cursor: crosshair; touch-action: none; }
+
+  #ui {
+    position: fixed; inset: 0; pointer-events: none;
+    font-family: "Optima", "Palatino Linotype", "Cormorant Garamond", Georgia, serif;
+    color: #f3e7cf; z-index: 10;
+  }
+  #title {
+    position: absolute; top: 5.2vh; left: 0; right: 0; text-align: center;
+    letter-spacing: .42em; text-transform: uppercase;
+    font-size: clamp(11px, 1.7vw, 17px); font-weight: 500;
+    color: rgba(243,231,207,.62); text-shadow: 0 1px 22px rgba(0,0,0,.85);
+    transition: opacity 1.2s ease;
+  }
+  #title small { display:block; letter-spacing:.24em; font-size:.62em; margin-top:.7em; color:rgba(243,231,207,.34); }
+
+  #palette {
+    position: absolute; bottom: 6.4vh; left: 0; right: 0; text-align: center;
+    letter-spacing: .34em; text-transform: uppercase;
+    font-size: clamp(10px, 1.35vw, 14px);
+    color: rgba(243,231,207,.55);
+    opacity: 0; transition: opacity .5s ease; text-shadow: 0 1px 18px rgba(0,0,0,.8);
+  }
+  #palette b { color: var(--accent, #e9c877); font-weight: 600; }
+
+  #hint {
+    position: absolute; bottom: 3.0vh; left: 0; right: 0; text-align: center;
+    letter-spacing: .26em; text-transform: uppercase;
+    font-size: clamp(8.5px, 1.05vw, 11px); color: rgba(243,231,207,.28);
+    transition: opacity 1.6s ease;
+  }
+  .faded { opacity: 0 !important; }
+</style>
+</head>
+<body>
+<canvas id="c"></canvas>
+<div id="ui">
+  <div id="title">Damask Kaleidoscope<small>Designer Wallcoverings</small></div>
+  <div id="palette">Palette · <b id="palName">Byzantine Gold</b></div>
+  <div id="hint">move to warp the symmetry · click to change palette</div>
+</div>
+
+<script>
+"use strict";
+(() => {
+  const canvas = document.getElementById("c");
+  const ctx = canvas.getContext("2d", { alpha: false });
+
+  // Offscreen "tube contents" — the evolving damask that the kaleidoscope mirrors.
+  const src = document.createElement("canvas");
+  const sctx = src.getContext("2d");
+
+  /* ---------- palettes (luxury gold / charcoal / blush families) ---------- */
+  const PALETTES = [
+    { name: "Byzantine Gold", deep:"#0c0a07", bg:"#1c150d", gold:"#d8b45a", blush:"#e5a79c", accent:"#f6e7c2" },
+    { name: "Charcoal & Blush", deep:"#0b0a0c", bg:"#221d22", gold:"#c9a24b", blush:"#dc8b98", accent:"#f3d9c5" },
+    { name: "Emerald Court", deep:"#050f0a", bg:"#0f1c15", gold:"#cfab61", blush:"#84c6a6", accent:"#eef2d6" },
+    { name: "Amethyst Royale", deep:"#0a0710", bg:"#1a1122", gold:"#caa64f", blush:"#c691d2", accent:"#f0e1f6" },
+  ];
+
+  let palIndex = 0;
+  let curPal = mixPalette(PALETTES[0], PALETTES[0], 0);
+  let fromPal = PALETTES[0], toPal = PALETTES[0], palT = 1; // crossfade progress
+
+  /* ---------- geometry / device ---------- */
+  let W = 0, H = 0, cx = 0, cy = 0, R = 0, dpr = 1;
+  const SIDES = 12;                 // kaleidoscope mirror segments (even -> seamless)
+  const SLICE = (Math.PI * 2) / SIDES;
+  let S = 0;                        // source (offscreen) size in px
+
+  function resize() {
+    dpr = Math.min(window.devicePixelRatio || 1, 2);
+    W = window.innerWidth; H = window.innerHeight;
+    canvas.width = Math.round(W * dpr);
+    canvas.height = Math.round(H * dpr);
+    canvas.style.width = W + "px";
+    canvas.style.height = H + "px";
+    cx = W / 2; cy = H / 2;
+    R = Math.hypot(W, H) / 2 + 4;   // cover the whole screen
+    // source square, capped for performance; center at S/2
+    S = Math.min(Math.round(R * 1.15), 900);
+    src.width = S; src.height = S;
+  }
+  window.addEventListener("resize", resize);
+  resize();
+
+  /* ---------- pointer warp ---------- */
+  let mx = 0, my = 0;               // normalized -1..1 from center (target)
+  let wx = 0, wy = 0;               // smoothed
+  let warp = 0, warpTarget = 0;     // 0..1 distance-driven warp strength
+  function pointer(px, py) {
+    mx = (px - cx) / (W / 2);
+    my = (py - cy) / (H / 2);
+    warpTarget = Math.min(Math.hypot(mx, my), 1.4);
+    fade(hint);
+  }
+  canvas.addEventListener("mousemove", e => pointer(e.clientX, e.clientY));
+  canvas.addEventListener("touchmove", e => {
+    if (e.touches[0]) pointer(e.touches[0].clientX, e.touches[0].clientY);
+    e.preventDefault();
+  }, { passive: false });
+
+  /* ---------- palette cycling ---------- */
+  function cyclePalette() {
+    fromPal = curPal;
+    palIndex = (palIndex + 1) % PALETTES.length;
+    toPal = PALETTES[palIndex];
+    palT = 0;
+    const el = document.getElementById("palName");
+    el.textContent = toPal.name;
+    const p = document.getElementById("palette");
+    document.getElementById("ui").style.setProperty("--accent", toPal.accent);
+    p.style.opacity = "1";
+    clearTimeout(cyclePalette._t);
+    cyclePalette._t = setTimeout(() => p.style.opacity = "0", 2200);
+    fade(document.getElementById("title"));
+  }
+  canvas.addEventListener("click", cyclePalette);
+  canvas.addEventListener("touchend", e => { cyclePalette(); e.preventDefault(); });
+
+  const hint = document.getElementById("hint");
+  function fade(el){ if(el && !el.classList.contains("faded")) el.classList.add("faded"); }
+
+  /* ---------- color helpers ---------- */
+  function hexToRgb(h){ const n = parseInt(h.slice(1),16); return [n>>16&255, n>>8&255, n&255]; }
+  function lerp(a,b,t){ return a+(b-a)*t; }
+  function mixHex(h1,h2,t){
+    const a=hexToRgb(h1), b=hexToRgb(h2);
+    return `rgb(${Math.round(lerp(a[0],b[0],t))},${Math.round(lerp(a[1],b[1],t))},${Math.round(lerp(a[2],b[2],t))})`;
+  }
+  function mixPalette(a,b,t){
+    return { name:b.name, deep:mixHex(a.deep,b.deep,t), bg:mixHex(a.bg,b.bg,t),
+             gold:mixHex(a.gold,b.gold,t), blush:mixHex(a.blush,b.blush,t), accent:mixHex(a.accent,b.accent,t) };
+  }
+  // parse an "rgb(...)" string back to [r,g,b] so we can build rgba() glow strings from mixed colors
+  function toRgb(str){ const m=str.match(/\d+/g); return m?[+m[0],+m[1],+m[2]]:[0,0,0]; }
+  function rgbaOf(str,al){ const a=toRgb(str); return `rgba(${a[0]},${a[1]},${a[2]},${al})`; }
+
+  /* ---------- the evolving damask motif (drawn into the source canvas) ----------
+     Rendered with 6-fold rotational symmetry so it already reads as ornament;
+     the 12-way kaleidoscope then mirrors it into a seamless mandala.            */
+  function drawSource(t, pal, swirl, amp) {
+    const g = sctx, rs = S / 2;
+    // background: deep radial well
+    const bgGrad = g.createRadialGradient(rs, rs, rs*0.02, rs, rs, rs);
+    bgGrad.addColorStop(0, pal.bg);
+    bgGrad.addColorStop(0.55, pal.bg);
+    bgGrad.addColorStop(1, pal.deep);
+    g.setTransform(1,0,0,1,0,0);
+    g.fillStyle = bgGrad;
+    g.fillRect(0,0,S,S);
+
+    g.translate(rs, rs);
+    g.globalCompositeOperation = "lighter";
+
+    const ARMS = 6;
+    for (let k=0; k<ARMS; k++) {
+      g.save();
+      g.rotate((k/ARMS)*Math.PI*2 + swirl*0.35);
+      drawFlourish(g, rs, t, pal, amp, k);
+      g.restore();
+    }
+
+    // central medallion bloom
+    const mr = rs * (0.16 + 0.03*Math.sin(t*0.9));
+    const med = g.createRadialGradient(0,0,0,0,0,mr);
+    med.addColorStop(0, rgbaOf(pal.accent, 0.9));
+    med.addColorStop(0.4, rgbaOf(pal.gold, 0.55));
+    med.addColorStop(1, rgbaOf(pal.gold, 0));
+    g.fillStyle = med;
+    g.beginPath(); g.arc(0,0,mr,0,Math.PI*2); g.fill();
+
+    g.globalCompositeOperation = "source-over";
+    g.setTransform(1,0,0,1,0,0);
+  }
+
+  // one ornamental "arm": a baseline ogee (S-curve) vine + counter-scroll, strung
+  // with damask leaves, buds and gilt dots. Curvy even at rest so it always reads
+  // as ornament, not spokes; morphs & opens up with time + pointer warp (amp).
+  function drawFlourish(g, rs, t, pal, amp, seed) {
+    const ph = seed * 1.7;
+    const sway = Math.sin(t*0.8 + ph) * (0.18 + amp*0.5);
+    const breathe = 0.9 + 0.1*Math.sin(t*1.1 + ph);
+
+    // Baseline ogee: an intrinsic S-curl that sweeps out and hooks back — the
+    // signature damask ogee — independent of sway so it's ornate with no input.
+    const p0 = { x: 0, y: 0 };
+    const p1 = { x: rs*(0.34 + 0.06*sway),  y: -rs*0.10 };            // curl out low
+    const p2 = { x: rs*(0.20 - 0.10*sway),  y: -rs*(0.52*breathe) };  // sweep up & back in
+    const p3 = { x: rs*(0.60 + 0.08*Math.sin(t*0.7+ph)),
+                 y: -rs*(0.90*breathe) };                             // hook to the tip
+
+    // gilt stem: soft outer glow pass, then a bright core pass
+    g.lineCap = "round"; g.lineJoin = "round";
+    for (let pass=0; pass<2; pass++) {
+      g.beginPath();
+      g.moveTo(p0.x,p0.y);
+      g.bezierCurveTo(p1.x,p1.y,p2.x,p2.y,p3.x,p3.y);
+      if (pass===0){ g.strokeStyle = rgbaOf(pal.gold,0.14); g.lineWidth = rs*0.060; }
+      else         { g.strokeStyle = rgbaOf(pal.gold,0.62); g.lineWidth = rs*0.020; }
+      g.stroke();
+    }
+
+    // counter-scroll: a smaller curl branching the other way for density
+    const c0 = bez(p0,p1,p2,p3,0.42);
+    const c1 = { x: c0.x - rs*(0.22+0.05*sway), y: c0.y - rs*0.06 };
+    const c2 = { x: c0.x - rs*0.10,             y: c0.y - rs*0.30*breathe };
+    const c3 = { x: c0.x + rs*0.06,             y: c0.y - rs*0.40*breathe };
+    g.beginPath();
+    g.moveTo(c0.x,c0.y);
+    g.bezierCurveTo(c1.x,c1.y,c2.x,c2.y,c3.x,c3.y);
+    g.strokeStyle = rgbaOf(pal.gold,0.4); g.lineWidth = rs*0.013; g.stroke();
+
+    // leaves / buds strung along the main stem
+    const LEAVES = 5;
+    for (let i=1;i<=LEAVES;i++){
+      const u = i/(LEAVES+0.5);
+      const b = bez(p0,p1,p2,p3,u);
+      const tang = bezTan(p0,p1,p2,p3,u);
+      const ang = Math.atan2(tang.y, tang.x);
+      const side = (i%2===0)?1:-1;
+      const scale = rs*(0.20*(1-u*0.4)) * (0.9+0.25*Math.sin(t*1.3+ph+i));
+      drawLeaf(g, b.x, b.y, ang + side*(1.2+0.2*Math.sin(t+i)), scale, side, pal, amp);
+    }
+    // a couple of leaves on the counter-scroll tip
+    drawLeaf(g, c3.x, c3.y, Math.atan2(c3.y-c2.y, c3.x-c2.x)-0.6, rs*0.14*breathe, -1, pal, amp);
+
+    // blossom teardrop at the tip
+    drawLeaf(g, p3.x, p3.y, Math.atan2(p3.y,p3.x), rs*0.15*(0.9+0.2*Math.sin(t*1.6+ph)), 1, pal, amp, true);
+
+    // gilt punctuation dots along the stem
+    for (let i=0;i<4;i++){
+      const u = 0.18 + i*0.2;
+      const b = bez(p0,p1,p2,p3,u);
+      const dr = rs*0.013*(1.4+Math.sin(t*2+i+ph));
+      g.beginPath();
+      g.fillStyle = rgbaOf(pal.accent, 0.55);
+      g.arc(b.x,b.y,Math.max(dr,0.5),0,Math.PI*2); g.fill();
+    }
+  }
+
+  // a filled damask leaf/petal (teardrop) with blush body + gilt rib
+  function drawLeaf(g, x, y, ang, size, side, pal, amp, bloom) {
+    g.save();
+    g.translate(x,y); g.rotate(ang);
+    const w = size*(bloom?0.85:0.55), l = size*(bloom?1.15:1.6);
+    g.beginPath();
+    g.moveTo(0,0);
+    g.quadraticCurveTo(w*side, -l*0.5, 0, -l);
+    g.quadraticCurveTo(-w*side*0.62, -l*0.5, 0, 0);
+    const lg = g.createLinearGradient(0,0,0,-l);
+    lg.addColorStop(0, rgbaOf(pal.blush, 0.34+amp*0.12));
+    lg.addColorStop(0.6, rgbaOf(pal.blush, 0.16));
+    lg.addColorStop(1, rgbaOf(pal.accent, 0.05));
+    g.fillStyle = lg; g.fill();
+    // gilt rib
+    g.beginPath(); g.moveTo(0,0); g.lineTo(0,-l*0.92);
+    g.strokeStyle = rgbaOf(pal.gold, 0.4); g.lineWidth = Math.max(size*0.03,0.4); g.stroke();
+    g.restore();
+  }
+
+  // cubic bezier point + tangent
+  function bez(p0,p1,p2,p3,u){ const m=1-u;
+    return { x: m*m*m*p0.x+3*m*m*u*p1.x+3*m*u*u*p2.x+u*u*u*p3.x,
+             y: m*m*m*p0.y+3*m*m*u*p1.y+3*m*u*u*p2.y+u*u*u*p3.y }; }
+  function bezTan(p0,p1,p2,p3,u){ const m=1-u;
+    return { x: 3*m*m*(p1.x-p0.x)+6*m*u*(p2.x-p1.x)+3*u*u*(p3.x-p2.x),
+             y: 3*m*m*(p1.y-p0.y)+6*m*u*(p2.y-p1.y)+3*u*u*(p3.y-p2.y) }; }
+
+  /* ---------- the kaleidoscope compositor ---------- */
+  function compose(rot) {
+    ctx.setTransform(dpr,0,0,dpr,0,0);
+    ctx.fillStyle = curPal.deep;
+    ctx.fillRect(0,0,W,H);
+
+    ctx.save();
+    ctx.translate(cx, cy);
+    ctx.rotate(rot);
+
+    const half = SLICE/2;
+    for (let i=0;i<SIDES;i++){
+      ctx.save();
+      ctx.rotate(i*SLICE);
+      if (i % 2 === 1) ctx.scale(1,-1);        // mirror alternate wedges -> seamless
+      // clip a wedge spanning [0, SLICE]
+      ctx.beginPath();
+      ctx.moveTo(0,0);
+      ctx.arc(0,0,R, -0.001, SLICE+0.001);
+      ctx.closePath();
+      ctx.clip();
+      // draw the source square so its center sits at the kaleidoscope center
+      ctx.drawImage(src, -R, -R, R*2, R*2);
+      ctx.restore();
+    }
+    ctx.restore();
+
+    // gilt center hub
+    const hub = ctx.createRadialGradient(cx,cy,0,cx,cy,R*0.10);
+    hub.addColorStop(0, rgbaOf(curPal.accent,0.55));
+    hub.addColorStop(0.5, rgbaOf(curPal.gold,0.18));
+    hub.addColorStop(1, rgbaOf(curPal.gold,0));
+    ctx.fillStyle = hub;
+    ctx.beginPath(); ctx.arc(cx,cy,R*0.10,0,Math.PI*2); ctx.fill();
+
+    // vignette for depth
+    const vg = ctx.createRadialGradient(cx,cy,R*0.35,cx,cy,R*0.98);
+    vg.addColorStop(0,"rgba(0,0,0,0)");
+    vg.addColorStop(1,"rgba(0,0,0,0.55)");
+    ctx.fillStyle = vg;
+    ctx.fillRect(0,0,W,H);
+  }
+
+  /* ---------- animation loop (time-based, 60fps) ---------- */
+  let t = 0, last = performance.now(), spin = 0;
+  function frame(now) {
+    let dt = (now - last) / 1000;
+    last = now;
+    if (dt > 0.05) dt = 0.05;                  // clamp huge first/hidden-tab deltas
+    t += dt;
+
+    // smooth pointer + warp
+    wx += (mx - wx) * Math.min(dt*6,1);
+    wy += (my - wy) * Math.min(dt*6,1);
+    warp += (warpTarget - warp) * Math.min(dt*4,1);
+
+    // palette crossfade
+    if (palT < 1) { palT = Math.min(palT + dt/0.8, 1); }
+    curPal = mixPalette(fromPal, toPal, palT<1 ? palT*palT*(3-2*palT) : 1);
+
+    // master rotation: gentle idle drift + pointer-driven push
+    spin += dt * (0.05 + wx * 0.55);
+    const rot = spin + wy * 0.4;
+
+    // the source swirls & the motif amplitude opens up with pointer distance
+    const swirl = t*0.15 + wx*1.6;
+    const amp = warp * 0.9;
+
+    drawSource(t, curPal, swirl, amp);
+    compose(rot);
+
+    requestAnimationFrame(frame);
+  }
+
+  document.getElementById("ui").style.setProperty("--accent", PALETTES[0].accent);
+  requestAnimationFrame(frame);
+})();
+</script>
+</body>
+</html>
diff --git a/index.html b/index.html
index e2b5ebe..ea41c81 100644
--- a/index.html
+++ b/index.html
@@ -1,54 +1,91 @@
 <!DOCTYPE html>
-<!-- Designer Wallcoverings — Damask Kaleidoscope · v1.0.1 -->
+<!-- Designer Wallcoverings — Damask Kaleidoscope · v2.0.0 -->
 <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>Designer Wallcoverings — Damask Kaleidoscope</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@500;600;700&display=swap" rel="stylesheet">
 <style>
-  :root { color-scheme: dark; }
-  html, body { margin: 0; height: 100%; overflow: hidden; background: #060505; }
-  canvas { display: block; position: fixed; inset: 0; cursor: crosshair; touch-action: none; }
-
-  #ui {
-    position: fixed; inset: 0; pointer-events: none;
-    font-family: "Optima", "Palatino Linotype", "Cormorant Garamond", Georgia, serif;
-    color: #f3e7cf; z-index: 10;
+  :root {
+    color-scheme: dark;
+    --bar-h: 56px;
+    --champagne: #EDE0C4;
+    --accent: #B5335A;              /* palette secondary — drives the pill dot */
   }
-  #title {
-    position: absolute; top: 5.2vh; left: 0; right: 0; text-align: center;
-    letter-spacing: .42em; text-transform: uppercase;
-    font-size: clamp(11px, 1.7vw, 17px); font-weight: 500;
-    color: rgba(243,231,207,.62); text-shadow: 0 1px 22px rgba(0,0,0,.85);
-    transition: opacity 1.2s ease;
+  * { margin: 0; padding: 0; box-sizing: border-box; }
+  html, body { height: 100%; overflow: hidden; background: #0d0b07; }
+  #c { display: block; position: fixed; inset: 0; z-index: 1; cursor: pointer; touch-action: none; }
+
+  /* ---------- fixed 56px top bar, above the canvas ---------- */
+  #topbar {
+    position: fixed; top: 0; left: 0; right: 0; height: var(--bar-h);
+    z-index: 10;
+    display: flex; align-items: center; justify-content: space-between;
+    padding: 0 24px;
+    background: linear-gradient(180deg, rgba(9,7,4,.90) 0%, rgba(9,7,4,.55) 62%, rgba(9,7,4,0) 100%);
+    -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
+    pointer-events: none; user-select: none;
   }
-  #title small { display:block; letter-spacing:.24em; font-size:.62em; margin-top:.7em; color:rgba(243,231,207,.34); }
-
-  #palette {
-    position: absolute; bottom: 6.4vh; left: 0; right: 0; text-align: center;
-    letter-spacing: .34em; text-transform: uppercase;
-    font-size: clamp(10px, 1.35vw, 14px);
-    color: rgba(243,231,207,.55);
-    opacity: 0; transition: opacity .5s ease; text-shadow: 0 1px 18px rgba(0,0,0,.8);
+  .wordmark {
+    font-family: "Cormorant Garamond", "Didot", "Bodoni MT", Georgia, serif;
+    color: #ffffff;
+    text-transform: uppercase;
+    letter-spacing: .18em;
+    font-weight: 600;
+    font-size: clamp(15px, 2vw, 22px);
+    line-height: 1;
+    white-space: nowrap;
+    text-shadow: 0 1px 14px rgba(0,0,0,.7);
   }
-  #palette b { color: var(--accent, #e9c877); font-weight: 600; }
-
-  #hint {
-    position: absolute; bottom: 3.0vh; left: 0; right: 0; text-align: center;
-    letter-spacing: .26em; text-transform: uppercase;
-    font-size: clamp(8.5px, 1.05vw, 11px); color: rgba(243,231,207,.28);
-    transition: opacity 1.6s ease;
+  .wordmark .mono {
+    display: inline-block;
+    padding-right: .32em; margin-right: .18em;
+    font-size: 1.14em; font-weight: 700;
+    color: var(--champagne);
+    border-right: 1px solid rgba(237,224,196,.30);
+  }
+  .right { display: flex; align-items: center; gap: 16px; }
+  .hint {
+    font-family: -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
+    color: rgba(237,224,196,.50);
+    font-size: 11px; letter-spacing: .16em; text-transform: uppercase;
+    white-space: nowrap;
+    transition: opacity 1.4s ease;
+  }
+  .hint::before { content: "↻  "; color: rgba(200,169,81,.8); }
+  .pill {
+    font-family: -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
+    font-size: 12px; letter-spacing: .15em; text-transform: uppercase;
+    color: #0d0b07;
+    background: linear-gradient(180deg, #EDE0C4, #C8A951);
+    padding: 6px 15px; border-radius: 999px;
+    box-shadow: 0 0 20px rgba(200,169,81,.28), inset 0 1px 0 rgba(255,255,255,.45);
+    display: flex; align-items: center; gap: 9px;
+    white-space: nowrap;
   }
+  .pill .dot {
+    width: 8px; height: 8px; border-radius: 50%;
+    background: var(--accent);
+    box-shadow: 0 0 7px var(--accent);
+    transition: background .5s ease, box-shadow .5s ease;
+  }
+  #palName { transition: opacity .35s ease; }
   .faded { opacity: 0 !important; }
+  @media (max-width: 520px) { .hint { display: none; } }
 </style>
 </head>
 <body>
 <canvas id="c"></canvas>
-<div id="ui">
-  <div id="title">Damask Kaleidoscope<small>Designer Wallcoverings</small></div>
-  <div id="palette">Palette · <b id="palName">Byzantine Gold</b></div>
-  <div id="hint">move to warp the symmetry · click to change palette</div>
-</div>
+<header id="topbar">
+  <div class="wordmark"><span class="mono">DW</span>Designer Wallcoverings</div>
+  <div class="right">
+    <span class="hint" id="hint">Click to shift palette</span>
+    <span class="pill"><span class="dot" id="pillDot"></span><span id="palName">Royal</span></span>
+  </div>
+</header>
 
 <script>
 "use strict";
@@ -60,12 +97,18 @@
   const src = document.createElement("canvas");
   const sctx = src.getContext("2d");
 
-  /* ---------- palettes (luxury gold / charcoal / blush families) ---------- */
+  const reduceMotion = matchMedia("(prefers-reduced-motion: reduce)").matches;
+
+  /* ---------- palettes ----------
+     Royal is MANDATORY + first: near-black ground, champagne highlight,
+     crisp gold petals, carmine secondary bloom (60°+ off gold so it pops).
+     Every palette keeps a gilt `gold` and a saturated `sec` bloom that is a
+     clear hue-jump from the gold — never a muddy warm beige.                 */
   const PALETTES = [
-    { name: "Byzantine Gold", deep:"#0c0a07", bg:"#1c150d", gold:"#d8b45a", blush:"#e5a79c", accent:"#f6e7c2" },
-    { name: "Charcoal & Blush", deep:"#0b0a0c", bg:"#221d22", gold:"#c9a24b", blush:"#dc8b98", accent:"#f3d9c5" },
-    { name: "Emerald Court", deep:"#050f0a", bg:"#0f1c15", gold:"#cfab61", blush:"#84c6a6", accent:"#eef2d6" },
-    { name: "Amethyst Royale", deep:"#0a0710", bg:"#1a1122", gold:"#caa64f", blush:"#c691d2", accent:"#f0e1f6" },
+    { name: "Royal",     deep:"#0d0b07", bg:"#191207", gold:"#C8A951", sec:"#B5335A", champ:"#EDE0C4" },
+    { name: "Sapphire",  deep:"#060a14", bg:"#0d1626", gold:"#C8A951", sec:"#5E90CC", champ:"#DCE6F2" },
+    { name: "Emerald",   deep:"#05100b", bg:"#0d1c14", gold:"#C8A951", sec:"#43A776", champ:"#E4EFE0" },
+    { name: "Amethyst",  deep:"#0a0710", bg:"#170f22", gold:"#C8A951", sec:"#A277C6", champ:"#ECE3F2" },
   ];
 
   let palIndex = 0;
@@ -74,7 +117,7 @@
 
   /* ---------- geometry / device ---------- */
   let W = 0, H = 0, cx = 0, cy = 0, R = 0, dpr = 1;
-  const SIDES = 12;                 // kaleidoscope mirror segments (even -> seamless)
+  const SIDES = 16;                 // 16 wedges (8 mirrored pairs) -> true 8-fold dihedral symmetry
   const SLICE = (Math.PI * 2) / SIDES;
   let S = 0;                        // source (offscreen) size in px
 
@@ -87,8 +130,7 @@
     canvas.style.height = H + "px";
     cx = W / 2; cy = H / 2;
     R = Math.hypot(W, H) / 2 + 4;   // cover the whole screen
-    // source square, capped for performance; center at S/2
-    S = Math.min(Math.round(R * 1.15), 900);
+    S = Math.min(Math.round(R * 1.15), 900);   // source square, capped for 60fps
     src.width = S; src.height = S;
   }
   window.addEventListener("resize", resize);
@@ -97,7 +139,7 @@
   /* ---------- pointer warp ---------- */
   let mx = 0, my = 0;               // normalized -1..1 from center (target)
   let wx = 0, wy = 0;               // smoothed
-  let warp = 0, warpTarget = 0;     // 0..1 distance-driven warp strength
+  let warp = 0, warpTarget = 0;     // 0..1+ distance-driven warp strength
   function pointer(px, py) {
     mx = (px - cx) / (W / 2);
     my = (py - cy) / (H / 2);
@@ -111,25 +153,29 @@
   }, { passive: false });
 
   /* ---------- palette cycling ---------- */
+  const palName = document.getElementById("palName");
+  const pillDot = document.getElementById("pillDot");
+  const hint    = document.getElementById("hint");
+
+  function applyPillChrome(pal) {
+    document.documentElement.style.setProperty("--accent", pal.sec);
+    document.documentElement.style.setProperty("--champagne", pal.champ);
+    document.body.style.background = pal.deep;
+  }
   function cyclePalette() {
     fromPal = curPal;
     palIndex = (palIndex + 1) % PALETTES.length;
     toPal = PALETTES[palIndex];
     palT = 0;
-    const el = document.getElementById("palName");
-    el.textContent = toPal.name;
-    const p = document.getElementById("palette");
-    document.getElementById("ui").style.setProperty("--accent", toPal.accent);
-    p.style.opacity = "1";
-    clearTimeout(cyclePalette._t);
-    cyclePalette._t = setTimeout(() => p.style.opacity = "0", 2200);
-    fade(document.getElementById("title"));
+    // swap the pill label with a tiny fade so it reads as a deliberate shift
+    palName.classList.add("faded");
+    setTimeout(() => { palName.textContent = toPal.name; palName.classList.remove("faded"); }, 180);
+    applyPillChrome(toPal);
   }
   canvas.addEventListener("click", cyclePalette);
   canvas.addEventListener("touchend", e => { cyclePalette(); e.preventDefault(); });
 
-  const hint = document.getElementById("hint");
-  function fade(el){ if(el && !el.classList.contains("faded")) el.classList.add("faded"); }
+  function fade(el){ if (el && !el.classList.contains("faded")) el.classList.add("faded"); }
 
   /* ---------- color helpers ---------- */
   function hexToRgb(h){ const n = parseInt(h.slice(1),16); return [n>>16&255, n>>8&255, n&255]; }
@@ -139,29 +185,34 @@
     return `rgb(${Math.round(lerp(a[0],b[0],t))},${Math.round(lerp(a[1],b[1],t))},${Math.round(lerp(a[2],b[2],t))})`;
   }
   function mixPalette(a,b,t){
-    return { name:b.name, deep:mixHex(a.deep,b.deep,t), bg:mixHex(a.bg,b.bg,t),
-             gold:mixHex(a.gold,b.gold,t), blush:mixHex(a.blush,b.blush,t), accent:mixHex(a.accent,b.accent,t) };
+    return { name:b.name,
+             deep:mixHex(a.deep,b.deep,t), bg:mixHex(a.bg,b.bg,t),
+             gold:mixHex(a.gold,b.gold,t), sec:mixHex(a.sec,b.sec,t), champ:mixHex(a.champ,b.champ,t) };
   }
   // parse an "rgb(...)" string back to [r,g,b] so we can build rgba() glow strings from mixed colors
   function toRgb(str){ const m=str.match(/\d+/g); return m?[+m[0],+m[1],+m[2]]:[0,0,0]; }
   function rgbaOf(str,al){ const a=toRgb(str); return `rgba(${a[0]},${a[1]},${a[2]},${al})`; }
 
   /* ---------- the evolving damask motif (drawn into the source canvas) ----------
-     Rendered with 6-fold rotational symmetry so it already reads as ornament;
-     the 12-way kaleidoscope then mirrors it into a seamless mandala.            */
+     6-fold rotational armature of ogee vines + bezier leaves + a central bezier
+     urn; the 16-way kaleidoscope then mirrors a wedge into a seamless 8-fold
+     mandala. The kaleidoscope "oscillation" is applied to the bezier anchor
+     points (leaves, urn, ogee tip) so the motif breathes and warps.           */
   function drawSource(t, pal, swirl, amp) {
     const g = sctx, rs = S / 2;
-    // background: deep radial well
+    // background: deep radial well (near-black ground)
     const bgGrad = g.createRadialGradient(rs, rs, rs*0.02, rs, rs, rs);
     bgGrad.addColorStop(0, pal.bg);
-    bgGrad.addColorStop(0.55, pal.bg);
+    bgGrad.addColorStop(0.5, pal.bg);
     bgGrad.addColorStop(1, pal.deep);
     g.setTransform(1,0,0,1,0,0);
     g.fillStyle = bgGrad;
     g.fillRect(0,0,S,S);
 
     g.translate(rs, rs);
-    g.globalCompositeOperation = "lighter";
+
+    // central bezier urn (foot of the damask), drawn first so foliage overlaps it
+    drawUrn(g, rs, t, pal, amp);
 
     const ARMS = 6;
     for (let k=0; k<ARMS; k++) {
@@ -171,10 +222,11 @@
       g.restore();
     }
 
-    // central medallion bloom
-    const mr = rs * (0.16 + 0.03*Math.sin(t*0.9));
+    // central medallion bloom (gilt sheen at the very center) — additive
+    g.globalCompositeOperation = "lighter";
+    const mr = rs * (0.15 + 0.03*Math.sin(t*0.9));
     const med = g.createRadialGradient(0,0,0,0,0,mr);
-    med.addColorStop(0, rgbaOf(pal.accent, 0.9));
+    med.addColorStop(0, rgbaOf(pal.champ, 0.9));
     med.addColorStop(0.4, rgbaOf(pal.gold, 0.55));
     med.addColorStop(1, rgbaOf(pal.gold, 0));
     g.fillStyle = med;
@@ -184,6 +236,45 @@
     g.setTransform(1,0,0,1,0,0);
   }
 
+  // A central damask urn/vase silhouette built from bezierCurveTo. Its shoulder
+  // and lip anchors oscillate with time + pointer warp (the kaleidoscope osc).
+  function drawUrn(g, rs, t, pal, amp) {
+    const o = Math.sin(t*1.1) * (0.5 + amp);      // anchor oscillator
+    const s = rs * 0.30;                          // urn scale
+    const shoulder = 1 + 0.10*o;                  // belly bulge
+    const lip = 1 + 0.14*o;                       // flared lip
+
+    g.save();
+    g.beginPath();
+    // start at the foot, sweep the right flank up through the belly to the flared lip
+    g.moveTo(0, s*0.10);
+    g.bezierCurveTo( s*0.34,           s*0.06,
+                     s*0.52*shoulder, -s*0.30,
+                     s*0.30,          -s*0.58);
+    g.bezierCurveTo( s*0.16,          -s*0.74,
+                     s*0.30*lip,      -s*0.86,
+                     s*0.20*lip,      -s*0.98);
+    // mirror the left flank back down to the foot
+    g.bezierCurveTo(-s*0.30*lip,      -s*0.86,
+                    -s*0.16,          -s*0.74,
+                    -s*0.30,          -s*0.58);
+    g.bezierCurveTo(-s*0.52*shoulder, -s*0.30,
+                    -s*0.34,           s*0.06,
+                     0,                s*0.10);
+    g.closePath();
+
+    const ug = g.createLinearGradient(0, s*0.10, 0, -s*0.98);
+    ug.addColorStop(0,   rgbaOf(pal.gold,  0.30));
+    ug.addColorStop(0.5, rgbaOf(pal.gold,  0.60));
+    ug.addColorStop(1,   rgbaOf(pal.champ, 0.55));
+    g.fillStyle = ug; g.fill();
+    // champagne rim-light for crisp definition
+    g.strokeStyle = rgbaOf(pal.champ, 0.45);
+    g.lineWidth = Math.max(rs*0.006, 1);
+    g.stroke();
+    g.restore();
+  }
+
   // one ornamental "arm": a baseline ogee (S-curve) vine + counter-scroll, strung
   // with damask leaves, buds and gilt dots. Curvy even at rest so it always reads
   // as ornament, not spokes; morphs & opens up with time + pointer warp (amp).
@@ -194,11 +285,11 @@
 
     // Baseline ogee: an intrinsic S-curl that sweeps out and hooks back — the
     // signature damask ogee — independent of sway so it's ornate with no input.
-    const p0 = { x: 0, y: 0 };
-    const p1 = { x: rs*(0.34 + 0.06*sway),  y: -rs*0.10 };            // curl out low
-    const p2 = { x: rs*(0.20 - 0.10*sway),  y: -rs*(0.52*breathe) };  // sweep up & back in
-    const p3 = { x: rs*(0.60 + 0.08*Math.sin(t*0.7+ph)),
-                 y: -rs*(0.90*breathe) };                             // hook to the tip
+    const p0 = { x: 0, y: -rs*0.14 };
+    const p1 = { x: rs*(0.34 + 0.06*sway),  y: -rs*0.24 };            // curl out low
+    const p2 = { x: rs*(0.20 - 0.10*sway),  y: -rs*(0.58*breathe) };  // sweep up & back in
+    const p3 = { x: rs*(0.52 + 0.08*Math.sin(t*0.7+ph)),
+                 y: -rs*(0.92*breathe) };                             // hook to the tip
 
     // gilt stem: soft outer glow pass, then a bright core pass
     g.lineCap = "round"; g.lineJoin = "round";
@@ -206,8 +297,8 @@
       g.beginPath();
       g.moveTo(p0.x,p0.y);
       g.bezierCurveTo(p1.x,p1.y,p2.x,p2.y,p3.x,p3.y);
-      if (pass===0){ g.strokeStyle = rgbaOf(pal.gold,0.14); g.lineWidth = rs*0.060; }
-      else         { g.strokeStyle = rgbaOf(pal.gold,0.62); g.lineWidth = rs*0.020; }
+      if (pass===0){ g.strokeStyle = rgbaOf(pal.gold,0.16); g.lineWidth = rs*0.058; }
+      else         { g.strokeStyle = rgbaOf(pal.gold,0.72); g.lineWidth = rs*0.020; }
       g.stroke();
     }
 
@@ -219,9 +310,9 @@
     g.beginPath();
     g.moveTo(c0.x,c0.y);
     g.bezierCurveTo(c1.x,c1.y,c2.x,c2.y,c3.x,c3.y);
-    g.strokeStyle = rgbaOf(pal.gold,0.4); g.lineWidth = rs*0.013; g.stroke();
+    g.strokeStyle = rgbaOf(pal.gold,0.45); g.lineWidth = rs*0.013; g.stroke();
 
-    // leaves / buds strung along the main stem
+    // leaves / buds strung along the main stem (alternating gold + carmine bloom)
     const LEAVES = 5;
     for (let i=1;i<=LEAVES;i++){
       const u = i/(LEAVES+0.5);
@@ -229,43 +320,66 @@
       const tang = bezTan(p0,p1,p2,p3,u);
       const ang = Math.atan2(tang.y, tang.x);
       const side = (i%2===0)?1:-1;
-      const scale = rs*(0.20*(1-u*0.4)) * (0.9+0.25*Math.sin(t*1.3+ph+i));
-      drawLeaf(g, b.x, b.y, ang + side*(1.2+0.2*Math.sin(t+i)), scale, side, pal, amp);
+      const scale = rs*(0.22*(1-u*0.4)) * (0.9+0.25*Math.sin(t*1.3+ph+i));
+      const bloom = (i % 2 === 0);                       // even = carmine secondary bloom
+      drawLeaf(g, b.x, b.y, ang + side*(1.2+0.2*Math.sin(t+i)), scale, side, pal, amp, t, i+ph, bloom);
     }
-    // a couple of leaves on the counter-scroll tip
-    drawLeaf(g, c3.x, c3.y, Math.atan2(c3.y-c2.y, c3.x-c2.x)-0.6, rs*0.14*breathe, -1, pal, amp);
-
-    // blossom teardrop at the tip
-    drawLeaf(g, p3.x, p3.y, Math.atan2(p3.y,p3.x), rs*0.15*(0.9+0.2*Math.sin(t*1.6+ph)), 1, pal, amp, true);
+    // a leaf on the counter-scroll tip
+    drawLeaf(g, c3.x, c3.y, Math.atan2(c3.y-c2.y, c3.x-c2.x)-0.6, rs*0.15*breathe, -1, pal, amp, t, ph, false);
+    // carmine blossom at the ogee tip
+    drawLeaf(g, p3.x, p3.y, Math.atan2(p3.y,p3.x), rs*0.16*(0.9+0.2*Math.sin(t*1.6+ph)), 1, pal, amp, t, ph, true);
 
     // gilt punctuation dots along the stem
     for (let i=0;i<4;i++){
-      const u = 0.18 + i*0.2;
+      const u = 0.20 + i*0.2;
       const b = bez(p0,p1,p2,p3,u);
       const dr = rs*0.013*(1.4+Math.sin(t*2+i+ph));
       g.beginPath();
-      g.fillStyle = rgbaOf(pal.accent, 0.55);
+      g.fillStyle = rgbaOf(pal.champ, 0.55);
       g.arc(b.x,b.y,Math.max(dr,0.5),0,Math.PI*2); g.fill();
     }
   }
 
-  // a filled damask leaf/petal (teardrop) with blush body + gilt rib
-  function drawLeaf(g, x, y, ang, size, side, pal, amp, bloom) {
+  // A filled damask leaf/petal built from bezierCurveTo, with a lobed acanthus
+  // profile. Its flank control anchors OSCILLATE (osc) so the petal breathes as
+  // the kaleidoscope warps. `bloom` -> carmine secondary petal, else gilt gold.
+  function drawLeaf(g, x, y, ang, size, side, pal, amp, t, phase, bloom) {
+    const osc = Math.sin(t*1.6 + phase) * (0.18 + amp*0.35);   // anchor oscillation
+    const w = size*(bloom?0.72:0.60) * side;                   // half-width (mirrored by side)
+    const l = size*(bloom?1.15:1.65);                          // length outward (−y)
+    const belly = 1 + 0.28*osc;                                // widest-point bulge
+    const curl  = w*0.22*osc;                                  // tip sway
+
     g.save();
     g.translate(x,y); g.rotate(ang);
-    const w = size*(bloom?0.85:0.55), l = size*(bloom?1.15:1.6);
     g.beginPath();
     g.moveTo(0,0);
-    g.quadraticCurveTo(w*side, -l*0.5, 0, -l);
-    g.quadraticCurveTo(-w*side*0.62, -l*0.5, 0, 0);
+    // right flank: base -> lobe -> pointed tip (acanthus ogee)
+    g.bezierCurveTo( w*0.55,          -l*0.14,
+                     w*1.02*belly,    -l*0.46,
+                     curl,            -l);
+    // left flank: tip -> lobe -> base (mirror)
+    g.bezierCurveTo(-w*1.02*belly,    -l*0.46,
+                    -w*0.55,          -l*0.14,
+                     0,                0);
+    g.closePath();
+
+    const body = bloom ? pal.sec : pal.gold;
     const lg = g.createLinearGradient(0,0,0,-l);
-    lg.addColorStop(0, rgbaOf(pal.blush, 0.34+amp*0.12));
-    lg.addColorStop(0.6, rgbaOf(pal.blush, 0.16));
-    lg.addColorStop(1, rgbaOf(pal.accent, 0.05));
+    lg.addColorStop(0,   rgbaOf(body,      bloom ? 0.55 : 0.42));
+    lg.addColorStop(0.55,rgbaOf(body,      bloom ? 0.40 : 0.30));
+    lg.addColorStop(1,   rgbaOf(pal.champ, 0.10));
     g.fillStyle = lg; g.fill();
-    // gilt rib
-    g.beginPath(); g.moveTo(0,0); g.lineTo(0,-l*0.92);
-    g.strokeStyle = rgbaOf(pal.gold, 0.4); g.lineWidth = Math.max(size*0.03,0.4); g.stroke();
+
+    // champagne crisp outline for petal-vs-ground definition (the "pop")
+    g.strokeStyle = rgbaOf(pal.champ, bloom ? 0.28 : 0.40);
+    g.lineWidth = Math.max(size*0.02, 0.5);
+    g.stroke();
+
+    // gilt central rib
+    g.beginPath(); g.moveTo(0,0); g.lineTo(curl*0.5,-l*0.9);
+    g.strokeStyle = rgbaOf(bloom ? pal.champ : pal.gold, 0.5);
+    g.lineWidth = Math.max(size*0.028, 0.4); g.stroke();
     g.restore();
   }
 
@@ -287,11 +401,10 @@
     ctx.translate(cx, cy);
     ctx.rotate(rot);
 
-    const half = SLICE/2;
     for (let i=0;i<SIDES;i++){
       ctx.save();
       ctx.rotate(i*SLICE);
-      if (i % 2 === 1) ctx.scale(1,-1);        // mirror alternate wedges -> seamless
+      if (i % 2 === 1) ctx.scale(1,-1);        // mirror alternate wedges -> seamless 8-fold
       // clip a wedge spanning [0, SLICE]
       ctx.beginPath();
       ctx.moveTo(0,0);
@@ -304,18 +417,20 @@
     }
     ctx.restore();
 
-    // gilt center hub
-    const hub = ctx.createRadialGradient(cx,cy,0,cx,cy,R*0.10);
-    hub.addColorStop(0, rgbaOf(curPal.accent,0.55));
-    hub.addColorStop(0.5, rgbaOf(curPal.gold,0.18));
+    // center gold sheen (hub)
+    const hub = ctx.createRadialGradient(cx,cy,0,cx,cy,R*0.11);
+    hub.addColorStop(0, rgbaOf(curPal.champ,0.60));
+    hub.addColorStop(0.45, rgbaOf(curPal.gold,0.20));
     hub.addColorStop(1, rgbaOf(curPal.gold,0));
+    ctx.globalCompositeOperation = "lighter";
     ctx.fillStyle = hub;
-    ctx.beginPath(); ctx.arc(cx,cy,R*0.10,0,Math.PI*2); ctx.fill();
+    ctx.beginPath(); ctx.arc(cx,cy,R*0.11,0,Math.PI*2); ctx.fill();
+    ctx.globalCompositeOperation = "source-over";
 
-    // vignette for depth
-    const vg = ctx.createRadialGradient(cx,cy,R*0.35,cx,cy,R*0.98);
+    // vignette overlay for depth
+    const vg = ctx.createRadialGradient(cx,cy,R*0.34,cx,cy,R*0.98);
     vg.addColorStop(0,"rgba(0,0,0,0)");
-    vg.addColorStop(1,"rgba(0,0,0,0.55)");
+    vg.addColorStop(1,"rgba(0,0,0,0.60)");
     ctx.fillStyle = vg;
     ctx.fillRect(0,0,W,H);
   }
@@ -326,19 +441,20 @@
     let dt = (now - last) / 1000;
     last = now;
     if (dt > 0.05) dt = 0.05;                  // clamp huge first/hidden-tab deltas
-    t += dt;
+    const rate = reduceMotion ? 0.25 : 1;
+    t += dt * rate;
 
     // smooth pointer + warp
     wx += (mx - wx) * Math.min(dt*6,1);
     wy += (my - wy) * Math.min(dt*6,1);
     warp += (warpTarget - warp) * Math.min(dt*4,1);
 
-    // palette crossfade
+    // palette crossfade (smoothstep)
     if (palT < 1) { palT = Math.min(palT + dt/0.8, 1); }
     curPal = mixPalette(fromPal, toPal, palT<1 ? palT*palT*(3-2*palT) : 1);
 
     // master rotation: gentle idle drift + pointer-driven push
-    spin += dt * (0.05 + wx * 0.55);
+    spin += dt * rate * (0.05 + wx * 0.55);
     const rot = spin + wy * 0.4;
 
     // the source swirls & the motif amplitude opens up with pointer distance
@@ -351,7 +467,7 @@
     requestAnimationFrame(frame);
   }
 
-  document.getElementById("ui").style.setProperty("--accent", PALETTES[0].accent);
+  applyPillChrome(PALETTES[0]);
   requestAnimationFrame(frame);
 })();
 </script>

← ff920e1 chore: remove dead rgba() helper, stamp v1.0.1 (session clos  ·  back to Damask Kaleidoscope  ·  auto-save: 2026-07-25T08:35:01 (1 files) — index.html cdcbfd9 →