← back to Model Arena
auto-save: 2026-07-25T01:32:27 (23 files) — data/challenges.json data/costlog.jsonl data/artifacts/6923e6180f0f/grok.html data/artifacts/6923e6180f0f/grok.png data/artifacts/71a0784e71c8/
90ddbb28279ba127c7c07960c21eec182144d0f4 · 2026-07-25 01:32:34 -0700 · Steve Abrams
Files touched
A data/artifacts/6923e6180f0f/grok.htmlA data/artifacts/6923e6180f0f/grok.pngA data/artifacts/71a0784e71c8/claude-code.htmlA data/artifacts/71a0784e71c8/claude-code.pngA data/artifacts/71a0784e71c8/gpt.htmlA data/artifacts/71a0784e71c8/gpt.pngA data/artifacts/71a0784e71c8/grok.htmlA data/artifacts/71a0784e71c8/grok.pngA data/artifacts/71a0784e71c8/kimi.htmlA data/artifacts/71a0784e71c8/kimi.pngA data/artifacts/71a0784e71c8/qwen25-7b.htmlA data/artifacts/71a0784e71c8/qwen25-7b.pngA data/artifacts/8443d9162963/gemma3-12b.htmlA data/artifacts/8443d9162963/gemma3-12b.pngA data/artifacts/8443d9162963/hermes3-8b.htmlA data/artifacts/8443d9162963/hermes3-8b.pngA data/artifacts/8443d9162963/hf-qwen-coder-32b.htmlA data/artifacts/8443d9162963/hf-qwen-coder-32b.pngA data/artifacts/8443d9162963/qwen3-14b.htmlA data/artifacts/8443d9162963/qwen3-14b.pngA data/artifacts/987bed245bf6/hf-qwen-coder-32b.htmlA data/artifacts/987bed245bf6/hf-qwen-coder-32b.pngA data/artifacts/b3721d9fe563/claude-code.htmlA data/artifacts/b3721d9fe563/claude-code.pngA data/artifacts/b3721d9fe563/gpt.htmlA data/artifacts/b3721d9fe563/gpt.pngA data/artifacts/b3721d9fe563/grok.htmlA data/artifacts/b3721d9fe563/grok.pngA data/artifacts/b3721d9fe563/kimi.htmlA data/artifacts/b3721d9fe563/kimi.pngM data/challenges.jsonM data/costlog.jsonl
Diff
commit 90ddbb28279ba127c7c07960c21eec182144d0f4
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sat Jul 25 01:32:34 2026 -0700
auto-save: 2026-07-25T01:32:27 (23 files) — data/challenges.json data/costlog.jsonl data/artifacts/6923e6180f0f/grok.html data/artifacts/6923e6180f0f/grok.png data/artifacts/71a0784e71c8/
---
data/artifacts/6923e6180f0f/grok.html | 1093 ++++++++++++++++++++
data/artifacts/6923e6180f0f/grok.png | Bin 0 -> 373446 bytes
data/artifacts/71a0784e71c8/claude-code.html | 454 ++++++++
data/artifacts/71a0784e71c8/claude-code.png | Bin 0 -> 308575 bytes
data/artifacts/71a0784e71c8/gpt.html | 910 ++++++++++++++++
data/artifacts/71a0784e71c8/gpt.png | Bin 0 -> 294252 bytes
data/artifacts/71a0784e71c8/grok.html | 798 ++++++++++++++
data/artifacts/71a0784e71c8/grok.png | Bin 0 -> 370889 bytes
data/artifacts/71a0784e71c8/kimi.html | 697 +++++++++++++
data/artifacts/71a0784e71c8/kimi.png | Bin 0 -> 201900 bytes
data/artifacts/71a0784e71c8/qwen25-7b.html | 109 ++
data/artifacts/71a0784e71c8/qwen25-7b.png | Bin 0 -> 18619 bytes
data/artifacts/8443d9162963/gemma3-12b.html | 212 ++++
data/artifacts/8443d9162963/gemma3-12b.png | Bin 0 -> 7048 bytes
data/artifacts/8443d9162963/hermes3-8b.html | 196 ++++
data/artifacts/8443d9162963/hermes3-8b.png | Bin 0 -> 6342 bytes
data/artifacts/8443d9162963/hf-qwen-coder-32b.html | 214 ++++
data/artifacts/8443d9162963/hf-qwen-coder-32b.png | Bin 0 -> 9718 bytes
data/artifacts/8443d9162963/qwen3-14b.html | 250 +++++
data/artifacts/8443d9162963/qwen3-14b.png | Bin 0 -> 10077 bytes
data/artifacts/987bed245bf6/hf-qwen-coder-32b.html | 246 +++++
data/artifacts/987bed245bf6/hf-qwen-coder-32b.png | Bin 0 -> 5358 bytes
data/artifacts/b3721d9fe563/claude-code.html | 591 +++++++++++
data/artifacts/b3721d9fe563/claude-code.png | Bin 0 -> 89669 bytes
data/artifacts/b3721d9fe563/gpt.html | 926 +++++++++++++++++
data/artifacts/b3721d9fe563/gpt.png | Bin 0 -> 260770 bytes
data/artifacts/b3721d9fe563/grok.html | 899 ++++++++++++++++
data/artifacts/b3721d9fe563/grok.png | Bin 0 -> 81952 bytes
data/artifacts/b3721d9fe563/kimi.html | 534 ++++++++++
data/artifacts/b3721d9fe563/kimi.png | Bin 0 -> 155721 bytes
data/challenges.json | 348 +++++--
data/costlog.jsonl | 7 +
32 files changed, 8384 insertions(+), 100 deletions(-)
diff --git a/data/artifacts/6923e6180f0f/grok.html b/data/artifacts/6923e6180f0f/grok.html
new file mode 100644
index 0000000..0b293a7
--- /dev/null
+++ b/data/artifacts/6923e6180f0f/grok.html
@@ -0,0 +1,1093 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Guess the Design Style</title>
+<style>
+:root {
+ --bg: #0d0d0f;
+ --surface: #17171c;
+ --surface2: #1e1e26;
+ --text: #f5f2ea;
+ --muted: #8a8578;
+ --accent: #c9a961;
+ --accent2: #7d6a45;
+ --correct: #6b9b7a;
+ --wrong: #b85c5c;
+ --ease: cubic-bezier(.22,.9,.24,1);
+ --overshoot: cubic-bezier(.34,1.56,.64,1);
+ --quint: cubic-bezier(.22,1,.36,1);
+ --serif: 'Didot', 'Bodoni MT', 'Playfair Display', Georgia, serif;
+ --sans: -apple-system, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
+}
+*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+html, body { height: 100%; }
+body {
+ font-family: var(--sans);
+ background: var(--bg);
+ color: var(--text);
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ overflow-x: hidden;
+ -webkit-font-smoothing: antialiased;
+}
+.grain {
+ pointer-events: none;
+ position: fixed;
+ inset: 0;
+ opacity: 0.035;
+ z-index: 1000;
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
+}
+.app {
+ width: min(920px, 100%);
+ padding: 28px 20px 40px;
+ display: flex;
+ flex-direction: column;
+ gap: 22px;
+ position: relative;
+ z-index: 1;
+}
+header {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ gap: 16px;
+ flex-wrap: wrap;
+}
+.brand {
+ font-family: var(--serif);
+ font-size: 28px;
+ letter-spacing: -0.03em;
+ font-weight: 400;
+ line-height: 1.1;
+}
+.brand span {
+ color: var(--accent);
+ font-style: italic;
+}
+.stats {
+ display: flex;
+ gap: 18px;
+ align-items: center;
+ flex-wrap: wrap;
+}
+.stat {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ gap: 2px;
+}
+.stat .label {
+ font-size: 11px;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: var(--muted);
+}
+.stat .value {
+ font-family: var(--serif);
+ font-size: 22px;
+ color: var(--text);
+ font-variant-numeric: tabular-nums;
+ letter-spacing: -0.02em;
+}
+.stat .value.accent { color: var(--accent); }
+.progress-track {
+ height: 2px;
+ background: var(--surface2);
+ border-radius: 2px;
+ overflow: hidden;
+}
+.progress-fill {
+ height: 100%;
+ background: linear-gradient(90deg, var(--accent2), var(--accent));
+ width: 0%;
+ transition: width 0.5s var(--ease);
+}
+.stage-wrap {
+ position: relative;
+ border-radius: 16px;
+ overflow: hidden;
+ background: var(--surface);
+ border: 1px solid rgba(201,169,97,0.12);
+ box-shadow:
+ 0 0 0 1px rgba(0,0,0,0.4),
+ 0 24px 48px -12px rgba(0,0,0,0.55),
+ inset 0 1px 0 rgba(255,255,255,0.04);
+}
+.stage {
+ aspect-ratio: 16 / 10;
+ width: 100%;
+ position: relative;
+ overflow: hidden;
+ background: #1a1814;
+}
+.motif {
+ position: absolute;
+ inset: 0;
+ opacity: 0;
+ transform: scale(1.04);
+ transition: opacity 0.55s var(--quint), transform 0.7s var(--quint);
+}
+.motif.show {
+ opacity: 1;
+ transform: scale(1);
+}
+.motif.exit {
+ opacity: 0;
+ transform: scale(0.97);
+ transition: opacity 0.28s var(--ease), transform 0.28s var(--ease);
+}
+.flash {
+ pointer-events: none;
+ position: absolute;
+ inset: 0;
+ opacity: 0;
+ z-index: 5;
+ transition: opacity 0.15s ease;
+}
+.flash.correct {
+ background: radial-gradient(ellipse at center, rgba(107,155,122,0.35), transparent 70%);
+ opacity: 1;
+ animation: flashOut 0.7s var(--ease) forwards;
+}
+.flash.wrong {
+ background: radial-gradient(ellipse at center, rgba(184,92,92,0.35), transparent 70%);
+ opacity: 1;
+ animation: flashOut 0.7s var(--ease) forwards;
+}
+@keyframes flashOut {
+ 0% { opacity: 1; }
+ 100% { opacity: 0; }
+}
+.timer-ring {
+ position: absolute;
+ top: 14px;
+ right: 14px;
+ width: 44px;
+ height: 44px;
+ z-index: 6;
+}
+.timer-ring svg { transform: rotate(-90deg); width: 100%; height: 100%; }
+.timer-ring circle {
+ fill: none;
+ stroke-width: 3;
+}
+.timer-ring .bg { stroke: rgba(245,242,234,0.08); }
+.timer-ring .fg {
+ stroke: var(--accent);
+ stroke-linecap: round;
+ stroke-dasharray: 113.1;
+ stroke-dashoffset: 0;
+ transition: stroke-dashoffset 0.1s linear, stroke 0.3s ease;
+}
+.timer-ring.urgent .fg { stroke: var(--wrong); }
+.timer-num {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-family: var(--serif);
+ font-size: 14px;
+ color: var(--text);
+ font-variant-numeric: tabular-nums;
+}
+.round-badge {
+ position: absolute;
+ top: 16px;
+ left: 16px;
+ z-index: 6;
+ font-size: 11px;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: var(--muted);
+ background: rgba(13,13,15,0.55);
+ backdrop-filter: blur(8px);
+ padding: 6px 12px;
+ border-radius: 100px;
+ border: 1px solid rgba(201,169,97,0.15);
+}
+.choices {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 10px;
+}
+@media (min-width: 560px) {
+ .choices { grid-template-columns: repeat(4, 1fr); }
+}
+.choice {
+ appearance: none;
+ border: 1px solid rgba(201,169,97,0.18);
+ background: var(--surface);
+ color: var(--text);
+ font-family: var(--sans);
+ font-size: 14px;
+ letter-spacing: 0.04em;
+ padding: 16px 12px;
+ border-radius: 10px;
+ cursor: pointer;
+ transition:
+ background 0.25s var(--ease),
+ border-color 0.25s var(--ease),
+ color 0.25s var(--ease),
+ transform 0.3s var(--overshoot),
+ box-shadow 0.25s var(--ease);
+ position: relative;
+ overflow: hidden;
+}
+.choice:hover:not(:disabled) {
+ border-color: var(--accent);
+ background: var(--surface2);
+ transform: translateY(-2px);
+ box-shadow: 0 8px 20px -8px rgba(201,169,97,0.25);
+}
+.choice:active:not(:disabled) { transform: translateY(0) scale(0.98); }
+.choice:disabled { cursor: default; }
+.choice.correct {
+ background: rgba(107,155,122,0.18);
+ border-color: var(--correct);
+ color: #b8d4c0;
+ animation: popIn 0.45s var(--overshoot);
+}
+.choice.wrong {
+ background: rgba(184,92,92,0.15);
+ border-color: var(--wrong);
+ color: #d4a0a0;
+ animation: shake 0.4s var(--ease);
+}
+.choice.dim { opacity: 0.35; }
+@keyframes popIn {
+ 0% { transform: scale(0.92); }
+ 100% { transform: scale(1); }
+}
+@keyframes shake {
+ 0%, 100% { transform: translateX(0); }
+ 20% { transform: translateX(-6px); }
+ 40% { transform: translateX(6px); }
+ 60% { transform: translateX(-4px); }
+ 80% { transform: translateX(3px); }
+}
+.feedback {
+ text-align: center;
+ min-height: 24px;
+ font-family: var(--serif);
+ font-size: 18px;
+ font-style: italic;
+ letter-spacing: -0.01em;
+ opacity: 0;
+ transform: translateY(6px);
+ transition: opacity 0.35s var(--ease), transform 0.35s var(--ease), color 0.3s ease;
+}
+.feedback.show {
+ opacity: 1;
+ transform: translateY(0);
+}
+.feedback.good { color: var(--correct); }
+.feedback.bad { color: var(--wrong); }
+
+/* ——— Overlay screens ——— */
+.overlay {
+ position: absolute;
+ inset: 0;
+ z-index: 20;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 18px;
+ background: rgba(13,13,15,0.92);
+ backdrop-filter: blur(12px);
+ padding: 32px 24px;
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 0.45s var(--ease);
+}
+.overlay.show {
+ opacity: 1;
+ pointer-events: auto;
+}
+.overlay h1 {
+ font-family: var(--serif);
+ font-size: clamp(32px, 6vw, 50px);
+ font-weight: 400;
+ letter-spacing: -0.03em;
+ line-height: 1.1;
+ text-align: center;
+}
+.overlay h1 em {
+ font-style: italic;
+ color: var(--accent);
+}
+.overlay p {
+ color: var(--muted);
+ font-size: 15px;
+ line-height: 1.6;
+ text-align: center;
+ max-width: 36ch;
+}
+.overlay .big-score {
+ font-family: var(--serif);
+ font-size: 67px;
+ letter-spacing: -0.04em;
+ color: var(--accent);
+ line-height: 1;
+}
+.overlay .sub {
+ font-size: 12px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--muted);
+ margin-top: -8px;
+}
+.btn-primary {
+ appearance: none;
+ border: none;
+ background: var(--accent);
+ color: var(--bg);
+ font-family: var(--sans);
+ font-size: 13px;
+ font-weight: 600;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ padding: 16px 36px;
+ border-radius: 100px;
+ cursor: pointer;
+ transition: transform 0.3s var(--overshoot), box-shadow 0.3s var(--ease), background 0.2s ease;
+ margin-top: 8px;
+}
+.btn-primary:hover {
+ transform: translateY(-2px) scale(1.02);
+ box-shadow: 0 12px 28px -8px rgba(201,169,97,0.45);
+ background: #d4b56e;
+}
+.btn-primary:active { transform: scale(0.98); }
+.meta-row {
+ display: flex;
+ gap: 28px;
+ margin-top: 4px;
+}
+.meta-row .m {
+ text-align: center;
+}
+.meta-row .m .n {
+ font-family: var(--serif);
+ font-size: 24px;
+ color: var(--text);
+}
+.meta-row .m .l {
+ font-size: 11px;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: var(--muted);
+ margin-top: 2px;
+}
+.styles-hint {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+ justify-content: center;
+ margin-top: 4px;
+ max-width: 420px;
+}
+.styles-hint i {
+ font-style: normal;
+ font-size: 11px;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ color: var(--accent2);
+ border: 1px solid rgba(125,106,69,0.4);
+ padding: 4px 10px;
+ border-radius: 100px;
+}
+
+/* ========== CSS MOTIFS ========== */
+
+/* DAMASK — ornate diamond medallions */
+.m-damask {
+ background-color: #1c1814;
+ background-image:
+ radial-gradient(ellipse 18% 22% at 50% 50%, rgba(201,169,97,0.22) 0%, transparent 70%),
+ radial-gradient(ellipse 8% 10% at 50% 30%, rgba(201,169,97,0.18) 0%, transparent 70%),
+ radial-gradient(ellipse 8% 10% at 50% 70%, rgba(201,169,97,0.18) 0%, transparent 70%),
+ radial-gradient(ellipse 10% 8% at 30% 50%, rgba(201,169,97,0.18) 0%, transparent 70%),
+ radial-gradient(ellipse 10% 8% at 70% 50%, rgba(201,169,97,0.18) 0%, transparent 70%),
+ radial-gradient(circle at 50% 18%, rgba(201,169,97,0.12) 0 2px, transparent 2.5px),
+ radial-gradient(circle at 50% 82%, rgba(201,169,97,0.12) 0 2px, transparent 2.5px),
+ radial-gradient(circle at 18% 50%, rgba(201,169,97,0.12) 0 2px, transparent 2.5px),
+ radial-gradient(circle at 82% 50%, rgba(201,169,97,0.12) 0 2px, transparent 2.5px);
+ background-size: 120px 140px;
+ background-position: 0 0, 0 0, 0 0, 0 0, 0 0, 0 0, 0 0, 0 0, 0 0;
+}
+.m-damask::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background-image:
+ radial-gradient(ellipse 40% 48% at 50% 50%, transparent 42%, rgba(201,169,97,0.1) 45%, transparent 52%),
+ radial-gradient(ellipse 22% 28% at 50% 50%, transparent 50%, rgba(201,169,97,0.14) 55%, transparent 62%);
+ background-size: 120px 140px;
+ opacity: 0.9;
+}
+.m-damask::after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background-image:
+ radial-gradient(ellipse 40% 48% at 50% 50%, transparent 42%, rgba(201,169,97,0.08) 45%, transparent 52%);
+ background-size: 120px 140px;
+ background-position: 60px 70px;
+}
+
+/* TOILE — pastoral line-drawing feel with tree/figure silhouettes */
+.m-toile {
+ background-color: #e8e0d4;
+ background-image:
+ /* tree canopy */
+ radial-gradient(ellipse 14px 12px at 30px 42px, #2a4a6b 0 60%, transparent 61%),
+ radial-gradient(ellipse 10px 9px at 22px 48px, #2a4a6b 0 60%, transparent 61%),
+ radial-gradient(ellipse 10px 9px at 38px 48px, #2a4a6b 0 60%, transparent 61%),
+ /* trunk */
+ linear-gradient(to right, transparent 28px, #2a4a6b 28px, #2a4a6b 32px, transparent 32px),
+ /* small figure */
+ radial-gradient(circle 3px at 70px 58px, #2a4a6b 0 100%, transparent 100%),
+ linear-gradient(to bottom, transparent 60px, #2a4a6b 60px, #2a4a6b 72px, transparent 72px),
+ /* bird */
+ radial-gradient(ellipse 5px 3px at 90px 30px, #2a4a6b 0 100%, transparent 100%),
+ /* ground line */
+ linear-gradient(to bottom, transparent 78px, rgba(42,74,107,0.35) 78px, rgba(42,74,107,0.35) 79px, transparent 79px),
+ /* distant bush */
+ radial-gradient(ellipse 12px 8px at 100px 72px, #2a4a6b 0 55%, transparent 56%),
+ radial-gradient(ellipse 8px 6px at 110px 74px, #2a4a6b 0 55%, transparent 56%),
+ /* little house */
+ linear-gradient(135deg, transparent 50%, #2a4a6b 50%, #2a4a6b 55%, transparent 55%),
+ linear-gradient(to top, #2a4a6b 0 10px, transparent 10px);
+ background-size: 130px 100px;
+ background-position:
+ 0 0, 0 0, 0 0,
+ 0 50px,
+ 0 0,
+ 68px 0,
+ 0 0,
+ 0 0,
+ 0 0, 0 0,
+ 48px 55px,
+ 50px 65px;
+}
+.m-toile::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background-image:
+ radial-gradient(ellipse 14px 12px at 30px 42px, #2a4a6b 0 60%, transparent 61%),
+ radial-gradient(ellipse 10px 9px at 22px 48px, #2a4a6b 0 60%, transparent 61%),
+ radial-gradient(ellipse 10px 9px at 38px 48px, #2a4a6b 0 60%, transparent 61%),
+ linear-gradient(to right, transparent 28px, #2a4a6b 28px, #2a4a6b 32px, transparent 32px),
+ radial-gradient(circle 3px at 70px 58px, #2a4a6b 0 100%, transparent 100%),
+ linear-gradient(to bottom, transparent 60px, #2a4a6b 60px, #2a4a6b 72px, transparent 72px),
+ radial-gradient(ellipse 5px 3px at 90px 30px, #2a4a6b 0 100%, transparent 100%),
+ linear-gradient(to bottom, transparent 78px, rgba(42,74,107,0.35) 78px, rgba(42,74,107,0.35) 79px, transparent 79px),
+ radial-gradient(ellipse 12px 8px at 100px 72px, #2a4a6b 0 55%, transparent 56%);
+ background-size: 130px 100px;
+ background-position: 65px 50px;
+ opacity: 0.55;
+ transform: scaleX(-1);
+}
+.m-toile::after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background:
+ repeating-linear-gradient(
+ 0deg,
+ transparent,
+ transparent 3px,
+ rgba(42,74,107,0.03) 3px,
+ rgba(42,74,107,0.03) 4px
+ );
+ opacity: 0.8;
+}
+
+/* IKAT — blurred feathered chevron / diamond weave */
+.m-ikat {
+ background-color: #2c1810;
+ background-image:
+ repeating-linear-gradient(
+ 90deg,
+ #c45c26 0px,
+ #c45c26 8px,
+ #e8a060 8px,
+ #e8a060 14px,
+ #2c1810 14px,
+ #2c1810 18px,
+ #d4a84b 18px,
+ #d4a84b 26px,
+ #8b3a1a 26px,
+ #8b3a1a 34px,
+ #2c1810 34px,
+ #2c1810 40px,
+ #e8c070 40px,
+ #e8c070 46px,
+ #c45c26 46px,
+ #c45c26 54px,
+ #2c1810 54px,
+ #2c1810 62px
+ );
+ filter: none;
+}
+.m-ikat::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background-image:
+ repeating-linear-gradient(
+ 90deg,
+ transparent 0px,
+ transparent 6px,
+ rgba(44,24,16,0.7) 10px,
+ transparent 14px,
+ transparent 22px,
+ rgba(44,24,16,0.55) 28px,
+ transparent 34px,
+ transparent 48px,
+ rgba(44,24,16,0.65) 54px,
+ transparent 60px
+ );
+ mix-blend-mode: multiply;
+ opacity: 0.85;
+ transform: skewY(-2deg) scaleY(1.05);
+ filter: blur(1.5px);
+}
+.m-ikat::after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background-image:
+ repeating-linear-gradient(
+ 0deg,
+ transparent 0px,
+ transparent 18px,
+ rgba(232,160,96,0.12) 18px,
+ rgba(232,160,96,0.12) 20px,
+ transparent 20px,
+ transparent 38px,
+ rgba(196,92,38,0.15) 38px,
+ rgba(196,92,38,0.15) 42px
+ );
+ filter: blur(0.8px);
+}
+
+/* CHINOISERIE — pagoda, branches, birds on lacquer */
+.m-chinoiserie {
+ background-color: #1a0f0a;
+ background-image:
+ /* moon */
+ radial-gradient(circle 28px at 78% 22%, rgba(232,210,160,0.55) 0 70%, transparent 71%),
+ radial-gradient(circle 28px at 78% 22%, rgba(232,210,160,0.15) 0 100%, transparent 100%),
+ /* pagoda roof tiers */
+ linear-gradient(160deg, transparent 40%, #c9a961 40%, #c9a961 44%, transparent 44%),
+ linear-gradient(20deg, transparent 40%, #c9a961 40%, #c9a961 44%, transparent 44%),
+ linear-gradient(160deg, transparent 42%, #a88b4a 42%, #a88b4a 45%, transparent 45%),
+ linear-gradient(20deg, transparent 42%, #a88b4a 42%, #a88b4a 45%, transparent 45%),
+ /* pagoda body */
+ linear-gradient(to right, transparent 46%, #8a7040 46%, #8a7040 54%, transparent 54%),
+ /* branch curves approximated */
+ radial-gradient(ellipse 80px 40px at 20% 70%, transparent 60%, rgba(201,169,97,0.35) 62%, rgba(201,169,97,0.35) 64%, transparent 66%),
+ radial-gradient(ellipse 60px 30px at 35% 55%, transparent 60%, rgba(201,169,97,0.3) 62%, rgba(201,169,97,0.3) 64%, transparent 66%),
+ /* blossoms */
+ radial-gradient(circle 4px at 18% 62%, #d4a0a0 0 100%, transparent 100%),
+ radial-gradient(circle 3px at 24% 58%, #d4a0a0 0 100%, transparent 100%),
+ radial-gradient(circle 3.5px at 28% 65%, #c08080 0 100%, transparent 100%),
+ radial-gradient(circle 4px at 40% 48%, #d4a0a0 0 100%, transparent 100%),
+ radial-gradient(circle 3px at 44% 52%, #c08080 0 100%, transparent 100%),
+ radial-gradient(circle 2.5px at 36% 50%, #e0b0b0 0 100%, transparent 100%),
+ /* bird silhouette */
+ radial-gradient(ellipse 8px 5px at 62% 38%, #c9a961 0 100%, transparent 100%),
+ radial-gradient(circle 2.5px at 58% 37%, #c9a961 0 100%, transparent 100%),
+ /* leaves */
+ radial-gradient(ellipse 6px 3px at 22% 72%, rgba(107,140,90,0.6) 0 100%, transparent 100%),
+ radial-gradient(ellipse 5px 3px at 30% 68%, rgba(107,140,90,0.5) 0 100%, transparent 100%),
+ radial-gradient(ellipse 6px 3px at 42% 56%, rgba(107,140,90,0.55) 0 100%, transparent 100%);
+ background-size: 100% 100%;
+ background-repeat: no-repeat;
+}
+.m-chinoiserie::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background-image:
+ radial-gradient(circle 28px at 22% 28%, rgba(232,210,160,0.2) 0 70%, transparent 71%),
+ linear-gradient(150deg, transparent 40%, rgba(201,169,97,0.45) 40%, rgba(201,169,97,0.45) 43%, transparent 43%),
+ linear-gradient(30deg, transparent 40%, rgba(201,169,97,0.45) 40%, rgba(201,169,97,0.45) 43%, transparent 43%),
+ linear-gradient(to right, transparent 47%, rgba(138,112,64,0.5) 47%, rgba(138,112,64,0.5) 53%, transparent 53%),
+ radial-gradient(ellipse 70px 35px at 75% 75%, transparent 60%, rgba(201,169,97,0.25) 62%, rgba(201,169,97,0.25) 64%, transparent 66%),
+ radial-gradient(circle 3px at 70% 70%, #d4a0a0 0 100%, transparent 100%),
+ radial-gradient(circle 3px at 78% 68%, #c08080 0 100%, transparent 100%),
+ radial-gradient(circle 2.5px at 74% 74%, #e0b0b0 0 100%, transparent 100%),
+ radial-gradient(ellipse 7px 4px at 55% 60%, #c9a961 0 100%, transparent 100%);
+ background-size: 55% 55%;
+ background-position: 100% 100%;
+ background-repeat: no-repeat;
+ opacity: 0.7;
+}
+.m-chinoiserie::after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background:
+ radial-gradient(ellipse at 50% 100%, rgba(201,169,97,0.08), transparent 50%),
+ repeating-linear-gradient(
+ 0deg,
+ transparent,
+ transparent 2px,
+ rgba(0,0,0,0.03) 2px,
+ rgba(0,0,0,0.03) 3px
+ );
+}
+
+/* GEOMETRIC — art deco / mid-century hard geometry */
+.m-geometric {
+ background-color: #12141a;
+ background-image:
+ /* large diamonds */
+ linear-gradient(45deg, #c9a961 25%, transparent 25%),
+ linear-gradient(-45deg, #c9a961 25%, transparent 25%),
+ linear-gradient(45deg, transparent 75%, #c9a961 75%),
+ linear-gradient(-45deg, transparent 75%, #c9a961 75%),
+ /* secondary teal triangles via gradients */
+ linear-gradient(45deg, transparent 40%, rgba(70,120,130,0.55) 40%, rgba(70,120,130,0.55) 50%, transparent 50%),
+ linear-gradient(-45deg, transparent 40%, rgba(70,120,130,0.55) 40%, rgba(70,120,130,0.55) 50%, transparent 50%);
+ background-size: 48px 48px, 48px 48px, 48px 48px, 48px 48px, 96px 96px, 96px 96px;
+ background-position: 0 0, 0 0, 0 0, 0 0, 0 0, 0 0;
+ background-blend-mode: normal;
+}
+.m-geometric::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background-image:
+ linear-gradient(45deg, transparent 46%, rgba(245,242,234,0.12) 46%, rgba(245,242,234,0.12) 54%, transparent 54%),
+ linear-gradient(-45deg, transparent 46%, rgba(245,242,234,0.12) 46%, rgba(245,242,234,0.12) 54%, transparent 54%),
+ radial-gradient(circle at center, rgba(201,169,97,0.25) 0 3px, transparent 3.5px);
+ background-size: 48px 48px, 48px 48px, 48px 48px;
+ opacity: 0.9;
+}
+.m-geometric::after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background-image:
+ linear-gradient(30deg, transparent 48%, rgba(184,92,92,0.4) 48%, rgba(184,92,92,0.4) 52%, transparent 52%),
+ linear-gradient(-30deg, transparent 48%, rgba(184,92,92,0.4) 48%, rgba(184,92,92,0.4) 52%, transparent 52%);
+ background-size: 96px 96px;
+ background-position: 24px 0, 24px 0;
+ opacity: 0.5;
+}
+
+/* GRASSCLOTH — fine horizontal weave / fiber texture */
+.m-grasscloth {
+ background-color: #c4b49a;
+ background-image:
+ repeating-linear-gradient(
+ 0deg,
+ transparent 0px,
+ transparent 1px,
+ rgba(90,70,45,0.07) 1px,
+ rgba(90,70,45,0.07) 2px
+ ),
+ repeating-linear-gradient(
+ 90deg,
+ transparent 0px,
+ transparent 3px,
+ rgba(70,55,35,0.06) 3px,
+ rgba(70,55,35,0.06) 4px,
+ transparent 4px,
+ transparent 7px,
+ rgba(110,90,60,0.05) 7px,
+ rgba(110,90,60,0.05) 8px
+ ),
+ repeating-linear-gradient(
+ 0deg,
+ rgba(140,120,90,0.15) 0px,
+ rgba(196,180,154,0.1) 3px,
+ rgba(160,140,110,0.12) 6px,
+ rgba(180,160,130,0.08) 10px,
+ rgba(150,130,100,0.14) 14px
+ ),
+ linear-gradient(
+ 90deg,
+ rgba(130,110,80,0.12) 0%,
+ transparent 30%,
+ transparent 70%,
+ rgba(100,80,55,0.1) 100%
+ );
+ background-size: 100% 2px, 8px 100%, 100% 14px, 100% 100%;
+}
+.m-grasscloth::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background-image:
+ repeating-linear-gradient(
+ 0deg,
+ transparent 0px,
+ transparent 5px,
+ rgba(80,60,40,0.04) 5px,
+ rgba(80,60,40,0.04) 6px
+ ),
+ repeating-linear-gradient(
+ 90deg,
+ rgba(60,45,30,0.03) 0px,
+ transparent 1px,
+ transparent 11px,
+ rgba(60,45,30,0.04) 12px
+ );
+ opacity: 1;
+}
+.m-grasscloth::after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background:
+ radial-gradient(ellipse 120% 80% at 30% 40%, rgba(220,200,170,0.15), transparent 50%),
+ radial-gradient(ellipse 100% 60% at 70% 70%, rgba(100,80,55,0.1), transparent 50%),
+ repeating-linear-gradient(
+ 1deg,
+ transparent 0px,
+ transparent 40px,
+ rgba(90,70,50,0.04) 40px,
+ rgba(90,70,50,0.04) 41px
+ );
+}
+
+/* subtle vignette on stage */
+.stage::after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ z-index: 2;
+ pointer-events: none;
+ background: radial-gradient(ellipse at center, transparent 50%, rgba(0,0,0,0.28) 100%);
+}
+</style>
+</head>
+<body>
+<div class="grain" aria-hidden="true"></div>
+<div class="app">
+ <header>
+ <div class="brand">Guess the <span>Style</span></div>
+ <div class="stats">
+ <div class="stat">
+ <span class="label">Score</span>
+ <span class="value accent" id="score">0</span>
+ </div>
+ <div class="stat">
+ <span class="label">Streak</span>
+ <span class="value" id="streak">0</span>
+ </div>
+ <div class="stat">
+ <span class="label">Best</span>
+ <span class="value" id="best">0</span>
+ </div>
+ </div>
+ </header>
+
+ <div class="progress-track"><div class="progress-fill" id="progress"></div></div>
+
+ <div class="stage-wrap">
+ <div class="stage" id="stage">
+ <div class="motif" id="motif"></div>
+ <div class="flash" id="flash"></div>
+ <div class="round-badge" id="roundBadge">Round 1 / 10</div>
+ <div class="timer-ring" id="timerRing">
+ <svg viewBox="0 0 44 44">
+ <circle class="bg" cx="22" cy="22" r="18"></circle>
+ <circle class="fg" id="timerArc" cx="22" cy="22" r="18"></circle>
+ </svg>
+ <div class="timer-num" id="timerNum">12</div>
+ </div>
+ </div>
+
+ <div class="overlay show" id="startOverlay">
+ <h1>Guess the <em>Design Style</em></h1>
+ <p>A wallpaper motif appears. Identify its style before time runs out. 10 rounds.</p>
+ <div class="styles-hint">
+ <i>Damask</i><i>Toile</i><i>Ikat</i><i>Chinoiserie</i><i>Geometric</i><i>Grasscloth</i>
+ </div>
+ <button class="btn-primary" id="startBtn">Begin</button>
+ </div>
+
+ <div class="overlay" id="endOverlay">
+ <div class="sub">Final score</div>
+ <div class="big-score" id="finalScore">0</div>
+ <h1 id="endTitle">Well played</h1>
+ <div class="meta-row">
+ <div class="m"><div class="n" id="endStreak">0</div><div class="l">Best streak</div></div>
+ <div class="m"><div class="n" id="endBest">0</div><div class="l">All-time best</div></div>
+ <div class="m"><div class="n" id="endAcc">0%</div><div class="l">Accuracy</div></div>
+ </div>
+ <button class="btn-primary" id="restartBtn">Play again</button>
+ </div>
+ </div>
+
+ <div class="feedback" id="feedback"></div>
+
+ <div class="choices" id="choices"></div>
+</div>
+
+<script>
+(function () {
+ const STYLES = ['Damask', 'Toile', 'Ikat', 'Chinoiserie', 'Geometric', 'Grasscloth'];
+ const CLASS_MAP = {
+ Damask: 'm-damask',
+ Toile: 'm-toile',
+ Ikat: 'm-ikat',
+ Chinoiserie: 'm-chinoiserie',
+ Geometric: 'm-geometric',
+ Grasscloth: 'm-grasscloth'
+ };
+ const FLAVOR = {
+ Damask: ['Exquisite.', 'A classic damask medallion.', 'Ornate & refined.'],
+ Toile: ['Pastoral perfection.', 'Toile de Jouy, bien sûr.', 'A story in every print.'],
+ Ikat: ['Feathered & dyed.', 'That ikat blur is unmistakable.', 'Woven fire.'],
+ Chinoiserie: ['Eastern elegance.', 'Lacquer & pagodas.', 'A chinoiserie dream.'],
+ Geometric: ['Clean. Bold. Modern.', 'Geometry never lies.', 'Art deco precision.'],
+ Grasscloth: ['Natural texture.', 'Woven grass, quiet luxury.', 'Tactile & timeless.']
+ };
+ const WRONG_FLAVOR = [
+ 'Not quite.',
+ 'A near miss.',
+ 'Look closer next time.',
+ 'The motif tells another story.',
+ 'Close, but no.'
+ ];
+ const TOTAL = 10;
+ const TIME = 12;
+ const CIRC = 2 * Math.PI * 18;
+
+ const $ = (id) => document.getElementById(id);
+ const motifEl = $('motif');
+ const flashEl = $('flash');
+ const scoreEl = $('score');
+ const streakEl = $('streak');
+ const bestEl = $('best');
+ const progressEl = $('progress');
+ const roundBadge = $('roundBadge');
+ const timerArc = $('timerArc');
+ const timerNum = $('timerNum');
+ const timerRing = $('timerRing');
+ const choicesEl = $('choices');
+ const feedbackEl = $('feedback');
+ const startOverlay = $('startOverlay');
+ const endOverlay = $('endOverlay');
+ const startBtn = $('startBtn');
+ const restartBtn = $('restartBtn');
+
+ let score = 0;
+ let streak = 0;
+ let bestStreak = 0;
+ let round = 0;
+ let correct = 0;
+ let currentStyle = null;
+ let locked = false;
+ let timer = null;
+ let timeLeft = TIME;
+ let best = parseInt(localStorage.getItem('gds_best') || '0', 10);
+ bestEl.textContent = best;
+
+ function shuffle(arr) {
+ const a = arr.slice();
+ for (let i = a.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [a[i], a[j]] = [a[j], a[i]];
+ }
+ return a;
+ }
+
+ function pickChoices(answer) {
+ const others = shuffle(STYLES.filter(s => s !== answer)).slice(0, 3);
+ return shuffle([answer, ...others]);
+ }
+
+ function setTimerVisual(t) {
+ const ratio = Math.max(0, t / TIME);
+ timerArc.style.strokeDasharray = CIRC;
+ timerArc.style.strokeDashoffset = CIRC * (1 - ratio);
+ timerNum.textContent = Math.ceil(t);
+ timerRing.classList.toggle('urgent', t <= 4);
+ }
+
+ function stopTimer() {
+ if (timer) { clearInterval(timer); timer = null; }
+ }
+
+ function startTimer() {
+ stopTimer();
+ timeLeft = TIME;
+ setTimerVisual(timeLeft);
+ timer = setInterval(() => {
+ timeLeft -= 0.1;
+ setTimerVisual(timeLeft);
+ if (timeLeft <= 0) {
+ timeLeft = 0;
+ setTimerVisual(0);
+ stopTimer();
+ onAnswer(null);
+ }
+ }, 100);
+ }
+
+ function showFeedback(text, good) {
+ feedbackEl.textContent = text;
+ feedbackEl.className = 'feedback show ' + (good ? 'good' : 'bad');
+ }
+
+ function hideFeedback() {
+ feedbackEl.className = 'feedback';
+ }
+
+ function renderChoices(options) {
+ choicesEl.innerHTML = '';
+ options.forEach((opt) => {
+ const btn = document.createElement('button');
+ btn.className = 'choice';
+ btn.textContent = opt;
+ btn.type = 'button';
+ btn.addEventListener('click', () => onAnswer(opt));
+ choicesEl.appendChild(btn);
+ });
+ }
+
+ function showMotif(style) {
+ motifEl.className = 'motif exit';
+ requestAnimationFrame(() => {
+ setTimeout(() => {
+ motifEl.className = 'motif ' + CLASS_MAP[style];
+ void motifEl.offsetWidth;
+ motifEl.classList.add('show');
+ }, 200);
+ });
+ }
+
+ function nextRound() {
+ if (round >= TOTAL) {
+ endGame();
+ return;
+ }
+ locked = false;
+ hideFeedback();
+ flashEl.className = 'flash';
+ round++;
+ roundBadge.textContent = 'Round ' + round + ' / ' + TOTAL;
+ progressEl.style.width = ((round - 1) / TOTAL * 100) + '%';
+
+ // avoid immediate repeat
+ let pool = STYLES.filter(s => s !== currentStyle);
+ currentStyle = pool[Math.floor(Math.random() * pool.length)];
+ showMotif(currentStyle);
+ renderChoices(pickChoices(currentStyle));
+ startTimer();
+ }
+
+ function onAnswer(choice) {
+ if (locked) return;
+ locked = true;
+ stopTimer();
+
+ const buttons = [...choicesEl.querySelectorAll('.choice')];
+ const isCorrect = choice === currentStyle;
+ const speedBonus = isCorrect ? Math.round(timeLeft * 2) : 0;
+ const streakBonus = isCorrect && streak >= 2 ? streak * 5 : 0;
+
+ buttons.forEach((btn) => {
+ btn.disabled = true;
+ if (btn.textContent === currentStyle) {
+ btn.classList.add('correct');
+ } else if (choice && btn.textContent === choice && !isCorrect) {
+ btn.classList.add('wrong');
+ } else {
+ btn.classList.add('dim');
+ }
+ });
+
+ if (isCorrect) {
+ correct++;
+ streak++;
+ bestStreak = Math.max(bestStreak, streak);
+ const pts = 100 + speedBonus + streakBonus;
+ score += pts;
+ scoreEl.textContent = score;
+ streakEl.textContent = streak;
+ flashEl.className = 'flash correct';
+ const line = FLAVOR[currentStyle][Math.floor(Math.random() * FLAVOR[currentStyle].length)];
+ let extra = '';
+ if (speedBonus > 16) extra = ' Lightning fast.';
+ else if (streakBonus) extra = ' ' + streak + ' streak!';
+ showFeedback(line + extra, true);
+ } else {
+ streak = 0;
+ streakEl.textContent = streak;
+ flashEl.className = 'flash wrong';
+ const miss = choice === null ? 'Time expired.' : WRONG_FLAVOR[Math.floor(Math.random() * WRONG_FLAVOR.length)];
+ showFeedback(miss + ' It was ' + currentStyle + '.', false);
+ }
+
+ progressEl.style.width = (round / TOTAL * 100) + '%';
+
+ setTimeout(() => nextRound(), isCorrect ? 1100 : 1450);
+ }
+
+ function endGame() {
+ stopTimer();
+ progressEl.style.width = '100%';
+ if (score > best) {
+ best = score;
+ localStorage.setItem('gds_best', String(best));
+ bestEl.textContent = best;
+ }
+ $('finalScore').textContent = score;
+ $('endStreak').textContent = bestStreak;
+ $('endBest').textContent = best;
+ $('endAcc').textContent = Math.round((correct / TOTAL) * 100) + '%';
+ let title = 'Well played';
+ if (correct === TOTAL) title = 'Flawless taste';
+ else if (correct >= 8) title = 'Refined eye';
+ else if (correct >= 5) title = 'Promising instinct';
+ else if (score > best - 1 && score > 0) title = 'A new high';
+ else title = 'Keep looking';
+ $('endTitle').textContent = title;
+ endOverlay.classList.add('show');
+ choicesEl.innerHTML = '';
+ }
+
+ function startGame() {
+ score = 0;
+ streak = 0;
+ bestStreak = 0;
+ round = 0;
+ correct = 0;
+ currentStyle = null;
+ locked = false;
+ scoreEl.textContent = '0';
+ streakEl.textContent = '0';
+ progressEl.style.width = '0%';
+ hideFeedback();
+ startOverlay.classList.remove('show');
+ endOverlay.classList.remove('show');
+ nextRound();
+ }
+
+ startBtn.addEventListener('click', startGame);
+ restartBtn.addEventListener('click', startGame);
+
+ // idle preview motif on start
+ motifEl.className = 'motif m-damask show';
+ setTimerVisual(TIME);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/6923e6180f0f/grok.png b/data/artifacts/6923e6180f0f/grok.png
new file mode 100644
index 0000000..a7ea1d9
Binary files /dev/null and b/data/artifacts/6923e6180f0f/grok.png differ
diff --git a/data/artifacts/71a0784e71c8/claude-code.html b/data/artifacts/71a0784e71c8/claude-code.html
new file mode 100644
index 0000000..af10358
--- /dev/null
+++ b/data/artifacts/71a0784e71c8/claude-code.html
@@ -0,0 +1,454 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
+<title>Designer Wallcoverings — Grasscloth Atelier</title>
+<style>
+ :root{
+ --ground:#12100c;
+ --surface:#17150f;
+ --brass:#C8A951;
+ --brass-soft:rgba(200,169,81,.30);
+ --ivory:#f5f2ea;
+ --muted:#8a8578;
+ --serif:'Cormorant Garamond','Didot','Bodoni MT','Playfair Display',Georgia,serif;
+ --sans:-apple-system,'Segoe UI','Helvetica Neue',Arial,sans-serif;
+ }
+ *{box-sizing:border-box;margin:0;padding:0}
+ html,body{height:100%}
+ body{
+ background:var(--ground);
+ color:var(--ivory);
+ font-family:var(--sans);
+ overflow:hidden;
+ -webkit-font-smoothing:antialiased;
+ }
+
+ /* ---------- Fixed top bar ---------- */
+ .topbar{
+ position:fixed;top:0;left:0;right:0;height:66px;z-index:20;
+ display:flex;align-items:center;justify-content:space-between;
+ padding:0 32px;
+ background:rgba(13,12,9,.82);
+ backdrop-filter:blur(14px) saturate(120%);
+ -webkit-backdrop-filter:blur(14px) saturate(120%);
+ border-bottom:1px solid var(--brass-soft);
+ }
+ .brand{display:flex;align-items:center;gap:16px}
+ .brand .mark{
+ width:11px;height:11px;transform:rotate(45deg);
+ border:1px solid var(--brass);
+ box-shadow:0 0 14px rgba(200,169,81,.35);
+ flex:none;
+ }
+ .wordmark{
+ font-family:var(--serif);
+ font-weight:500;
+ font-size:22px;
+ line-height:1;
+ text-transform:uppercase;
+ letter-spacing:.18em;
+ color:var(--ivory);
+ white-space:nowrap;
+ }
+ .tagline{
+ font-family:var(--sans);
+ font-size:10px;
+ text-transform:uppercase;
+ letter-spacing:.30em;
+ color:var(--muted);
+ }
+ @media (max-width:640px){ .tagline{display:none} .wordmark{font-size:18px} .topbar{padding:0 20px} }
+
+ /* ---------- Stage ---------- */
+ #stage{position:fixed;inset:0;z-index:1}
+ #weave{display:block;width:100%;height:100%}
+
+ /* ---------- Control panel ---------- */
+ .panel{
+ position:fixed;z-index:15;
+ top:98px;right:32px;width:312px;
+ background:rgba(18,16,12,.70);
+ backdrop-filter:blur(18px) saturate(120%);
+ -webkit-backdrop-filter:blur(18px) saturate(120%);
+ border:1px solid var(--brass-soft);
+ border-radius:14px;
+ padding:28px 26px 30px;
+ box-shadow:0 24px 64px rgba(0,0,0,.55);
+ animation:fadeUp .8s cubic-bezier(.22,1,.36,1) both;
+ }
+ @keyframes fadeUp{from{opacity:0;transform:translateY(24px)}to{opacity:1;transform:none}}
+
+ .panel .eyebrow{
+ font-size:10px;letter-spacing:.28em;text-transform:uppercase;color:var(--brass);
+ margin-bottom:6px;
+ }
+ .panel h2{
+ font-family:var(--serif);font-weight:500;font-size:27px;line-height:1.05;
+ letter-spacing:.01em;color:var(--ivory);margin-bottom:4px;
+ }
+ .panel .sub{font-size:11px;color:var(--muted);letter-spacing:.06em;margin-bottom:26px}
+
+ .hr{height:1px;background:linear-gradient(90deg,transparent,var(--brass-soft),transparent);margin:24px 0}
+
+ .field{margin-bottom:24px}
+ .field:last-child{margin-bottom:0}
+ .field .row{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:12px}
+ .field label{
+ font-size:11px;letter-spacing:.15em;text-transform:uppercase;color:var(--muted);
+ }
+ .field .val{font-family:var(--serif);font-size:15px;color:var(--brass);letter-spacing:.04em}
+
+ /* hairline range */
+ input[type=range]{
+ -webkit-appearance:none;appearance:none;width:100%;height:16px;background:transparent;cursor:pointer;
+ }
+ input[type=range]::-webkit-slider-runnable-track{
+ height:1px;background:linear-gradient(90deg,var(--brass) 0%,rgba(200,169,81,.18) 100%);
+ }
+ input[type=range]::-moz-range-track{
+ height:1px;background:rgba(200,169,81,.35);
+ }
+ input[type=range]::-webkit-slider-thumb{
+ -webkit-appearance:none;appearance:none;width:13px;height:13px;margin-top:-6px;border-radius:50%;
+ background:var(--ivory);border:1px solid var(--brass);
+ box-shadow:0 0 0 4px rgba(200,169,81,.10),0 2px 6px rgba(0,0,0,.5);
+ transition:transform .18s ease, box-shadow .18s ease;
+ }
+ input[type=range]:hover::-webkit-slider-thumb{transform:scale(1.15);box-shadow:0 0 0 6px rgba(200,169,81,.14),0 2px 8px rgba(0,0,0,.5)}
+ input[type=range]::-moz-range-thumb{
+ width:13px;height:13px;border-radius:50%;background:var(--ivory);border:1px solid var(--brass);
+ box-shadow:0 0 0 4px rgba(200,169,81,.10);
+ }
+
+ /* two-tone swatches */
+ .tones{display:flex;gap:14px}
+ .tone{flex:1}
+ .swatch{
+ position:relative;height:44px;border-radius:9px;overflow:hidden;
+ border:1px solid var(--brass-soft);
+ box-shadow:inset 0 1px 2px rgba(0,0,0,.4);
+ }
+ .swatch input[type=color]{
+ position:absolute;inset:-4px;width:calc(100% + 8px);height:calc(100% + 8px);
+ border:none;padding:0;background:transparent;cursor:pointer;
+ }
+ .tone .cap{
+ display:block;margin-top:9px;font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);
+ }
+
+ .footnote{margin-top:26px;font-size:10px;letter-spacing:.10em;color:var(--muted);line-height:1.7}
+ .footnote b{color:var(--ivory);font-weight:500}
+
+ @media (max-width:640px){
+ .panel{top:auto;bottom:16px;left:16px;right:16px;width:auto;padding:22px 20px 24px}
+ .field{margin-bottom:18px}
+ .panel h2{font-size:23px}
+ }
+</style>
+</head>
+<body>
+
+ <header class="topbar">
+ <div class="brand">
+ <span class="mark"></span>
+ <span class="wordmark">Designer Wallcoverings</span>
+ </div>
+ <span class="tagline">Grasscloth Atelier</span>
+ </header>
+
+ <div id="stage">
+ <canvas id="weave"></canvas>
+ </div>
+
+ <aside class="panel">
+ <div class="eyebrow">Bespoke Weave</div>
+ <h2>Natural Fibre Study</h2>
+ <div class="sub">Hand-loomed grasscloth · No. IX</div>
+
+ <div class="field">
+ <div class="row"><label>Weave Density</label><span class="val" id="vDensity">Fine</span></div>
+ <input id="density" type="range" min="1" max="10" step="1" value="5">
+ </div>
+
+ <div class="hr"></div>
+
+ <div class="field">
+ <div class="row"><label>Fibre Tones</label></div>
+ <div class="tones">
+ <div class="tone">
+ <div class="swatch"><input type="color" id="toneA" value="#cbb488"></div>
+ <span class="cap">Warp · Light</span>
+ </div>
+ <div class="tone">
+ <div class="swatch"><input type="color" id="toneB" value="#4b3d27"></div>
+ <span class="cap">Weft · Deep</span>
+ </div>
+ </div>
+ </div>
+
+ <div class="hr"></div>
+
+ <div class="field">
+ <div class="row"><label>Sheen</label><span class="val" id="vSheen">0.42</span></div>
+ <input id="sheen" type="range" min="0" max="100" step="1" value="52">
+ </div>
+
+ <div class="field">
+ <div class="row"><label>Light Sweep</label><span class="val" id="vSweep">Andante</span></div>
+ <input id="sweep" type="range" min="1" max="10" step="1" value="4">
+ </div>
+
+ <div class="footnote">
+ <b>Atelier finish.</b> Every fibre reflects the passing light as it would upon a dressed wall.
+ </div>
+ </aside>
+
+<script>
+(function(){
+ "use strict";
+ const cv = document.getElementById('weave');
+ const ctx = cv.getContext('2d');
+ const base = document.createElement('canvas');
+ const sheenC = document.createElement('canvas');
+ const scratch = document.createElement('canvas');
+ const bctx = base.getContext('2d');
+ const sctx = sheenC.getContext('2d');
+ const xctx = scratch.getContext('2d');
+
+ let W = 0, H = 0, DPR = 1;
+
+ const P = {
+ density: 5,
+ toneA: '#cbb488',
+ toneB: '#4b3d27',
+ sheen: 0.52,
+ speed: 4
+ };
+
+ // ---------- helpers ----------
+ function hexToRgb(h){
+ h = h.replace('#','');
+ if(h.length===3) h = h.split('').map(c=>c+c).join('');
+ return { r:parseInt(h.substr(0,2),16), g:parseInt(h.substr(2,2),16), b:parseInt(h.substr(4,2),16) };
+ }
+ const cl = v => v<0?0:v>255?255:v;
+ function scale(c,f){ return { r:cl(c.r*f), g:cl(c.g*f), b:cl(c.b*f) }; }
+ function mixWhite(c,t){ return { r:cl(c.r+(255-c.r)*t), g:cl(c.g+(255-c.g)*t), b:cl(c.b+(255-c.b)*t) }; }
+ function rgb(c){ return 'rgb('+(c.r|0)+','+(c.g|0)+','+(c.b|0)+')'; }
+ function mulberry32(a){ return function(){ a|=0; a=a+0x6D2B79F5|0; let t=Math.imul(a^a>>>15,1|a); t=t+Math.imul(t^t>>>7,61|t)^t; return ((t^t>>>14)>>>0)/4294967296; }; }
+
+ function path(c,x,y,w,h,r){
+ r = Math.min(r, w/2, h/2);
+ c.beginPath();
+ c.moveTo(x+r,y);
+ c.arcTo(x+w,y,x+w,y+h,r);
+ c.arcTo(x+w,y+h,x,y+h,r);
+ c.arcTo(x,y+h,x,y,r);
+ c.arcTo(x,y,x+w,y,r);
+ c.closePath();
+ }
+
+ // ---------- weave rendering ----------
+ function drawFiber(c, x, y, w, h, dir, col, mode){
+ const r = Math.min(w,h)*0.30;
+ // cylindrical shading across the short axis
+ let g;
+ if(dir==='v') g = c.createLinearGradient(x,0,x+w,0);
+ else g = c.createLinearGradient(0,y,0,y+h);
+
+ if(mode==='sheen'){
+ const edge = scale(col,0.34), mid = scale(col,0.82), hot = mixWhite(col,0.72);
+ g.addColorStop(0, rgb(edge));
+ g.addColorStop(0.40,rgb(mid));
+ g.addColorStop(0.50,rgb(hot));
+ g.addColorStop(0.60,rgb(mid));
+ g.addColorStop(1, rgb(edge));
+ } else {
+ const edge = scale(col,0.52), mid = scale(col,1.06), hi = mixWhite(col,0.14);
+ g.addColorStop(0, rgb(edge));
+ g.addColorStop(0.46,rgb(mid));
+ g.addColorStop(0.52,rgb(hi));
+ g.addColorStop(0.58,rgb(mid));
+ g.addColorStop(1, rgb(edge));
+ }
+ path(c,x,y,w,h,r);
+ c.fillStyle = g;
+ c.fill();
+
+ if(mode==='sheen') return; // striations only baked into base
+
+ // fine striations along the fibre length (grasscloth texture)
+ c.save();
+ path(c,x,y,w,h,r);
+ c.clip();
+ const n = dir==='v' ? Math.max(2, (w/DPR/3)|0) : Math.max(2, (h/DPR/3)|0);
+ c.lineWidth = Math.max(1, 0.7*DPR);
+ for(let k=0;k<n;k++){
+ const f = (k+0.5)/n;
+ const a = (k%2? 0.10 : -0.09);
+ c.strokeStyle = a>0 ? 'rgba(255,248,232,'+a+')' : 'rgba(0,0,0,'+(-a)+')';
+ c.beginPath();
+ if(dir==='v'){ const px = x + w*f; c.moveTo(px,y+r*0.5); c.lineTo(px,y+h-r*0.5); }
+ else { const py = y + h*f; c.moveTo(x+r*0.5,py); c.lineTo(x+w-r*0.5,py); }
+ c.stroke();
+ }
+ c.restore();
+ }
+
+ function renderWeave(c, mode){
+ c.clearRect(0,0,W,H);
+ // warm near-black ground reads as the shadow between fibres
+ c.fillStyle = '#0d0b07';
+ c.fillRect(0,0,W,H);
+
+ const A = hexToRgb(P.toneA);
+ const B = hexToRgb(P.toneB);
+
+ const logical = 34 - (P.density-1)*(22/9); // 34px .. 12px
+ const cell = Math.max(8, logical*DPR);
+ const cols = Math.ceil(W/cell)+1;
+ const rows = Math.ceil(H/cell)+1;
+ const rand = mulberry32(1337);
+
+ // slight over-under offset so the bundles overlap like a real weave
+ const gap = cell*0.10;
+
+ for(let j=0;j<rows;j++){
+ for(let i=0;i<cols;i++){
+ const rx = rand(), ry = rand();
+ const x = i*cell, y = j*cell;
+ const warpTop = ((i+j)&1)===0;
+
+ // colour: two-tone by orientation, gently jittered
+ const jitA = 0.92 + rx*0.14;
+ const jitB = 0.92 + ry*0.14;
+ const colWarp = scale(A, jitA);
+ const colWeft = scale(B, jitB);
+
+ // draw the UNDER fibre faintly first, then the OVER fibre
+ const fx = x+gap, fy = y+gap, fw = cell-2*gap, fh = cell-2*gap;
+
+ if(warpTop){
+ // weft (horizontal) beneath
+ drawFiber(c, fx, fy+fh*0.16, fw, fh*0.68, 'h', scale(colWeft,0.72), mode);
+ // warp (vertical) on top
+ drawFiber(c, fx+fw*0.16, fy, fw*0.68, fh, 'v', colWarp, mode);
+ } else {
+ drawFiber(c, fx+fw*0.16, fy, fw*0.68, fh, 'v', scale(colWarp,0.72), mode);
+ drawFiber(c, fx, fy+fh*0.16, fw, fh*0.68, 'h', colWeft, mode);
+ }
+ }
+ }
+
+ if(mode==='base'){
+ // editorial vignette + top ambient light
+ const vg = c.createRadialGradient(W*0.5,H*0.42,Math.min(W,H)*0.25, W*0.5,H*0.55,Math.max(W,H)*0.75);
+ vg.addColorStop(0,'rgba(0,0,0,0)');
+ vg.addColorStop(1,'rgba(0,0,0,0.42)');
+ c.fillStyle = vg;
+ c.fillRect(0,0,W,H);
+ const top = c.createLinearGradient(0,0,0,H);
+ top.addColorStop(0,'rgba(245,242,234,0.05)');
+ top.addColorStop(0.25,'rgba(245,242,234,0)');
+ c.fillStyle = top;
+ c.fillRect(0,0,W,H);
+ }
+ }
+
+ // ---------- sizing ----------
+ function resize(){
+ DPR = Math.min(window.devicePixelRatio||1, 1.5);
+ const cssW = window.innerWidth, cssH = window.innerHeight;
+ W = Math.round(cssW*DPR);
+ H = Math.round(cssH*DPR);
+ [cv,base,sheenC,scratch].forEach(cn=>{ cn.width=W; cn.height=H; });
+ cv.style.width = cssW+'px'; cv.style.height = cssH+'px';
+ rebuild();
+ }
+
+ let rebuildQueued = false;
+ function rebuild(){
+ if(rebuildQueued) return;
+ rebuildQueued = true;
+ requestAnimationFrame(()=>{
+ renderWeave(bctx,'base');
+ renderWeave(sctx,'sheen');
+ rebuildQueued = false;
+ });
+ }
+
+ // ---------- light sweep gradient ----------
+ function bandGradient(pos){
+ const bw = 0.16;
+ const c = pos*(1+2*bw) - bw;
+ const g = xctx.createLinearGradient(0,0,W,H);
+ const stops = [[0,0],[c-bw,0],[c-bw*0.5,0.55],[c,1],[c+bw*0.5,0.55],[c+bw,0],[1,0]];
+ let last = 0;
+ for(const s of stops){
+ let o = s[0]; if(o<0)o=0; if(o>1)o=1; if(o<last)o=last; last=o;
+ g.addColorStop(o,'rgba(255,246,226,'+s[1]+')');
+ }
+ return g;
+ }
+
+ // ---------- animation ----------
+ let start = null;
+ function frame(t){
+ if(start===null) start=t;
+ const elapsed = t-start;
+ const period = 10500 - (P.speed-1)*(8000/9); // ms per sweep
+ const pos = (elapsed % period)/period;
+
+ ctx.globalCompositeOperation='source-over';
+ ctx.globalAlpha=1;
+ ctx.drawImage(base,0,0);
+
+ if(P.sheen>0.001){
+ // mask the bright sheen weave to the moving band
+ xctx.globalCompositeOperation='source-over';
+ xctx.clearRect(0,0,W,H);
+ xctx.drawImage(sheenC,0,0);
+ xctx.globalCompositeOperation='destination-in';
+ xctx.fillStyle=bandGradient(pos);
+ xctx.fillRect(0,0,W,H);
+ xctx.globalCompositeOperation='source-over';
+
+ ctx.globalCompositeOperation='screen';
+ ctx.globalAlpha=P.sheen*0.85;
+ ctx.drawImage(scratch,0,0);
+ ctx.globalCompositeOperation='source-over';
+ ctx.globalAlpha=1;
+ }
+ requestAnimationFrame(frame);
+ }
+
+ // ---------- controls ----------
+ const densityNames = ['','Coarse','Coarse','Rustic','Natural','Fine','Fine','Refined','Silken','Silken','Gossamer'];
+ const sweepNames = ['','Grave','Largo','Adagio','Andante','Andante','Moderato','Allegretto','Allegro','Vivace','Presto'];
+
+ const dEl=document.getElementById('density'), vD=document.getElementById('vDensity');
+ const shEl=document.getElementById('sheen'), vS=document.getElementById('vSheen');
+ const swEl=document.getElementById('sweep'), vW=document.getElementById('vSweep');
+ const aEl=document.getElementById('toneA'), bEl=document.getElementById('toneB');
+
+ dEl.addEventListener('input',()=>{ P.density=+dEl.value; vD.textContent=densityNames[P.density]; rebuild(); });
+ shEl.addEventListener('input',()=>{ P.sheen=+shEl.value/100; vS.textContent=P.sheen.toFixed(2); });
+ swEl.addEventListener('input',()=>{ P.speed=+swEl.value; vW.textContent=sweepNames[P.speed]; });
+ aEl.addEventListener('input',()=>{ P.toneA=aEl.value; rebuild(); });
+ bEl.addEventListener('input',()=>{ P.toneB=bEl.value; rebuild(); });
+
+ vD.textContent=densityNames[P.density];
+ vS.textContent=P.sheen.toFixed(2);
+ vW.textContent=sweepNames[P.speed];
+
+ let rt;
+ window.addEventListener('resize',()=>{ clearTimeout(rt); rt=setTimeout(resize,120); });
+
+ resize();
+ requestAnimationFrame(frame);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/71a0784e71c8/claude-code.png b/data/artifacts/71a0784e71c8/claude-code.png
new file mode 100644
index 0000000..34cd6ce
Binary files /dev/null and b/data/artifacts/71a0784e71c8/claude-code.png differ
diff --git a/data/artifacts/71a0784e71c8/gpt.html b/data/artifacts/71a0784e71c8/gpt.html
new file mode 100644
index 0000000..298e108
--- /dev/null
+++ b/data/artifacts/71a0784e71c8/gpt.html
@@ -0,0 +1,910 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8" />
+<title>Designer Wallcoverings · Grasscloth Weave Simulator</title>
+<style>
+:root {
+ /* Palette from luxe-noir, tweaked to brief */
+ --bg: #12100c;
+ --surface: #17171c;
+ --surface-soft: #1f1f26;
+ --text: #f5f2ea;
+ --muted: #8a8578;
+ --accent: #C8A951;
+ --accent-soft: rgba(200, 169, 81, 0.25);
+ --shadow-soft: 0 22px 45px rgba(0, 0, 0, 0.7);
+ --radius-lg: 18px;
+ --radius-md: 12px;
+ --radius-sm: 999px;
+ --border-hairline: 1px;
+ --easing-ui: cubic-bezier(.22,.9,.24,1);
+ --font-serif: "Cormorant Garamond", "Times New Roman", Georgia, serif;
+ --font-sans: "Avenir Next", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+}
+
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+html, body {
+ margin: 0;
+ padding: 0;
+ width: 100%;
+ height: 100%;
+}
+
+body {
+ background: radial-gradient(circle at top, #18130d 0, var(--bg) 38%, #060507 100%);
+ color: var(--text);
+ font-family: var(--font-sans);
+ -webkit-font-smoothing: antialiased;
+ text-rendering: geometricPrecision;
+}
+
+.app-shell {
+ display: flex;
+ flex-direction: column;
+ height: 100vh;
+ color: var(--text);
+}
+
+/* Top brand bar */
+.app-header {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 64px;
+ padding: 0 40px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background: radial-gradient(circle at top left, rgba(200,169,81,0.08) 0, rgba(0,0,0,0.95) 46%, rgba(0,0,0,0.98) 100%);
+ border-bottom: 1px solid rgba(255,255,255,0.03);
+ backdrop-filter: blur(18px);
+ box-shadow: 0 26px 40px rgba(0,0,0,0.78);
+ z-index: 10;
+}
+
+.brand-wordmark {
+ font-family: var(--font-serif);
+ font-size: 18px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--text);
+ display: flex;
+ align-items: baseline;
+ gap: 12px;
+}
+
+.brand-wordmark span.primary {
+ font-weight: 500;
+}
+
+.brand-wordmark span.secondary {
+ font-size: 11px;
+ letter-spacing: 0.24em;
+ text-transform: uppercase;
+ color: var(--muted);
+}
+
+.header-meta {
+ display: flex;
+ align-items: center;
+ gap: 18px;
+ font-size: 11px;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: var(--muted);
+}
+
+.header-meta-dot {
+ width: 4px;
+ height: 4px;
+ border-radius: 999px;
+ background: var(--accent);
+ box-shadow: 0 0 0 4px rgba(200,169,81,0.18);
+}
+
+/* Layout */
+.app-main {
+ flex: 1;
+ display: flex;
+ padding: 96px 40px 36px;
+ gap: 32px;
+ max-width: 1400px;
+ margin: 0 auto;
+}
+
+.canvas-panel {
+ flex: 1.8;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.controls-panel {
+ flex: 1;
+ max-width: 420px;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+/* Typography */
+.panel-title {
+ font-family: var(--font-serif);
+ font-size: 26px;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ margin: 0 0 2px;
+}
+
+.panel-subtitle {
+ font-size: 12px;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: var(--muted);
+ margin: 0;
+}
+
+.section-label {
+ font-family: var(--font-serif);
+ font-size: 14px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ margin-bottom: 10px;
+ color: var(--muted);
+}
+
+/* Canvas shell */
+.canvas-shell {
+ position: relative;
+ flex: 1;
+ border-radius: 24px;
+ background: radial-gradient(circle at top left, rgba(255,255,255,0.04) 0, rgba(0,0,0,0.9) 35%, rgba(0,0,0,1) 100%);
+ overflow: hidden;
+ box-shadow: var(--shadow-soft);
+ border: 1px solid rgba(255,255,255,0.04);
+}
+
+#weaveCanvas {
+ width: 100%;
+ height: 100%;
+ display: block;
+ background: transparent;
+}
+
+/* Canvas overlay information */
+.canvas-overlay {
+ position: absolute;
+ inset: 18px 20px auto 20px;
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ pointer-events: none;
+}
+
+.canvas-tag {
+ padding: 4px 10px 5px;
+ border-radius: 999px;
+ background: rgba(6,5,7,0.78);
+ border: 1px solid rgba(255,255,255,0.06);
+ font-size: 10px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--muted);
+ backdrop-filter: blur(14px);
+}
+
+.canvas-lux-meta {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ gap: 4px;
+ font-size: 11px;
+}
+
+.canvas-lux-meta span {
+ padding: 3px 9px 4px;
+ border-radius: 999px;
+ background: rgba(6,5,7,0.82);
+ border: 1px solid rgba(255,255,255,0.05);
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: var(--muted);
+}
+
+/* Controls card */
+.controls-card {
+ background: radial-gradient(circle at top right, rgba(200,169,81,0.12) 0, rgba(15,13,10,0.96) 32%, rgba(5,4,4,0.98) 100%);
+ border-radius: var(--radius-lg);
+ padding: 20px 20px 18px;
+ box-shadow: var(--shadow-soft);
+ border: 1px solid rgba(255,255,255,0.05);
+ position: relative;
+ overflow: hidden;
+}
+
+.controls-card::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background: radial-gradient(circle at top center, rgba(255,255,255,0.05), transparent 55%);
+ opacity: 0.65;
+ pointer-events: none;
+}
+
+.controls-inner {
+ position: relative;
+ z-index: 1;
+}
+
+.control-group {
+ margin-bottom: 18px;
+}
+
+.control-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.control-label {
+ flex: 0 0 120px;
+}
+
+.control-label-title {
+ font-size: 12px;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: var(--text);
+ margin-bottom: 2px;
+ font-family: var(--font-serif);
+}
+
+.control-label-meta {
+ font-size: 11px;
+ color: var(--muted);
+}
+
+/* Slider styling */
+.slider-wrapper {
+ flex: 1;
+ position: relative;
+ display: flex;
+ align-items: center;
+}
+
+.slider-wrapper input[type="range"] {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 100%;
+ background: transparent;
+ height: 18px;
+ margin: 0;
+}
+
+.slider-wrapper input[type="range"]::-webkit-slider-runnable-track {
+ height: 1px;
+ background: linear-gradient(90deg, rgba(255,255,255,0.02), rgba(255,255,255,0.2));
+ border-radius: 999px;
+}
+
+.slider-wrapper input[type="range"]::-moz-range-track {
+ height: 1px;
+ background: linear-gradient(90deg, rgba(255,255,255,0.02), rgba(255,255,255,0.2));
+ border-radius: 999px;
+}
+
+.slider-wrapper input[type="range"]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 14px;
+ height: 14px;
+ border-radius: 999px;
+ background: radial-gradient(circle at 30% 20%, #fff 0, #f5e5b3 40%, #a28333 100%);
+ border: 0;
+ margin-top: -6.5px;
+ box-shadow:
+ 0 0 0 1px rgba(0,0,0,0.6),
+ 0 0 0 4px rgba(200,169,81,0.24),
+ 0 10px 20px rgba(0,0,0,0.7);
+ cursor: ew-resize;
+ transition: box-shadow 140ms var(--easing-ui), transform 140ms var(--easing-ui);
+}
+
+.slider-wrapper input[type="range"]::-moz-range-thumb {
+ width: 14px;
+ height: 14px;
+ border-radius: 999px;
+ background: radial-gradient(circle at 30% 20%, #fff 0, #f5e5b3 40%, #a28333 100%);
+ border: 0;
+ box-shadow:
+ 0 0 0 1px rgba(0,0,0,0.6),
+ 0 0 0 4px rgba(200,169,81,0.24),
+ 0 10px 20px rgba(0,0,0,0.7);
+ cursor: ew-resize;
+ transition: box-shadow 140ms var(--easing-ui), transform 140ms var(--easing-ui);
+}
+
+.slider-wrapper input[type="range"]:focus-visible {
+ outline: none;
+}
+
+.slider-wrapper input[type="range"]:focus::-webkit-slider-thumb {
+ box-shadow:
+ 0 0 0 1px rgba(0,0,0,0.6),
+ 0 0 0 6px rgba(200,169,81,0.34),
+ 0 14px 26px rgba(0,0,0,0.85);
+ transform: translateY(-0.5px) scale(1.02);
+}
+
+.slider-wrapper input[type="range"]:focus::-moz-range-thumb {
+ box-shadow:
+ 0 0 0 1px rgba(0,0,0,0.6),
+ 0 0 0 6px rgba(200,169,81,0.34),
+ 0 14px 26px rgba(0,0,0,0.85);
+ transform: translateY(-0.5px) scale(1.02);
+}
+
+/* Slider value label */
+.slider-value {
+ min-width: 40px;
+ text-align: right;
+ font-size: 11px;
+ color: var(--muted);
+}
+
+/* Fiber swatches */
+.fiber-sample-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: 6px;
+ gap: 10px;
+}
+
+.fiber-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 11px;
+ color: var(--muted);
+}
+
+.fiber-chip-swatch {
+ width: 20px;
+ height: 12px;
+ border-radius: 999px;
+ border: 1px solid rgba(255,255,255,0.2);
+ background: linear-gradient(135deg, #d6c7aa, #b69a6b);
+}
+
+.fiber-chip-swatch.secondary {
+ background: linear-gradient(135deg, #3a2f25, #17120f);
+}
+
+.pill {
+ padding: 3px 9px 4px;
+ border-radius: 999px;
+ border: 1px solid rgba(255,255,255,0.07);
+ background: rgba(8,7,6,0.92);
+ font-size: 10px;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: var(--muted);
+}
+
+/* Footer strip */
+.footer-strip {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 11px;
+ color: var(--muted);
+ padding-top: 4px;
+}
+
+.footer-strip span.accent {
+ color: var(--accent);
+}
+
+/* Responsive */
+@media (max-width: 1040px) {
+ .app-main {
+ flex-direction: column;
+ padding: 96px 20px 24px;
+ }
+ .controls-panel {
+ max-width: none;
+ }
+}
+
+@media (max-width: 720px) {
+ .app-header {
+ padding: 0 20px;
+ height: 60px;
+ }
+ .brand-wordmark span.secondary {
+ display: none;
+ }
+ .app-main {
+ padding-top: 84px;
+ }
+ .panel-title {
+ font-size: 22px;
+ }
+ .control-row {
+ flex-direction: column;
+ align-items: stretch;
+ }
+ .control-label {
+ flex: none;
+ }
+}
+
+/* Light sweep overlay */
+.light-sweep {
+ position: absolute;
+ inset: -40%;
+ background:
+ radial-gradient(circle at 0% 0%, rgba(255,255,255,0.12) 0, transparent 45%),
+ radial-gradient(circle at 100% 0%, rgba(255,255,255,0.04) 0, transparent 52%),
+ radial-gradient(circle at 50% 60%, rgba(255,255,255,0.06) 0, transparent 55%);
+ mix-blend-mode: soft-light;
+ opacity: 0.34;
+ pointer-events: none;
+}
+
+</style>
+</head>
+<body>
+<div class="app-shell">
+ <header class="app-header">
+ <div class="brand-wordmark">
+ <span class="primary">DESIGNER WALLCOVERINGS</span>
+ <span class="secondary">TEXTILE SURFACE STUDIO</span>
+ </div>
+ <div class="header-meta">
+ <div class="header-meta-dot"></div>
+ <span>GRASSCLOTH · WEAVE SIMULATOR</span>
+ </div>
+ </header>
+
+ <main class="app-main">
+ <section class="canvas-panel">
+ <div class="canvas-copy">
+ <h1 class="panel-title">Canvas Weave Study</h1>
+ <p class="panel-subtitle">Fine-tune density, fiber tone and sheen with real-time lighting.</p>
+ </div>
+ <div class="canvas-shell">
+ <canvas id="weaveCanvas"></canvas>
+ <div class="light-sweep" id="lightSweep"></div>
+ <div class="canvas-overlay">
+ <div class="canvas-tag">Studio preview · 2 × 2 m panel</div>
+ <div class="canvas-lux-meta">
+ <span id="metaWeave">Balanced weave</span>
+ <span id="metaSheen">Soft matte</span>
+ </div>
+ </div>
+ </div>
+ </section>
+
+ <aside class="controls-panel">
+ <div class="controls-card">
+ <div class="controls-inner">
+ <div class="control-group">
+ <h2 class="section-label">Weave architecture</h2>
+ <div class="control-row">
+ <div class="control-label">
+ <div class="control-label-title">Density</div>
+ <div class="control-label-meta">Thread count & scale of the grasscloth weave.</div>
+ </div>
+ <div class="slider-wrapper">
+ <input id="density" type="range" min="0" max="100" value="52" />
+ </div>
+ <div class="slider-value" id="densityValue">52</div>
+ </div>
+ </div>
+
+ <div class="control-group">
+ <h2 class="section-label">Fiber coloration</h2>
+ <div class="control-row">
+ <div class="control-label">
+ <div class="control-label-title">Warmth balance</div>
+ <div class="control-label-meta">Shifts between cool flax and deep toasted jute.</div>
+ </div>
+ <div class="slider-wrapper">
+ <input id="tone" type="range" min="-50" max="50" value="12" />
+ </div>
+ <div class="slider-value" id="toneValue">+12</div>
+ </div>
+ <div class="fiber-sample-row">
+ <div class="fiber-chip">
+ <div class="fiber-chip-swatch" id="primarySwatch"></div>
+ <span>Primary warp</span>
+ </div>
+ <div class="fiber-chip">
+ <div class="fiber-chip-swatch secondary" id="secondarySwatch"></div>
+ <span>Counter weft</span>
+ </div>
+ <div class="pill" id="twoToneLabel">Two‑tone blend</div>
+ </div>
+ </div>
+
+ <div class="control-group">
+ <h2 class="section-label">Surface light</h2>
+ <div class="control-row">
+ <div class="control-label">
+ <div class="control-label-title">Sheen</div>
+ <div class="control-label-meta">From dry matte paper to brushed metallic thread.</div>
+ </div>
+ <div class="slider-wrapper">
+ <input id="sheen" type="range" min="0" max="100" value="38" />
+ </div>
+ <div class="slider-value" id="sheenValue">38</div>
+ </div>
+ </div>
+
+ <div class="footer-strip">
+ <span>Preview represents <span class="accent">approximate</span> material behaviour.</span>
+ <span>Studio Series · No. 07</span>
+ </div>
+ </div>
+ </div>
+ </aside>
+ </main>
+</div>
+
+<script>
+(function () {
+ const canvas = document.getElementById('weaveCanvas');
+ const ctx = canvas.getContext('2d', { alpha: false });
+
+ let width = 0;
+ let height = 0;
+ let devicePixelRatio = window.devicePixelRatio || 1;
+
+ const densityInput = document.getElementById('density');
+ const toneInput = document.getElementById('tone');
+ const sheenInput = document.getElementById('sheen');
+
+ const densityValue = document.getElementById('densityValue');
+ const toneValue = document.getElementById('toneValue');
+ const sheenValue = document.getElementById('sheenValue');
+
+ const primarySwatch = document.getElementById('primarySwatch');
+ const secondarySwatch = document.getElementById('secondarySwatch');
+ const twoToneLabel = document.getElementById('twoToneLabel');
+
+ const metaWeave = document.getElementById('metaWeave');
+ const metaSheen = document.getElementById('metaSheen');
+ const lightSweep = document.getElementById('lightSweep');
+
+ let density = +densityInput.value;
+ let tone = +toneInput.value;
+ let sheen = +sheenInput.value;
+
+ let lastTime = performance.now();
+ let sweepPhase = 0;
+
+ function resizeCanvas() {
+ const rect = canvas.getBoundingClientRect();
+ width = rect.width;
+ height = rect.height;
+ const dpr = window.devicePixelRatio || 1;
+ devicePixelRatio = dpr;
+ canvas.width = Math.round(width * dpr);
+ canvas.height = Math.round(height * dpr);
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+ }
+
+ window.addEventListener('resize', () => {
+ resizeCanvas();
+ });
+
+ function mapDensityToSpacing(d) {
+ const t = d / 100;
+ const minSpacing = 5;
+ const maxSpacing = 44;
+ return maxSpacing - t * (maxSpacing - minSpacing);
+ }
+
+ function mapDensityToThickness(d) {
+ const t = d / 100;
+ const min = 0.6;
+ const max = 3.0;
+ return min + t * (max - min);
+ }
+
+ function lerp(a, b, t) {
+ return a + (b - a) * t;
+ }
+
+ function toneToColors(t) {
+ const k = (t + 50) / 100;
+ const primaryCool = { r: 214, g: 199, b: 170 };
+ const primaryWarm = { r: 204, g: 164, b: 102 };
+ const secondaryCool = { r: 56, g: 49, b: 40 };
+ const secondaryWarm = { r: 102, g: 70, b: 38 };
+
+ const p = {
+ r: Math.round(lerp(primaryCool.r, primaryWarm.r, k)),
+ g: Math.round(lerp(primaryCool.g, primaryWarm.g, k)),
+ b: Math.round(lerp(primaryCool.b, primaryWarm.b, k))
+ };
+ const s = {
+ r: Math.round(lerp(secondaryCool.r, secondaryWarm.r, k)),
+ g: Math.round(lerp(secondaryCool.g, secondaryWarm.g, k)),
+ b: Math.round(lerp(secondaryCool.b, secondaryWarm.b, k))
+ };
+ return { primary: p, secondary: s };
+ }
+
+ function rgbToCss(c, alpha) {
+ if (alpha === undefined) {
+ return 'rgb(' + c.r + ',' + c.g + ',' + c.b + ')';
+ }
+ return 'rgba(' + c.r + ',' + c.g + ',' + c.b + ',' + alpha + ')';
+ }
+
+ function setSwatches() {
+ const { primary, secondary } = toneToColors(tone);
+ primarySwatch.style.background = 'linear-gradient(135deg, ' +
+ rgbToCss({ r: primary.r + 10, g: primary.g + 10, b: primary.b + 10 }) + ', ' +
+ rgbToCss(primary) + ')';
+
+ secondarySwatch.style.background = 'linear-gradient(135deg, ' +
+ rgbToCss({ r: secondary.r + 10, g: secondary.g + 10, b: secondary.b + 10 }) + ', ' +
+ rgbToCss(secondary) + ')';
+
+ if (Math.abs(tone) < 6) {
+ twoToneLabel.textContent = 'Neutral blend';
+ } else if (tone > 0) {
+ twoToneLabel.textContent = 'Toasted jute';
+ } else {
+ twoToneLabel.textContent = 'Cool flax';
+ }
+ }
+
+ function setMeta() {
+ if (density < 25) {
+ metaWeave.textContent = 'Open basket weave';
+ } else if (density < 55) {
+ metaWeave.textContent = 'Balanced weave';
+ } else if (density < 80) {
+ metaWeave.textContent = 'Fine linen weave';
+ } else {
+ metaWeave.textContent = 'Gallery-grade micro weave';
+ }
+
+ if (sheen < 20) {
+ metaSheen.textContent = 'Dry paper matte';
+ } else if (sheen < 50) {
+ metaSheen.textContent = 'Soft matte';
+ } else if (sheen < 75) {
+ metaSheen.textContent = 'Brushed lustre';
+ } else {
+ metaSheen.textContent = 'Gallery satin highlight';
+ }
+ }
+
+ function updateLabels() {
+ densityValue.textContent = density;
+ toneValue.textContent = (tone > 0 ? '+' : tone < 0 ? '' : '') + tone;
+ sheenValue.textContent = sheen;
+ }
+
+ function updateLightSweep(dt) {
+ const speed = 0.00005;
+ sweepPhase += dt * speed;
+ const p = sweepPhase % 1;
+ const translateX = (p - 0.5) * 40;
+ const rotate = Math.sin(p * Math.PI * 2) * 4;
+ const scale = 1.0 + 0.06 * Math.sin(p * Math.PI * 2);
+ const opacity = 0.22 + (sheen / 100) * 0.36;
+ lightSweep.style.transform =
+ 'translateX(' + translateX + '%) rotate(' + rotate + 'deg) scale(' + scale + ')';
+ lightSweep.style.opacity = opacity.toFixed(3);
+ }
+
+ function drawWeave(time) {
+ const spacing = mapDensityToSpacing(density);
+ const thickness = mapDensityToThickness(density);
+
+ const noiseScale = 0.015;
+ const timeSpeed = 0.00005;
+ const shimmer = (Math.sin(time * timeSpeed * (1 + sheen / 120)) + 1) * 0.5;
+
+ const { primary, secondary } = toneToColors(tone);
+
+ ctx.clearRect(0, 0, width, height);
+
+ const bgGradient = ctx.createLinearGradient(0, 0, width, height);
+ bgGradient.addColorStop(0, '#0b0906');
+ bgGradient.addColorStop(0.4, '#14100b');
+ bgGradient.addColorStop(1, '#070608');
+ ctx.fillStyle = bgGradient;
+ ctx.fillRect(0, 0, width, height);
+
+ const verticalCount = Math.ceil(width / spacing);
+ const horizontalCount = Math.ceil(height / spacing);
+
+ const sheenStrength = 0.18 + (sheen / 100) * 0.55;
+ const specPower = 12 + 60 * (sheen / 100);
+
+ function random2D(x, y) {
+ const s = Math.sin(x * 127.1 + y * 311.7) * 43758.5453;
+ return s - Math.floor(s);
+ }
+
+ ctx.save();
+ ctx.globalCompositeOperation = 'screen';
+
+ for (let i = -1; i <= verticalCount + 1; i++) {
+ const x = i * spacing + (i % 3) * 0.6;
+ const t = i / verticalCount;
+ const n = random2D(i, 1.3);
+ const jitter = (n - 0.5) * spacing * 0.14;
+ const baseX = x + jitter;
+
+ const vNoise = random2D(i * 1.9, 2.1);
+ const light = 0.35 + vNoise * 0.12 + shimmer * sheenStrength * (0.3 + vNoise * 0.4);
+
+ const highlight = Math.pow(Math.abs((t - 0.5)) * 2, specPower);
+ const l = Math.min(1, light + highlight * sheenStrength * 3);
+
+ const r = Math.round(primary.r * l);
+ const g = Math.round(primary.g * l);
+ const b = Math.round(primary.b * l);
+
+ const opacity = 0.6 + sheen / 300 + vNoise * 0.2;
+
+ const fadeEdge = 0.17;
+ let topFade = 1.0, bottomFade = 1.0;
+ if (height > 0) {
+ const topT = 0;
+ const bottomT = 1;
+ topFade = 1 - (fadeEdge - topT) / fadeEdge;
+ bottomFade = 1 - (bottomT - (1 - fadeEdge)) / fadeEdge;
+ topFade = Math.max(0.25, Math.min(1, topFade));
+ bottomFade = Math.max(0.25, Math.min(1, bottomFade));
+ }
+
+ const pad = spacing * 0.4;
+ const grad = ctx.createLinearGradient(0, pad, 0, height - pad);
+ grad.addColorStop(0, 'rgba(' + r + ',' + g + ',' + b + ',' + (opacity * topFade * 0.65).toFixed(3) + ')');
+ grad.addColorStop(0.52, 'rgba(' + r + ',' + g + ',' + b + ',' + opacity.toFixed(3) + ')');
+ grad.addColorStop(1, 'rgba(' + r + ',' + g + ',' + b + ',' + (opacity * bottomFade * 0.65).toFixed(3) + ')');
+
+ ctx.strokeStyle = grad;
+ ctx.lineWidth = thickness * (0.9 + vNoise * 0.4);
+ ctx.beginPath();
+
+ const segments = 10;
+ const segH = (height + 2 * pad) / segments;
+ for (let s = 0; s <= segments; s++) {
+ const yy = -pad + s * segH;
+ const segmentNoise = random2D(i * 2.3, s * 0.7);
+ const offsetX = (segmentNoise - 0.5) * spacing * 0.32;
+ const localShimmer = (Math.sin((yy * noiseScale + time * timeSpeed * 120) + i * 0.3) + 1) * 0.5;
+ const shimmerOffset = (localShimmer - 0.5) * sheen * 0.03;
+ const finalX = baseX + offsetX + shimmerOffset;
+ if (s === 0) {
+ ctx.moveTo(finalX, yy);
+ } else {
+ ctx.lineTo(finalX, yy);
+ }
+ }
+ ctx.stroke();
+ }
+
+ for (let j = -1; j <= horizontalCount + 1; j++) {
+ const y = j * spacing + (j % 2) * 0.8;
+ const t = j / horizontalCount;
+ const n = random2D(0.7, j);
+ const jitter = (n - 0.5) * spacing * 0.18;
+ const baseY = y + jitter;
+
+ const hNoise = random2D(3.1, j * 1.7);
+ const light = 0.32 + hNoise * 0.16 + shimmer * sheenStrength * (0.4 + hNoise * 0.4);
+
+ const highlight = Math.pow(Math.abs((t - 0.5)) * 2, specPower * 0.9);
+ const l = Math.min(1, light + highlight * sheenStrength * 2.4);
+
+ const r = Math.round(secondary.r * l);
+ const g = Math.round(secondary.g * l);
+ const b = Math.round(secondary.b * l);
+
+ const opacity = 0.45 + sheen / 350 + hNoise * 0.25;
+
+ const pad = spacing * 0.4;
+ const grad = ctx.createLinearGradient(pad, 0, width - pad, 0);
+ grad.addColorStop(0, 'rgba(' + r + ',' + g + ',' + b + ',' + (opacity * 0.6).toFixed(3) + ')');
+ grad.addColorStop(0.5, 'rgba(' + r + ',' + g + ',' + b + ',' + opacity.toFixed(3) + ')');
+ grad.addColorStop(1, 'rgba(' + r + ',' + g + ',' + b + ',' + (opacity * 0.6).toFixed(3) + ')');
+
+ ctx.strokeStyle = grad;
+ ctx.lineWidth = thickness * 0.9 * (0.9 + hNoise * 0.4);
+ ctx.beginPath();
+
+ const segments = 14;
+ const segW = (width + 2 * pad) / segments;
+ for (let s = 0; s <= segments; s++) {
+ const xx = -pad + s * segW;
+ const segmentNoise = random2D(s * 0.9, j * 2.1);
+ const offsetY = (segmentNoise - 0.5) * spacing * 0.22;
+ const localShimmer = (Math.cos((xx * noiseScale * 0.9 + time * timeSpeed * 130) - j * 0.4) + 1) * 0.5;
+ const shimmerOffset = (localShimmer - 0.5) * sheen * 0.03;
+ const finalY = baseY + offsetY + shimmerOffset;
+ if (s === 0) {
+ ctx.moveTo(xx, finalY);
+ } else {
+ ctx.lineTo(xx, finalY);
+ }
+ }
+ ctx.stroke();
+ }
+
+ ctx.restore();
+
+ const vignette = ctx.createRadialGradient(width * 0.5, height * 0.3, Math.min(width, height) * 0.1,
+ width * 0.5, height * 0.5, Math.max(width, height) * 0.9);
+ vignette.addColorStop(0, 'rgba(0,0,0,0)');
+ vignette.addColorStop(1, 'rgba(0,0,0,0.7)');
+ ctx.fillStyle = vignette;
+ ctx.fillRect(0, 0, width, height);
+ }
+
+ function animate(now) {
+ const dt = now - lastTime;
+ lastTime = now;
+ updateLightSweep(dt);
+ drawWeave(now);
+ requestAnimationFrame(animate);
+ }
+
+ function init() {
+ resizeCanvas();
+ updateLabels();
+ setSwatches();
+ setMeta();
+
+ densityInput.addEventListener('input', () => {
+ density = +densityInput.value;
+ updateLabels();
+ setMeta();
+ });
+
+ toneInput.addEventListener('input', () => {
+ tone = +toneInput.value;
+ updateLabels();
+ setSwatches();
+ });
+
+ sheenInput.addEventListener('input', () => {
+ sheen = +sheenInput.value;
+ updateLabels();
+ setMeta();
+ });
+
+ requestAnimationFrame(time => {
+ lastTime = time;
+ requestAnimationFrame(animate);
+ });
+ }
+
+ if (document.readyState === 'complete' || document.readyState === 'interactive') {
+ init();
+ } else {
+ document.addEventListener('DOMContentLoaded', init);
+ }
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/71a0784e71c8/gpt.png b/data/artifacts/71a0784e71c8/gpt.png
new file mode 100644
index 0000000..2740e7b
Binary files /dev/null and b/data/artifacts/71a0784e71c8/gpt.png differ
diff --git a/data/artifacts/71a0784e71c8/grok.html b/data/artifacts/71a0784e71c8/grok.html
new file mode 100644
index 0000000..08208c9
--- /dev/null
+++ b/data/artifacts/71a0784e71c8/grok.html
@@ -0,0 +1,798 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1" />
+<title>Designer Wallcoverings — Grasscloth Studio</title>
+<style>
+ :root {
+ --bg: #12100c;
+ --surface: #1a1712;
+ --surface-elevated: #221e18;
+ --ivory: #f5f2ea;
+ --ivory-dim: #c8c2b4;
+ --muted: #8a8476;
+ --brass: #C8A951;
+ --brass-soft: rgba(200, 169, 81, 0.35);
+ --brass-glow: rgba(200, 169, 81, 0.12);
+ --hairline: rgba(245, 242, 234, 0.10);
+ --hairline-strong: rgba(245, 242, 234, 0.18);
+ --shadow: 0 24px 64px rgba(0, 0, 0, 0.45), 0 2px 8px rgba(0, 0, 0, 0.3);
+ --ease: cubic-bezier(.22, .9, .24, 1);
+ --ease-hero: cubic-bezier(.22, 1, .36, 1);
+ --serif: "Cormorant Garamond", "Didot", "Bodoni MT", "Playfair Display", Georgia, serif;
+ --sans: "Avenir Next", "Helvetica Neue", Arial, sans-serif;
+ }
+
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+
+ html, body {
+ height: 100%;
+ background: var(--bg);
+ color: var(--ivory);
+ font-family: var(--sans);
+ font-size: 14px;
+ line-height: 1.55;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ overflow: hidden;
+ }
+
+ /* ── Top bar ── */
+ .topbar {
+ position: fixed;
+ top: 0; left: 0; right: 0;
+ z-index: 100;
+ height: 64px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: linear-gradient(180deg, rgba(18, 16, 12, 0.97) 0%, rgba(18, 16, 12, 0.88) 100%);
+ border-bottom: 1px solid var(--hairline);
+ backdrop-filter: blur(12px);
+ -webkit-backdrop-filter: blur(12px);
+ }
+
+ .wordmark {
+ font-family: var(--serif);
+ font-weight: 400;
+ font-size: 13px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--ivory);
+ line-height: 1;
+ }
+
+ .wordmark span {
+ color: var(--brass);
+ margin: 0 0.55em;
+ opacity: 0.9;
+ }
+
+ /* ── Layout ── */
+ .app {
+ display: grid;
+ grid-template-columns: minmax(300px, 360px) 1fr;
+ height: 100vh;
+ padding-top: 64px;
+ }
+
+ /* ── Control panel ── */
+ .panel {
+ display: flex;
+ flex-direction: column;
+ padding: 48px 40px 40px;
+ border-right: 1px solid var(--hairline);
+ background: var(--bg);
+ overflow-y: auto;
+ gap: 0;
+ }
+
+ .panel-eyebrow {
+ font-family: var(--sans);
+ font-size: 11px;
+ font-weight: 500;
+ letter-spacing: 0.2em;
+ text-transform: uppercase;
+ color: var(--brass);
+ margin-bottom: 16px;
+ }
+
+ .panel-title {
+ font-family: var(--serif);
+ font-weight: 400;
+ font-size: 32px;
+ line-height: 1.12;
+ letter-spacing: -0.01em;
+ color: var(--ivory);
+ margin-bottom: 14px;
+ }
+
+ .panel-lede {
+ font-family: var(--sans);
+ font-size: 13.5px;
+ line-height: 1.65;
+ color: var(--muted);
+ max-width: 32ch;
+ margin-bottom: 40px;
+ }
+
+ .divider {
+ height: 1px;
+ background: var(--hairline);
+ margin: 8px 0 36px;
+ border: none;
+ }
+
+ /* ── Controls ── */
+ .control-group {
+ margin-bottom: 32px;
+ }
+
+ .control-head {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ margin-bottom: 14px;
+ }
+
+ .control-label {
+ font-family: var(--sans);
+ font-size: 11px;
+ font-weight: 500;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: var(--ivory-dim);
+ }
+
+ .control-value {
+ font-family: var(--sans);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ color: var(--brass);
+ font-variant-numeric: tabular-nums;
+ }
+
+ /* Hairline range sliders */
+ input[type="range"] {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 100%;
+ height: 24px;
+ background: transparent;
+ cursor: pointer;
+ outline: none;
+ }
+
+ input[type="range"]::-webkit-slider-runnable-track {
+ height: 1px;
+ background: var(--hairline-strong);
+ border-radius: 1px;
+ }
+
+ input[type="range"]::-moz-range-track {
+ height: 1px;
+ background: var(--hairline-strong);
+ border-radius: 1px;
+ }
+
+ input[type="range"]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 14px;
+ height: 14px;
+ margin-top: -6.5px;
+ border-radius: 50%;
+ background: var(--ivory);
+ border: 1px solid rgba(18, 16, 12, 0.3);
+ box-shadow: 0 0 0 0 var(--brass-soft), 0 2px 6px rgba(0,0,0,0.35);
+ transition: box-shadow 0.35s var(--ease), background 0.25s var(--ease), transform 0.25s var(--ease);
+ }
+
+ input[type="range"]::-moz-range-thumb {
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ background: var(--ivory);
+ border: 1px solid rgba(18, 16, 12, 0.3);
+ box-shadow: 0 2px 6px rgba(0,0,0,0.35);
+ transition: box-shadow 0.35s var(--ease), background 0.25s var(--ease);
+ }
+
+ input[type="range"]:hover::-webkit-slider-thumb {
+ background: var(--brass);
+ box-shadow: 0 0 0 6px var(--brass-glow), 0 2px 8px rgba(0,0,0,0.4);
+ }
+
+ input[type="range"]:active::-webkit-slider-thumb {
+ transform: scale(0.92);
+ background: var(--brass);
+ box-shadow: 0 0 0 8px var(--brass-glow), 0 2px 8px rgba(0,0,0,0.4);
+ }
+
+ input[type="range"]:hover::-moz-range-thumb {
+ background: var(--brass);
+ }
+
+ /* Color pair */
+ .swatch-row {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 16px;
+ }
+
+ .swatch-field {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ }
+
+ .swatch-field .control-label {
+ font-size: 10px;
+ letter-spacing: 0.14em;
+ }
+
+ .color-wrap {
+ position: relative;
+ height: 40px;
+ border-radius: 2px;
+ overflow: hidden;
+ border: 1px solid var(--hairline-strong);
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.04);
+ transition: border-color 0.3s var(--ease), box-shadow 0.3s var(--ease);
+ }
+
+ .color-wrap:hover {
+ border-color: var(--brass-soft);
+ box-shadow: 0 0 0 3px var(--brass-glow);
+ }
+
+ .color-wrap input[type="color"] {
+ position: absolute;
+ inset: -4px;
+ width: calc(100% + 8px);
+ height: calc(100% + 8px);
+ border: none;
+ cursor: pointer;
+ padding: 0;
+ background: none;
+ }
+
+ .color-wrap input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
+ .color-wrap input[type="color"]::-webkit-color-swatch { border: none; border-radius: 0; }
+ .color-wrap input[type="color"]::-moz-color-swatch { border: none; }
+
+ /* Footer meta */
+ .panel-footer {
+ margin-top: auto;
+ padding-top: 36px;
+ }
+
+ .panel-footer p {
+ font-size: 11px;
+ letter-spacing: 0.04em;
+ color: var(--muted);
+ line-height: 1.6;
+ }
+
+ .panel-footer .sig {
+ margin-top: 18px;
+ font-family: var(--serif);
+ font-size: 12px;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: var(--ivory-dim);
+ }
+
+ /* ── Stage ── */
+ .stage {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background:
+ radial-gradient(ellipse 80% 60% at 50% 45%, rgba(34, 30, 24, 0.9) 0%, transparent 70%),
+ var(--bg);
+ overflow: hidden;
+ padding: 48px;
+ }
+
+ .stage-frame {
+ position: relative;
+ width: min(100%, 720px);
+ aspect-ratio: 4 / 5;
+ max-height: calc(100vh - 64px - 96px);
+ border-radius: 2px;
+ box-shadow: var(--shadow), 0 0 0 1px var(--hairline);
+ overflow: hidden;
+ background: #1a1610;
+ animation: frameIn 1.1s var(--ease-hero) both;
+ }
+
+ @keyframes frameIn {
+ from { opacity: 0; transform: translateY(18px) scale(0.985); }
+ to { opacity: 1; transform: translateY(0) scale(1); }
+ }
+
+ canvas#weave {
+ display: block;
+ width: 100%;
+ height: 100%;
+ }
+
+ /* Light sweep overlay (CSS, additive feel) */
+ .sweep {
+ pointer-events: none;
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(
+ 105deg,
+ transparent 0%,
+ transparent 40%,
+ rgba(255, 248, 230, 0.045) 48%,
+ rgba(200, 169, 81, 0.06) 50%,
+ rgba(255, 248, 230, 0.035) 52%,
+ transparent 60%,
+ transparent 100%
+ );
+ background-size: 220% 100%;
+ animation: lightSweep 9s var(--ease-hero) infinite;
+ mix-blend-mode: soft-light;
+ }
+
+ @keyframes lightSweep {
+ 0% { background-position: 120% 0; }
+ 45% { background-position: -120% 0; }
+ 100% { background-position: -120% 0; }
+ }
+
+ .stage-caption {
+ position: absolute;
+ bottom: 28px;
+ left: 0; right: 0;
+ text-align: center;
+ font-family: var(--sans);
+ font-size: 10px;
+ letter-spacing: 0.22em;
+ text-transform: uppercase;
+ color: var(--muted);
+ opacity: 0.7;
+ }
+
+ /* Entrance for panel */
+ .panel > * {
+ animation: rise 0.85s var(--ease-hero) both;
+ }
+ .panel-eyebrow { animation-delay: 0.05s; }
+ .panel-title { animation-delay: 0.12s; }
+ .panel-lede { animation-delay: 0.18s; }
+ .divider { animation-delay: 0.22s; }
+ .control-group:nth-of-type(1) { animation-delay: 0.28s; }
+ .control-group:nth-of-type(2) { animation-delay: 0.34s; }
+ .control-group:nth-of-type(3) { animation-delay: 0.40s; }
+ .control-group:nth-of-type(4) { animation-delay: 0.46s; }
+ .panel-footer { animation-delay: 0.55s; }
+
+ @keyframes rise {
+ from { opacity: 0; transform: translateY(12px); }
+ to { opacity: 1; transform: translateY(0); }
+ }
+
+ @media (max-width: 860px) {
+ .app {
+ grid-template-columns: 1fr;
+ grid-template-rows: auto 1fr;
+ overflow-y: auto;
+ }
+ .panel {
+ border-right: none;
+ border-bottom: 1px solid var(--hairline);
+ padding: 32px 28px;
+ }
+ .stage {
+ padding: 28px;
+ min-height: 55vh;
+ }
+ .stage-frame {
+ max-height: none;
+ width: min(100%, 420px);
+ }
+ .panel-footer { display: none; }
+ }
+</style>
+</head>
+<body>
+ <header class="topbar">
+ <div class="wordmark">Designer <span>·</span> Wallcoverings</div>
+ </header>
+
+ <div class="app">
+ <aside class="panel">
+ <div class="panel-eyebrow">Grasscloth Atelier</div>
+ <h1 class="panel-title">Handwoven<br/>Texture Studio</h1>
+ <p class="panel-lede">Compose density, fiber, and sheen for a bespoke grasscloth wallcovering. Preview updates in real time.</p>
+ <hr class="divider" />
+
+ <div class="control-group">
+ <div class="control-head">
+ <label class="control-label" for="density">Weave Density</label>
+ <span class="control-value" id="densityVal">48</span>
+ </div>
+ <input type="range" id="density" min="18" max="96" value="48" step="1" />
+ </div>
+
+ <div class="control-group">
+ <div class="control-head">
+ <span class="control-label">Fiber Tones</span>
+ </div>
+ <div class="swatch-row">
+ <div class="swatch-field">
+ <label class="control-label" for="fiberA">Warp</label>
+ <div class="color-wrap">
+ <input type="color" id="fiberA" value="#c4a574" />
+ </div>
+ </div>
+ <div class="swatch-field">
+ <label class="control-label" for="fiberB">Weft</label>
+ <div class="color-wrap">
+ <input type="color" id="fiberB" value="#8a7349" />
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="control-group">
+ <div class="control-head">
+ <label class="control-label" for="sheen">Surface Sheen</label>
+ <span class="control-value" id="sheenVal">32%</span>
+ </div>
+ <input type="range" id="sheen" min="0" max="100" value="32" step="1" />
+ </div>
+
+ <div class="control-group">
+ <div class="control-head">
+ <label class="control-label" for="irregularity">Artisan Irregularity</label>
+ <span class="control-value" id="irregVal">40%</span>
+ </div>
+ <input type="range" id="irregularity" min="0" max="100" value="40" step="1" />
+ </div>
+
+ <div class="panel-footer">
+ <p>Natural sisal & abaca fibers, hand-loomed. Variations in tone and gauge are inherent to the craft.</p>
+ <div class="sig">Est. Atelier</div>
+ </div>
+ </aside>
+
+ <main class="stage">
+ <div class="stage-frame">
+ <canvas id="weave"></canvas>
+ <div class="sweep" aria-hidden="true"></div>
+ </div>
+ <div class="stage-caption">Live material preview</div>
+ </main>
+ </div>
+
+<script>
+(function () {
+ const canvas = document.getElementById('weave');
+ const ctx = canvas.getContext('2d', { alpha: false, desynchronized: true });
+
+ const ui = {
+ density: document.getElementById('density'),
+ densityVal: document.getElementById('densityVal'),
+ fiberA: document.getElementById('fiberA'),
+ fiberB: document.getElementById('fiberB'),
+ sheen: document.getElementById('sheen'),
+ sheenVal: document.getElementById('sheenVal'),
+ irregularity: document.getElementById('irregularity'),
+ irregVal: document.getElementById('irregVal'),
+ };
+
+ let W = 0, H = 0, dpr = 1;
+ let needsFullRedraw = true;
+ let time = 0;
+ let lastTs = 0;
+
+ // Deterministic noise
+ function hash(x, y) {
+ let n = x * 374761393 + y * 668265263;
+ n = (n ^ (n >>> 13)) * 1274126177;
+ n = n ^ (n >>> 16);
+ return (n >>> 0) / 4294967296;
+ }
+
+ function smoothNoise(x, y) {
+ const xi = Math.floor(x), yi = Math.floor(y);
+ const xf = x - xi, yf = y - yi;
+ const u = xf * xf * (3 - 2 * xf);
+ const v = yf * yf * (3 - 2 * yf);
+ const a = hash(xi, yi);
+ const b = hash(xi + 1, yi);
+ const c = hash(xi, yi + 1);
+ const d = hash(xi + 1, yi + 1);
+ return a + (b - a) * u + (c - a) * v + (a - b - c + d) * u * v;
+ }
+
+ function fbm(x, y) {
+ let v = 0, a = 0.5, f = 1;
+ for (let i = 0; i < 4; i++) {
+ v += a * smoothNoise(x * f, y * f);
+ a *= 0.5;
+ f *= 2.03;
+ }
+ return v;
+ }
+
+ function hexToRgb(hex) {
+ const h = hex.replace('#', '');
+ return [
+ parseInt(h.slice(0, 2), 16),
+ parseInt(h.slice(2, 4), 16),
+ parseInt(h.slice(4, 6), 16),
+ ];
+ }
+
+ function lerp(a, b, t) { return a + (b - a) * t; }
+ function clamp(v, a, b) { return Math.max(a, Math.min(b, v)); }
+
+ function resize() {
+ const frame = canvas.parentElement;
+ const rect = frame.getBoundingClientRect();
+ dpr = Math.min(window.devicePixelRatio || 1, 2);
+ W = Math.max(2, Math.floor(rect.width * dpr));
+ H = Math.max(2, Math.floor(rect.height * dpr));
+ canvas.width = W;
+ canvas.height = H;
+ needsFullRedraw = true;
+ }
+
+ // Offscreen buffer for the static weave (rebuilt when params change)
+ let weaveBuf = null;
+ let weaveCtx = null;
+ let lastParams = '';
+
+ function ensureBuf() {
+ if (!weaveBuf || weaveBuf.width !== W || weaveBuf.height !== H) {
+ weaveBuf = document.createElement('canvas');
+ weaveBuf.width = W;
+ weaveBuf.height = H;
+ weaveCtx = weaveBuf.getContext('2d', { alpha: false });
+ }
+ }
+
+ function buildWeave() {
+ ensureBuf();
+ const density = +ui.density.value;
+ const irreg = +ui.irregularity.value / 100;
+ const [rA, gA, bA] = hexToRgb(ui.fiberA.value);
+ const [rB, gB, bB] = hexToRgb(ui.fiberB.value);
+
+ const img = weaveCtx.createImageData(W, H);
+ const data = img.data;
+
+ // Thread pitch in pixels
+ const pitch = Math.max(2.5, (W / density) * 0.55);
+ const fiberW = pitch * 0.42;
+
+ // Pre-dim ambient of warm near-black ground under fibers
+ const baseR = 22, baseG = 18, baseB = 12;
+
+ for (let y = 0; y < H; y++) {
+ for (let x = 0; x < W; x++) {
+ const i = (y * W + x) * 4;
+
+ // Irregular warp/weft displacement (hand-loomed feel)
+ const n1 = fbm(x * 0.018, y * 0.018);
+ const n2 = fbm(x * 0.04 + 20, y * 0.04 + 7);
+ const dispX = (n1 - 0.5) * pitch * 0.55 * irreg;
+ const dispY = (n2 - 0.5) * pitch * 0.55 * irreg;
+
+ const wx = x + dispX;
+ const wy = y + dispY;
+
+ // Basket / plain weave cell
+ const cx = wx / pitch;
+ const cy = wy / pitch;
+ const fx = cx - Math.floor(cx);
+ const fy = cy - Math.floor(cy);
+ const ix = Math.floor(cx);
+ const iy = Math.floor(cy);
+
+ // Which yarn is on top (plain weave)
+ const warpOnTop = ((ix + iy) & 1) === 0;
+
+ // Distance to fiber centerlines
+ const dxWarp = Math.abs(fx - 0.5);
+ const dyWeft = Math.abs(fy - 0.5);
+
+ // Fiber profiles (soft elliptical cross-section)
+ const half = fiberW / pitch;
+ const warpMask = clamp(1 - (dxWarp / half), 0, 1);
+ const weftMask = clamp(1 - (dyWeft / half), 0, 1);
+ // Soft edge falloff
+ const warpCov = warpMask * warpMask * (3 - 2 * warpMask);
+ const weftCov = weftMask * weftMask * (3 - 2 * weftMask);
+
+ // Fiber longitudinal grain
+ const grainWarp = fbm(ix * 1.7, wy * 0.12) * 0.5 + fbm(ix * 3.1, wy * 0.35) * 0.25;
+ const grainWeft = fbm(wx * 0.12, iy * 1.7) * 0.5 + fbm(wx * 0.35, iy * 3.1) * 0.25;
+
+ // Per-fiber tone variation
+ const toneWarp = 0.82 + grainWarp * 0.36 + (hash(ix, 0) - 0.5) * 0.08 * irreg;
+ const toneWeft = 0.82 + grainWeft * 0.36 + (hash(0, iy) - 0.5) * 0.08 * irreg;
+
+ // Cross-section shading (cylindrical)
+ const shadeWarp = Math.pow(clamp(1 - Math.abs(dxWarp) / (half * 1.05), 0, 1), 0.65);
+ const shadeWeft = Math.pow(clamp(1 - Math.abs(dyWeft) / (half * 1.05), 0, 1), 0.65);
+
+ // Interlace shadow where yarns cross under
+ const underShadow = warpOnTop
+ ? (1 - weftCov * 0.55) * (0.72 + 0.28 * shadeWarp)
+ : (1 - warpCov * 0.55) * (0.72 + 0.28 * shadeWeft);
+
+ // Compose fibers
+ let cov, shade, tone, fr, fg, fb;
+ if (warpOnTop) {
+ cov = Math.max(warpCov, weftCov * 0.35);
+ // Prefer warp on top
+ const top = warpCov;
+ const bot = weftCov * (1 - warpCov * 0.85);
+ fr = (rA * toneWarp * top + rB * toneWeft * bot) / Math.max(0.001, top + bot);
+ fg = (gA * toneWarp * top + gB * toneWeft * bot) / Math.max(0.001, top + bot);
+ fb = (bA * toneWarp * top + bB * toneWeft * bot) / Math.max(0.001, top + bot);
+ shade = shadeWarp * 0.75 + 0.25;
+ tone = underShadow;
+ } else {
+ const top = weftCov;
+ const bot = warpCov * (1 - weftCov * 0.85);
+ fr = (rB * toneWeft * top + rA * toneWarp * bot) / Math.max(0.001, top + bot);
+ fg = (gB * toneWeft * top + gA * toneWarp * bot) / Math.max(0.001, top + bot);
+ fb = (bB * toneWeft * top + bA * toneWarp * bot) / Math.max(0.001, top + bot);
+ shade = shadeWeft * 0.75 + 0.25;
+ tone = underShadow;
+ cov = Math.max(weftCov, warpCov * 0.35);
+ }
+
+ // Gap / ground between yarns (visible at low density)
+ const gap = clamp(1 - (warpCov + weftCov) * 1.05, 0, 1);
+ const groundN = fbm(x * 0.08, y * 0.08) * 0.15;
+
+ let R = fr * shade * tone;
+ let G = fg * shade * tone;
+ let B = fb * shade * tone;
+
+ // Micro flecks in fiber
+ const fleck = hash(x * 3, y * 5);
+ if (fleck > 0.97) {
+ R *= 1.12; G *= 1.1; B *= 1.05;
+ } else if (fleck < 0.03) {
+ R *= 0.88; G *= 0.9; B *= 0.92;
+ }
+
+ // Mix to ground in gaps
+ const gShade = 0.55 + groundN;
+ R = lerp(R, baseR * gShade, gap);
+ G = lerp(G, baseG * gShade, gap);
+ B = lerp(B, baseB * gShade, gap);
+
+ // Subtle overall vignette baked lightly
+ const nx = (x / W) * 2 - 1;
+ const ny = (y / H) * 2 - 1;
+ const vig = 1 - 0.12 * (nx * nx + ny * ny);
+
+ data[i] = clamp(R * vig, 0, 255) | 0;
+ data[i + 1] = clamp(G * vig, 0, 255) | 0;
+ data[i + 2] = clamp(B * vig, 0, 255) | 0;
+ data[i + 3] = 255;
+ }
+ }
+
+ weaveCtx.putImageData(img, 0, 0);
+ needsFullRedraw = false;
+ }
+
+ function paramsKey() {
+ return [
+ ui.density.value,
+ ui.fiberA.value,
+ ui.fiberB.value,
+ ui.irregularity.value,
+ W, H
+ ].join('|');
+ }
+
+ function frame(ts) {
+ if (!lastTs) lastTs = ts;
+ const dt = Math.min(0.05, (ts - lastTs) / 1000);
+ lastTs = ts;
+ time += dt;
+
+ if (W < 2 || H < 2) {
+ requestAnimationFrame(frame);
+ return;
+ }
+
+ const key = paramsKey();
+ if (key !== lastParams || needsFullRedraw) {
+ lastParams = key;
+ buildWeave();
+ }
+
+ // Draw weave
+ ctx.drawImage(weaveBuf, 0, 0);
+
+ // Live sheen + light sweep (GPU-cheap overlay each frame)
+ const sheen = +ui.sheen.value / 100;
+ if (sheen > 0.01) {
+ const sweepX = ((time * 0.08) % 1.6) - 0.3;
+ const grd = ctx.createLinearGradient(
+ sweepX * W, 0,
+ (sweepX + 0.45) * W, H * 0.15
+ );
+ const a0 = 0;
+ const a1 = 0.045 + sheen * 0.11;
+ const a2 = 0.02 + sheen * 0.05;
+ grd.addColorStop(0, 'rgba(255,248,230,0)');
+ grd.addColorStop(0.35, 'rgba(255,248,230,' + (a1 * 0.35).toFixed(3) + ')');
+ grd.addColorStop(0.5, 'rgba(232,210,150,' + a1.toFixed(3) + ')');
+ grd.addColorStop(0.65, 'rgba(255,248,230,' + (a2).toFixed(3) + ')');
+ grd.addColorStop(1, 'rgba(255,248,230,0)');
+
+ ctx.save();
+ ctx.globalCompositeOperation = 'soft-light';
+ ctx.fillStyle = grd;
+ ctx.fillRect(0, 0, W, H);
+
+ // Soft specular ridge following weave direction
+ const g2 = ctx.createLinearGradient(0, 0, W * 0.2, H);
+ g2.addColorStop(0, 'rgba(200,169,81,0)');
+ g2.addColorStop(0.5, 'rgba(200,169,81,' + (sheen * 0.06).toFixed(3) + ')');
+ g2.addColorStop(1, 'rgba(200,169,81,0)');
+ ctx.globalCompositeOperation = 'overlay';
+ ctx.fillStyle = g2;
+ ctx.fillRect(0, 0, W, H);
+ ctx.restore();
+ }
+
+ // Gentle breathing ambient (very subtle)
+ const breathe = 0.5 + 0.5 * Math.sin(time * 0.35);
+ ctx.save();
+ ctx.globalCompositeOperation = 'soft-light';
+ ctx.fillStyle = 'rgba(255, 246, 220,' + (0.012 + breathe * 0.01).toFixed(3) + ')';
+ ctx.fillRect(0, 0, W, H);
+ ctx.restore();
+
+ requestAnimationFrame(frame);
+ }
+
+ function bind() {
+ const sync = () => {
+ ui.densityVal.textContent = ui.density.value;
+ ui.sheenVal.textContent = ui.sheen.value + '%';
+ ui.irregVal.textContent = ui.irregularity.value + '%';
+ needsFullRedraw = true;
+ };
+ ['density', 'sheen', 'irregularity'].forEach(id => {
+ ui[id].addEventListener('input', sync);
+ });
+ ui.fiberA.addEventListener('input', () => { needsFullRedraw = true; });
+ ui.fiberB.addEventListener('input', () => { needsFullRedraw = true; });
+ sync();
+ }
+
+ window.addEventListener('resize', () => {
+ resize();
+ });
+
+ // Init
+ bind();
+ resize();
+ // Double-rAF to ensure layout settled
+ requestAnimationFrame(() => {
+ resize();
+ requestAnimationFrame(frame);
+ });
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/71a0784e71c8/grok.png b/data/artifacts/71a0784e71c8/grok.png
new file mode 100644
index 0000000..a0ec26b
Binary files /dev/null and b/data/artifacts/71a0784e71c8/grok.png differ
diff --git a/data/artifacts/71a0784e71c8/kimi.html b/data/artifacts/71a0784e71c8/kimi.html
new file mode 100644
index 0000000..da36fb1
--- /dev/null
+++ b/data/artifacts/71a0784e71c8/kimi.html
@@ -0,0 +1,697 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>DESIGNER WALLCOVERINGS — Texture Atelier</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@400;600&family=Inter:wght@300;400;500&display=swap" rel="stylesheet">
+<style>
+:root {
+ --ground: #12100c;
+ --surface: #1a1814;
+ --surface-2: #252320;
+ --accent: #C8A951;
+ --accent-dim: #9a7f3a;
+ --text: #f5f2ea;
+ --text-muted: #8a8578;
+ --hairline: rgba(245, 242, 234, 0.08);
+ --shadow: rgba(0, 0, 0, 0.4);
+
+ --ease-swift: cubic-bezier(.22,.9,.24,1);
+ --ease-power: cubic-bezier(.77,0,.18,1);
+
+ --font-display: 'Cormorant Garamond', Georgia, 'Times New Roman', serif;
+ --font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+html, body {
+ height: 100%;
+ background: var(--ground);
+ color: var(--text);
+ font-family: var(--font-body);
+ font-size: 15px;
+ line-height: 1.6;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+/* Fixed Header */
+.brand-header {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 72px;
+ background: rgba(18, 16, 12, 0.92);
+ backdrop-filter: blur(12px);
+ -webkit-backdrop-filter: blur(12px);
+ border-bottom: 1px solid var(--hairline);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 100;
+}
+
+.brand-wordmark {
+ font-family: var(--font-display);
+ font-size: 15px;
+ font-weight: 400;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--text);
+ text-decoration: none;
+}
+
+/* Main Layout */
+.atelier {
+ display: grid;
+ grid-template-columns: 380px 1fr;
+ height: 100vh;
+ padding-top: 72px;
+}
+
+/* Controls Panel */
+.controls-panel {
+ background: var(--surface);
+ border-right: 1px solid var(--hairline);
+ padding: 48px 40px;
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+ gap: 36px;
+ box-shadow: 4px 0 24px var(--shadow);
+}
+
+.panel-title {
+ font-family: var(--font-display);
+ font-size: 28px;
+ font-weight: 400;
+ line-height: 1.1;
+ color: var(--text);
+ margin-bottom: 8px;
+}
+
+.panel-subtitle {
+ font-size: 12px;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: var(--text-muted);
+ font-weight: 400;
+}
+
+.control-group {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.control-label {
+ font-size: 11px;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: var(--text-muted);
+ font-weight: 500;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.control-value {
+ color: var(--accent);
+ font-family: var(--font-body);
+ font-weight: 400;
+}
+
+/* Hairline Sliders */
+.slider-container {
+ position: relative;
+ height: 24px;
+ display: flex;
+ align-items: center;
+}
+
+input[type="range"] {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 100%;
+ height: 1px;
+ background: var(--hairline);
+ outline: none;
+ cursor: pointer;
+ position: relative;
+}
+
+input[type="range"]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ background: var(--accent);
+ cursor: pointer;
+ box-shadow: 0 2px 8px rgba(200, 169, 81, 0.3);
+ transition: transform 0.2s var(--ease-swift), box-shadow 0.2s var(--ease-swift);
+}
+
+input[type="range"]::-webkit-slider-thumb:hover {
+ transform: scale(1.15);
+ box-shadow: 0 4px 16px rgba(200, 169, 81, 0.5);
+}
+
+input[type="range"]::-moz-range-thumb {
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ background: var(--accent);
+ cursor: pointer;
+ border: none;
+ box-shadow: 0 2px 8px rgba(200, 169, 81, 0.3);
+}
+
+/* Color Swatches */
+.color-control {
+ display: flex;
+ gap: 16px;
+ align-items: center;
+}
+
+.color-input-wrapper {
+ position: relative;
+ width: 48px;
+ height: 48px;
+ border-radius: 50%;
+ overflow: hidden;
+ border: 1px solid var(--hairline);
+ transition: transform 0.3s var(--ease-swift), border-color 0.3s var(--ease-swift);
+}
+
+.color-input-wrapper:hover {
+ transform: scale(1.05);
+ border-color: var(--accent-dim);
+}
+
+input[type="color"] {
+ position: absolute;
+ top: -50%;
+ left: -50%;
+ width: 200%;
+ height: 200%;
+ cursor: pointer;
+ border: none;
+ padding: 0;
+ background: none;
+}
+
+.color-labels {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.color-name {
+ font-size: 13px;
+ color: var(--text);
+ font-weight: 400;
+}
+
+.color-hex {
+ font-family: 'SF Mono', monospace;
+ font-size: 11px;
+ color: var(--text-muted);
+ letter-spacing: 0.05em;
+}
+
+/* Canvas Stage */
+.canvas-stage {
+ position: relative;
+ background: var(--ground);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+}
+
+#weaveCanvas {
+ max-width: 90%;
+ max-height: 90%;
+ box-shadow: 0 24px 64px var(--shadow), 0 8px 24px rgba(0,0,0,0.3);
+}
+
+/* Light Sweep Overlay */
+.light-sweep {
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 50%;
+ height: 100%;
+ background: linear-gradient(90deg,
+ transparent 0%,
+ rgba(200, 169, 81, 0.03) 40%,
+ rgba(200, 169, 81, 0.08) 50%,
+ rgba(200, 169, 81, 0.03) 60%,
+ transparent 100%
+ );
+ pointer-events: none;
+ animation: lightSweep 8s var(--ease-power) infinite;
+}
+
+@keyframes lightSweep {
+ 0%, 85%, 100% { left: -100%; opacity: 0; }
+ 10% { opacity: 1; }
+ 40% { left: 150%; opacity: 1; }
+ 50% { opacity: 0; }
+}
+
+/* Preset Chips */
+.preset-chips {
+ display: flex;
+ gap: 10px;
+ flex-wrap: wrap;
+ margin-top: 8px;
+}
+
+.chip {
+ padding: 8px 16px;
+ background: var(--surface-2);
+ border: 1px solid var(--hairline);
+ border-radius: 2px;
+ font-size: 11px;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--text-muted);
+ cursor: pointer;
+ transition: all 0.25s var(--ease-swift);
+}
+
+.chip:hover {
+ border-color: var(--accent-dim);
+ color: var(--text);
+}
+
+.chip.active {
+ background: rgba(200, 169, 81, 0.1);
+ border-color: var(--accent);
+ color: var(--accent);
+}
+
+/* Responsive */
+@media (max-width: 1024px) {
+ .atelier {
+ grid-template-columns: 320px 1fr;
+ }
+ .controls-panel {
+ padding: 36px 28px;
+ }
+}
+
+@media (max-width: 768px) {
+ .atelier {
+ grid-template-columns: 1fr;
+ grid-template-rows: auto 1fr;
+ }
+ .controls-panel {
+ border-right: none;
+ border-bottom: 1px solid var(--hairline);
+ max-height: 40vh;
+ }
+}
+</style>
+</head>
+<body>
+ <header class="brand-header">
+ <span class="brand-wordmark">DESIGNER WALLCOVERINGS</span>
+ </header>
+
+ <main class="atelier">
+ <aside class="controls-panel">
+ <div>
+ <p class="panel-subtitle">Texture Atelier</p>
+ <h1 class="panel-title">Grasscloth<br>Weave</h1>
+ </div>
+
+ <div class="control-group">
+ <label class="control-label">
+ <span>Weave Density</span>
+ <span class="control-value" id="densityValue">24</span>
+ </label>
+ <div class="slider-container">
+ <input type="range" id="density" min="8" max="48" value="24" step="2">
+ </div>
+ </div>
+
+ <div class="control-group">
+ <label class="control-label">Fiber Colors</label>
+ <div class="color-control">
+ <div class="color-input-wrapper">
+ <input type="color" id="color1" value="#d4c5a8">
+ </div>
+ <div class="color-labels">
+ <span class="color-name">Primary Fiber</span>
+ <span class="color-hex" id="hex1">#d4c5a8</span>
+ </div>
+ </div>
+ <div class="color-control">
+ <div class="color-input-wrapper">
+ <input type="color" id="color2" value="#8b7d6b">
+ </div>
+ <div class="color-labels">
+ <span class="color-name">Secondary Fiber</span>
+ <span class="color-hex" id="hex2">#8b7d6b</span>
+ </div>
+ </div>
+ </div>
+
+ <div class="control-group">
+ <label class="control-label">
+ <span>Sheen / Luster</span>
+ <span class="control-value" id="sheenValue">40%</span>
+ </label>
+ <div class="slider-container">
+ <input type="range" id="sheen" min="0" max="100" value="40">
+ </div>
+ </div>
+
+ <div class="control-group">
+ <label class="control-label">
+ <span>Thread Thickness</span>
+ <span class="control-value" id="thicknessValue">2.0</span>
+ </label>
+ <div class="slider-container">
+ <input type="range" id="thickness" min="1" max="4" value="2" step="0.2">
+ </div>
+ </div>
+
+ <div class="control-group">
+ <label class="control-label">Quick Presets</label>
+ <div class="preset-chips">
+ <button class="chip active" data-preset="natural">Natural</button>
+ <button class="chip" data-preset="charcoal">Charcoal</button>
+ <button class="chip" data-preset="sand">Sand</button>
+ <button class="chip" data-preset="midnight">Midnight</button>
+ </div>
+ </div>
+ </aside>
+
+ <section class="canvas-stage">
+ <canvas id="weaveCanvas" width="800" height="800"></canvas>
+ <div class="light-sweep"></div>
+ </section>
+ </main>
+
+<script>
+const canvas = document.getElementById('weaveCanvas');
+const ctx = canvas.getContext('2d');
+
+// High DPI handling
+const dpr = window.devicePixelRatio || 1;
+const displayWidth = 800;
+const displayHeight = 800;
+canvas.width = displayWidth * dpr;
+canvas.height = displayHeight * dpr;
+canvas.style.width = displayWidth + 'px';
+canvas.style.height = displayHeight + 'px';
+ctx.scale(dpr, dpr);
+
+// State
+const state = {
+ density: 24,
+ color1: '#d4c5a8',
+ color2: '#8b7d6b',
+ sheen: 0.4,
+ thickness: 2.0,
+ time: 0
+};
+
+// Controls
+const densityInput = document.getElementById('density');
+const densityValue = document.getElementById('densityValue');
+const color1Input = document.getElementById('color1');
+const color2Input = document.getElementById('color2');
+const hex1 = document.getElementById('hex1');
+const hex2 = document.getElementById('hex2');
+const sheenInput = document.getElementById('sheen');
+const sheenValue = document.getElementById('sheenValue');
+const thicknessInput = document.getElementById('thickness');
+const thicknessValue = document.getElementById('thicknessValue');
+const chips = document.querySelectorAll('.chip');
+
+// Presets
+const presets = {
+ natural: { color1: '#d4c5a8', color2: '#8b7d6b', sheen: 40, thickness: 2.0 },
+ charcoal: { color1: '#4a4a4a', color2: '#2a2a2a', sheen: 25, thickness: 2.5 },
+ sand: { color1: '#e8dcc4', color2: '#c4b59a', sheen: 55, thickness: 1.8 },
+ midnight: { color1: '#2c3e50', color2: '#1a252f', sheen: 30, thickness: 2.2 }
+};
+
+// Event Listeners
+densityInput.addEventListener('input', (e) => {
+ state.density = parseInt(e.target.value);
+ densityValue.textContent = state.density;
+});
+
+color1Input.addEventListener('input', (e) => {
+ state.color1 = e.target.value;
+ hex1.textContent = state.color1;
+});
+
+color2Input.addEventListener('input', (e) => {
+ state.color2 = e.target.value;
+ hex2.textContent = state.color2;
+});
+
+sheenInput.addEventListener('input', (e) => {
+ state.sheen = parseInt(e.target.value) / 100;
+ sheenValue.textContent = e.target.value + '%';
+});
+
+thicknessInput.addEventListener('input', (e) => {
+ state.thickness = parseFloat(e.target.value);
+ thicknessValue.textContent = state.thickness.toFixed(1);
+});
+
+chips.forEach(chip => {
+ chip.addEventListener('click', () => {
+ chips.forEach(c => c.classList.remove('active'));
+ chip.classList.add('active');
+
+ const preset = presets[chip.dataset.preset];
+ if (preset) {
+ state.color1 = preset.color1;
+ state.color2 = preset.color2;
+ state.sheen = preset.sheen / 100;
+ state.thickness = preset.thickness;
+
+ color1Input.value = preset.color1;
+ color2Input.value = preset.color2;
+ hex1.textContent = preset.color1;
+ hex2.textContent = preset.color2;
+ sheenInput.value = preset.sheen;
+ sheenValue.textContent = preset.sheen + '%';
+ thicknessInput.value = preset.thickness;
+ thicknessValue.textContent = preset.thickness.toFixed(1);
+ }
+ });
+});
+
+// Color utilities
+function hexToRgb(hex) {
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+ return result ? {
+ r: parseInt(result[1], 16),
+ g: parseInt(result[2], 16),
+ b: parseInt(result[3], 16)
+ } : { r: 200, g: 180, b: 150 };
+}
+
+function blendColors(c1, c2, t) {
+ return {
+ r: Math.round(c1.r + (c2.r - c1.r) * t),
+ g: Math.round(c1.g + (c2.g - c1.g) * t),
+ b: Math.round(c1.b + (c2.b - c1.b) * t)
+ };
+}
+
+function rgbToString(c) {
+ return `rgb(${c.r}, ${c.g}, ${c.b})`;
+}
+
+// Perlin noise for organic variation
+function noise(x, y) {
+ const n = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
+ return n - Math.floor(n);
+}
+
+function smoothNoise(x, y) {
+ const i = Math.floor(x);
+ const j = Math.floor(y);
+ const u = x - i;
+ const v = y - j;
+
+ const a = noise(i, j);
+ const b = noise(i + 1, j);
+ const c = noise(i, j + 1);
+ const d = noise(i + 1, j + 1);
+
+ const u2 = u * u * (3 - 2 * u);
+ const v2 = v * v * (3 - 2 * v);
+
+ return (a * (1 - u2) + b * u2) * (1 - v2) + (c * (1 - u2) + d * u2) * v2;
+}
+
+// Weave rendering
+function drawWeave() {
+ const width = displayWidth;
+ const height = displayHeight;
+ const threads = state.density;
+ const spacing = width / threads;
+ const thickness = Math.max(1, spacing * 0.4 * state.thickness);
+
+ const rgb1 = hexToRgb(state.color1);
+ const rgb2 = hexToRgb(state.color2);
+
+ // Clear with base
+ ctx.fillStyle = rgbToString(blendColors(rgb1, rgb2, 0.5));
+ ctx.fillRect(0, 0, width, height);
+
+ // Create weave pattern
+ for (let i = 0; i < threads; i++) {
+ for (let j = 0; j < threads; j++) {
+ const x = i * spacing;
+ const y = j * spacing;
+ const centerX = x + spacing / 2;
+ const centerY = y + spacing / 2;
+
+ // Alternating pattern for basket weave
+ const isHorizontal = (i + j) % 2 === 0;
+
+ // Organic variation
+ const n = smoothNoise(i * 0.5, j * 0.5 + state.time * 0.0005);
+ const colorVar = blendColors(rgb1, rgb2, n);
+
+ // Light calculation for sheen
+ const lightAngle = Math.PI / 4 + state.time * 0.001;
+ const dx = Math.cos(lightAngle);
+ const dy = Math.sin(lightAngle);
+
+ // Shadow and highlight based on weave direction
+ const shadowOffset = thickness * 0.3;
+
+ ctx.save();
+
+ if (isHorizontal) {
+ // Horizontal thread
+ const yOffset = centerY + (n - 0.5) * spacing * 0.2;
+
+ // Shadow (below)
+ ctx.fillStyle = `rgba(0,0,0,${0.15 + state.sheen * 0.2})`;
+ ctx.fillRect(x + 2, yOffset - thickness/2 + shadowOffset, spacing - 2, thickness);
+
+ // Thread with sheen gradient
+ const grad = ctx.createLinearGradient(x, yOffset - thickness/2, x, yOffset + thickness/2);
+ const baseColor = rgbToString(colorVar);
+ const highlight = rgbToString(blendColors(colorVar, {r:255,g:255,b:255}, state.sheen * 0.4));
+ const shadow = rgbToString(blendColors(colorVar, {r:0,g:0,b:0}, 0.2));
+
+ grad.addColorStop(0, shadow);
+ grad.addColorStop(0.3, baseColor);
+ grad.addColorStop(0.5, highlight);
+ grad.addColorStop(0.7, baseColor);
+ grad.addColorStop(1, shadow);
+
+ ctx.fillStyle = grad;
+ ctx.fillRect(x, yOffset - thickness/2, spacing, thickness);
+
+ // Subtle texture lines
+ ctx.strokeStyle = `rgba(0,0,0,${0.05 + state.sheen * 0.1})`;
+ ctx.lineWidth = 0.5;
+ for (let k = 0; k < 3; k++) {
+ ctx.beginPath();
+ ctx.moveTo(x, yOffset - thickness/2 + k * thickness/3);
+ ctx.lineTo(x + spacing, yOffset - thickness/2 + k * thickness/3);
+ ctx.stroke();
+ }
+
+ } else {
+ // Vertical thread
+ const xOffset = centerX + (n - 0.5) * spacing * 0.2;
+
+ // Shadow (right)
+ ctx.fillStyle = `rgba(0,0,0,${0.15 + state.sheen * 0.2})`;
+ ctx.fillRect(xOffset - thickness/2 + shadowOffset, y + 2, thickness, spacing - 2);
+
+ // Thread with sheen gradient
+ const grad = ctx.createLinearGradient(xOffset - thickness/2, y, xOffset + thickness/2, y);
+ const baseColor = rgbToString(colorVar);
+ const highlight = rgbToString(blendColors(colorVar, {r:255,g:255,b:255}, state.sheen * 0.4));
+ const shadow = rgbToString(blendColors(colorVar, {r:0,g:0,b:0}, 0.2));
+
+ grad.addColorStop(0, shadow);
+ grad.addColorStop(0.3, baseColor);
+ grad.addColorStop(0.5, highlight);
+ grad.addColorStop(0.7, baseColor);
+ grad.addColorStop(1, shadow);
+
+ ctx.fillStyle = grad;
+ ctx.fillRect(xOffset - thickness/2, y, thickness, spacing);
+
+ // Subtle texture lines
+ ctx.strokeStyle = `rgba(0,0,0,${0.05 + state.sheen * 0.1})`;
+ ctx.lineWidth = 0.5;
+ for (let k = 0; k < 3; k++) {
+ ctx.beginPath();
+ ctx.moveTo(xOffset - thickness/2 + k * thickness/3, y);
+ ctx.lineTo(xOffset - thickness/2 + k * thickness/3, y + spacing);
+ ctx.stroke();
+ }
+ }
+
+ ctx.restore();
+ }
+ }
+
+ // Global sheen overlay for luxury effect
+ const sheenGrad = ctx.createLinearGradient(0, 0, width, height);
+ sheenGrad.addColorStop(0, 'rgba(255,255,255,0)');
+ sheenGrad.addColorStop(0.5, `rgba(200,169,81,${state.sheen * 0.03})`);
+ sheenGrad.addColorStop(1, 'rgba(255,255,255,0)');
+ ctx.fillStyle = sheenGrad;
+ ctx.fillRect(0, 0, width, height);
+}
+
+// Animation loop
+let lastTime = 0;
+function animate(timestamp) {
+ state.time = timestamp;
+
+ // Only redraw on changes or subtle animation
+ if (timestamp - lastTime > 33) { // ~30fps for subtle animation
+ drawWeave();
+ lastTime = timestamp;
+ } else {
+ // Force redraw on first frame
+ if (state.time < 50) drawWeave();
+ }
+
+ requestAnimationFrame(animate);
+}
+
+// Initial draw
+drawWeave();
+requestAnimationFrame(animate);
+
+// Handle resize
+window.addEventListener('resize', () => {
+ drawWeave();
+});
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/71a0784e71c8/kimi.png b/data/artifacts/71a0784e71c8/kimi.png
new file mode 100644
index 0000000..9929e77
Binary files /dev/null and b/data/artifacts/71a0784e71c8/kimi.png differ
diff --git a/data/artifacts/71a0784e71c8/qwen25-7b.html b/data/artifacts/71a0784e71c8/qwen25-7b.html
new file mode 100644
index 0000000..3d1dab0
--- /dev/null
+++ b/data/artifacts/71a0784e71c8/qwen25-7b.html
@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>DESIGNER WALLCOVERINGS</title>
+ <style>
+ body, html {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ overflow: hidden;
+ background-color: #12100c;
+ color: #f5f2ea;
+ font-family: 'Didot', Georgia, serif;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+ .header {
+ position: fixed;
+ top: 0;
+ width: 100%;
+ background-color: #17171c;
+ color: #f5f2ea;
+ font-size: 24px;
+ letter-spacing: .18em;
+ padding: 16px;
+ }
+ .content {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+ height: calc(100% - 52px);
+ background-color: #17171c;
+ color: #f5f2ea;
+ padding: 96px;
+ }
+ .weave {
+ width: 400px;
+ height: 300px;
+ border-radius: 14px;
+ overflow: hidden;
+ box-shadow: 0 8px 24px rgba(0,0,0,.12);
+ background-color: #fff9f1;
+ }
+ .controls {
+ margin-top: 24px;
+ }
+ input[type="range"] {
+ width: 100%;
+ border-radius: 6px;
+ outline: none;
+ appearance: none;
+ -webkit-appearance: none;
+ }
+ input[type="range"]::-webkit-slider-thumb {
+ width: 8px;
+ height: 8px;
+ background-color: #c9a961;
+ border-radius: 50%;
+ cursor: pointer;
+ }
+ .slider-label {
+ font-size: 12px;
+ letter-spacing: .15em;
+ color: #f3e9ff;
+ margin-left: 8px;
+ }
+ @keyframes fadeUp {
+ from { opacity: 0; transform: translateY(28px); }
+ to { opacity: 1; transform: none; }
+ }
+ </style>
+</head>
+<body>
+ <div class="header">DESIGNER WALLCOVERINGS</div>
+ <div class="content">
+ <canvas id="weave" width="400" height="300"></canvas>
+ <div class="controls">
+ <label>WEAVE DENSITY</label><input type="range" min="1" max="100" value="50" id="density" oninput="drawWeave()">
+ <label>2-TONE FIBER COLOR</label><input type="range" min="1" max="100" value="50" id="color" oninput="drawWeave()">
+ <label>SHEEN</label><input type="range" min="1" max="100" value="50" id="sheen" oninput="drawWeave()">
+ </div>
+ </div>
+
+ <script>
+ const canvas = document.getElementById('weave');
+ const ctx = canvas.getContext('2d');
+
+ function drawWeave() {
+ const density = parseInt(document.getElementById('density').value);
+ const color = parseInt(document.getElementById('color').value);
+ const sheen = parseInt(document.getElementById('sheen').value);
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ ctx.fillStyle = `hsl(${210 - sheen}, 85%, ${100 - color}%)`;
+ for (let i = 0; i < density; i++) {
+ if (i % 2 === 0) {
+ ctx.fillRect(i * (canvas.width / density), 0, canvas.width / density, canvas.height);
+ }
+ }
+ }
+
+ drawWeave();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/71a0784e71c8/qwen25-7b.png b/data/artifacts/71a0784e71c8/qwen25-7b.png
new file mode 100644
index 0000000..cb7a9fe
Binary files /dev/null and b/data/artifacts/71a0784e71c8/qwen25-7b.png differ
diff --git a/data/artifacts/8443d9162963/gemma3-12b.html b/data/artifacts/8443d9162963/gemma3-12b.html
new file mode 100644
index 0000000..25f4a68
--- /dev/null
+++ b/data/artifacts/8443d9162963/gemma3-12b.html
@@ -0,0 +1,212 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Ragdoll Physics Toy</title>
+ <style>
+ body { margin: 0; overflow: hidden; background-color: #f0f0f0; }
+ canvas { background-color: #fff; display: block; border: 1px solid black;}
+ #controls { position: absolute; top: 10px; left: 10px; font-family: sans-serif; font-size: small; }
+ </style>
+</head>
+<body>
+
+<div id="controls">
+ Drag to fling! <button id="resetBtn">Reset</button>
+</div>
+
+<canvas id="myCanvas"></canvas>
+
+<script>
+ const canvas = document.getElementById("myCanvas");
+ const ctx = canvas.getContext("2d");
+ const resetBtn = document.getElementById("resetBtn");
+
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+
+ let ragdoll = {
+ segments: [
+ { x: 100, y: 250, mass: 1, radius: 15 }, // Head
+ { x: 100, y: 230, mass: 1.2, radius: 12 }, // Neck
+ { x: 90, y: 210, mass: 1.8, radius: 20 }, // Upper Torso
+ { x: 90, y: 240, mass: 2.5, radius: 25 }, // Lower Torso
+ { x: 90, y: 270, mass: 1.8, radius: 18 }, // Upper Leg
+ { x: 90, y: 310, mass: 1.5, radius: 14 } // Lower Leg
+ ],
+ jointStiffness: 0.97,
+ damping: 0.96,
+ };
+
+ let mouseX = 0;
+ let mouseY = 0;
+ let draggingSegmentIndex = -1;
+
+ const platforms = [
+ { x: 50, y: canvas.height - 50, width: 200, height: 20 },
+ { x: canvas.width - 250, y: canvas.height - 80, width: 200, height: 20 },
+ { x: 150, y: canvas.height - 150, width: 150, height: 20 }
+
+ ];
+
+
+ let gravity = { x: 0, y: 0.6 };
+
+ function distance(x1, y1, x2, y2) {
+ return Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2);
+ }
+
+ let lastTime = 0;
+
+ function animate(timestamp) {
+ const dt = (timestamp - lastTime) / 1000;
+ lastTime = timestamp;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ // Handle Mouse Interaction
+ if (draggingSegmentIndex !== -1) {
+ ragdoll.segments[draggingSegmentIndex].x = clamp(mouseX - ragdoll.segments[draggingSegmentIndex].radius, 0 , canvas.width - ragdoll.segments[draggingSegmentIndex].radius *2);
+ ragdoll.segments[draggingSegmentIndex].y = clamp(mouseY - ragdoll.segments[draggingSegmentIndex].radius, 0, canvas.height - ragdoll.segments[draggingSegmentIndex].radius*2 );
+ }
+
+
+ // Apply Gravity and Resolve Collisions
+ for (let i = 0; i < ragdoll.segments.length; i++) {
+ ragdoll.segments[i].x += gravity.x * dt;
+ ragdoll.segments[i].y += gravity.y * dt;
+
+ // Floor Collision
+ if (ragdoll.segments[i].y + ragdoll.segments[i].radius > canvas.height) {
+ ragdoll.segments[i].y = canvas.height - ragdoll.segments[i].radius;
+ ragdoll.segments[i].velocity_y = 0; // Stop downward velocity on collision
+
+ }
+
+
+ // Platform Collisions
+ for (const platform of platforms) {
+ if (ragdoll.segments[i].x + ragdoll.segments[i].radius > platform.x &&
+ ragdoll.segments[i].x - ragdoll.segments[i].radius < platform.x + platform.width &&
+ ragdoll.segments[i].y + ragdoll.segments[i].radius > platform.y &&
+ ragdoll.segments[i].y - ragdoll.segments[i].radius < platform.y + platform.height) {
+
+ // Simple collision response: Prevent penetration and reverse velocity
+ if (ragdoll.segments[i].y + ragdoll.segments[i].radius > platform.y ) {
+ ragdoll.segments[i].y = platform.y - ragdoll.segments[i].radius;
+ ragdoll.segments[i].velocity_y = -Math.abs(ragdoll.segments[i].velocity_y) * 0.6 ; //reverse velocity slightly
+ }
+
+ if (ragdoll.segments[i].x + ragdoll.segments[i].radius > platform.x ) {
+ ragdoll.segments[i].x = platform.x - ragdoll.segments[i].radius;
+ ragdoll.segments[i].velocity_x = -Math.abs(ragdoll.segments[i].velocity_x) * 0.5 ; //reverse velocity slightly
+ }
+
+ if (ragdoll.segments[i].x - ragdoll.segments[i].radius < platform.x + platform.width ) {
+ ragdoll.segments[i].x = platform.x + platform.width + ragdoll.segments[i].radius;
+ ragdoll.segments[i].velocity_x = -Math.abs(ragdoll.segments[i].velocity_x) * 0.5 ; //reverse velocity slightly
+ }
+
+ }
+ }
+
+
+ }
+
+
+
+
+ // Apply Joint Constraints (simplified)
+ for (let i = 1; i < ragdoll.segments.length; i++) {
+ const segment1 = ragdoll.segments[i - 1];
+ const segment2 = ragdoll.segments[i];
+
+ const distance_ = distance(segment1.x, segment1.y, segment2.x, segment2.y);
+ const idealDistance = (segment1.radius + segment2.radius) ;
+
+ let dx = segment2.x - segment1.x;
+ let dy = segment2.y - segment1.y;
+
+ dx = dx / distance_;
+ dy = dy / distance_;
+
+
+
+ segment2.x += (idealDistance - distance_) * dx * ragdoll.jointStiffness;
+ segment2.y += (idealDistance - distance_) * dy * ragdoll.jointStiffness;
+
+
+ }
+
+ // Damping
+ for (let i = 0; i < ragdoll.segments.length; i++) {
+
+ ragdoll.segments[i].x *= ragdoll.damping;
+ ragdoll.segments[i].y *= ragdoll.damping;
+ }
+
+
+
+ // Draw Ragdoll
+ ctx.beginPath();
+ ctx.strokeStyle = "black";
+ ctx.lineWidth = 2;
+
+ for (let i = 0; i < ragdoll.segments.length - 1; i++) {
+ ctx.moveTo(ragdoll.segments[i].x, ragdoll.segments[i].y);
+ ctx.lineTo(ragdoll.segments[i + 1].x, ragdoll.segments[i + 1].y);
+ }
+
+ ctx.stroke();
+
+ for (let i = 0; i < ragdoll.segments.length; i++) {
+ ctx.beginPath();
+ ctx.arc(ragdoll.segments[i].x, ragdoll.segments[i].y, ragdoll.segments[i].radius, 0, Math.PI * 2);
+ ctx.fillStyle = "red";
+ ctx.fill();
+ }
+
+ requestAnimationFrame(animate);
+ }
+
+
+ // Mouse Event Handlers
+ canvas.addEventListener("mousedown", (e) => {
+ mouseX = e.clientX;
+ mouseY = e.clientY;
+
+ for (let i = 0; i < ragdoll.segments.length; i++) {
+ const distToSegment = distance(mouseX, mouseY, ragdoll.segments[i].x, ragdoll.segments[i].y);
+ if (distToSegment <= ragdoll.segments[i].radius) {
+ draggingSegmentIndex = i;
+ break;
+ }
+ }
+ });
+
+ canvas.addEventListener("mouseup", () => {
+ draggingSegmentIndex = -1;
+ });
+
+ canvas.addEventListener("mousemove", (e) => {
+ mouseX = e.clientX;
+ mouseY = e.clientY;
+ });
+
+
+ resetBtn.addEventListener("click", () => {
+ for (let i = 0; i < ragdoll.segments.length; i++) {
+ ragdoll.segments[i].x = 100 + i*20;
+ ragdoll.segments[i].y = 250 + i*10 ;
+
+ }
+
+ });
+
+
+ requestAnimationFrame(animate);
+</script>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/8443d9162963/gemma3-12b.png b/data/artifacts/8443d9162963/gemma3-12b.png
new file mode 100644
index 0000000..2ee703b
Binary files /dev/null and b/data/artifacts/8443d9162963/gemma3-12b.png differ
diff --git a/data/artifacts/8443d9162963/hermes3-8b.html b/data/artifacts/8443d9162963/hermes3-8b.html
new file mode 100644
index 0000000..f1ef84a
--- /dev/null
+++ b/data/artifacts/8443d9162963/hermes3-8b.html
@@ -0,0 +1,196 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Improved Ragdoll Physics Toy</title>
+<style>
+body { margin: 0; overflow: hidden; background-color: #eee; }
+canvas { background-color: #fff; display: block; }
+#controls { position: absolute; top: 10px; left: 10px; font-family: sans-serif; }
+</style>
+</head>
+<body>
+
+<div id="controls">
+ Drag to fling! <button id="resetBtn">Reset</button>
+</div>
+
+<canvas id="myCanvas"></canvas>
+
+<script>
+const canvas = document.getElementById("myCanvas");
+const ctx = canvas.getContext("2d");
+const resetBtn = document.getElementById("resetBtn");
+
+canvas.width = window.innerWidth;
+canvas.height = window.innerHeight;
+
+let ragdoll = {
+ segments: [
+ { x: 100, y: 200, mass: 1, radius: 10 }, // Head
+ { x: 80, y: 250, mass: 1, radius: 12 }, // Neck
+ { x: 60, y: 300, mass: 1.5, radius: 15 }, // Upper Torso
+ { x: 60, y: 340, mass: 2, radius: 20 }, // Lower Torso
+ { x: 60, y: 380, mass: 1.5, radius: 15 }, // Upper Leg
+ { x: 60, y: 420, mass: 1.2, radius: 12 } // Lower Leg
+ ],
+ jointStiffness: 0.98,
+ damping: 0.95,
+};
+
+let mouseX = 0;
+let mouseY = 0;
+let draggingSegmentIndex = -1;
+
+const platforms = [
+ { x: 0, y: canvas.height - 50, width: 200, height: 20 },
+ { x: canvas.width - 250, y: canvas.height - 80, width: 200, height: 20 },
+ { x: 100, y: canvas.height - 150, width: 100, height: 20 }
+];
+
+let gravity = { x: 0, y: 0.5 };
+
+function distance(x1, y1, x2, y2) {
+ return Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2);
+}
+
+function clamp(value, min, max) {
+ return Math.min(Math.max(value, min), max);
+}
+
+let lastTime = 0;
+
+function animate(timestamp) {
+ const dt = (timestamp - lastTime) / 1000;
+ lastTime = timestamp;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ if (draggingSegmentIndex !== -1) {
+ ragdoll.segments[draggingSegmentIndex].x = clamp(mouseX - ragdoll.segments[draggingSegmentIndex].radius, 0, canvas.width - ragdoll.segments[draggingSegmentIndex].radius * 2);
+ ragdoll.segments[draggingSegmentIndex].y = clamp(mouseY - ragdoll.segments[draggingSegmentIndex].radius, 0, canvas.height - ragdoll.segments[draggingSegmentIndex].radius * 2);
+ }
+
+ // Apply Gravity and Resolve Collisions
+ for (let i = 0; i < ragdoll.segments.length; i++) {
+ let collidedWithFloor = false;
+
+ ragdoll.segments[i].x += gravity.x * dt;
+ ragdoll.segments[i].y += gravity.y * dt;
+
+ if (ragdoll.segments[i].y + ragdoll.segments[i].radius > canvas.height) {
+ ragdoll.segments[i].y = canvas.height - ragdoll.segments[i].radius;
+ ragdoll.segments[i].velocity_y = 0; // Stop downward velocity on collision
+ collidedWithFloor = true;
+ }
+
+ for (const platform of platforms) {
+ if (
+ ragdoll.segments[i].x + ragdoll.segments[i].radius > platform.x &&
+ ragdoll.segments[i].x - ragdoll.segments[i].radius < platform.x + platform.width &&
+ ragdoll.segments[i].y + ragdoll.segments[i].radius > platform.y &&
+ ragdoll.segments[i].y - ragdoll.segments[i].radius < platform.y + platform.height
+ ) {
+ if (!collidedWithFloor && ragdoll.segments[i].y + ragdoll.segments[i].radius > platform.y) {
+ ragdoll.segments[i].y = platform.y - ragdoll.segments[i].radius;
+ ragdoll.segments[i].velocity_y *= -0.5; // Reverse velocity slightly
+ }
+
+ if (ragdoll.segments[i].x + ragdoll.segments[i].radius > platform.x) {
+ ragdoll.segments[i].x = platform.x - ragdoll.segments[i].radius;
+ ragdoll.segments[i].velocity_x *= -0.5; // Reverse velocity slightly
+ }
+
+ if (ragdoll.segments[i].x - ragdoll.segments[i].radius < platform.x + platform.width) {
+ ragdoll.segments[i].x = platform.x + platform.width + ragdoll.segments[i].radius;
+ ragdoll.segments[i].velocity_x *= -0.5; // Reverse velocity slightly
+ }
+ }
+ }
+
+ if (!collidedWithFloor && ragdoll.segments[i].y < 0) {
+ ragdoll.segments[i].y = 2 - ragdoll.segments[i].radius;
+ ragdoll.segments[i].velocity_y *= -0.5; // Reverse velocity slightly
+ }
+ }
+
+ // Apply Joint Constraints (simplified)
+ for (let i = 1; i < ragdoll.segments.length; i++) {
+ const segment1 = ragdoll.segments[i - 1];
+ const segment2 = ragdoll.segments[i];
+
+ let dx = segment2.x - segment1.x;
+ let dy = segment2.y - segment1.y;
+
+ let distance_ = Math.sqrt(dx * dx + dy * dy);
+ let idealDistance = segment1.radius + segment2.radius;
+
+ dx /= distance_;
+ dy /= distance_;
+
+ segment2.x += (idealDistance - distance_) * dx * ragdoll.jointStiffness;
+ segment2.y += (idealDistance - distance_) * dy * ragdoll.jointStiffness;
+ }
+
+ // Damping
+ for (let i = 0; i < ragdoll.segments.length; i++) {
+ ragdoll.segments[i].x *= ragdoll.damping;
+ ragdoll.segments[i].y *= ragdoll.damping;
+ }
+
+ // Draw Ragdoll
+ ctx.beginPath();
+ ctx.strokeStyle = "black";
+ ctx.lineWidth = 2;
+
+ for (let i = 0; i < ragdoll.segments.length - 1; i++) {
+ ctx.moveTo(ragdoll.segments[i].x, ragdoll.segments[i].y);
+ ctx.lineTo(ragdoll.segments[i + 1].x, ragdoll.segments[i + 1].y);
+ }
+
+ ctx.stroke();
+
+ for (let i = 0; i < ragdoll.segments.length; i++) {
+ ctx.beginPath();
+ ctx.arc(ragdoll.segments[i].x, ragdoll.segments[i].y, ragdoll.segments[i].radius, 0, Math.PI * 2);
+ ctx.fillStyle = "red";
+ ctx.fill();
+ }
+
+ requestAnimationFrame(animate);
+}
+
+// Mouse Event Handlers
+canvas.addEventListener("mousedown", (e) => {
+ mouseX = e.clientX;
+ mouseY = e.clientY;
+
+ for (let i = 0; i < ragdoll.segments.length; i++) {
+ let distToSegment = distance(mouseX, mouseY, ragdoll.segments[i].x, ragdoll.segments[i].y);
+ if (distToSegment <= ragdoll.segments[i].radius) {
+ draggingSegmentIndex = i;
+ break;
+ }
+ }
+});
+
+canvas.addEventListener("mouseup", () => {
+ draggingSegmentIndex = -1;
+});
+
+canvas.addEventListener("mousemove", (e) => {
+ mouseX = e.clientX;
+ mouseY = e.clientY;
+});
+
+resetBtn.addEventListener("click", () => {
+ for (let i = 0; i < ragdoll.segments.length; i++) {
+ ragdoll.segments[i].x = 100 + i * 20;
+ ragdoll.segments[i].y = 200 + i * 10;
+ }
+});
+
+requestAnimationFrame(animate);
+</script>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/8443d9162963/hermes3-8b.png b/data/artifacts/8443d9162963/hermes3-8b.png
new file mode 100644
index 0000000..b88767a
Binary files /dev/null and b/data/artifacts/8443d9162963/hermes3-8b.png differ
diff --git a/data/artifacts/8443d9162963/hf-qwen-coder-32b.html b/data/artifacts/8443d9162963/hf-qwen-coder-32b.html
new file mode 100644
index 0000000..e0298aa
--- /dev/null
+++ b/data/artifacts/8443d9162963/hf-qwen-coder-32b.html
@@ -0,0 +1,214 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Ragdoll Physics Toy</title>
+<style>
+body { margin: 0; overflow: hidden; background-color: #eee; }
+canvas { display: block; background-color: #fff; }
+#controls { position: absolute; top: 10px; left: 10px; font-family: sans-serif; color: #333; }
+button { padding: 5px 10px; cursor: pointer; background-color: #ddd; border: none; border-radius: 4px; box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); }
+button:hover { background-color: #ccc; }
+</style>
+</head>
+<body>
+
+<div id="controls">
+ Drag to fling! <button id="resetBtn">Reset</button>
+</div>
+
+<canvas id="myCanvas"></canvas>
+
+<script>
+const canvas = document.getElementById("myCanvas");
+const ctx = canvas.getContext("2d");
+const resetBtn = document.getElementById("resetBtn");
+
+canvas.width = window.innerWidth;
+canvas.height = window.innerHeight;
+
+let ragdoll = {
+ segments: [
+ { x: 100, y: 200, mass: 1, radius: 15 }, // Head
+ { x: 80, y: 260, mass: 1, radius: 13 }, // Neck
+ { x: 60, y: 320, mass: 1.5, radius: 20 }, // Upper Torso
+ { x: 60, y: 370, mass: 2, radius: 25 }, // Lower Torso
+ { x: 45, y: 420, mass: 1.2, radius: 18 }, // Left Upper Arm
+ { x: 30, y: 460, mass: 1.0, radius: 15 }, // Left Lower Arm
+ { x: 75, y: 420, mass: 1.2, radius: 18 }, // Right Upper Arm
+ { x: 60, y: 460, mass: 1.0, radius: 15 }, // Right Lower Arm
+ { x: 60, y: 490, mass: 1.5, radius: 20 }, // Upper Leg
+ { x: 60, y: 530, mass: 1.2, radius: 18 } // Lower Leg
+ ],
+ jointStiffness: 0.97,
+ damping: 0.95,
+};
+
+let mouseX = 0;
+let mouseY = 0;
+let draggingSegmentIndex = -1;
+
+const platforms = [
+ { x: 0, y: canvas.height - 50, width: 200, height: 20 },
+ { x: canvas.width - 200, y: canvas.height - 80, width: 200, height: 20 },
+ { x: 100, y: canvas.height - 150, width: 100, height: 20 }
+];
+
+let gravity = { x: 0, y: 1 };
+
+function distance(x1, y1, x2, y2) {
+ return Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2);
+}
+
+function clamp(value, min, max) {
+ return Math.min(Math.max(value, min), max);
+}
+
+let lastTime = 0;
+
+function animate(timestamp) {
+ const dt = (timestamp - lastTime) / 1000;
+ lastTime = timestamp;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ // Handle Mouse Interaction
+ if (draggingSegmentIndex !== -1) {
+ ragdoll.segments[draggingSegmentIndex].x = mouseX;
+ ragdoll.segments[draggingSegmentIndex].y = mouseY;
+ }
+
+ // Apply Gravity and Resolve Collisions
+ for (let i = 0; i < ragdoll.segments.length; i++) {
+ const segment = ragdoll.segments[i];
+ segment.velocity_x = segment.velocity_x || 0;
+ segment.velocity_y = segment.velocity_y || 0;
+
+ segment.x += segment.velocity_x * dt;
+ segment.y += segment.velocity_y * dt;
+ segment.velocity_x *= ragdoll.damping;
+ segment.velocity_y *= ragdoll.damping;
+
+ segment.velocity_y += gravity.y * dt;
+
+ // Floor Collision
+ if (segment.y + segment.radius > canvas.height) {
+ segment.y = canvas.height - segment.radius;
+ segment.velocity_y *= -0.5; // Reverse velocity with damping
+ }
+
+ // Platform Collisions
+ for (const platform of platforms) {
+ if (segment.x + segment.radius > platform.x &&
+ segment.x - segment.radius < platform.x + platform.width &&
+ segment.y + segment.radius > platform.y &&
+ segment.y - segment.radius < platform.y + platform.height) {
+
+ // Simple collision response: Prevent penetration and reverse velocity
+ if (segment.y + segment.radius > platform.y) {
+ segment.y = platform.y - segment.radius;
+ segment.velocity_y *= -0.5; // Reverse velocity with damping
+ }
+ }
+ }
+
+ // Wall Collisions
+ if (segment.x - segment.radius < 0 || segment.x + segment.radius > canvas.width) {
+ segment.x = clamp(segment.x, segment.radius, canvas.width - segment.radius);
+ segment.velocity_x *= -0.5; // Reverse velocity with damping
+ }
+ }
+
+ // Apply Joint Constraints (simplified)
+ for (let i = 1; i < ragdoll.segments.length; i++) {
+ const segment1 = ragdoll.segments[i - 1];
+ const segment2 = ragdoll.segments[i];
+
+ const dx = segment2.x - segment1.x;
+ const dy = segment2.y - segment1.y;
+ const distance_ = Math.sqrt(dx * dx + dy * dy);
+ const idealDistance = (segment1.radius + segment2.radius) * 0.95;
+
+ if (distance_ > 0) {
+ const diff = distance_ - idealDistance;
+ const scaleX = 1 - ragdoll.jointStiffness * (diff / distance_);
+ segment2.x -= dx * (scaleX - 1);
+ segment2.y -= dy * (scaleX - 1);
+ }
+ }
+
+ // Draw Platforms
+ ctx.fillStyle = "#333";
+ for (const platform of platforms) {
+ ctx.fillRect(platform.x, platform.y, platform.width, platform.height);
+ }
+
+ // Draw Ragdoll
+ ctx.beginPath();
+ ctx.strokeStyle = "black";
+ ctx.lineWidth = 4;
+ for (let i = 0; i < ragdoll.segments.length - 1; i++) {
+ const segment1 = ragdoll.segments[i];
+ const segment2 = ragdoll.segments[i + 1];
+ ctx.moveTo(segment1.x, segment1.y);
+ ctx.lineTo(segment2.x, segment2.y);
+ }
+ ctx.stroke();
+
+ ctx.fillStyle = "red";
+ for (let i = 0; i < ragdoll.segments.length; i++) {
+ const segment = ragdoll.segments[i];
+ ctx.beginPath();
+ ctx.arc(segment.x, segment.y, segment.radius, 0, Math.PI * 2);
+ ctx.fill();
+ }
+
+ requestAnimationFrame(animate);
+}
+
+// Mouse Event Handlers
+canvas.addEventListener("mousedown", (e) => {
+ mouseX = e.clientX;
+ mouseY = e.clientY;
+
+ for (let i = 0; i < ragdoll.segments.length; i++) {
+ const segment = ragdoll.segments[i];
+ const distToSegment = distance(mouseX, mouseY, segment.x, segment.y);
+ if (distToSegment <= segment.radius) {
+ draggingSegmentIndex = i;
+ break;
+ }
+ }
+});
+
+canvas.addEventListener("mouseup", () => {
+ draggingSegmentIndex = -1;
+});
+
+canvas.addEventListener("mousemove", (e) => {
+ mouseX = e.clientX;
+ mouseY = e.clientY;
+});
+
+resetBtn.addEventListener("click", () => {
+ ragdoll.segments[0].x = 100; ragdoll.segments[0].y = 200;
+ ragdoll.segments[1].x = 80; ragdoll.segments[1].y = 260;
+ ragdoll.segments[2].x = 60; ragdoll.segments[2].y = 320;
+ ragdoll.segments[3].x = 60; ragdoll.segments[3].y = 370;
+ ragdoll.segments[4].x = 45; ragdoll.segments[4].y = 420;
+ ragdoll.segments[5].x = 30; ragdoll.segments[5].y = 460;
+ ragdoll.segments[6].x = 75; ragdoll.segments[6].y = 420;
+ ragdoll.segments[7].x = 60; ragdoll.segments[7].y = 460;
+ ragdoll.segments[8].x = 60; ragdoll.segments[8].y = 490;
+ ragdoll.segments[9].x = 60; ragdoll.segments[9].y = 530;
+
+ for (let segment of ragdoll.segments) {
+ segment.velocity_x = 0;
+ segment.velocity_y = 0;
+ }
+});
+
+requestAnimationFrame(animate);
+</script>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/8443d9162963/hf-qwen-coder-32b.png b/data/artifacts/8443d9162963/hf-qwen-coder-32b.png
new file mode 100644
index 0000000..84f4cf3
Binary files /dev/null and b/data/artifacts/8443d9162963/hf-qwen-coder-32b.png differ
diff --git a/data/artifacts/8443d9162963/qwen3-14b.html b/data/artifacts/8443d9162963/qwen3-14b.html
new file mode 100644
index 0000000..0865de9
--- /dev/null
+++ b/data/artifacts/8443d9162963/qwen3-14b.html
@@ -0,0 +1,250 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Interactive Ragdoll Physics Toy</title>
+ <style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ background-color: #1e1e1e;
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ }
+ canvas {
+ display: block;
+ background-color: #2e2e2e;
+ }
+ #controls {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ color: #fff;
+ z-index: 10;
+ font-size: 14px;
+ user-select: none;
+ }
+ #resetBtn {
+ margin-left: 10px;
+ padding: 6px 12px;
+ background-color: #444;
+ color: #fff;
+ border: 1px solid #666;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: background-color 0.2s, border-color 0.2s;
+ }
+ #resetBtn:hover {
+ background-color: #555;
+ border-color: #777;
+ }
+ </style>
+</head>
+<body>
+ <div id="controls">
+ Click and drag to fling! <button id="resetBtn">Reset</button>
+ </div>
+ <canvas id="myCanvas"></canvas>
+ <script>
+ const canvas = document.getElementById("myCanvas");
+ const ctx = canvas.getContext("2d");
+ const resetBtn = document.getElementById("resetBtn");
+
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+
+ // Ragdoll configuration
+ const ragdoll = {
+ segments: [
+ { x: 100, y: 200, mass: 1, radius: 10, vx: 0, vy: 0 }, // Head
+ { x: 80, y: 250, mass: 1, radius: 12, vx: 0, vy: 0 }, // Neck
+ { x: 60, y: 300, mass: 1.5, radius: 15, vx: 0, vy: 0 }, // Upper Torso
+ { x: 60, y: 340, mass: 2, radius: 20, vx: 0, vy: 0 }, // Lower Torso
+ { x: 60, y: 380, mass: 1.5, radius: 15, vx: 0, vy: 0 }, // Upper Leg
+ { x: 60, y: 420, mass: 1.2, radius: 12, vx: 0, vy: 0 }, // Lower Leg
+ ],
+ jointStiffness: 0.98,
+ damping: 0.95,
+ jointLengths: [
+ 30, 40, 40, 40, 40
+ ]
+ };
+
+ const platforms = [
+ { x: 0, y: canvas.height - 50, width: 200, height: 20 },
+ { x: canvas.width - 200, y: canvas.height - 80, width: 200, height: 20 },
+ { x: 100, y: canvas.height - 150, width: 100, height: 20 },
+ ];
+
+ const gravity = { x: 0, y: 0.5 };
+
+ let mouseX = 0;
+ let mouseY = 0;
+ let draggingSegmentIndex = -1;
+
+ function distance(x1, y1, x2, y2) {
+ return Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2);
+ }
+
+ function clamp(value, min, max) {
+ return Math.min(Math.max(value, min), max);
+ }
+
+ let lastTime = 0;
+
+ function animate(timestamp) {
+ const dt = (timestamp - lastTime) / 1000;
+ lastTime = timestamp;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ // Apply gravity and update velocity
+ for (let i = 0; i < ragdoll.segments.length; i++) {
+ ragdoll.segments[i].vy += gravity.y * dt;
+ ragdoll.segments[i].x += ragdoll.segments[i].vx * dt;
+ ragdoll.segments[i].y += ragdoll.segments[i].vy * dt;
+ }
+
+ // Collision resolution
+ for (let i = 0; i < ragdoll.segments.length; i++) {
+ const segment = ragdoll.segments[i];
+
+ // Floor collision
+ if (segment.y + segment.radius > canvas.height) {
+ segment.y = canvas.height - segment.radius;
+ segment.vy *= -0.5; // Bounce
+ }
+
+ // Platform collision
+ for (const platform of platforms) {
+ const px = segment.x;
+ const py = segment.y;
+ const pr = segment.radius;
+
+ const platLeft = platform.x;
+ const platRight = platform.x + platform.width;
+ const platTop = platform.y;
+ const platBottom = platform.y + platform.height;
+
+ if (
+ px + pr >= platLeft &&
+ px - pr <= platRight &&
+ py + pr >= platTop &&
+ py - pr <= platBottom
+ ) {
+ // Check bottom collision
+ if (py + pr > platTop) {
+ segment.y = platTop - pr;
+ segment.vy *= -0.5;
+ }
+
+ // Check top collision
+ if (py - pr < platBottom) {
+ segment.y = platBottom + pr;
+ segment.vy *= -0.5;
+ }
+
+ // Check left collision
+ if (px - pr < platRight) {
+ segment.x = platRight + pr;
+ segment.vx *= -0.5;
+ }
+
+ // Check right collision
+ if (px + pr > platLeft) {
+ segment.x = platLeft - pr;
+ segment.vx *= -0.5;
+ }
+ }
+ }
+ }
+
+ // Apply joint constraints
+ for (let i = 0; i < ragdoll.jointLengths.length; i++) {
+ const segment1 = ragdoll.segments[i];
+ const segment2 = ragdoll.segments[i + 1];
+ const idealDistance = ragdoll.jointLengths[i];
+
+ const dx = segment2.x - segment1.x;
+ const dy = segment2.y - segment1.y;
+ const dist = Math.sqrt(dx ** 2 + dy ** 2);
+
+ if (dist < idealDistance * 0.5) continue;
+
+ const correctionFactor = (idealDistance - dist) / dist * 0.5;
+ segment2.x += dx * correctionFactor * ragdoll.jointStiffness;
+ segment2.y += dy * correctionFactor * ragdoll.jointStiffness;
+ }
+
+ // Apply damping
+ for (let i = 0; i < ragdoll.segments.length; i++) {
+ ragdoll.segments[i].vx *= ragdoll.damping;
+ ragdoll.segments[i].vy *= ragdoll.damping;
+ }
+
+ // Draw platforms
+ for (const platform of platforms) {
+ ctx.fillStyle = "#444";
+ ctx.fillRect(platform.x, platform.y, platform.width, platform.height);
+ }
+
+ // Draw ragdoll
+ ctx.strokeStyle = "#fff";
+ ctx.lineWidth = 3;
+
+ for (let i = 0; i < ragdoll.segments.length - 1; i++) {
+ const seg1 = ragdoll.segments[i];
+ const seg2 = ragdoll.segments[i + 1];
+ ctx.beginPath();
+ ctx.moveTo(seg1.x, seg1.y);
+ ctx.lineTo(seg2.x, seg2.y);
+ ctx.stroke();
+ }
+
+ ctx.fillStyle = "#00f";
+ for (const seg of ragdoll.segments) {
+ ctx.beginPath();
+ ctx.arc(seg.x, seg.y, seg.radius, 0, Math.PI * 2);
+ ctx.fill();
+ }
+
+ requestAnimationFrame(animate);
+ }
+
+ // Mouse interaction
+ canvas.addEventListener("mousedown", (e) => {
+ mouseX = e.clientX;
+ mouseY = e.clientY;
+
+ for (let i = 0; i < ragdoll.segments.length; i++) {
+ const seg = ragdoll.segments[i];
+ if (distance(mouseX, mouseY, seg.x, seg.y) <= seg.radius) {
+ draggingSegmentIndex = i;
+ break;
+ }
+ }
+ });
+
+ canvas.addEventListener("mouseup", () => {
+ draggingSegmentIndex = -1;
+ });
+
+ canvas.addEventListener("mousemove", (e) => {
+ mouseX = e.clientX;
+ mouseY = e.clientY;
+ });
+
+ // Reset button
+ resetBtn.addEventListener("click", () => {
+ for (let i = 0; i < ragdoll.segments.length; i++) {
+ ragdoll.segments[i].x = 100 + i * 20;
+ ragdoll.segments[i].y = 200 + i * 10;
+ ragdoll.segments[i].vx = 0;
+ ragdoll.segments[i].vy = 0;
+ }
+ });
+
+ // Start animation
+ requestAnimationFrame(animate);
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/8443d9162963/qwen3-14b.png b/data/artifacts/8443d9162963/qwen3-14b.png
new file mode 100644
index 0000000..7537e97
Binary files /dev/null and b/data/artifacts/8443d9162963/qwen3-14b.png differ
diff --git a/data/artifacts/987bed245bf6/hf-qwen-coder-32b.html b/data/artifacts/987bed245bf6/hf-qwen-coder-32b.html
new file mode 100644
index 0000000..2f0300b
--- /dev/null
+++ b/data/artifacts/987bed245bf6/hf-qwen-coder-32b.html
@@ -0,0 +1,246 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Tiny Grid Roguelike</title>
+ <style>
+ body {
+ font-family: 'Courier New', monospace;
+ background-color: #000;
+ color: #fff;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ }
+
+ canvas {
+ border: 2px solid #444;
+ background-color: #111;
+ }
+
+ .hud {
+ position: absolute;
+ bottom: 5px;
+ left: 5px;
+ background-color: rgba(0, 0, 0, 0.8);
+ padding: 5px;
+ border-radius: 5px;
+ font-size: 14px;
+ }
+
+ .game-over {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(255, 0, 0, 0.8);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 36px;
+ color: #fff;
+ }
+ </style>
+</head>
+<body>
+ <canvas id="game"></canvas>
+ <div class="hud">
+ HP: <span id="hp">100</span><br>
+ Level: <span id="level">1</span><br>
+ Gold: <span id="gold">0</span>
+ </div>
+
+ <div class="game-over" style="display:none;">
+ Game Over! Your final stats were:
+ <br>
+ HP: <span id="finalHp"></span>
+ <br>
+ Level: <span id="finalLevel"></span>
+ <br>
+ Gold: <span id="finalGold"></span>
+ </div>
+
+ <script>
+ const canvas = document.getElementById('game');
+ const ctx = canvas.getContext('2d');
+
+ const hero = { x: 0, y: 0, hp: 100, gold: 0, level: 1 };
+ let mapWidth = 30;
+ let mapHeight = 20;
+ let cellSize = 20;
+ let map = generateMap(mapWidth, mapHeight);
+ let gameOver = false;
+ let goblins = [];
+ let stairsX, stairsY;
+
+ canvas.width = mapWidth * cellSize;
+ canvas.height = mapHeight * cellSize;
+
+ function generateMap(width, height) {
+ let map = Array.from({ length: height }, () => Array(width).fill('.'));
+
+ for (let i = 0; i < 5; i++) {
+ let roomX = Math.floor(Math.random() * (width - 6)) + 1;
+ let roomY = Math.floor(Math.random() * (height - 4)) + 1;
+ for (let y = roomY; y < roomY + 3; y++) {
+ for (let x = roomX; x < roomX + 5; x++) {
+ map[y][x] = ' ';
+ }
+ }
+ }
+
+ connectRooms(map, width, height);
+
+ stairsX = Math.floor(Math.random() * (width - 2)) + 1;
+ stairsY = Math.floor(Math.random() * (height - 2)) + 1;
+ map[stairsY][stairsX] = '>';
+
+ hero.x = Math.floor(width / 2);
+ hero.y = Math.floor(height / 2);
+
+ return map;
+ }
+
+ function connectRooms(map, width, height) {
+ for (let y = 0; y < height - 1; y++) {
+ for (let x = 0; x < width - 1; x++) {
+ if (map[y][x] === ' ' && map[y + 1][x] !== ' ' && Math.random() > 0.5) {
+ map[y + 1][x] = '#';
+ }
+ if (map[y][x] === ' ' && map[y][x + 1] !== ' ' && Math.random() > 0.5) {
+ map[y][x + 1] = '#';
+ }
+ }
+ }
+ }
+
+ function drawMap() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ for (let y = 0; y < mapHeight; y++) {
+ for (let x = 0; x < mapWidth; x++) {
+ let cell = map[y][x];
+ if (cell === '@') {
+ ctx.fillStyle = '#1ff';
+ } else if (cell === ' ') {
+ ctx.fillStyle = '#444';
+ } else if (cell === '#') {
+ ctx.fillStyle = '#00f';
+ } else if (cell === 'g') {
+ ctx.fillStyle = '#f50';
+ } else if (cell === '>') {
+ ctx.fillStyle = '#0d0';
+ } else {
+ ctx.fillStyle = '#222';
+ }
+ ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
+ ctx.fillStyle = '#fff';
+ ctx.fillText(cell, x * cellSize + 5, y * cellSize + 15);
+ }
+ }
+
+ updateHUD();
+ }
+
+ function updateHUD() {
+ document.getElementById('hp').textContent = hero.hp;
+ document.getElementById('level').textContent = hero.level;
+ document.getElementById('gold').textContent = hero.gold;
+ }
+
+ function moveHero(event) {
+ const key = event.key;
+ let dx = 0, dy = 0;
+
+ switch (key) {
+ case 'ArrowUp': dy = -1; break;
+ case 'ArrowDown': dy = 1; break;
+ case 'ArrowLeft': dx = -1; break;
+ case 'ArrowRight': dx = 1; break;
+ }
+
+ if (!gameOver) {
+ let newHeroX = hero.x + dx;
+ let newHeroY = hero.y + dy;
+
+ if (map[newHeroY] && map[newHeroY][newHeroX] !== '#') {
+ switch (map[newHeroY][newHeroX]) {
+ case 'g':
+ fightGoblin(newHeroX, newHeroY);
+ break;
+ case '>':
+ stairsLevel++;
+ hero.level = stairsLevel + 1;
+ hero.hp += 5 * (stairsLevel + 1);
+ hero.gold *= 2;
+ map = generateMap(mapWidth, mapHeight);
+ drawMap();
+ return;
+ }
+
+ if (!gameOver) {
+ map[hero.y][hero.x] = ' ';
+ map[newHeroY][newHeroX] = '@';
+
+ hero.x = newHeroX;
+ hero.y = newHeroY;
+
+ drawMap();
+ }
+ }
+ }
+
+ event.preventDefault();
+ }
+
+ function fightGoblin(x, y) {
+ if (!gameOver) {
+ let damage = Math.floor(Math.random() * 5) + 1;
+
+ hero.hp -= damage;
+
+ if (hero.hp <= 0) {
+ document.getElementById('finalHp').textContent = 0;
+ document.getElementById('finalLevel').textContent = hero.level;
+ document.getElementById('finalGold').textContent = hero.gold;
+ gameOver = true;
+ document.querySelector('.game-over').style.display = 'flex';
+ return;
+ }
+
+ hero.gold += 10;
+
+ map[y][x] = ' ';
+ goblins = goblins.filter(goblin => !(goblin.x === x && goblin.y === y));
+
+ updateHUD();
+ }
+ }
+
+ function startGame() {
+ drawMap();
+
+ for (let i = 0; i < 5; i++) {
+ let x, y;
+ do {
+ x = Math.floor(Math.random() * mapWidth);
+ y = Math.floor(Math.random() * mapHeight);
+ } while (map[y][x] !== ' ');
+
+ goblins.push({ x: x, y: y });
+ map[y][x] = 'g';
+ }
+
+ updateHUD();
+ }
+
+ window.addEventListener('keydown', moveHero);
+ startGame();
+
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/987bed245bf6/hf-qwen-coder-32b.png b/data/artifacts/987bed245bf6/hf-qwen-coder-32b.png
new file mode 100644
index 0000000..27062b7
Binary files /dev/null and b/data/artifacts/987bed245bf6/hf-qwen-coder-32b.png differ
diff --git a/data/artifacts/b3721d9fe563/claude-code.html b/data/artifacts/b3721d9fe563/claude-code.html
new file mode 100644
index 0000000..4c7888c
--- /dev/null
+++ b/data/artifacts/b3721d9fe563/claude-code.html
@@ -0,0 +1,591 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+<title>Maison Aurelle — Shop by Color</title>
+<style>
+ :root{
+ --bg:#0d0d0f; --surface:#17171c; --text:#f5f2ea; --muted:#8a8578;
+ --accent:#c9a961; --accent2:#7d6a45;
+ --radius:14px;
+ --card-shadow:0 8px 24px rgba(0,0,0,.35);
+ --hover-shadow:0 16px 48px rgba(0,0,0,.5);
+ --glow:0 0 24px rgba(201,169,97,.35);
+ --serif:'Didot','Bodoni MT','Playfair Display',Georgia,serif;
+ --sans:-apple-system,'Segoe UI','Helvetica Neue',Arial,sans-serif;
+ }
+ *{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent;}
+ html,body{height:100%;}
+ body{
+ background:var(--bg); color:var(--text);
+ font-family:var(--sans);
+ min-height:100vh; display:flex; align-items:center; justify-content:center;
+ padding:24px 16px; overflow-x:hidden;
+ position:relative;
+ }
+ /* ambient drift */
+ body::before{
+ content:''; position:fixed; inset:-30%; z-index:0; pointer-events:none;
+ background:
+ radial-gradient(circle at 25% 30%, rgba(201,169,97,.10), transparent 45%),
+ radial-gradient(circle at 78% 72%, rgba(125,106,69,.12), transparent 50%);
+ animation:drift 18s ease-in-out infinite alternate;
+ }
+ @keyframes drift{
+ from{transform:translate3d(-2%,-1%,0) scale(1.02);}
+ to{transform:translate3d(3%,2%,0) scale(1.08);}
+ }
+ .post{
+ position:relative; z-index:1;
+ width:100%; max-width:440px;
+ background:var(--surface);
+ border-radius:20px;
+ border:1px solid rgba(201,169,97,.14);
+ box-shadow:var(--card-shadow), inset 0 1px 0 rgba(255,255,255,.03);
+ overflow:hidden;
+ animation:fadeUp .7s cubic-bezier(.22,1,.36,1) both;
+ }
+ @keyframes fadeUp{from{opacity:0;transform:translateY(28px);}to{opacity:1;transform:none;}}
+
+ /* header */
+ header{
+ padding:24px 24px 16px; text-align:center;
+ border-bottom:1px solid rgba(255,255,255,.05);
+ }
+ .eyebrow{
+ font-size:12px; letter-spacing:.28em; text-transform:uppercase;
+ color:var(--muted); margin-bottom:10px;
+ }
+ .wordmark{
+ font-family:var(--serif); font-weight:500;
+ font-size:32px; letter-spacing:.02em; line-height:1;
+ color:var(--text);
+ }
+ .wordmark .amp{color:var(--accent); font-style:italic;}
+ .tag{
+ margin-top:8px; font-size:12px; letter-spacing:.15em; text-transform:uppercase;
+ color:var(--muted);
+ }
+
+ /* wheel section */
+ .wheel-wrap{
+ padding:28px 24px 8px;
+ display:flex; flex-direction:column; align-items:center;
+ }
+ .wheel-hint{
+ font-size:12px; letter-spacing:.15em; text-transform:uppercase;
+ color:var(--muted); margin-bottom:18px;
+ }
+ .wheel-stage{
+ position:relative; width:236px; height:236px;
+ touch-action:none;
+ }
+ .wheel{
+ position:absolute; inset:0; border-radius:50%;
+ cursor:grab;
+ box-shadow:0 10px 40px rgba(0,0,0,.5), inset 0 0 0 1px rgba(255,255,255,.04);
+ transition:transform .05s linear;
+ }
+ .wheel:active{cursor:grabbing;}
+ .wheel svg{display:block; width:100%; height:100%; border-radius:50%;}
+ .wheel-seg{
+ stroke:#17171c; stroke-width:1.5;
+ transition:opacity .2s ease;
+ }
+ /* inner hub */
+ .hub{
+ position:absolute; top:50%; left:50%;
+ width:96px; height:96px; margin:-48px 0 0 -48px;
+ border-radius:50%;
+ background:radial-gradient(circle at 50% 40%, #1c1c22, #101014);
+ box-shadow:inset 0 0 0 1px rgba(201,169,97,.18), 0 6px 18px rgba(0,0,0,.55);
+ display:flex; flex-direction:column; align-items:center; justify-content:center;
+ text-align:center; pointer-events:none; z-index:3;
+ }
+ .hub .swatch{
+ width:30px; height:30px; border-radius:50%;
+ box-shadow:var(--glow), inset 0 0 0 1px rgba(255,255,255,.15);
+ transition:background .3s ease, box-shadow .3s ease;
+ margin-bottom:7px;
+ }
+ .hub .hub-label{
+ font-size:9.5px; letter-spacing:.14em; text-transform:uppercase;
+ color:var(--muted);
+ }
+ /* pointer */
+ .pointer{
+ position:absolute; top:-6px; left:50%; margin-left:-9px;
+ width:0;height:0; z-index:4;
+ border-left:9px solid transparent; border-right:9px solid transparent;
+ border-top:15px solid var(--accent);
+ filter:drop-shadow(0 2px 4px rgba(0,0,0,.5));
+ }
+
+ /* selected color name */
+ .color-name{
+ margin-top:22px; text-align:center; min-height:52px;
+ }
+ .color-name .cn-title{
+ font-family:var(--serif); font-size:26px; line-height:1.05;
+ color:var(--text);
+ animation:fadeUp .5s cubic-bezier(.22,1,.36,1) both;
+ }
+ .color-name .cn-code{
+ font-size:12px; letter-spacing:.18em; text-transform:uppercase;
+ color:var(--accent); margin-top:6px;
+ }
+
+ /* filmstrip */
+ .strip-region{padding:8px 0 20px;}
+ .strip-label{
+ font-size:12px; letter-spacing:.15em; text-transform:uppercase;
+ color:var(--muted); text-align:center; margin-bottom:14px;
+ }
+ .filmstrip{
+ display:flex; gap:14px; overflow-x:auto; scroll-snap-type:x mandatory;
+ padding:4px 24px 14px; -webkit-overflow-scrolling:touch;
+ scrollbar-width:none;
+ }
+ .filmstrip::-webkit-scrollbar{display:none;}
+ .swatch-card{
+ flex:0 0 132px; scroll-snap-align:center;
+ background:#101014; border-radius:var(--radius);
+ border:1px solid rgba(255,255,255,.05);
+ overflow:hidden; box-shadow:var(--card-shadow);
+ transition:transform .25s cubic-bezier(.22,1,.36,1), box-shadow .25s ease;
+ }
+ .swatch-card:hover{transform:translateY(-4px); box-shadow:var(--hover-shadow);}
+ .pattern{
+ height:132px; width:100%;
+ transition:background .4s ease;
+ }
+ .swatch-card .meta{padding:11px 12px 13px;}
+ .swatch-card .pname{
+ font-family:var(--serif); font-size:15px; color:var(--text);
+ line-height:1.15; margin-bottom:4px;
+ }
+ .swatch-card .ptype{
+ font-size:10px; letter-spacing:.14em; text-transform:uppercase; color:var(--muted);
+ }
+ .strip-anim > *{animation:cardIn .6s cubic-bezier(.22,1,.36,1) both;}
+ .strip-anim > *:nth-child(1){animation-delay:.00s;}
+ .strip-anim > *:nth-child(2){animation-delay:.09s;}
+ .strip-anim > *:nth-child(3){animation-delay:.18s;}
+ .strip-anim > *:nth-child(4){animation-delay:.27s;}
+ @keyframes cardIn{from{opacity:0;transform:translateY(20px);}to{opacity:1;transform:none;}}
+
+ /* share bar */
+ .sharebar{
+ display:flex; align-items:center; justify-content:space-between;
+ padding:16px 24px 22px; border-top:1px solid rgba(255,255,255,.05);
+ }
+ .share-actions{display:flex; gap:12px;}
+ .sbtn{
+ width:40px; height:40px; border-radius:50%;
+ background:#101014; border:1px solid rgba(201,169,97,.16);
+ display:flex; align-items:center; justify-content:center;
+ cursor:pointer; color:var(--muted);
+ transition:transform .18s ease, color .18s ease, box-shadow .18s ease, border-color .18s ease;
+ }
+ .sbtn:hover{color:var(--accent); border-color:rgba(201,169,97,.5); transform:translateY(-2px); box-shadow:var(--glow);}
+ .sbtn:active{transform:scale(.92);}
+ .sbtn svg{width:18px;height:18px;fill:currentColor;}
+ .savebtn{
+ font-family:var(--sans); font-size:11px; letter-spacing:.16em; text-transform:uppercase;
+ color:var(--bg); background:var(--accent);
+ border:none; border-radius:999px; padding:12px 20px; cursor:pointer;
+ transition:transform .18s ease, box-shadow .18s ease, background .2s ease;
+ }
+ .savebtn:hover{transform:translateY(-2px); box-shadow:var(--glow); background:#d8b872;}
+ .savebtn:active{transform:scale(.96);}
+
+ .toast{
+ position:fixed; left:50%; bottom:26px; transform:translateX(-50%) translateY(20px);
+ background:var(--accent); color:var(--bg);
+ font-size:12px; letter-spacing:.12em; text-transform:uppercase; font-weight:600;
+ padding:11px 20px; border-radius:999px; z-index:50;
+ opacity:0; pointer-events:none; transition:all .35s cubic-bezier(.22,1,.36,1);
+ box-shadow:var(--hover-shadow);
+ }
+ .toast.show{opacity:1; transform:translateX(-50%) translateY(0);}
+</style>
+</head>
+<body>
+ <div class="post">
+ <header>
+ <div class="eyebrow">Wallcoverings</div>
+ <div class="wordmark">Maison <span class="amp">&</span> Aurelle</div>
+ <div class="tag">Shop by Color</div>
+ </header>
+
+ <div class="wheel-wrap">
+ <div class="wheel-hint">Drag the wheel · tap a hue</div>
+ <div class="wheel-stage">
+ <div class="pointer"></div>
+ <div class="wheel" id="wheel">
+ <svg id="wheelSvg" viewBox="0 0 200 200" aria-label="Color wheel"></svg>
+ </div>
+ <div class="hub">
+ <div class="swatch" id="hubSwatch"></div>
+ <div class="hub-label">Selected</div>
+ </div>
+ </div>
+
+ <div class="color-name">
+ <div class="cn-title" id="cnTitle">Celadon Mist</div>
+ <div class="cn-code" id="cnCode">Hue 01 · Sage Family</div>
+ </div>
+ </div>
+
+ <div class="strip-region">
+ <div class="strip-label">In this color family</div>
+ <div class="filmstrip" id="filmstrip"></div>
+ </div>
+
+ <div class="sharebar">
+ <div class="share-actions">
+ <div class="sbtn" data-share="link" title="Copy link" role="button" aria-label="Copy link">
+ <svg viewBox="0 0 24 24"><path d="M3.9 12a3.1 3.1 0 0 1 3.1-3.1h4V7h-4a5 5 0 0 0 0 10h4v-1.9h-4A3.1 3.1 0 0 1 3.9 12zm5.1 1h6v-2H9v2zm4-6v1.9h4A3.1 3.1 0 0 1 17 13.1h-4V15h4a5 5 0 0 0 0-10h-4z"/></svg>
+ </div>
+ <div class="sbtn" data-share="pin" title="Pin it" role="button" aria-label="Pin it">
+ <svg viewBox="0 0 24 24"><path d="M12 2a10 10 0 0 0-3.6 19.3c-.1-.8-.2-2 0-2.9l1.2-5s-.3-.6-.3-1.5c0-1.4.8-2.4 1.8-2.4.9 0 1.3.6 1.3 1.4 0 .9-.5 2.1-.8 3.3-.2 1 .5 1.8 1.5 1.8 1.8 0 3.1-2.3 3.1-5 0-2.1-1.4-3.6-3.9-3.6-2.8 0-4.5 2.1-4.5 4.3 0 .8.2 1.4.6 1.8.2.2.2.3.1.5l-.2.9c-.1.3-.3.4-.6.2-1.1-.5-1.7-1.9-1.7-3.1 0-2.5 2.1-5.5 6.3-5.5 3.4 0 5.6 2.4 5.6 5 0 3.5-1.9 6.1-4.8 6.1-1 0-1.9-.5-2.2-1.1l-.6 2.4c-.2.8-.7 1.7-1 2.3A10 10 0 1 0 12 2z"/></svg>
+ </div>
+ <div class="sbtn" data-share="x" title="Share on X" role="button" aria-label="Share on X">
+ <svg viewBox="0 0 24 24"><path d="M18.9 3H21l-6.5 7.5L22 21h-6l-4.7-6.2L5.9 21H3.8l7-8L2.5 3h6.1l4.2 5.6L18.9 3zm-2.1 16.2h1.2L7.3 4.7H6l10.8 14.5z"/></svg>
+ </div>
+ </div>
+ <button class="savebtn" id="saveBtn">Save Palette</button>
+ </div>
+ </div>
+
+ <div class="toast" id="toast">Copied</div>
+
+<script>
+(function(){
+ // ---- Color family data ---------------------------------------------------
+ const families = [
+ { key:'sage', name:'Celadon Mist', fam:'Sage', hue:145, base:'#8fa98a',
+ patterns:[
+ {n:'Verdance Damask', t:'Damask', c:['#7f9a7a','#a9c0a2']},
+ {n:'Fern Trellis', t:'Trellis', c:['#6f8c6a','#9db797']},
+ {n:'Willow Stripe', t:'Stripe', c:['#88a382','#c3d4bd']},
+ {n:'Moss Grasscloth', t:'Texture', c:['#728a6c','#93aa8b']}
+ ]},
+ { key:'teal', name:'Deauville Teal', fam:'Teal', hue:180, base:'#5f9a9c',
+ patterns:[
+ {n:'Lagoon Ogee', t:'Ogee', c:['#4f8a8c','#7fb3b4']},
+ {n:'Reef Diamond', t:'Trellis', c:['#3f7c7e','#6fa8aa']},
+ {n:'Marseille Stripe',t:'Stripe', c:['#5f9a9c','#a3cccd']},
+ {n:'Tidewater Weave', t:'Texture', c:['#4a8082','#6c9c9e']}
+ ]},
+ { key:'indigo', name:'Nocturne Indigo',fam:'Indigo', hue:220, base:'#5a6ba8',
+ patterns:[
+ {n:'Empire Damask', t:'Damask', c:['#4c5c98','#8090c8']},
+ {n:'Cobalt Lattice', t:'Trellis', c:['#3d4e88','#6c7cb8']},
+ {n:'Regency Stripe', t:'Stripe', c:['#5a6ba8','#9aa8d4']},
+ {n:'Midnight Linen', t:'Texture', c:['#48568c','#6674ac']}
+ ]},
+ { key:'plum', name:'Byzantine Plum', fam:'Plum', hue:285, base:'#8a5f9c',
+ patterns:[
+ {n:'Orchid Arabesque',t:'Arabesque',c:['#7a4f8c','#ad86bd']},
+ {n:'Amethyst Ogee', t:'Ogee', c:['#6c4080','#9c6faf']},
+ {n:'Vineyard Stripe', t:'Stripe', c:['#8a5f9c','#bfa0cc']},
+ {n:'Mulberry Suede', t:'Texture', c:['#704f80','#8c6a9c']}
+ ]},
+ { key:'rouge', name:'Cardinal Rouge', fam:'Rouge', hue:355, base:'#b0524e',
+ patterns:[
+ {n:'Florentine Toile',t:'Toile', c:['#a0423e','#cf827e']},
+ {n:'Ruby Medallion', t:'Medallion',c:['#94342f','#c06661']},
+ {n:'Carmine Stripe', t:'Stripe', c:['#b0524e','#d99793']},
+ {n:'Garnet Bouclé', t:'Texture', c:['#96423d','#b4645f']}
+ ]},
+ { key:'terra', name:'Tuscan Terracotta',fam:'Terracotta',hue:20, base:'#c07a4b',
+ patterns:[
+ {n:'Amphora Damask', t:'Damask', c:['#b06a3b','#d99f74']},
+ {n:'Sienna Trellis', t:'Trellis', c:['#a05c30','#cc8a5d']},
+ {n:'Umbria Stripe', t:'Stripe', c:['#c07a4b','#e0ab84']},
+ {n:'Clay Grasscloth', t:'Texture', c:['#a86538','#c4855a']}
+ ]},
+ { key:'gold', name:'Byzance Gold', fam:'Gold', hue:45, base:'#c9a961',
+ patterns:[
+ {n:'Versailles Damask',t:'Damask', c:['#bd9a4c','#e0c98a']},
+ {n:'Gilt Arabesque', t:'Arabesque',c:['#b08e3f','#d9bd72']},
+ {n:'Champagne Stripe',t:'Stripe', c:['#c9a961','#e6d3a0']},
+ {n:'Raffia Weave', t:'Texture', c:['#b5964a','#cdb066']}
+ ]},
+ { key:'ochre', name:'Amber Ochre', fam:'Ochre', hue:38, base:'#cf9440',
+ patterns:[
+ {n:'Saffron Ogee', t:'Ogee', c:['#c48432','#e2b672']},
+ {n:'Marigold Lattice',t:'Trellis', c:['#b6772a','#d9a55e']},
+ {n:'Honey Stripe', t:'Stripe', c:['#cf9440','#ecc684']},
+ {n:'Cork Texture', t:'Texture', c:['#bb8236','#d29e54']}
+ ]},
+ { key:'stone', name:'Alabaster Stone',fam:'Stone', hue:40, base:'#b8ac97',
+ patterns:[
+ {n:'Greige Damask', t:'Damask', c:['#a89c87','#d0c6b3']},
+ {n:'Oatmeal Trellis', t:'Trellis', c:['#9a8e79','#c2b8a3']},
+ {n:'Chalk Stripe', t:'Stripe', c:['#b8ac97','#dcd4c4']},
+ {n:'Linen Grasscloth',t:'Texture', c:['#a89c87','#c0b6a1']}
+ ]},
+ { key:'noir', name:'Obsidian Noir', fam:'Noir', hue:250, base:'#3a3a42',
+ patterns:[
+ {n:'Onyx Damask', t:'Damask', c:['#2e2e36','#565663']},
+ {n:'Ebony Arabesque', t:'Arabesque',c:['#26262c','#4a4a56']},
+ {n:'Slate Stripe', t:'Stripe', c:['#3a3a42','#61616e']},
+ {n:'Char Bouclé', t:'Texture', c:['#2c2c33','#48484f']}
+ ]},
+ { key:'rose', name:'Blush Quartz', fam:'Rose', hue:340, base:'#c98a94',
+ patterns:[
+ {n:'Peony Toile', t:'Toile', c:['#bd7a84','#e0aeb5']},
+ {n:'Dusty Medallion', t:'Medallion',c:['#b06a75','#d69aa2']},
+ {n:'Rosewater Stripe',t:'Stripe', c:['#c98a94','#e8bcc1']},
+ {n:'Petal Weave', t:'Texture', c:['#bb7a84','#d29aa2']}
+ ]},
+ { key:'olive', name:'Provence Olive', fam:'Olive', hue:80, base:'#8f9256',
+ patterns:[
+ {n:'Laurel Damask', t:'Damask', c:['#7f8248','#adb082']},
+ {n:'Bay Trellis', t:'Trellis', c:['#6f7440','#9ba06e']},
+ {n:'Cypress Stripe', t:'Stripe', c:['#8f9256','#bfc292']},
+ {n:'Hemp Grasscloth', t:'Texture', c:['#7d8048','#979a62']}
+ ]}
+ ];
+
+ const N = families.length;
+ const segAngle = 360 / N;
+ const svg = document.getElementById('wheelSvg');
+ const NS = 'http://www.w3.org/2000/svg';
+ const cx=100, cy=100, rOut=99, rIn=34;
+
+ function polar(cxx,cyy,r,deg){
+ const a = (deg-90) * Math.PI/180;
+ return [cxx + r*Math.cos(a), cyy + r*Math.sin(a)];
+ }
+ function segPath(startDeg,endDeg){
+ const [x1,y1]=polar(cx,cy,rOut,startDeg);
+ const [x2,y2]=polar(cx,cy,rOut,endDeg);
+ const [x3,y3]=polar(cx,cy,rIn,endDeg);
+ const [x4,y4]=polar(cx,cy,rIn,startDeg);
+ const large = (endDeg-startDeg)>180?1:0;
+ return `M${x1} ${y1} A${rOut} ${rOut} 0 ${large} 1 ${x2} ${y2} L${x3} ${y3} A${rIn} ${rIn} 0 ${large} 0 ${x4} ${y4} Z`;
+ }
+
+ // build wheel segments — segment i centered at angle i*segAngle (top = 0)
+ families.forEach((f,i)=>{
+ const start = i*segAngle - segAngle/2;
+ const end = i*segAngle + segAngle/2;
+ const p = document.createElementNS(NS,'path');
+ p.setAttribute('d', segPath(start,end));
+ p.setAttribute('fill', f.base);
+ p.setAttribute('class','wheel-seg');
+ p.dataset.idx = i;
+ svg.appendChild(p);
+ });
+ // subtle gilt rim
+ const rim = document.createElementNS(NS,'circle');
+ rim.setAttribute('cx',cx); rim.setAttribute('cy',cy); rim.setAttribute('r',rOut);
+ rim.setAttribute('fill','none'); rim.setAttribute('stroke','rgba(201,169,97,.35)'); rim.setAttribute('stroke-width','1');
+ svg.appendChild(rim);
+
+ // ---- state & rendering ---------------------------------------------------
+ const wheelEl = document.getElementById('wheel');
+ const hubSwatch = document.getElementById('hubSwatch');
+ const cnTitle = document.getElementById('cnTitle');
+ const cnCode = document.getElementById('cnCode');
+ const filmstrip = document.getElementById('filmstrip');
+
+ let rotation = 0; // current wheel rotation in degrees
+ let currentIdx = -1;
+
+ function normalize(a){ a%=360; return a<0?a+360:a; }
+
+ // which family is under the pointer (top, 0deg) given rotation
+ function idxUnderPointer(){
+ // pointer sits at top. segment i center appears at (i*segAngle + rotation).
+ // we want the center closest to 0 (top).
+ const target = normalize(-rotation);
+ let idx = Math.round(target / segAngle) % N;
+ return (idx+N)%N;
+ }
+
+ function patternCSS(p){
+ const [a,b]=p.c;
+ switch(p.t){
+ case 'Stripe':
+ return `repeating-linear-gradient(90deg, ${a} 0 10px, ${b} 10px 20px)`;
+ case 'Trellis':
+ return `${a} repeating-linear-gradient(45deg, ${b} 0 2px, transparent 2px 14px),
+ repeating-linear-gradient(-45deg, ${b} 0 2px, transparent 2px 14px)`;
+ case 'Texture':
+ return `${a} repeating-linear-gradient(0deg, rgba(0,0,0,.10) 0 1px, transparent 1px 4px),
+ repeating-linear-gradient(90deg, rgba(255,255,255,.08) 0 1px, transparent 1px 4px)`;
+ case 'Damask':
+ case 'Arabesque':
+ case 'Medallion':
+ return `radial-gradient(circle at 25% 25%, ${b} 0 6px, transparent 7px),
+ radial-gradient(circle at 75% 75%, ${b} 0 6px, transparent 7px),
+ radial-gradient(circle at 50% 50%, ${b} 0 9px, transparent 10px),
+ ${a}`;
+ case 'Ogee':
+ return `radial-gradient(ellipse 12px 18px at 50% 0, transparent 0 10px, ${b} 10px 12px, transparent 12px),
+ radial-gradient(ellipse 12px 18px at 0 50%, transparent 0 10px, ${b} 10px 12px, transparent 12px),
+ ${a}`;
+ case 'Toile':
+ return `radial-gradient(circle at 30% 40%, ${b} 0 3px, transparent 4px),
+ radial-gradient(circle at 70% 65%, ${b} 0 3px, transparent 4px),
+ radial-gradient(circle at 50% 20%, ${b} 0 2px, transparent 3px),
+ ${a}`;
+ default:
+ return a;
+ }
+ }
+ function bgSize(t){
+ if(t==='Stripe') return 'auto';
+ if(t==='Trellis'||t==='Texture') return 'auto';
+ if(t==='Damask'||t==='Arabesque'||t==='Medallion') return '36px 36px';
+ if(t==='Ogee') return '30px 34px';
+ if(t==='Toile') return '40px 40px';
+ return 'auto';
+ }
+
+ function selectFamily(idx, animate){
+ if(idx===currentIdx) return;
+ currentIdx = idx;
+ const f = families[idx];
+
+ hubSwatch.style.background = f.base;
+ hubSwatch.style.boxShadow = `0 0 24px ${f.base}88, inset 0 0 0 1px rgba(255,255,255,.15)`;
+
+ cnTitle.textContent = f.name;
+ cnTitle.style.animation='none'; void cnTitle.offsetWidth; cnTitle.style.animation='';
+ cnCode.textContent = `Hue ${String(idx+1).padStart(2,'0')} · ${f.fam} Family`;
+
+ // highlight segment
+ svg.querySelectorAll('.wheel-seg').forEach(s=>{
+ s.style.opacity = (+s.dataset.idx===idx)?'1':'0.55';
+ });
+
+ // filmstrip
+ filmstrip.classList.remove('strip-anim');
+ filmstrip.innerHTML='';
+ f.patterns.forEach(p=>{
+ const card=document.createElement('div');
+ card.className='swatch-card';
+ const pat=document.createElement('div');
+ pat.className='pattern';
+ pat.style.background = patternCSS(p);
+ pat.style.backgroundSize = bgSize(p.t);
+ card.appendChild(pat);
+ const meta=document.createElement('div');
+ meta.className='meta';
+ meta.innerHTML=`<div class="pname">${p.n}</div><div class="ptype">${p.t}</div>`;
+ card.appendChild(meta);
+ filmstrip.appendChild(card);
+ });
+ if(animate!==false){ void filmstrip.offsetWidth; filmstrip.classList.add('strip-anim'); }
+ }
+
+ function applyRotation(){
+ wheelEl.style.transform = `rotate(${rotation}deg)`;
+ selectFamily(idxUnderPointer());
+ }
+
+ // snap rotation so nearest segment center is at top
+ function snap(){
+ const idx = idxUnderPointer();
+ const targetRot = normalize(-idx*segAngle);
+ // choose nearest equivalent to current rotation
+ let cur = rotation;
+ let diff = ((targetRot - cur + 540)%360)-180;
+ rotation = cur + diff;
+ wheelEl.style.transition='transform .4s cubic-bezier(.22,1,.36,1)';
+ wheelEl.style.transform = `rotate(${rotation}deg)`;
+ setTimeout(()=>{ wheelEl.style.transition='transform .05s linear'; },400);
+ selectFamily(idx);
+ }
+
+ // ---- drag interaction ----------------------------------------------------
+ const stage = document.querySelector('.wheel-stage');
+ let dragging=false, lastAngle=0, moved=false, startRot=0;
+
+ function pointerAngle(e){
+ const rect = wheelEl.getBoundingClientRect();
+ const px = (e.touches?e.touches[0].clientX:e.clientX) - (rect.left+rect.width/2);
+ const py = (e.touches?e.touches[0].clientY:e.clientY) - (rect.top+rect.height/2);
+ return Math.atan2(py,px)*180/Math.PI;
+ }
+ function down(e){
+ dragging=true; moved=false; lastAngle=pointerAngle(e); startRot=rotation;
+ wheelEl.style.transition='transform .05s linear';
+ }
+ function move(e){
+ if(!dragging) return;
+ const a=pointerAngle(e);
+ let d=a-lastAngle;
+ if(d>180)d-=360; if(d<-180)d+=360;
+ if(Math.abs(d)>0.4) moved=true;
+ rotation += d;
+ lastAngle=a;
+ applyRotation();
+ if(e.cancelable) e.preventDefault();
+ }
+ function up(){
+ if(!dragging) return;
+ dragging=false;
+ snap();
+ }
+
+ wheelEl.addEventListener('mousedown',down);
+ window.addEventListener('mousemove',move);
+ window.addEventListener('mouseup',up);
+ wheelEl.addEventListener('touchstart',down,{passive:true});
+ window.addEventListener('touchmove',move,{passive:false});
+ window.addEventListener('touchend',up);
+
+ // tap a segment to select it directly
+ svg.addEventListener('click',(e)=>{
+ if(moved) return;
+ const seg = e.target.closest('.wheel-seg');
+ if(!seg) return;
+ const idx=+seg.dataset.idx;
+ const targetRot = normalize(-idx*segAngle);
+ let diff=((targetRot - rotation + 540)%360)-180;
+ rotation += diff;
+ wheelEl.style.transition='transform .5s cubic-bezier(.22,1,.36,1)';
+ wheelEl.style.transform=`rotate(${rotation}deg)`;
+ setTimeout(()=>{ wheelEl.style.transition='transform .05s linear'; },500);
+ selectFamily(idx);
+ });
+
+ // ---- share bar -----------------------------------------------------------
+ const toast=document.getElementById('toast');
+ let toastTimer;
+ function showToast(msg){
+ toast.textContent=msg; toast.classList.add('show');
+ clearTimeout(toastTimer);
+ toastTimer=setTimeout(()=>toast.classList.remove('show'),1600);
+ }
+ document.querySelectorAll('.sbtn').forEach(b=>{
+ b.addEventListener('click',()=>{
+ const k=b.dataset.share;
+ const f=families[currentIdx];
+ if(k==='link'){
+ const txt=`Maison & Aurelle — ${f.name} (${f.fam} family)`;
+ if(navigator.clipboard){ navigator.clipboard.writeText(txt).catch(()=>{}); }
+ showToast('Link copied');
+ } else if(k==='pin'){ showToast('Pinned '+f.fam);
+ } else if(k==='x'){ showToast('Shared to X'); }
+ });
+ });
+ document.getElementById('saveBtn').addEventListener('click',()=>{
+ showToast(families[currentIdx].fam+' palette saved');
+ });
+
+ // ---- init ----------------------------------------------------------------
+ rotation = 0;
+ wheelEl.style.transform='rotate(0deg)';
+ selectFamily(0,false);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/b3721d9fe563/claude-code.png b/data/artifacts/b3721d9fe563/claude-code.png
new file mode 100644
index 0000000..2a1e8e7
Binary files /dev/null and b/data/artifacts/b3721d9fe563/claude-code.png differ
diff --git a/data/artifacts/b3721d9fe563/gpt.html b/data/artifacts/b3721d9fe563/gpt.html
new file mode 100644
index 0000000..17feea6
--- /dev/null
+++ b/data/artifacts/b3721d9fe563/gpt.html
@@ -0,0 +1,926 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8" />
+<title>Shop by Color — Wallcovering Wheel</title>
+<meta name="viewport" content="width=device-width, initial-scale=1" />
+<style>
+:root {
+ --bg: #0d0d0f;
+ --surface: #17171c;
+ --surface-soft: #1f1f26;
+ --text: #f5f2ea;
+ --muted: #8a8578;
+ --accent: #c9a961;
+ --accent2: #7d6a45;
+
+ --radius-xl: 32px;
+ --radius-lg: 20px;
+ --radius-md: 14px;
+ --radius-pill: 999px;
+
+ --font-display: "Didot", "Bodoni MT", "Playfair Display", Georgia, serif;
+ --font-sans: -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
+
+ --fs-caption: 12px;
+ --fs-body: 16px;
+ --fs-lead: 21px;
+ --fs-h3: 28px;
+ --fs-h2: 38px;
+ --fs-h1: 50px;
+
+ --shadow-soft: 0 18px 45px rgba(0,0,0,0.65);
+ --shadow-chip: 0 8px 20px rgba(0,0,0,0.5);
+
+ --easing-soft: cubic-bezier(.22,1,.36,1);
+}
+
+/* HyperFrames snippets */
+@keyframes fadeUp {
+ from { opacity:0; transform:translateY(28px); }
+ to { opacity:1; transform:none; }
+}
+@keyframes scaleIn {
+ from { opacity:0; transform:scale(.92); }
+ to { opacity:1; transform:none; }
+}
+@keyframes clipReveal {
+ from { clip-path:inset(0 100% 0 0); }
+ to { clip-path:inset(0); }
+}
+@keyframes blurIn {
+ from { opacity:0; filter:blur(12px); }
+ to { opacity:1; filter:blur(0); }
+}
+
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+html, body {
+ margin: 0;
+ padding: 0;
+ background: radial-gradient(circle at top, #22232b 0, #0a0a0c 40%, #050506 100%);
+ color: var(--text);
+ font-family: var(--font-sans);
+ -webkit-font-smoothing: antialiased;
+}
+
+body {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 100vh;
+}
+
+/* Social post frame */
+.post {
+ width: min(420px, 100vw);
+ height: min(520px, 100vh);
+ background: radial-gradient(circle at top left, #23232b 0, var(--surface) 40%, #0c0c10 100%);
+ border-radius: 30px;
+ padding: 18px 18px 14px;
+ position: relative;
+ overflow: hidden;
+ box-shadow: var(--shadow-soft);
+ color: var(--text);
+}
+
+/* subtle perimeter glow */
+.post::before {
+ content: "";
+ position: absolute;
+ inset: -1px;
+ border-radius: 32px;
+ background: linear-gradient(135deg, rgba(201,169,97,0.55), transparent 45%, rgba(125,106,69,0.5));
+ mix-blend-mode: screen;
+ opacity: 0.35;
+ pointer-events: none;
+}
+
+/* inner surface */
+.post-inner {
+ position: relative;
+ z-index: 1;
+ width: 100%;
+ height: 100%;
+ border-radius: 24px;
+ background:
+ radial-gradient(circle at 10% 0%, rgba(201,169,97,0.26), transparent 55%),
+ radial-gradient(circle at 80% 110%, rgba(125,106,69,0.4), transparent 55%),
+ linear-gradient(145deg, #15151a, #101016);
+ padding: 16px 16px 10px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+/* Header */
+.post-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+ animation: fadeUp .7s var(--easing-soft) both;
+}
+
+.brand {
+ display: flex;
+ align-items: baseline;
+ gap: 8px;
+}
+
+.brand-mark {
+ font-family: var(--font-display);
+ font-size: 18px;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: var(--text);
+}
+
+.brand-tagline {
+ font-size: 10px;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--muted);
+}
+
+.badge-live {
+ font-size: 10px;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ padding: 4px 9px;
+ border-radius: 999px;
+ border: 1px solid rgba(249,246,236,0.1);
+ background: radial-gradient(circle at top, rgba(201,169,97,0.35), rgba(24,24,30,0.9));
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ color: var(--text);
+}
+
+.badge-dot {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ background: radial-gradient(circle, #ffdd9b 0, #c9a961 40%, #7d6a45 100%);
+ box-shadow: 0 0 9px rgba(255,221,155,0.8);
+}
+
+/* Title */
+.post-title {
+ margin-top: 4px;
+ animation: fadeUp .7s var(--easing-soft) .04s both;
+}
+
+.post-kicker {
+ font-size: 11px;
+ text-transform: uppercase;
+ letter-spacing: 0.18em;
+ color: var(--muted);
+ margin-bottom: 2px;
+}
+
+.post-heading {
+ font-family: var(--font-display);
+ font-size: 28px;
+ letter-spacing: -0.03em;
+ line-height: 1.08;
+}
+
+.post-heading span {
+ color: var(--accent);
+}
+
+/* Main layout */
+.post-main {
+ flex: 1;
+ display: grid;
+ grid-template-columns: minmax(0, 1.1fr) minmax(0, 1.4fr);
+ gap: 14px;
+ align-items: stretch;
+ margin-top: 6px;
+ min-height: 0;
+}
+
+/* Wheel panel */
+.wheel-panel {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ gap: 6px;
+ padding: 8px 6px;
+ border-radius: 24px;
+ background:
+ radial-gradient(circle at 10% 0%, rgba(255,255,255,0.07), transparent 55%),
+ linear-gradient(145deg, #14141b, #101018);
+ box-shadow: inset 0 0 0 1px rgba(255,255,255,0.02);
+ animation: scaleIn .7s var(--easing-soft) .05s both;
+}
+
+/* Color wheel */
+.wheel-shell {
+ position: relative;
+ width: 150px;
+ height: 150px;
+ border-radius: 50%;
+ background:
+ conic-gradient(
+ #f69c9c,
+ #f6c49c,
+ #f6e39c,
+ #c5f69c,
+ #9cf6c8,
+ #9cd5f6,
+ #a49cf6,
+ #f69ce0,
+ #f69c9c
+ );
+ padding: 16px;
+ box-shadow:
+ 0 14px 45px rgba(0,0,0,0.75),
+ inset 0 0 0 1px rgba(255,255,255,0.1);
+ cursor: grab;
+ touch-action: none;
+}
+
+.wheel-shell:active {
+ cursor: grabbing;
+}
+
+.wheel-inner {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ background:
+ radial-gradient(circle at 30% 0%, rgba(255,255,255,0.35), transparent 50%),
+ radial-gradient(circle at 70% 100%, rgba(0,0,0,0.75), transparent 55%),
+ radial-gradient(circle, #101015 0, #050509 80%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+/* indicator needle */
+.wheel-indicator {
+ position: absolute;
+ top: 6px;
+ left: 50%;
+ width: 2px;
+ height: 16px;
+ border-radius: 999px;
+ transform: translateX(-50%);
+ background: linear-gradient(to bottom, #f5f2ea, rgba(201,169,97,0.1));
+ box-shadow: 0 0 10px rgba(245,242,234,0.8);
+}
+
+/* center chip showing active color */
+.current-chip {
+ position: relative;
+ width: 62px;
+ height: 62px;
+ border-radius: 24px;
+ box-shadow: var(--shadow-chip);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+ background: radial-gradient(circle at 20% 0%, rgba(255,255,255,0.4), transparent 55%);
+}
+
+.current-chip-inner {
+ position: absolute;
+ inset: 1px;
+ border-radius: 23px;
+ background:
+ radial-gradient(circle at 10% 10%, rgba(255,255,255,0.45), transparent 40%),
+ linear-gradient(145deg, #e2e2e2, #7b7b7b);
+ mix-blend-mode: screen;
+ opacity: 0.45;
+ pointer-events: none;
+}
+
+.current-chip-swatch {
+ position: absolute;
+ inset: 0;
+ border-radius: inherit;
+}
+
+/* chip label */
+.chip-label {
+ margin-top: 8px;
+ text-align: center;
+}
+
+.chip-tone {
+ font-size: 11px;
+ letter-spacing: 0.2em;
+ text-transform: uppercase;
+ color: var(--muted);
+}
+
+.chip-name {
+ margin-top: 2px;
+ font-family: var(--font-display);
+ font-size: 15px;
+ letter-spacing: 0.06em;
+}
+
+.chip-meta {
+ margin-top: 4px;
+ font-size: 11px;
+ color: var(--muted);
+}
+
+/* helper hint */
+.hint {
+ margin-top: 4px;
+ font-size: 10px;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: rgba(245,242,234,0.6);
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.hint-line {
+ flex: 1;
+ height: 1px;
+ border-radius: 999px;
+ background: linear-gradient(to right, rgba(245,242,234,0.35), transparent);
+}
+
+/* Filmstrip panel */
+.filmstrip-panel {
+ border-radius: 24px;
+ padding: 10px 9px 8px;
+ background:
+ radial-gradient(circle at 0% 0%, rgba(255,255,255,0.08), transparent 55%),
+ radial-gradient(circle at 100% 100%, rgba(0,0,0,0.85), transparent 55%),
+ linear-gradient(135deg, #181821, #101017);
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ animation: clipReveal .7s var(--easing-soft) .05s both;
+}
+
+/* filmstrip header */
+.filmstrip-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ gap: 8px;
+}
+
+.filmstrip-label {
+ font-size: 10px;
+ text-transform: uppercase;
+ letter-spacing: 0.16em;
+ color: var(--muted);
+}
+
+.filmstrip-hue {
+ font-size: 12px;
+ text-transform: uppercase;
+ letter-spacing: 0.18em;
+}
+
+.filmstrip-hue span {
+ color: var(--accent);
+}
+
+.filmstrip-meta {
+ font-size: 11px;
+ color: rgba(245,242,234,0.7);
+}
+
+/* patterns row */
+.pattern-row {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 8px;
+}
+
+/* individual tile */
+.pattern-tile {
+ border-radius: 16px;
+ overflow: hidden;
+ background: radial-gradient(circle at 15% 0%, rgba(255,255,255,0.3), transparent 55%);
+ position: relative;
+ min-height: 90px;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ box-shadow: 0 8px 20px rgba(0,0,0,0.6);
+ animation: blurIn .7s var(--easing-soft) both;
+}
+
+.pattern-art {
+ position: absolute;
+ inset: 0;
+ border-radius: inherit;
+}
+
+/* CSS patterns */
+/* 1: herringbone */
+.pattern-herringbone {
+ background-size: 16px 16px;
+ background-image:
+ linear-gradient(135deg, rgba(0,0,0,0.2) 25%, transparent 25%, transparent 50%, rgba(0,0,0,0.2) 50%, rgba(0,0,0,0.2) 75%, transparent 75%, transparent),
+ linear-gradient(225deg, rgba(0,0,0,0.2) 25%, transparent 25%, transparent 50%, rgba(0,0,0,0.2) 50%, rgba(0,0,0,0.2) 75%, transparent 75%, transparent);
+ background-position: 0 0, 8px 8px;
+}
+
+/* 2: arches */
+.pattern-arches {
+ background-image:
+ radial-gradient(circle at 0 100%, rgba(0,0,0,0.18) 12px, transparent 12px),
+ radial-gradient(circle at 50% 100%, rgba(0,0,0,0.12) 12px, transparent 12px),
+ radial-gradient(circle at 100% 100%, rgba(0,0,0,0.18) 12px, transparent 12px);
+ background-size: 32px 18px;
+ background-position: 0 0, 16px 9px, 32px 0;
+}
+
+/* 3: woven grid */
+.pattern-woven {
+ background-image:
+ linear-gradient(rgba(0,0,0,0.18) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(0,0,0,0.25) 1px, transparent 1px),
+ linear-gradient(rgba(255,255,255,0.12) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px);
+ background-size: 18px 18px, 18px 18px, 6px 6px, 6px 6px;
+ background-position: 0 0, 0 0, 0 0, 0 0;
+}
+
+/* 4: painterly stripes */
+.pattern-stripes {
+ background-image:
+ repeating-linear-gradient(135deg,
+ rgba(0,0,0,0.32) 0,
+ rgba(0,0,0,0.32) 4px,
+ transparent 4px,
+ transparent 10px
+ ),
+ radial-gradient(circle at 10% 0%, rgba(255,255,255,0.35), transparent 45%);
+ background-size: 32px 32px, auto;
+ mix-blend-mode: soft-light;
+}
+
+/* pattern labels overlay */
+.pattern-info {
+ position: relative;
+ padding: 7px 7px 6px;
+ z-index: 1;
+ backdrop-filter: blur(11px);
+ background: linear-gradient(to top, rgba(6,6,10,0.9), rgba(6,6,10,0.0));
+ color: var(--text);
+}
+
+.pattern-name {
+ font-family: var(--font-display);
+ font-size: 12px;
+ letter-spacing: 0.04em;
+}
+
+.pattern-tag {
+ font-size: 9px;
+ text-transform: uppercase;
+ letter-spacing: 0.18em;
+ color: rgba(245,242,234,0.7);
+}
+
+/* Credits + share */
+.post-footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding-top: 4px;
+ margin-top: 2px;
+ border-top: 1px solid rgba(255,255,255,0.05);
+ font-size: 10px;
+ color: var(--muted);
+}
+
+.footer-left {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.footer-left span:first-child {
+ text-transform: uppercase;
+ letter-spacing: 0.14em;
+}
+
+.footer-left span:last-child {
+ font-size: 10px;
+}
+
+.footer-accent {
+ color: var(--accent);
+}
+
+/* share bar */
+.share-bar {
+ display: inline-flex;
+ align-items: center;
+ border-radius: 999px;
+ background: radial-gradient(circle at 0% 0%, rgba(255,255,255,0.18), transparent 55%);
+ padding: 2px 2px 2px 8px;
+ gap: 6px;
+}
+
+.share-label {
+ font-size: 9px;
+ text-transform: uppercase;
+ letter-spacing: 0.16em;
+}
+
+.share-icons {
+ display: inline-flex;
+ gap: 4px;
+}
+
+.share-icon {
+ width: 22px;
+ height: 22px;
+ border-radius: 999px;
+ border: 1px solid rgba(255,255,255,0.18);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ background: radial-gradient(circle at 30% 0%, rgba(255,255,255,0.4), transparent 55%);
+ cursor: pointer;
+ position: relative;
+ overflow: hidden;
+}
+
+.share-icon::after {
+ content: "";
+ position: absolute;
+ inset: -60%;
+ background: conic-gradient(from 180deg, rgba(201,169,97,0.2), transparent 40%, rgba(255,255,255,0.1), transparent 80%, rgba(125,106,69,0.4));
+ opacity: 0;
+ transition: opacity .35s var(--easing-soft), transform .35s var(--easing-soft);
+}
+
+.share-icon:hover::after {
+ opacity: 0.5;
+ transform: rotate(20deg);
+}
+
+.share-icon svg {
+ width: 11px;
+ height: 11px;
+ fill: var(--text);
+}
+
+/* responsive tweaks (very small screens) */
+@media (max-width: 390px) {
+ .post {
+ height: 500px;
+ }
+ .post-main {
+ grid-template-columns: 1fr;
+ grid-template-rows: auto auto;
+ }
+ .wheel-panel {
+ order: 1;
+ }
+ .filmstrip-panel {
+ order: 2;
+ }
+ .wheel-shell {
+ width: 140px;
+ height: 140px;
+ }
+}
+</style>
+</head>
+<body>
+<div class="post" aria-label="Wallcovering shop by color interactive">
+ <div class="post-inner">
+ <header class="post-header">
+ <div class="brand">
+ <div>
+ <div class="brand-mark">Haus Wall</div>
+ <div class="brand-tagline">Artful Surfaces</div>
+ </div>
+ </div>
+ <div class="badge-live" aria-hidden="true">
+ <span class="badge-dot"></span>
+ LIVE PALETTE
+ </div>
+ </header>
+
+ <section class="post-title">
+ <div class="post-kicker">Shop by color</div>
+ <h1 class="post-heading">
+ Spin the <span>Hue Wheel</span>
+ </h1>
+ </section>
+
+ <main class="post-main">
+ <!-- Wheel side -->
+ <section class="wheel-panel" aria-label="Select color family">
+ <div class="wheel-shell" id="colorWheel" aria-label="Draggable color wheel" role="slider" aria-valuemin="0" aria-valuemax="359" aria-valuenow="0">
+ <div class="wheel-indicator"></div>
+ <div class="wheel-inner">
+ <div class="current-chip" aria-hidden="true">
+ <div class="current-chip-swatch" id="currentChip"></div>
+ <div class="current-chip-inner"></div>
+ </div>
+ </div>
+ </div>
+
+ <div class="chip-label">
+ <div class="chip-tone" id="chipTone">Warm Neutrals</div>
+ <div class="chip-name" id="chipName">Champagne Shadow</div>
+ <div class="chip-meta" id="chipMeta">Luxury wallcoverings in your perfect hue.</div>
+ </div>
+
+ <div class="hint">
+ <span class="hint-line"></span>
+ Drag or tap the wheel to change mood
+ </div>
+ </section>
+
+ <!-- Filmstrip side -->
+ <section class="filmstrip-panel" aria-label="Wallcovering patterns in selected color">
+ <header class="filmstrip-header">
+ <div>
+ <div class="filmstrip-label">Curated patterns</div>
+ <div class="filmstrip-hue" id="filmstripHue">
+ <span>Champagne</span> Editions
+ </div>
+ </div>
+ <div class="filmstrip-meta" id="filmstripMeta">
+ 4 ways to live in soft evening light.
+ </div>
+ </header>
+
+ <div class="pattern-row" id="patternRow">
+ <!-- pattern tiles populated via JS -->
+ </div>
+ </section>
+ </main>
+
+ <footer class="post-footer">
+ <div class="footer-left">
+ <span>Color story</span>
+ <span>Crafted in Haus Wall Studio · <span class="footer-accent" id="footerHue">Champagne Shadow</span></span>
+ </div>
+ <div class="share-bar" aria-label="Share this palette">
+ <span class="share-label">Share</span>
+ <div class="share-icons">
+ <!-- link icon -->
+ <button class="share-icon" type="button" aria-label="Copy link">
+ <svg viewBox="0 0 16 16" aria-hidden="true">
+ <path d="M6.75 11.5 5.5 12.75a2.5 2.5 0 1 1-3.54-3.54l2-2a2.5 2.5 0 0 1 3.54 0 .75.75 0 0 0 1.06-1.06 4 4 0 0 0-5.66 0l-2 2a4 4 0 1 0 5.66 5.66l1.25-1.25a.75.75 0 0 0-1.06-1.06Z"/>
+ <path d="M14.04 1.79a4 4 0 0 0-5.66 0L7.13 3.04a.75.75 0 1 0 1.06 1.06L9.44 2.8a2.5 2.5 0 1 1 3.54 3.54l-2 2a2.5 2.5 0 0 1-3.54 0 .75.75 0 0 0-1.06 1.06 4 4 0 0 0 5.66 0l2-2a4 4 0 0 0 0-5.66Z"/>
+ </svg>
+ </button>
+ <!-- download icon -->
+ <button class="share-icon" type="button" aria-label="Save image">
+ <svg viewBox="0 0 16 16" aria-hidden="true">
+ <path d="M8 1.25a.75.75 0 0 0-.75.75v6.19L5.53 6.47a.75.75 0 0 0-1.06 1.06l3 3 .01.01a.76.76 0 0 0 .52.21h.01a.76.76 0 0 0 .52-.21l.01-.01 3-3a.75.75 0 1 0-1.06-1.06L8.75 8.19V2A.75.75 0 0 0 8 1.25Z"/>
+ <path d="M3 9.75a.75.75 0 0 0-.75.75v2.5A1.75 1.75 0 0 0 4 14.75h8a1.75 1.75 0 0 0 1.75-1.75v-2.5a.75.75 0 0 0-1.5 0v2.5a.25.25 0 0 1-.25.25H4a.25.25 0 0 1-.25-.25v-2.5A.75.75 0 0 0 3 9.75Z"/>
+ </svg>
+ </button>
+ <!-- share arrow icon -->
+ <button class="share-icon" type="button" aria-label="Share to social">
+ <svg viewBox="0 0 16 16" aria-hidden="true">
+ <path d="M8.53 1.47a.75.75 0 0 0-1.06 0l-4 4a.75.75 0 0 0 1.06 1.06L7.25 3.8V11a.75.75 0 0 0 1.5 0V3.8l2.72 2.73a.75.75 0 0 0 1.06-1.06l-4-4Z"/>
+ <path d="M3.5 9.75a.75.75 0 0 0-.75.75v2A2.5 2.5 0 0 0 5.25 15h5.5A2.5 2.5 0 0 0 13.5 12.5v-2a.75.75 0 0 0-1.5 0v2a1 1 0 0 1-1 1h-5.5a1 1 0 0 1-1-1v-2a.75.75 0 0 0-.75-.75Z"/>
+ </svg>
+ </button>
+ </div>
+ </div>
+ </footer>
+ </div>
+</div>
+
+<script>
+(function() {
+ const wheel = document.getElementById('colorWheel');
+ const chip = document.getElementById('currentChip');
+ const chipTone = document.getElementById('chipTone');
+ const chipName = document.getElementById('chipName');
+ const chipMeta = document.getElementById('chipMeta');
+ const filmstripHue = document.getElementById('filmstripHue');
+ const filmstripMeta = document.getElementById('filmstripMeta');
+ const footerHue = document.getElementById('footerHue');
+ const patternRow = document.getElementById('patternRow');
+
+ const patternTemplates = [
+ { key: 'herringbone', className: 'pattern-herringbone', tag: 'TAILORED HERRINGBONE' },
+ { key: 'arches', className: 'pattern-arches', tag: 'ARCHED PANELS' },
+ { key: 'woven', className: 'pattern-woven', tag: 'WOVEN GRID' },
+ { key: 'stripes', className: 'pattern-stripes', tag: 'BRUSHED STRIPE' }
+ ];
+
+ function hueToData(h) {
+ const ranges = [
+ {
+ name: 'Champagne Shadow',
+ tone: 'Warm Neutrals',
+ meta: 'Soft champagne, candlelit glow.',
+ hueLabel: 'Champagne',
+ filmMeta: '4 ways to live in soft evening light.'
+ },
+ {
+ name: 'Terracotta Veil',
+ tone: 'Sunset Terra',
+ meta: 'Clay-washed walls, golden dusk.',
+ hueLabel: 'Terracotta',
+ filmMeta: 'Earthy warmth with a gallery finish.'
+ },
+ {
+ name: 'Gilded Saffron',
+ tone: 'Golden Hour',
+ meta: 'Saffron fields, brass reflections.',
+ hueLabel: 'Saffron Gold',
+ filmMeta: 'Make every wall a focal glow.'
+ },
+ {
+ name: 'Olive Salon',
+ tone: 'Cultivated Greens',
+ meta: 'Olive leaves, atelier calm.',
+ hueLabel: 'Olive',
+ filmMeta: 'Botanical tones for modern salons.'
+ },
+ {
+ name: 'Celadon Mist',
+ tone: 'Quiet Greens',
+ meta: 'Spa-soft celadon, misted glass.',
+ hueLabel: 'Celadon',
+ filmMeta: 'Tranquil textures for retreat rooms.'
+ },
+ {
+ name: 'Parisian Ink',
+ tone: 'Inky Blues',
+ meta: 'Midnight gallery, lacquered trim.',
+ hueLabel: 'Ink Blue',
+ filmMeta: 'Luxurious depth for city nights.'
+ },
+ {
+ name: 'Noir Fig',
+ tone: 'Poised Violet',
+ meta: 'Black fig, velvet lounges.',
+ hueLabel: 'Fig Noir',
+ filmMeta: 'Shadow-rich walls with a secret.'
+ },
+ {
+ name: 'Rose Opera',
+ tone: 'Blushed Rouge',
+ meta: 'Opera velvet, whispered pink.',
+ hueLabel: 'Soft Rose',
+ filmMeta: 'Romantic texture for sculpted light.'
+ }
+ ];
+ const segment = Math.floor((h % 360) / 45);
+ return ranges[segment];
+ }
+
+ function hueToPatternBase(h) {
+ const sat = 0.6;
+ const light = 0.6;
+ return `hsl(${h}, ${Math.round(sat*100)}%, ${Math.round(light*100)}%)`;
+ }
+
+ function updateUI(heroHue) {
+ const data = hueToData(heroHue);
+ const base = hueToPatternBase(heroHue);
+
+ chip.style.background = `radial-gradient(circle at 15% 0%, rgba(255,255,255,0.6), transparent 55%), linear-gradient(145deg, ${base}, hsl(${heroHue}, 45%, 38%))`;
+ chipTone.textContent = data.tone;
+ chipName.textContent = data.name;
+ chipMeta.textContent = data.meta;
+ filmstripHue.innerHTML = `<span>${data.hueLabel}</span> Editions`;
+ filmstripMeta.textContent = data.filmMeta;
+ footerHue.textContent = data.name;
+
+ patternRow.innerHTML = "";
+ patternTemplates.forEach((tpl, i) => {
+ const tileHue = (heroHue + (i - 1) * 6 + 360) % 360;
+ const tileBase = hueToPatternBase(tileHue);
+ const overlay = `linear-gradient(145deg, ${tileBase}, hsl(${tileHue}, 32%, 26%))`;
+ const accent = `rgba(0,0,0,0.2)`;
+
+ const tile = document.createElement('article');
+ tile.className = 'pattern-tile';
+ tile.style.setProperty('--i', i);
+ tile.style.animationDelay = (0.04 * i) + 0.12 + 's';
+
+ const art = document.createElement('div');
+ art.className = 'pattern-art ' + tpl.className;
+ art.style.backgroundColor = tileBase;
+ art.style.backgroundImage = (tpl.className === 'pattern-stripes')
+ ? `repeating-linear-gradient(135deg, rgba(0,0,0,0.28) 0, rgba(0,0,0,0.28) 4px, transparent 4px, transparent 11px), radial-gradient(circle at 10% 0%, rgba(255,255,255,0.4), transparent 45%), ${overlay}`
+ : `${art.style.backgroundImage}, ${overlay}, radial-gradient(circle at 10% 0%, rgba(255,255,255,0.35), transparent 50%)`;
+ art.style.boxShadow = `inset 0 0 0 1px rgba(255,255,255,0.06), inset 0 -18px 40px ${accent}`;
+
+ const info = document.createElement('div');
+ info.className = 'pattern-info';
+
+ const nameEl = document.createElement('div');
+ nameEl.className = 'pattern-name';
+ const numbered = (i+1) + '. ' + data.name.split(' ')[0];
+ nameEl.textContent = numbered;
+
+ const tagEl = document.createElement('div');
+ tagEl.className = 'pattern-tag';
+ tagEl.textContent = tpl.tag;
+
+ info.appendChild(nameEl);
+ info.appendChild(tagEl);
+ tile.appendChild(art);
+ tile.appendChild(info);
+ patternRow.appendChild(tile);
+ });
+ }
+
+ let dragging = false;
+ let center = { x: 0, y: 0 };
+
+ function getAngleFromEvent(ev) {
+ const rect = wheel.getBoundingClientRect();
+ center.x = rect.left + rect.width / 2;
+ center.y = rect.top + rect.height / 2;
+ const clientX = ev.touches ? ev.touches[0].clientX : ev.clientX;
+ const clientY = ev.touches ? ev.touches[0].clientY : ev.clientY;
+ const dx = clientX - center.x;
+ const dy = clientY - center.y;
+ let angle = Math.atan2(dy, dx) * 180 / Math.PI;
+ angle = (angle + 450) % 360; // 0 at top, clockwise
+ return angle;
+ }
+
+ function handleMove(ev) {
+ if (!dragging) return;
+ ev.preventDefault();
+ const angle = getAngleFromEvent(ev);
+ wheel.style.transform = `rotate(${angle}deg)`;
+ wheel.setAttribute('aria-valuenow', Math.round(angle));
+ updateUI(angle);
+ }
+
+ function startDrag(ev) {
+ dragging = true;
+ document.addEventListener('mousemove', handleMove);
+ document.addEventListener('touchmove', handleMove, { passive: false });
+ document.addEventListener('mouseup', endDrag);
+ document.addEventListener('touchend', endDrag);
+ handleMove(ev);
+ }
+
+ function endDrag() {
+ dragging = false;
+ document.removeEventListener('mousemove', handleMove);
+ document.removeEventListener('touchmove', handleMove);
+ document.removeEventListener('mouseup', endDrag);
+ document.removeEventListener('touchend', endDrag);
+ }
+
+ wheel.addEventListener('mousedown', startDrag);
+ wheel.addEventListener('touchstart', function(ev) {
+ startDrag(ev);
+ }, { passive: false });
+
+ wheel.addEventListener('click', function(ev) {
+ if (dragging) return;
+ const angle = getAngleFromEvent(ev);
+ wheel.style.transform = `rotate(${angle}deg)`;
+ wheel.setAttribute('aria-valuenow', Math.round(angle));
+ updateUI(angle);
+ });
+
+ // keyboard support
+ wheel.tabIndex = 0;
+ wheel.addEventListener('keydown', function(ev) {
+ const step = (ev.shiftKey ? 15 : 5);
+ let current = parseFloat(wheel.getAttribute('aria-valuenow')) || 0;
+ if (ev.key === 'ArrowRight' || ev.key === 'ArrowDown') {
+ current = (current + step) % 360;
+ } else if (ev.key === 'ArrowLeft' || ev.key === 'ArrowUp') {
+ current = (current - step + 360) % 360;
+ } else {
+ return;
+ }
+ ev.preventDefault();
+ wheel.style.transform = `rotate(${current}deg)`;
+ wheel.setAttribute('aria-valuenow', Math.round(current));
+ updateUI(current);
+ });
+
+ // Initialize
+ updateUI(30);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/b3721d9fe563/gpt.png b/data/artifacts/b3721d9fe563/gpt.png
new file mode 100644
index 0000000..be505f1
Binary files /dev/null and b/data/artifacts/b3721d9fe563/gpt.png differ
diff --git a/data/artifacts/b3721d9fe563/grok.html b/data/artifacts/b3721d9fe563/grok.html
new file mode 100644
index 0000000..a877885
--- /dev/null
+++ b/data/artifacts/b3721d9fe563/grok.html
@@ -0,0 +1,899 @@
+<!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>SABLE — Shop by Color</title>
+<style>
+ :root {
+ --bg: #faf8f4;
+ --surface: #ffffff;
+ --text: #1c1a17;
+ --muted: #9b948a;
+ --accent: #b0472e;
+ --accent2: #2f4c39;
+ --gold: #c9a961;
+ --gold-deep: #7d6a45;
+ --ease: cubic-bezier(.22,.9,.24,1);
+ --ease-out: cubic-bezier(.22,1,.36,1);
+ --ease-pop: cubic-bezier(.34,1.56,.64,1);
+ --serif: Didot, "Bodoni MT", "Playfair Display", Georgia, serif;
+ --sans: "Avenir Next", "Century Gothic", Futura, "Segoe UI", sans-serif;
+ --hue: 18;
+ --sat: 55%;
+ --lit: 48%;
+ }
+
+ * { box-sizing: border-box; margin: 0; padding: 0; }
+ html, body {
+ height: 100%;
+ background: var(--bg);
+ color: var(--text);
+ font-family: var(--sans);
+ -webkit-font-smoothing: antialiased;
+ -webkit-tap-highlight-color: transparent;
+ overflow-x: hidden;
+ }
+
+ body {
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ min-height: 100%;
+ padding: 20px 16px 40px;
+ }
+
+ .post {
+ width: 100%;
+ max-width: 420px;
+ background: var(--surface);
+ border-radius: 28px;
+ box-shadow:
+ 0 1px 0 rgba(28,26,23,.04),
+ 0 24px 64px rgba(28,26,23,.08),
+ 0 8px 24px rgba(28,26,23,.04);
+ overflow: hidden;
+ position: relative;
+ animation: rise .9s var(--ease-out) both;
+ }
+
+ @keyframes rise {
+ from { opacity: 0; transform: translateY(28px) scale(.98); }
+ to { opacity: 1; transform: translateY(0) scale(1); }
+ }
+
+ /* ── Header ── */
+ header {
+ padding: 28px 28px 8px;
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ }
+
+ .wordmark {
+ font-family: var(--serif);
+ font-size: 22px;
+ letter-spacing: .28em;
+ font-weight: 400;
+ color: var(--text);
+ line-height: 1;
+ }
+ .wordmark span {
+ display: block;
+ font-family: var(--sans);
+ font-size: 9px;
+ letter-spacing: .22em;
+ text-transform: uppercase;
+ color: var(--muted);
+ margin-top: 7px;
+ font-weight: 500;
+ }
+
+ .badge {
+ font-size: 10px;
+ letter-spacing: .16em;
+ text-transform: uppercase;
+ color: var(--gold-deep);
+ background: linear-gradient(135deg, #f5edd8, #efe4c8);
+ padding: 7px 12px;
+ border-radius: 100px;
+ font-weight: 600;
+ border: 1px solid rgba(201,169,97,.25);
+ }
+
+ /* ── Intro ── */
+ .intro {
+ padding: 18px 28px 6px;
+ text-align: center;
+ }
+ .intro h1 {
+ font-family: var(--serif);
+ font-size: 34px;
+ font-weight: 400;
+ letter-spacing: -.02em;
+ line-height: 1.1;
+ color: var(--text);
+ }
+ .intro h1 em {
+ font-style: italic;
+ color: var(--accent);
+ }
+ .intro p {
+ margin-top: 10px;
+ font-size: 13px;
+ color: var(--muted);
+ letter-spacing: .04em;
+ line-height: 1.5;
+ }
+
+ /* ── Wheel Stage ── */
+ .stage {
+ position: relative;
+ padding: 28px 0 12px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ user-select: none;
+ -webkit-user-select: none;
+ touch-action: none;
+ }
+
+ .wheel-wrap {
+ position: relative;
+ width: 260px;
+ height: 260px;
+ }
+
+ .wheel {
+ width: 260px;
+ height: 260px;
+ border-radius: 50%;
+ position: relative;
+ cursor: grab;
+ filter: drop-shadow(0 12px 28px rgba(28,26,23,.12));
+ transition: transform .15s var(--ease);
+ }
+ .wheel:active { cursor: grabbing; }
+ .wheel.dragging { transition: none; }
+
+ /* Conic hue wheel */
+ .wheel-disk {
+ position: absolute;
+ inset: 0;
+ border-radius: 50%;
+ background: conic-gradient(
+ from 0deg,
+ hsl(0,70%,55%),
+ hsl(30,75%,55%),
+ hsl(60,70%,50%),
+ hsl(90,60%,45%),
+ hsl(120,55%,42%),
+ hsl(150,55%,42%),
+ hsl(180,55%,45%),
+ hsl(210,65%,50%),
+ hsl(240,60%,55%),
+ hsl(270,60%,55%),
+ hsl(300,60%,52%),
+ hsl(330,70%,55%),
+ hsl(0,70%,55%)
+ );
+ box-shadow: inset 0 0 0 1px rgba(28,26,23,.06);
+ }
+
+ /* Soft inner ring + hole */
+ .wheel-inner {
+ position: absolute;
+ inset: 48px;
+ border-radius: 50%;
+ background:
+ radial-gradient(circle at 40% 35%, rgba(255,255,255,.55), transparent 55%),
+ var(--bg);
+ box-shadow:
+ inset 0 2px 8px rgba(28,26,23,.06),
+ 0 0 0 10px rgba(250,248,244,.55);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ z-index: 2;
+ }
+
+ .swatch-live {
+ width: 64px;
+ height: 64px;
+ border-radius: 50%;
+ background: hsl(var(--hue), var(--sat), var(--lit));
+ box-shadow:
+ 0 6px 20px hsla(var(--hue), var(--sat), calc(var(--lit) - 15%), .4),
+ inset 0 1px 0 rgba(255,255,255,.35);
+ transition: background .18s var(--ease), box-shadow .18s var(--ease);
+ border: 3px solid #fff;
+ }
+
+ .swatch-name {
+ margin-top: 10px;
+ font-family: var(--serif);
+ font-size: 13px;
+ font-style: italic;
+ color: var(--text);
+ letter-spacing: .02em;
+ max-width: 120px;
+ text-align: center;
+ line-height: 1.2;
+ transition: opacity .25s var(--ease);
+ }
+
+ /* Pointer / tick on rim */
+ .pointer {
+ position: absolute;
+ top: -2px;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 0;
+ height: 0;
+ border-left: 7px solid transparent;
+ border-right: 7px solid transparent;
+ border-top: 12px solid var(--text);
+ z-index: 4;
+ filter: drop-shadow(0 1px 2px rgba(0,0,0,.15));
+ }
+
+ .ring-label {
+ position: absolute;
+ bottom: -4px;
+ left: 50%;
+ transform: translateX(-50%);
+ font-size: 10px;
+ letter-spacing: .18em;
+ text-transform: uppercase;
+ color: var(--muted);
+ white-space: nowrap;
+ }
+
+ /* Hue chips around for quick tap */
+ .chips {
+ display: flex;
+ gap: 8px;
+ margin-top: 28px;
+ flex-wrap: wrap;
+ justify-content: center;
+ padding: 0 24px;
+ }
+ .chip {
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+ border: 2px solid #fff;
+ box-shadow: 0 2px 8px rgba(28,26,23,.12);
+ cursor: pointer;
+ transition: transform .25s var(--ease-pop), box-shadow .25s var(--ease);
+ position: relative;
+ }
+ .chip:hover, .chip:focus-visible {
+ transform: scale(1.18);
+ outline: none;
+ }
+ .chip.active {
+ transform: scale(1.22);
+ box-shadow: 0 0 0 2px var(--text), 0 4px 12px rgba(28,26,23,.18);
+ }
+
+ /* ── Filmstrip ── */
+ .reveal {
+ padding: 8px 0 0;
+ opacity: 0;
+ transform: translateY(16px);
+ transition: opacity .55s var(--ease-out), transform .55s var(--ease-out);
+ pointer-events: none;
+ }
+ .reveal.show {
+ opacity: 1;
+ transform: translateY(0);
+ pointer-events: auto;
+ }
+
+ .reveal-head {
+ padding: 0 28px 14px;
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ }
+ .reveal-head h2 {
+ font-family: var(--serif);
+ font-size: 18px;
+ font-weight: 400;
+ letter-spacing: -.01em;
+ }
+ .reveal-head h2 b {
+ font-weight: 400;
+ font-style: italic;
+ color: hsl(var(--hue), var(--sat), calc(var(--lit) - 8%));
+ transition: color .3s var(--ease);
+ }
+ .reveal-head .count {
+ font-size: 10px;
+ letter-spacing: .16em;
+ text-transform: uppercase;
+ color: var(--muted);
+ }
+
+ .filmstrip {
+ display: flex;
+ gap: 12px;
+ overflow-x: auto;
+ scroll-snap-type: x mandatory;
+ padding: 4px 28px 18px;
+ -webkit-overflow-scrolling: touch;
+ scrollbar-width: none;
+ }
+ .filmstrip::-webkit-scrollbar { display: none; }
+
+ .card {
+ flex: 0 0 148px;
+ scroll-snap-align: start;
+ background: var(--bg);
+ border-radius: 16px;
+ overflow: hidden;
+ border: 1px solid rgba(28,26,23,.06);
+ transition: transform .35s var(--ease), box-shadow .35s var(--ease);
+ cursor: pointer;
+ animation: cardIn .6s var(--ease-out) both;
+ }
+ .card:nth-child(1) { animation-delay: .05s; }
+ .card:nth-child(2) { animation-delay: .12s; }
+ .card:nth-child(3) { animation-delay: .19s; }
+ .card:nth-child(4) { animation-delay: .26s; }
+
+ @keyframes cardIn {
+ from { opacity: 0; transform: translateY(14px) scale(.96); }
+ to { opacity: 1; transform: translateY(0) scale(1); }
+ }
+
+ .card:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 12px 28px rgba(28,26,23,.1);
+ }
+ .card.selected {
+ box-shadow: 0 0 0 2px var(--text), 0 12px 28px rgba(28,26,23,.1);
+ }
+
+ .pattern {
+ height: 148px;
+ position: relative;
+ overflow: hidden;
+ }
+
+ .card-meta {
+ padding: 12px 14px 14px;
+ background: var(--surface);
+ }
+ .card-meta .pname {
+ font-family: var(--serif);
+ font-size: 14px;
+ font-style: italic;
+ line-height: 1.2;
+ color: var(--text);
+ }
+ .card-meta .pcode {
+ margin-top: 4px;
+ font-size: 10px;
+ letter-spacing: .14em;
+ text-transform: uppercase;
+ color: var(--muted);
+ }
+
+ /* ── CSS Patterns ── */
+ /* 1. Damask diamond lattice */
+ .pat-damask {
+ background-color: var(--c1);
+ background-image:
+ radial-gradient(ellipse 30% 30% at 50% 50%, var(--c2) 0%, transparent 70%),
+ repeating-linear-gradient(45deg, transparent, transparent 18px, var(--c3) 18px, var(--c3) 19px),
+ repeating-linear-gradient(-45deg, transparent, transparent 18px, var(--c3) 18px, var(--c3) 19px);
+ }
+ .pat-damask::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background:
+ radial-gradient(circle at 25% 25%, var(--c2) 0 3px, transparent 4px),
+ radial-gradient(circle at 75% 25%, var(--c2) 0 3px, transparent 4px),
+ radial-gradient(circle at 25% 75%, var(--c2) 0 3px, transparent 4px),
+ radial-gradient(circle at 75% 75%, var(--c2) 0 3px, transparent 4px),
+ radial-gradient(circle at 50% 50%, var(--c3) 0 5px, transparent 6px);
+ opacity: .85;
+ }
+
+ /* 2. Botanical stripes */
+ .pat-botanical {
+ background-color: var(--c1);
+ background-image:
+ repeating-linear-gradient(
+ 90deg,
+ var(--c1) 0 14px,
+ var(--c2) 14px 16px,
+ var(--c1) 16px 30px,
+ var(--c3) 30px 31px
+ );
+ }
+ .pat-botanical::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background-image:
+ radial-gradient(ellipse 8px 14px at 22px 30px, var(--c3) 0 40%, transparent 50%),
+ radial-gradient(ellipse 8px 14px at 22px 70px, var(--c2) 0 40%, transparent 50%),
+ radial-gradient(ellipse 8px 14px at 22px 110px, var(--c3) 0 40%, transparent 50%),
+ radial-gradient(ellipse 8px 14px at 52px 50px, var(--c2) 0 40%, transparent 50%),
+ radial-gradient(ellipse 8px 14px at 52px 90px, var(--c3) 0 40%, transparent 50%),
+ radial-gradient(ellipse 8px 14px at 82px 35px, var(--c3) 0 40%, transparent 50%),
+ radial-gradient(ellipse 8px 14px at 82px 85px, var(--c2) 0 40%, transparent 50%),
+ radial-gradient(ellipse 8px 14px at 112px 55px, var(--c3) 0 40%, transparent 50%),
+ radial-gradient(ellipse 8px 14px at 112px 105px, var(--c2) 0 40%, transparent 50%),
+ radial-gradient(ellipse 8px 14px at 142px 40px, var(--c2) 0 40%, transparent 50%);
+ opacity: .7;
+ }
+
+ /* 3. Herringbone / chevron weave */
+ .pat-herringbone {
+ background-color: var(--c1);
+ background-image:
+ repeating-linear-gradient(
+ 60deg,
+ var(--c2) 0 2px,
+ transparent 2px 10px
+ ),
+ repeating-linear-gradient(
+ -60deg,
+ var(--c3) 0 2px,
+ transparent 2px 10px
+ );
+ background-size: 20px 34px;
+ }
+ .pat-herringbone::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(
+ 180deg,
+ transparent 0%,
+ hsla(0,0%,100%,.08) 50%,
+ transparent 100%
+ );
+ background-size: 100% 12px;
+ }
+
+ /* 4. Terrazzo speckles */
+ .pat-terrazzo {
+ background-color: var(--c1);
+ }
+ .pat-terrazzo::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background-image:
+ radial-gradient(circle at 12% 20%, var(--c2) 0 4px, transparent 5px),
+ radial-gradient(circle at 38% 14%, var(--c3) 0 3px, transparent 4px),
+ radial-gradient(circle at 62% 28%, var(--c2) 0 5px, transparent 6px),
+ radial-gradient(circle at 84% 16%, var(--c3) 0 2px, transparent 3px),
+ radial-gradient(circle at 20% 48%, var(--c3) 0 3px, transparent 4px),
+ radial-gradient(circle at 48% 52%, var(--c2) 0 6px, transparent 7px),
+ radial-gradient(circle at 72% 44%, var(--c3) 0 3px, transparent 4px),
+ radial-gradient(circle at 90% 58%, var(--c2) 0 4px, transparent 5px),
+ radial-gradient(circle at 14% 78%, var(--c2) 0 3px, transparent 4px),
+ radial-gradient(circle at 40% 84%, var(--c3) 0 5px, transparent 6px),
+ radial-gradient(circle at 58% 72%, var(--c2) 0 2px, transparent 3px),
+ radial-gradient(circle at 78% 88%, var(--c3) 0 4px, transparent 5px),
+ radial-gradient(circle at 30% 34%, var(--c3) 0 2px, transparent 3px),
+ radial-gradient(circle at 66% 66%, var(--c2) 0 3px, transparent 4px),
+ radial-gradient(ellipse 10px 6px at 25% 62%, var(--c2) 0 40%, transparent 50%),
+ radial-gradient(ellipse 8px 5px at 55% 30%, var(--c3) 0 40%, transparent 50%),
+ radial-gradient(ellipse 9px 5px at 85% 75%, var(--c2) 0 40%, transparent 50%);
+ }
+
+ /* ── Share bar ── */
+ footer {
+ margin-top: 6px;
+ padding: 18px 28px 24px;
+ border-top: 1px solid rgba(28,26,23,.06);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ }
+
+ .share-label {
+ font-size: 10px;
+ letter-spacing: .18em;
+ text-transform: uppercase;
+ color: var(--muted);
+ }
+
+ .share-bar {
+ display: flex;
+ gap: 8px;
+ }
+
+ .share-btn {
+ appearance: none;
+ border: 1px solid rgba(28,26,23,.1);
+ background: var(--bg);
+ color: var(--text);
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ display: grid;
+ place-items: center;
+ cursor: pointer;
+ transition: transform .25s var(--ease-pop), background .2s var(--ease), border-color .2s var(--ease);
+ }
+ .share-btn:hover {
+ transform: scale(1.08);
+ background: var(--surface);
+ border-color: rgba(28,26,23,.2);
+ }
+ .share-btn:active { transform: scale(.96); }
+ .share-btn svg {
+ width: 16px;
+ height: 16px;
+ fill: none;
+ stroke: currentColor;
+ stroke-width: 1.6;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ }
+ .share-btn.primary {
+ background: var(--text);
+ color: var(--bg);
+ border-color: var(--text);
+ width: auto;
+ padding: 0 16px;
+ border-radius: 100px;
+ gap: 8px;
+ display: flex;
+ align-items: center;
+ font-size: 11px;
+ letter-spacing: .12em;
+ text-transform: uppercase;
+ font-weight: 600;
+ }
+ .share-btn.primary:hover {
+ background: var(--accent);
+ border-color: var(--accent);
+ }
+
+ .toast {
+ position: absolute;
+ left: 50%;
+ bottom: 88px;
+ transform: translateX(-50%) translateY(8px);
+ background: var(--text);
+ color: var(--bg);
+ font-size: 12px;
+ letter-spacing: .06em;
+ padding: 10px 18px;
+ border-radius: 100px;
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity .35s var(--ease), transform .35s var(--ease);
+ white-space: nowrap;
+ z-index: 10;
+ }
+ .toast.show {
+ opacity: 1;
+ transform: translateX(-50%) translateY(0);
+ }
+
+ /* Decorative corner fleuron */
+ .fleuron {
+ text-align: center;
+ padding: 4px 0 2px;
+ color: var(--gold);
+ font-size: 14px;
+ letter-spacing: .4em;
+ opacity: .7;
+ }
+</style>
+</head>
+<body>
+ <article class="post" id="post" aria-label="SABLE Shop by Color">
+ <header>
+ <div class="wordmark">SABLE<span>Wallcovering Atelier</span></div>
+ <div class="badge">SS26</div>
+ </header>
+
+ <div class="intro">
+ <h1>Shop by <em>Color</em></h1>
+ <p>Turn the wheel. Find your hue.<br>Four patterns, one family.</p>
+ </div>
+
+ <div class="fleuron" aria-hidden="true">◆</div>
+
+ <section class="stage" id="stage" aria-label="Color wheel">
+ <div class="wheel-wrap">
+ <div class="pointer" aria-hidden="true"></div>
+ <div class="wheel" id="wheel" role="slider" aria-valuemin="0" aria-valuemax="360" aria-valuenow="18" aria-label="Hue selector">
+ <div class="wheel-disk" id="disk"></div>
+ <div class="wheel-inner">
+ <div class="swatch-live" id="swatch"></div>
+ <div class="swatch-name" id="swatchName">Terracotta Clay</div>
+ </div>
+ </div>
+ </div>
+ <div class="ring-label">Drag · Tap · Explore</div>
+
+ <div class="chips" id="chips" role="listbox" aria-label="Quick hues"></div>
+ </section>
+
+ <section class="reveal" id="reveal" aria-live="polite">
+ <div class="reveal-head">
+ <h2>In <b id="familyName">Terracotta Clay</b></h2>
+ <span class="count">4 patterns</span>
+ </div>
+ <div class="filmstrip" id="filmstrip"></div>
+ </section>
+
+ <footer>
+ <div class="share-label">Share</div>
+ <div class="share-bar">
+ <button class="share-btn" id="btnCopy" aria-label="Copy link" title="Copy link">
+ <svg viewBox="0 0 24 24"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
+ </button>
+ <button class="share-btn" id="btnSave" aria-label="Save palette" title="Save">
+ <svg viewBox="0 0 24 24"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/></svg>
+ </button>
+ <button class="share-btn primary" id="btnShare" aria-label="Share">
+ <svg viewBox="0 0 24 24" style="width:14px;height:14px"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><polyline points="16 6 12 2 8 6"/><line x1="12" y1="2" x2="12" y2="15"/></svg>
+ Share
+ </button>
+ </div>
+ </footer>
+
+ <div class="toast" id="toast">Link copied</div>
+ </article>
+
+<script>
+(function () {
+ const NAMES = [
+ { h: 8, name: "Pompeiian Red", sat: 58, lit: 44 },
+ { h: 18, name: "Terracotta Clay", sat: 55, lit: 48 },
+ { h: 28, name: "Saffron Dust", sat: 62, lit: 52 },
+ { h: 38, name: "Honey Ochre", sat: 58, lit: 50 },
+ { h: 48, name: "Marigold Wash", sat: 60, lit: 52 },
+ { h: 72, name: "Celadon Leaf", sat: 38, lit: 46 },
+ { h: 98, name: "Botanical Sage", sat: 32, lit: 44 },
+ { h: 132, name: "Verdigris", sat: 36, lit: 40 },
+ { h: 168, name: "Aegean Mist", sat: 34, lit: 44 },
+ { h: 198, name: " indico Smoke", sat: 42, lit: 42 },
+ { h: 220, name: "Delft Blue", sat: 48, lit: 46 },
+ { h: 248, name: "Wisteria Dusk", sat: 36, lit: 48 },
+ { h: 292, name: "Mulberry Silk", sat: 40, lit: 44 },
+ { h: 330, name: "Rosewood Blush", sat: 46, lit: 50 },
+ { h: 350, name: "Garnet Noir", sat: 52, lit: 40 }
+ ];
+ // fix typo
+ NAMES[9].name = "Indico Smoke";
+
+ const PATTERNS = [
+ { cls: "pat-damask", label: "Damask Lattice", code: "SL-01" },
+ { cls: "pat-botanical", label: "Botanical Stripe", code: "SL-02" },
+ { cls: "pat-herringbone",label: "Herringbone Weave", code: "SL-03" },
+ { cls: "pat-terrazzo", label: "Terrazzo Speckle", code: "SL-04" }
+ ];
+
+ const root = document.documentElement;
+ const wheel = document.getElementById("wheel");
+ const disk = document.getElementById("disk");
+ const swatch = document.getElementById("swatch");
+ const swatchName = document.getElementById("swatchName");
+ const familyName = document.getElementById("familyName");
+ const reveal = document.getElementById("reveal");
+ const filmstrip = document.getElementById("filmstrip");
+ const chips = document.getElementById("chips");
+ const toast = document.getElementById("toast");
+
+ let hue = 18;
+ let rotation = 0; // visual rotation of disk
+ let dragging = false;
+ let lastAngle = 0;
+ let hasRevealed = false;
+ let toastTimer = null;
+
+ // Build quick chips
+ const CHIP_HUES = [18, 38, 98, 168, 220, 292, 350];
+ CHIP_HUES.forEach((h, i) => {
+ const n = nearestName(h);
+ const b = document.createElement("button");
+ b.className = "chip" + (h === 18 ? " active" : "");
+ b.style.background = `hsl(${h}, ${n.sat}%, ${n.lit}%)`;
+ b.setAttribute("role", "option");
+ b.setAttribute("aria-label", n.name);
+ b.dataset.hue = h;
+ b.addEventListener("click", () => {
+ setHue(h, true);
+ pulseChip(h);
+ });
+ chips.appendChild(b);
+ });
+
+ function nearestName(h) {
+ let best = NAMES[0], bestD = 999;
+ for (const n of NAMES) {
+ let d = Math.abs(n.h - h);
+ if (d > 180) d = 360 - d;
+ if (d < bestD) { bestD = d; best = n; }
+ }
+ return best;
+ }
+
+ function pulseChip(h) {
+ [...chips.children].forEach(c => {
+ c.classList.toggle("active", Number(c.dataset.hue) === h || Math.abs(Number(c.dataset.hue) - h) < 8);
+ });
+ }
+
+ function setHue(h, snapName) {
+ hue = ((h % 360) + 360) % 360;
+ const n = nearestName(hue);
+ const sat = n.sat, lit = n.lit;
+
+ root.style.setProperty("--hue", hue.toFixed(1));
+ root.style.setProperty("--sat", sat + "%");
+ root.style.setProperty("--lit", lit + "%");
+
+ // Rotate disk so selected hue sits under the top pointer.
+ // conic-gradient starts at top (after from 0deg — actually at 3 o'clock by default, we used from 0deg = 12 o'clock in modern browsers?
+ // CSS conic-gradient from 0deg starts at top (12 o'clock). Hue 0 is at top.
+ // Pointer is at top, so disk rotation = -hue.
+ rotation = -hue;
+ disk.style.transform = `rotate(${rotation}deg)`;
+ disk.style.transition = snapName ? "transform .55s cubic-bezier(.22,1,.36,1)" : "none";
+
+ const displayName = snapName ? n.name : (Math.abs(n.h - hue) < 12 ? n.name : approxName(hue));
+ swatchName.textContent = displayName;
+ familyName.textContent = n.name;
+ wheel.setAttribute("aria-valuenow", Math.round(hue));
+
+ renderPatterns(n);
+ if (!hasRevealed) {
+ hasRevealed = true;
+ requestAnimationFrame(() => reveal.classList.add("show"));
+ }
+ pulseChip(n.h);
+ }
+
+ function approxName(h) {
+ // While dragging, still show nearest designer name
+ return nearestName(h).name;
+ }
+
+ function renderPatterns(n) {
+ const h = n.h, s = n.sat, l = n.lit;
+ const c1 = `hsl(${h}, ${s}%, ${Math.min(92, l + 32)}%)`;
+ const c2 = `hsl(${h}, ${Math.min(70, s + 8)}%, ${Math.max(28, l - 8)}%)`;
+ const c3 = `hsl(${h}, ${Math.max(20, s - 12)}%, ${Math.min(70, l + 12)}%)`;
+ const cDeep = `hsl(${h}, ${s}%, ${Math.max(22, l - 18)}%)`;
+
+ // Rebuild cards with fresh animation
+ filmstrip.innerHTML = "";
+ PATTERNS.forEach((p, i) => {
+ const card = document.createElement("article");
+ card.className = "card";
+ card.style.animationDelay = (0.04 + i * 0.07) + "s";
+ card.innerHTML = `
+ <div class="pattern ${p.cls}" style="--c1:${c1};--c2:${c2};--c3:${c3};--cDeep:${cDeep}"></div>
+ <div class="card-meta">
+ <div class="pname">${p.label}</div>
+ <div class="pcode">${p.code} · ${n.name}</div>
+ </div>`;
+ card.addEventListener("click", () => {
+ filmstrip.querySelectorAll(".card").forEach(c => c.classList.remove("selected"));
+ card.classList.add("selected");
+ showToast("Saved · " + p.label);
+ });
+ filmstrip.appendChild(card);
+ });
+ }
+
+ // Angle helpers
+ function getAngle(e, el) {
+ const r = el.getBoundingClientRect();
+ const cx = r.left + r.width / 2;
+ const cy = r.top + r.height / 2;
+ const pt = e.touches ? e.touches[0] : e;
+ // atan2: 0 at east, we want 0 at north, clockwise matching conic
+ let ang = Math.atan2(pt.clientY - cy, pt.clientX - cx); // -PI..PI, 0 = east
+ ang = ang * 180 / Math.PI; // -180..180
+ ang = ang + 90; // 0 at north
+ if (ang < 0) ang += 360;
+ return ang;
+ }
+
+ function onDown(e) {
+ dragging = true;
+ wheel.classList.add("dragging");
+ lastAngle = getAngle(e, wheel);
+ e.preventDefault();
+ }
+ function onMove(e) {
+ if (!dragging) return;
+ const ang = getAngle(e, wheel);
+ // Direct absolute mapping: hue = angle under pointer
+ setHue(ang, false);
+ lastAngle = ang;
+ e.preventDefault();
+ }
+ function onUp() {
+ if (!dragging) return;
+ dragging = false;
+ wheel.classList.remove("dragging");
+ // Snap to nearest named hue
+ const n = nearestName(hue);
+ setHue(n.h, true);
+ }
+
+ wheel.addEventListener("mousedown", onDown);
+ window.addEventListener("mousemove", onMove);
+ window.addEventListener("mouseup", onUp);
+ wheel.addEventListener("touchstart", onDown, { passive: false });
+ window.addEventListener("touchmove", onMove, { passive: false });
+ window.addEventListener("touchend", onUp);
+
+ // Keyboard
+ wheel.addEventListener("keydown", (e) => {
+ if (e.key === "ArrowRight" || e.key === "ArrowUp") {
+ const n = nearestName(hue);
+ const idx = NAMES.indexOf(n);
+ setHue(NAMES[(idx + 1) % NAMES.length].h, true);
+ e.preventDefault();
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowDown") {
+ const n = nearestName(hue);
+ const idx = NAMES.indexOf(n);
+ setHue(NAMES[(idx - 1 + NAMES.length) % NAMES.length].h, true);
+ e.preventDefault();
+ }
+ });
+ wheel.tabIndex = 0;
+
+ // Share actions
+ function showToast(msg) {
+ toast.textContent = msg;
+ toast.classList.add("show");
+ clearTimeout(toastTimer);
+ toastTimer = setTimeout(() => toast.classList.remove("show"), 1800);
+ }
+
+ document.getElementById("btnCopy").addEventListener("click", () => {
+ const n = nearestName(hue);
+ const text = `SABLE · Shop by Color — ${n.name} wallcovering family`;
+ if (navigator.clipboard && navigator.clipboard.writeText) {
+ navigator.clipboard.writeText(text).then(() => showToast("Copied to clipboard")).catch(() => showToast(text));
+ } else {
+ showToast("SABLE · " + n.name);
+ }
+ });
+
+ document.getElementById("btnSave").addEventListener("click", () => {
+ const n = nearestName(hue);
+ showToast("Saved · " + n.name);
+ });
+
+ document.getElementById("btnShare").addEventListener("click", async () => {
+ const n = nearestName(hue);
+ const data = {
+ title: "SABLE — Shop by Color",
+ text: `I'm exploring the ${n.name} wallcovering family at SABLE Atelier.`
+ };
+ if (navigator.share) {
+ try { await navigator.share(data); } catch (_) {}
+ } else {
+ showToast("Shared · " + n.name);
+ }
+ });
+
+ // Init
+ setHue(18, true);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/b3721d9fe563/grok.png b/data/artifacts/b3721d9fe563/grok.png
new file mode 100644
index 0000000..7b0113f
Binary files /dev/null and b/data/artifacts/b3721d9fe563/grok.png differ
diff --git a/data/artifacts/b3721d9fe563/kimi.html b/data/artifacts/b3721d9fe563/kimi.html
new file mode 100644
index 0000000..9ef45f1
--- /dev/null
+++ b/data/artifacts/b3721d9fe563/kimi.html
@@ -0,0 +1,534 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Luxe Walls — Shop by Color</title>
+<style>
+:root{
+ --bg:#0d0d0f;
+ --surface:#17171c;
+ --text:#f5f2ea;
+ --muted:#8a8578;
+ --accent:#c9a961;
+ --accent2:#7d6a45;
+ --font-display:'Didot','Bodoni MT','Playfair Display',Georgia,serif;
+ --font-ui:-apple-system,'Segoe UI','Helvetica Neue',Arial,sans-serif;
+ --ease-swift:cubic-bezier(.22,.9,.24,1);
+ --ease-power:cubic-bezier(.77,0,.18,1);
+ --ease-hero:cubic-bezier(.22,1,.36,1);
+ --radius-card:14px;
+ --radius-pill:999px;
+ --space-xs:4px;
+ --space-sm:8px;
+ --space-md:16px;
+ --space-lg:24px;
+ --space-xl:32px;
+ --space-2xl:48px;
+ --space-3xl:64px;
+ --selected-hue:35;
+ --selected-sat:65%;
+ --selected-light:45%;
+}
+
+*{box-sizing:border-box;margin:0;padding:0}
+
+body{
+ background:var(--bg);
+ color:var(--text);
+ font-family:var(--font-ui);
+ min-height:100vh;
+ display:flex;
+ flex-direction:column;
+ align-items:center;
+ padding:var(--space-lg);
+ overflow-x:hidden;
+}
+
+@keyframes fadeUp{
+ from{opacity:0;transform:translateY(28px)}
+ to{opacity:1;transform:none}
+}
+
+@keyframes scaleIn{
+ from{opacity:0;transform:scale(.92)}
+ to{opacity:1;transform:none}
+}
+
+@keyframes clipReveal{
+ from{clip-path:inset(0 100% 0 0)}
+ to{clip-path:inset(0)}
+}
+
+@keyframes float{
+ 0%,100%{transform:translateY(0)}
+ 50%{transform:translateY(-6px)}
+}
+
+.brand{
+ font-family:var(--font-display);
+ font-size:clamp(28px,5vw,50px);
+ letter-spacing:-0.02em;
+ color:var(--text);
+ margin-bottom:var(--space-xs);
+ animation:fadeUp .8s var(--ease-hero) both;
+}
+
+.brand span{
+ color:var(--accent);
+}
+
+.tagline{
+ font-size:12px;
+ text-transform:uppercase;
+ letter-spacing:0.18em;
+ color:var(--muted);
+ margin-bottom:var(--space-2xl);
+ animation:fadeUp .8s var(--ease-hero) 0.1s both;
+}
+
+.container{
+ width:100%;
+ max-width:420px;
+ display:flex;
+ flex-direction:column;
+ align-items:center;
+ gap:var(--space-xl);
+}
+
+/* Color Wheel */
+.wheel-container{
+ position:relative;
+ width:280px;
+ height:280px;
+ animation:scaleIn .9s var(--ease-hero) 0.2s both;
+}
+
+.wheel{
+ width:100%;
+ height:100%;
+ border-radius:50%;
+ background:conic-gradient(
+ from 0deg,
+ hsl(0,70%,50%),
+ hsl(35,80%,55%),
+ hsl(60,70%,50%),
+ hsl(120,60%,45%),
+ hsl(180,60%,45%),
+ hsl(220,60%,50%),
+ hsl(280,50%,50%),
+ hsl(320,60%,50%),
+ hsl(0,70%,50%)
+ );
+ position:relative;
+ box-shadow:
+ 0 0 0 2px var(--surface),
+ 0 0 60px rgba(201,169,97,0.15),
+ inset 0 0 80px rgba(0,0,0,0.4);
+ cursor:pointer;
+ touch-action:none;
+}
+
+.wheel::before{
+ content:'';
+ position:absolute;
+ inset:35px;
+ background:var(--bg);
+ border-radius:50%;
+ box-shadow:0 4px 20px rgba(0,0,0,0.5);
+}
+
+.wheel::after{
+ content:'DRAG';
+ position:absolute;
+ inset:0;
+ display:flex;
+ align-items:center;
+ justify-content:center;
+ font-size:11px;
+ letter-spacing:0.15em;
+ color:var(--muted);
+ pointer-events:none;
+}
+
+.selector{
+ position:absolute;
+ width:24px;
+ height:24px;
+ background:var(--text);
+ border:3px solid var(--accent);
+ border-radius:50%;
+ top:50%;
+ left:50%;
+ margin:-12px;
+ pointer-events:none;
+ box-shadow:0 4px 12px rgba(0,0,0,0.4);
+ transition:transform 0.1s var(--ease-swift);
+ z-index:10;
+}
+
+/* Color Info */
+.color-info{
+ text-align:center;
+ animation:fadeUp .8s var(--ease-hero) 0.3s both;
+}
+
+.color-name{
+ font-family:var(--font-display);
+ font-size:38px;
+ line-height:1.1;
+ margin-bottom:var(--space-xs);
+ transition:all 0.4s var(--ease-swift);
+}
+
+.color-meta{
+ font-size:12px;
+ letter-spacing:0.12em;
+ text-transform:uppercase;
+ color:var(--muted);
+}
+
+/* Pattern Filmstrip */
+.filmstrip{
+ width:100%;
+ display:grid;
+ grid-template-columns:repeat(4,1fr);
+ gap:var(--space-sm);
+ animation:fadeUp .8s var(--ease-hero) 0.4s both;
+}
+
+.pattern-card{
+ aspect-ratio:3/4;
+ background:var(--surface);
+ border-radius:var(--radius-card);
+ overflow:hidden;
+ position:relative;
+ cursor:pointer;
+ transition:transform 0.3s var(--ease-swift),box-shadow 0.3s var(--ease-swift);
+}
+
+.pattern-card:hover{
+ transform:translateY(-4px) scale(1.02);
+ box-shadow:0 12px 40px rgba(0,0,0,0.4);
+}
+
+.pattern-card::before{
+ content:'';
+ position:absolute;
+ inset:0;
+ opacity:0.9;
+ transition:all 0.5s var(--ease-swift);
+}
+
+/* Pattern 1: Trellis */
+.pattern-card:nth-child(1)::before{
+ background:
+ linear-gradient(90deg,transparent 48%,hsl(var(--selected-hue),var(--selected-sat),var(--selected-light)) 48%,hsl(var(--selected-hue),var(--selected-sat),var(--selected-light)) 52%,transparent 52%),
+ linear-gradient(0deg,transparent 48%,hsl(var(--selected-hue),var(--selected-sat),var(--selected-light)) 48%,hsl(var(--selected-hue),var(--selected-sat),var(--selected-light)) 52%,transparent 52%);
+ background-size:20px 20px;
+}
+
+/* Pattern 2: Stripes */
+.pattern-card:nth-child(2)::before{
+ background:repeating-linear-gradient(
+ 45deg,
+ hsl(var(--selected-hue),var(--selected-sat),var(--selected-light)),
+ hsl(var(--selected-hue),var(--selected-sat),var(--selected-light)) 4px,
+ transparent 4px,
+ transparent 12px
+ );
+}
+
+/* Pattern 3: Dots */
+.pattern-card:nth-child(3)::before{
+ background:radial-gradient(
+ circle at center,
+ hsl(var(--selected-hue),var(--selected-sat),var(--selected-light)) 2px,
+ transparent 2px
+ );
+ background-size:16px 16px;
+}
+
+/* Pattern 4: Herringbone */
+.pattern-card:nth-child(4)::before{
+ background:
+ linear-gradient(135deg,hsl(var(--selected-hue),var(--selected-sat),var(--selected-light)) 25%,transparent 25%) -10px 0,
+ linear-gradient(225deg,hsl(var(--selected-hue),var(--selected-sat),var(--selected-light)) 25%,transparent 25%) -10px 0,
+ linear-gradient(315deg,hsl(var(--selected-hue),var(--selected-sat),var(--selected-light)) 25%,transparent 25%),
+ linear-gradient(45deg,hsl(var(--selected-hue),var(--selected-sat),var(--selected-light)) 25%,transparent 25%);
+ background-size:20px 20px;
+}
+
+.pattern-label{
+ position:absolute;
+ bottom:0;
+ left:0;
+ right:0;
+ padding:var(--space-md) var(--space-sm);
+ background:linear-gradient(to top,rgba(13,13,15,0.95),transparent);
+ font-size:11px;
+ letter-spacing:0.08em;
+ text-transform:uppercase;
+ color:var(--text);
+}
+
+/* Share Bar */
+.share-bar{
+ width:100%;
+ display:flex;
+ gap:var(--space-sm);
+ justify-content:center;
+ margin-top:var(--space-lg);
+ animation:fadeUp .8s var(--ease-hero) 0.5s both;
+}
+
+.share-btn{
+ flex:1;
+ max-width:120px;
+ padding:var(--space-md) var(--space-lg);
+ border-radius:var(--radius-pill);
+ border:1px solid var(--surface);
+ background:transparent;
+ color:var(--text);
+ font-size:12px;
+ letter-spacing:0.1em;
+ text-transform:uppercase;
+ cursor:pointer;
+ transition:all 0.3s var(--ease-swift);
+ display:flex;
+ align-items:center;
+ justify-content:center;
+ gap:var(--space-sm);
+}
+
+.share-btn:hover{
+ background:var(--surface);
+ border-color:var(--accent);
+ transform:translateY(-2px);
+}
+
+.share-btn.primary{
+ background:var(--accent);
+ color:var(--bg);
+ border-color:var(--accent);
+ font-weight:600;
+}
+
+.share-btn.primary:hover{
+ background:var(--text);
+ border-color:var(--text);
+}
+
+/* Touch feedback */
+@media(hover:none){
+ .pattern-card:active{transform:scale(0.96)}
+ .share-btn:active{transform:scale(0.96)}
+}
+
+/* Utility */
+.glow{
+ position:fixed;
+ width:600px;
+ height:600px;
+ border-radius:50%;
+ background:radial-gradient(circle,hsla(var(--selected-hue),60%,50%,0.08),transparent 70%);
+ pointer-events:none;
+ top:50%;
+ left:50%;
+ transform:translate(-50%,-50%);
+ transition:all 0.8s var(--ease-power);
+ z-index:-1;
+}
+</style>
+</head>
+<body>
+<div class="glow" id="glow"></div>
+
+<div class="brand">LUXE<span>WALLS</span></div>
+<div class="tagline">Curated Wallcoverings</div>
+
+<div class="container">
+ <div class="wheel-container">
+ <div class="wheel" id="wheel" role="slider" aria-label="Color selector" aria-valuemin="0" aria-valuemax="360" aria-valuenow="35">
+ <div class="selector" id="selector"></div>
+ </div>
+ </div>
+
+ <div class="color-info">
+ <div class="color-name" id="colorName">Burnished Gold</div>
+ <div class="color-meta" id="colorMeta">HSL 35° · Collection 2024</div>
+ </div>
+
+ <div class="filmstrip">
+ <div class="pattern-card">
+ <div class="pattern-label">Trellis</div>
+ </div>
+ <div class="pattern-card">
+ <div class="pattern-label">Stripe</div>
+ </div>
+ <div class="pattern-card">
+ <div class="pattern-label">Pearl</div>
+ </div>
+ <div class="pattern-card">
+ <div class="pattern-label">Weave</div>
+ </div>
+ </div>
+
+ <div class="share-bar">
+ <button class="share-btn" onclick="share('save')">Save</button>
+ <button class="share-btn primary" onclick="share('shop')">Shop</button>
+ <button class="share-btn" onclick="share('share')">Share</button>
+ </div>
+</div>
+
+<script>
+const wheel=document.getElementById('wheel');
+const selector=document.getElementById('selector');
+const colorName=document.getElementById('colorName');
+const colorMeta=document.getElementById('colorMeta');
+const glow=document.getElementById('glow');
+const root=document.documentElement;
+
+// Designer color names mapped to hue ranges
+const colorNames=[
+ {name:'Ember',hue:0,range:15},
+ {name:'Terracotta',hue:20,range:15},
+ {name:'Burnished Gold',hue:35,range:15},
+ {name:'Champagne',hue:50,range:15},
+ {name:'Sage',hue:120,range:25},
+ {name:'Patina',hue:160,range:20},
+ {name:'Midnight',hue:220,range:30},
+ {name:'Lavender',hue:270,range:25},
+ {name:'Bordeaux',hue:340,range:20}
+];
+
+let isDragging=false;
+let currentHue=35;
+
+function getColorName(hue){
+ const normalized=hue<0?hue+360:(hue>=360?hue-360:hue);
+ let closest=colorNames[0];
+ let minDist=360;
+ colorNames.forEach(c=>{
+ const dist=Math.abs(((normalized-c.hue+180)%360)-180);
+ if(dist<minDist){minDist=dist;closest=c;}
+ });
+ return closest;
+}
+
+function updateColor(hue){
+ currentHue=hue;
+ const color=getColorName(hue);
+
+ // Update CSS variables for pattern colors
+ root.style.setProperty('--selected-hue',hue);
+
+ // Adjust saturation/lightness based on hue for luxury feel
+ let sat=65;
+ let light=45;
+ if(hue>200&&hue<260){sat=55;light=40;}// deeper blues
+ if(hue>30&&hue<60){sat=75;light=50;}// warm golds
+
+ root.style.setProperty('--selected-sat',sat+'%');
+ root.style.setProperty('--selected-light',light+'%');
+
+ colorName.textContent=color.name;
+ colorMeta.textContent=`HSL ${Math.round(hue)}° · Collection 2024`;
+
+ // Move selector
+ const rad=(hue-90)*Math.PI/180;
+ const radius=105;
+ const x=Math.cos(rad)*radius;
+ const y=Math.sin(rad)*radius;
+ selector.style.transform=`translate(${x}px,${y}px)`;
+
+ // Update glow position slightly for dynamism
+ glow.style.transform=`translate(calc(-50% + ${x*0.3}px),calc(-50% + ${y*0.3}px))`;
+}
+
+function handleInteraction(e){
+ const rect=wheel.getBoundingClientRect();
+ const centerX=rect.left+rect.width/2;
+ const centerY=rect.top+rect.height/2;
+
+ const clientX=e.touches?e.touches[0].clientX:e.clientX;
+ const clientY=e.touches?e.touches[0].clientY:e.clientY;
+
+ const dx=clientX-centerX;
+ const dy=clientY-centerY;
+ let angle=Math.atan2(dy,dx)*180/Math.PI+90;
+ if(angle<0)angle+=360;
+
+ updateColor(angle);
+}
+
+wheel.addEventListener('mousedown',(e)=>{
+ isDragging=true;
+ handleInteraction(e);
+ wheel.style.cursor='grabbing';
+});
+
+wheel.addEventListener('touchstart',(e)=>{
+ isDragging=true;
+ handleInteraction(e);
+},{passive:false});
+
+document.addEventListener('mousemove',(e)=>{
+ if(!isDragging)return;
+ e.preventDefault();
+ handleInteraction(e);
+});
+
+document.addEventListener('touchmove',(e)=>{
+ if(!isDragging)return;
+ e.preventDefault();
+ handleInteraction(e);
+},{passive:false});
+
+document.addEventListener('mouseup',()=>{
+ isDragging=false;
+ wheel.style.cursor='pointer';
+});
+
+document.addEventListener('touchend',()=>{
+ isDragging=false;
+});
+
+// Initialize
+updateColor(35);
+
+function share(type){
+ const names=['Ember','Terracotta','Burnished Gold','Champagne','Sage','Patina','Midnight','Lavender','Bordeaux'];
+ const currentColor=getColorName(currentHue);
+
+ if(type==='shop'){
+ alert(`Viewing ${currentColor.name} wallcoverings in your selected hue.`);
+ }else if(type==='save'){
+ const btn=event.target;
+ const original=btn.textContent;
+ btn.textContent='Saved!';
+ btn.style.borderColor='var(--accent)';
+ setTimeout(()=>{
+ btn.textContent=original;
+ btn.style.borderColor='';
+ },1500);
+ }else{
+ if(navigator.share){
+ navigator.share({
+ title:'Luxe Walls - Shop by Color',
+ text:`Discover wallcoverings in ${currentColor.name}`,
+ url:window.location.href
+ });
+ }else{
+ alert(`Share this ${currentColor.name} color selection!`);
+ }
+ }
+}
+
+// Animate on load
+document.querySelectorAll('.pattern-card').forEach((card,i)=>{
+ card.style.animationDelay=`${0.4+i*0.1}s`;
+});
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/b3721d9fe563/kimi.png b/data/artifacts/b3721d9fe563/kimi.png
new file mode 100644
index 0000000..d372276
Binary files /dev/null and b/data/artifacts/b3721d9fe563/kimi.png differ
diff --git a/data/challenges.json b/data/challenges.json
index 5c4da80..cf79a4a 100644
--- a/data/challenges.json
+++ b/data/challenges.json
@@ -14095,7 +14095,9 @@
"aiSpread": 0
}
],
- "judging": true
+ "judging": false,
+ "aiPick": "hf-qwen-coder-32b",
+ "judged_at": "2026-07-25T08:27:37.319Z"
},
{
"id": "f2e21f2299c9",
@@ -14268,7 +14270,14 @@
"finished_at": "2026-07-25T07:30:13.394Z",
"queued_at": "2026-07-25T07:27:10.414Z",
"bytes": 18675,
- "thumb": true
+ "thumb": true,
+ "aiScore": 9.3,
+ "aiReason": "The dashboard is visually appealing and fulfills all the requirements of the challenge. The odometer animations are smooth, and the layout is clean.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 9.5
+ },
+ "aiSpread": 0.5
}
],
"judging": true
@@ -14518,7 +14527,14 @@
"finished_at": "2026-07-25T07:39:33.281Z",
"queued_at": "2026-07-25T07:39:01.071Z",
"bytes": 4190,
- "thumb": true
+ "thumb": true,
+ "aiScore": 6.3,
+ "aiReason": "The HTML file is present but lacks content and styling necessary for the Instagram carousel.",
+ "aiScores": {
+ "qwen2.5vl:7b": 5,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 2.5
},
{
"model": "hf-qwen-coder-32b",
@@ -14659,7 +14675,14 @@
"finished_at": "2026-07-25T07:40:03.061Z",
"queued_at": "2026-07-25T07:39:01.158Z",
"bytes": 4104,
- "thumb": true
+ "thumb": true,
+ "aiScore": 6.5,
+ "aiReason": "The HTML file is functional but lacks the animated captions and polished aesthetic required for a high score.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 6
+ },
+ "aiSpread": 1
},
{
"model": "hf-qwen-coder-32b",
@@ -14800,7 +14823,14 @@
"finished_at": "2026-07-25T07:40:59.083Z",
"queued_at": "2026-07-25T07:39:01.234Z",
"bytes": 5364,
- "thumb": true
+ "thumb": true,
+ "aiScore": 5.5,
+ "aiReason": "The HTML file is present but lacks the visual elements and interactivity required for a relaxing tile-matching puzzle.",
+ "aiScores": {
+ "qwen2.5vl:7b": 5,
+ "minicpm-v:latest": 6
+ },
+ "aiSpread": 1
},
{
"model": "hf-qwen-coder-32b",
@@ -14923,7 +14953,14 @@
"finished_at": "2026-07-25T07:50:39.802Z",
"queued_at": "2026-07-25T07:39:01.308Z",
"bytes": 5966,
- "thumb": true
+ "thumb": true,
+ "aiScore": 6.5,
+ "aiReason": "The post is visually appealing and functional but lacks the realistic Instagram chrome elements.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 6
+ },
+ "aiSpread": 1
},
{
"model": "qwen25-7b",
@@ -15058,7 +15095,14 @@
"finished_at": "2026-07-25T07:56:58.834Z",
"queued_at": "2026-07-25T07:49:29.678Z",
"bytes": 5260,
- "thumb": true
+ "thumb": true,
+ "aiScore": 7.5,
+ "aiReason": "The page fulfills the challenge well and has good visual quality.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 6
+ },
+ "aiSpread": 3
},
{
"model": "qwen25-7b",
@@ -15127,7 +15171,14 @@
"finished_at": "2026-07-25T08:02:11.118Z",
"queued_at": "2026-07-25T07:49:49.661Z",
"bytes": 7000,
- "thumb": true
+ "thumb": true,
+ "aiScore": 6.8,
+ "aiReason": "The simulation works well but lacks polish and visual quality in terms of color variety and smoothness.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 0.5
},
{
"model": "hermes3-8b",
@@ -15216,7 +15267,14 @@
"finished_at": "2026-07-25T08:06:45.893Z",
"queued_at": "2026-07-25T07:49:49.743Z",
"bytes": 12798,
- "thumb": true
+ "thumb": true,
+ "aiScore": 9.3,
+ "aiReason": "The HTML is well-structured and visually appealing, fulfilling the challenge requirements effectively.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 9.5
+ },
+ "aiSpread": 0.5
},
{
"model": "hermes3-8b",
@@ -15350,7 +15408,14 @@
"finished_at": "2026-07-25T08:12:15.373Z",
"queued_at": "2026-07-25T08:01:12.462Z",
"bytes": 8248,
- "thumb": true
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The page is fully functional and visually appealing with smooth color cycling and a polished interface.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 1.5
},
{
"model": "gemma3-12b",
@@ -15427,7 +15492,14 @@
"hyperframes"
],
"bytes": 5409,
- "thumb": true
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The HTML file successfully fulfills the challenge requirements and has a clean, minimalist design.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 1.5
},
{
"model": "gemma3-12b",
@@ -15578,7 +15650,14 @@
"opendesign"
],
"bytes": 5902,
- "thumb": true
+ "thumb": true,
+ "aiScore": 6.8,
+ "aiReason": "The design fulfills the mandatory requirements but lacks vibrancy and contrast in the palette.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 0.5
},
{
"model": "gemma3-12b",
@@ -15758,15 +15837,18 @@
},
{
"model": "hf-qwen-coder-32b",
- "status": "running",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
+ "seconds": 93,
+ "cost": 0,
"started_at": "2026-07-25T08:25:20.784Z",
- "finished_at": null,
- "queued_at": "2026-07-25T08:12:12.488Z"
+ "finished_at": "2026-07-25T08:26:54.225Z",
+ "queued_at": "2026-07-25T08:12:12.488Z",
+ "bytes": 7744,
+ "thumb": true
}
- ]
+ ],
+ "judging": true
},
{
"id": "8443d9162963",
@@ -15780,33 +15862,39 @@
"runs": [
{
"model": "qwen3-14b",
- "status": "queued",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
- "started_at": null,
- "finished_at": null,
- "queued_at": "2026-07-25T08:23:13.392Z"
+ "seconds": 54,
+ "cost": 0,
+ "started_at": "2026-07-25T08:27:56.207Z",
+ "finished_at": "2026-07-25T08:28:49.791Z",
+ "queued_at": "2026-07-25T08:23:13.392Z",
+ "bytes": 7090,
+ "thumb": true
},
{
"model": "gemma3-12b",
- "status": "queued",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
- "started_at": null,
- "finished_at": null,
- "queued_at": "2026-07-25T08:23:13.402Z"
+ "seconds": 58,
+ "cost": 0,
+ "started_at": "2026-07-25T08:28:49.800Z",
+ "finished_at": "2026-07-25T08:29:48.033Z",
+ "queued_at": "2026-07-25T08:23:13.402Z",
+ "bytes": 7241,
+ "thumb": true
},
{
"model": "hermes3-8b",
- "status": "queued",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
- "started_at": null,
- "finished_at": null,
- "queued_at": "2026-07-25T08:23:13.409Z"
+ "seconds": 28,
+ "cost": 0,
+ "started_at": "2026-07-25T08:29:48.040Z",
+ "finished_at": "2026-07-25T08:30:16.014Z",
+ "queued_at": "2026-07-25T08:23:13.409Z",
+ "bytes": 6138,
+ "thumb": true
},
{
"model": "qwen25-7b",
@@ -15822,15 +15910,18 @@
},
{
"model": "hf-qwen-coder-32b",
- "status": "queued",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
- "started_at": null,
- "finished_at": null,
- "queued_at": "2026-07-25T08:23:13.419Z"
+ "seconds": 110,
+ "cost": 0,
+ "started_at": "2026-07-25T08:30:16.023Z",
+ "finished_at": "2026-07-25T08:32:05.876Z",
+ "queued_at": "2026-07-25T08:23:13.419Z",
+ "bytes": 6636,
+ "thumb": true
}
- ]
+ ],
+ "judging": true
},
{
"id": "6923e6180f0f",
@@ -15843,11 +15934,11 @@
"runs": [
{
"model": "qwen3-14b",
- "status": "queued",
+ "status": "running",
"error": null,
"seconds": null,
"cost": null,
- "started_at": null,
+ "started_at": "2026-07-25T08:32:05.879Z",
"finished_at": null,
"queued_at": "2026-07-25T08:23:13.482Z"
},
@@ -15942,13 +16033,20 @@
},
{
"model": "grok",
- "status": "running",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
+ "seconds": 151,
+ "cost": 0.1836,
"started_at": "2026-07-25T08:23:13.533Z",
- "finished_at": null,
- "queued_at": "2026-07-25T08:23:13.518Z"
+ "finished_at": "2026-07-25T08:25:44.421Z",
+ "queued_at": "2026-07-25T08:23:13.518Z",
+ "toolCalls": [
+ "opendesign",
+ "opendesign",
+ "hyperframes"
+ ],
+ "bytes": 32372,
+ "thumb": true
}
]
},
@@ -16015,43 +16113,68 @@
},
{
"model": "claude-code",
- "status": "running",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
+ "seconds": 140,
+ "cost": 0,
"started_at": "2026-07-25T08:24:46.963Z",
- "finished_at": null,
- "queued_at": "2026-07-25T08:23:13.590Z"
+ "finished_at": "2026-07-25T08:27:07.225Z",
+ "queued_at": "2026-07-25T08:23:13.590Z",
+ "bytes": 24309,
+ "thumb": true
},
{
"model": "kimi",
- "status": "running",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
+ "seconds": 96,
+ "cost": 0.0145,
"started_at": "2026-07-25T08:24:58.607Z",
- "finished_at": null,
- "queued_at": "2026-07-25T08:23:13.593Z"
+ "finished_at": "2026-07-25T08:26:34.639Z",
+ "queued_at": "2026-07-25T08:23:13.593Z",
+ "toolCalls": [
+ "opendesign",
+ "opendesign",
+ "hyperframes",
+ "opendesign",
+ "hyperframes"
+ ],
+ "bytes": 13005,
+ "thumb": true
},
{
"model": "gpt",
- "status": "running",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
+ "seconds": 57,
+ "cost": 0.1212,
"started_at": "2026-07-25T08:24:29.217Z",
- "finished_at": null,
- "queued_at": "2026-07-25T08:23:13.595Z"
+ "finished_at": "2026-07-25T08:25:26.197Z",
+ "queued_at": "2026-07-25T08:23:13.595Z",
+ "toolCalls": [
+ "opendesign",
+ "opendesign",
+ "hyperframes"
+ ],
+ "bytes": 25232,
+ "thumb": true
},
{
"model": "grok",
- "status": "queued",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
- "started_at": null,
- "finished_at": null,
- "queued_at": "2026-07-25T08:23:13.598Z"
+ "seconds": 117,
+ "cost": 0.1481,
+ "started_at": "2026-07-25T08:25:44.430Z",
+ "finished_at": "2026-07-25T08:27:41.109Z",
+ "queued_at": "2026-07-25T08:23:13.598Z",
+ "toolCalls": [
+ "opendesign",
+ "opendesign",
+ "hyperframes"
+ ],
+ "bytes": 26485,
+ "thumb": true
}
]
},
@@ -16096,13 +16219,15 @@
},
{
"model": "qwen25-7b",
- "status": "running",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
+ "seconds": 26,
+ "cost": 0,
"started_at": "2026-07-25T08:25:23.414Z",
- "finished_at": null,
- "queued_at": "2026-07-25T08:25:23.399Z"
+ "finished_at": "2026-07-25T08:25:49.019Z",
+ "queued_at": "2026-07-25T08:25:23.399Z",
+ "bytes": 3481,
+ "thumb": true
},
{
"model": "hf-qwen-coder-32b",
@@ -16116,43 +16241,66 @@
},
{
"model": "claude-code",
- "status": "queued",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
- "started_at": null,
- "finished_at": null,
- "queued_at": "2026-07-25T08:25:23.403Z"
+ "seconds": 212,
+ "cost": 0,
+ "started_at": "2026-07-25T08:27:07.229Z",
+ "finished_at": "2026-07-25T08:30:38.767Z",
+ "queued_at": "2026-07-25T08:25:23.403Z",
+ "bytes": 15769,
+ "thumb": true
},
{
"model": "kimi",
- "status": "queued",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
- "started_at": null,
- "finished_at": null,
- "queued_at": "2026-07-25T08:25:23.406Z"
+ "seconds": 100,
+ "cost": 0.0167,
+ "started_at": "2026-07-25T08:26:34.657Z",
+ "finished_at": "2026-07-25T08:28:15.087Z",
+ "queued_at": "2026-07-25T08:25:23.406Z",
+ "toolCalls": [
+ "opendesign",
+ "opendesign",
+ "hyperframes"
+ ],
+ "bytes": 18317,
+ "thumb": true
},
{
"model": "gpt",
- "status": "queued",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
- "started_at": null,
- "finished_at": null,
- "queued_at": "2026-07-25T08:25:23.409Z"
+ "seconds": 65,
+ "cost": 0.1145,
+ "started_at": "2026-07-25T08:25:26.200Z",
+ "finished_at": "2026-07-25T08:26:31.239Z",
+ "queued_at": "2026-07-25T08:25:23.409Z",
+ "toolCalls": [
+ "opendesign",
+ "opendesign",
+ "hyperframes"
+ ],
+ "bytes": 24844,
+ "thumb": true
},
{
"model": "grok",
- "status": "queued",
+ "status": "done",
"error": null,
- "seconds": null,
- "cost": null,
- "started_at": null,
- "finished_at": null,
- "queued_at": "2026-07-25T08:25:23.412Z"
+ "seconds": 95,
+ "cost": 0.1285,
+ "started_at": "2026-07-25T08:27:41.115Z",
+ "finished_at": "2026-07-25T08:29:16.515Z",
+ "queued_at": "2026-07-25T08:25:23.412Z",
+ "toolCalls": [
+ "opendesign",
+ "opendesign",
+ "hyperframes"
+ ],
+ "bytes": 22768,
+ "thumb": true
}
]
}
diff --git a/data/costlog.jsonl b/data/costlog.jsonl
index 261196c..1d6112a 100644
--- a/data/costlog.jsonl
+++ b/data/costlog.jsonl
@@ -224,3 +224,10 @@
{"ts":"2026-07-25T08:17:12.970Z","provider":"adobe","model":"gemini-image","task":"model-arena-tools","cost_usd":0.04}
{"ts":"2026-07-25T08:24:29.171Z","provider":"openai","model":"gpt-5.1","task":"model-arena-tools","input_tokens":2368,"output_tokens":10704,"cost_usd":0.154}
{"ts":"2026-07-25T08:24:58.596Z","provider":"moonshot","model":"kimi-k2.5","task":"model-arena-tools","input_tokens":2341,"output_tokens":6535,"cost_usd":0.017742}
+{"ts":"2026-07-25T08:25:26.196Z","provider":"openai","model":"gpt-5.1","task":"model-arena-tools","input_tokens":2207,"output_tokens":8382,"cost_usd":0.12121}
+{"ts":"2026-07-25T08:25:44.418Z","provider":"xai","model":"grok-4.5","task":"model-arena-tools","input_tokens":2879,"output_tokens":11661,"cost_usd":0.183552}
+{"ts":"2026-07-25T08:26:31.236Z","provider":"openai","model":"gpt-5.1","task":"model-arena-tools","input_tokens":2391,"output_tokens":7882,"cost_usd":0.114532}
+{"ts":"2026-07-25T08:26:34.636Z","provider":"moonshot","model":"kimi-k2.5","task":"model-arena-tools","input_tokens":4460,"output_tokens":4726,"cost_usd":0.014491}
+{"ts":"2026-07-25T08:27:41.106Z","provider":"xai","model":"grok-4.5","task":"model-arena-tools","input_tokens":3245,"output_tokens":9224,"cost_usd":0.148095}
+{"ts":"2026-07-25T08:28:15.085Z","provider":"moonshot","model":"kimi-k2.5","task":"model-arena-tools","input_tokens":2490,"output_tokens":6094,"cost_usd":0.016729}
+{"ts":"2026-07-25T08:29:16.510Z","provider":"xai","model":"grok-4.5","task":"model-arena-tools","input_tokens":3100,"output_tokens":7944,"cost_usd":0.12846}
← 530376d Systemic beauty fix: run-next appends DW LUXURY-UI shell (wo
·
back to Model Arena
·
night-loop: cycle 01:34 — judged=8443d9162963 · (backlog 4/4 816d048 →