[object Object]

← back to Damask Kaleidoscope

Damask Kaleidoscope: rebuild Gemma3 artifact into a true wedge-mirror kaleidoscope

15752e559305467c400c2a742c72b5c6e437d2dd · 2026-07-25 00:35:52 -0700 · Steve Abrams

Files touched

Diff

commit 15752e559305467c400c2a742c72b5c6e437d2dd
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat Jul 25 00:35:52 2026 -0700

    Damask Kaleidoscope: rebuild Gemma3 artifact into a true wedge-mirror kaleidoscope
---
 .gitignore |   8 ++
 index.html | 343 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 351 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1924158
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..f6f7feb
--- /dev/null
+++ b/index.html
@@ -0,0 +1,343 @@
+<!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>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 rgba(hex,al){ const a=hexToRgb(hex); return `rgba(${a[0]},${a[1]},${a[2]},${al})`; }
+  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": an ogee vine sweeping outward with leaves, buds and gilt dots
+  function drawFlourish(g, rs, t, pal, amp, seed) {
+    const ph = seed * 1.7;
+    const sway = Math.sin(t*0.8 + ph) * (0.28 + amp*0.5);
+    const reach = 0.94;
+
+    // the sweeping ogee stem
+    const p0 = { x: 0, y: 0 };
+    const p1 = { x: rs*0.30*Math.cos(sway),            y: -rs*0.30 };
+    const p2 = { x: rs*(0.58+0.05*Math.sin(t+ph)),     y: -rs*0.34 - rs*0.18*sway };
+    const p3 = { x: rs*reach*Math.cos(0.32 + sway*0.4), y: -rs*reach*Math.sin(0.9 + sway*0.2) };
+
+    // gilt stem with soft outer glow then bright core
+    g.lineCap = "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.10); g.lineWidth = rs*0.055; }
+      else         { g.strokeStyle = rgbaOf(pal.gold,0.55); g.lineWidth = rs*0.018; }
+      g.stroke();
+    }
+
+    // leaves / buds strung along the 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.16*(1-u*0.45)) * (0.85+0.3*Math.sin(t*1.3+ph+i));
+      drawLeaf(g, b.x, b.y, ang + side*(1.15+0.25*Math.sin(t+i)), scale, side, pal, amp);
+    }
+
+    // a blossom teardrop at the tip
+    drawLeaf(g, p3.x, p3.y, Math.atan2(p3.y,p3.x), rs*0.13*(0.9+0.2*Math.sin(t*1.6+ph)), 1, pal, amp, true);
+
+    // gilt punctuation dots
+    for (let i=0;i<4;i++){
+      const u = 0.2 + i*0.2;
+      const b = bez(p0,p1,p2,p3,u);
+      const dr = rs*0.012*(1.4+Math.sin(t*2+i+ph));
+      g.beginPath();
+      g.fillStyle = rgbaOf(pal.accent, 0.5);
+      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>

(oldest)  ·  back to Damask Kaleidoscope  ·  Richer damask motif: baseline ogee curl + counter-scroll + f 05dd868 →