forked from mikael-lovqvist/websperiments
- Remove Pipeline stage, Palette, Mesh depth, Relaxation UI sections - Remove Build button; mesh always builds as relaxed Goldberg depth-5 - Hardcode get_params() — no DOM reads for build parameters - Remove current_stage; use_relaxed is always true - Tool buttons no longer toggle paint off when re-clicking active tool Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
256 lines
14 KiB
HTML
256 lines
14 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title>Goldberg Polyhedron Paint</title>
|
||
<style>
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
body { background: #080c10; color: #bcd; font-family: monospace; font-size: 13px;
|
||
display: flex; height: 100vh; overflow: hidden; }
|
||
|
||
/* ── sidebar ───────────────────────────────────────── */
|
||
#sidebar {
|
||
width: 240px; min-width: 240px;
|
||
background: #0c1218; border-right: 1px solid #1a2a3a;
|
||
padding: 10px; overflow-y: auto;
|
||
display: flex; flex-direction: column; gap: 12px;
|
||
}
|
||
h3 { color: #4af; font-size: 12px; text-transform: uppercase;
|
||
letter-spacing: 1px; border-bottom: 1px solid #1a2a3a; padding-bottom: 4px; }
|
||
|
||
/* stage buttons */
|
||
.stage-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 3px; }
|
||
.sb {
|
||
background: #0f1e2a; border: 1px solid #2a3a4a; color: #7ab;
|
||
padding: 6px 2px; cursor: pointer; font-family: monospace; font-size: 11px;
|
||
text-align: center; line-height: 1.3;
|
||
}
|
||
.sb:hover { background: #1a3040; color: #cef; }
|
||
.sb.active { background: #0a2d4a; border-color: #4af; color: #4af; }
|
||
|
||
/* param rows */
|
||
.param { display: flex; align-items: center; gap: 6px; }
|
||
.param label { width: 80px; color: #7ab; font-size: 11px; flex-shrink: 0; }
|
||
.param input[type=range] { flex: 1; max-width: 90px; }
|
||
.param input[type=number] {
|
||
width: 72px; background: #091520; border: 1px solid #1e3448; color: #cdf;
|
||
padding: 3px 5px; font-family: monospace; font-size: 12px;
|
||
}
|
||
.param select {
|
||
flex: 1; background: #091520; border: 1px solid #1e3448; color: #cdf;
|
||
padding: 3px 5px; font-family: monospace; font-size: 12px;
|
||
}
|
||
.val { width: 24px; text-align: right; color: #cdf; font-size: 11px; }
|
||
|
||
#spin-btn.active { background: #0a2d4a; border-color: #4af; color: #4af; }
|
||
.swatch { width: 18px; height: 18px; cursor: pointer; border: 1px solid #1a2a3a; flex-shrink: 0; }
|
||
.swatch:hover { outline: 2px solid #fff; outline-offset: 1px; }
|
||
#cd-swatch-grid .swatch { width: 28px; height: 28px; }
|
||
.swatch-tab { flex: 1; background: #0a1a2a; border: 1px solid #2a4a6a; color: #7ab; padding: 3px 2px; cursor: pointer; font-family: monospace; font-size: 10px; text-align: center; }
|
||
.swatch-tab.active { background: #0a2d4a; border-color: #4af; color: #4af; }
|
||
|
||
/* ── color picker dialog ─────────────────────────────── */
|
||
#color-dialog {
|
||
background: #0c1218; border: 1px solid #2a4a6a; color: #bcd;
|
||
padding: 16px; width: 420px; border-radius: 3px;
|
||
font-family: monospace; font-size: 12px;
|
||
position: fixed; inset: 0; margin: auto; height: fit-content;
|
||
}
|
||
#color-dialog::backdrop { background: rgba(0,0,0,0.65); }
|
||
.cd-model-tab {
|
||
flex: 1; background: #0a1a2a; border: 1px solid #2a4a6a; color: #7ab;
|
||
padding: 4px 2px; cursor: pointer; font-family: monospace; font-size: 11px; text-align: center;
|
||
}
|
||
.cd-model-tab.active { background: #0a2d4a; border-color: #4af; color: #4af; }
|
||
.cd-row { display: flex; align-items: center; gap: 6px; margin-top: 6px; }
|
||
.cd-row label { width: 16px; color: #7ab; font-size: 11px; flex-shrink: 0; }
|
||
.cd-row input[type=range] { flex: 1; }
|
||
.cd-row .val { width: 28px; text-align: right; color: #cdf; font-size: 11px; }
|
||
#paint-btn.active, #paint-brush-btn.active, #paint-circle-btn.active, #paint-fill-btn.active, #paint-line-btn.active, #paint-pick-btn.active { background: #1a2d0a; border-color: #8d4; color: #8d4; }
|
||
|
||
#build-btn {
|
||
background: #0a2d4a; border: 1px solid #4af; color: #4af;
|
||
padding: 7px; cursor: pointer; font-family: monospace; font-size: 13px; width: 100%;
|
||
}
|
||
#build-btn:hover { background: #1a3d5a; }
|
||
#build-btn:disabled { opacity: 0.45; cursor: default; }
|
||
|
||
#status { color: #fa8; font-size: 11px; min-height: 14px; }
|
||
#stats { color: #6a9; font-size: 11px; line-height: 1.7; white-space: pre; }
|
||
|
||
/* ── canvas ─────────────────────────────────────────── */
|
||
#canvas-wrap { flex: 1; position: relative; overflow: hidden; }
|
||
canvas { display: block; width: 100%; height: 100%; cursor: grab; }
|
||
canvas:active { cursor: grabbing; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="sidebar">
|
||
<div>
|
||
<h3>Sphere Playground</h3>
|
||
</div>
|
||
|
||
<div>
|
||
<h3>Paint</h3>
|
||
<div style="display:flex; gap:4px; margin-top:6px">
|
||
<button id="brush-tab-left" style="flex:1; background:#0a2d4a; border:1px solid #4af; color:#4af; padding:5px; cursor:pointer; font-family:monospace; font-size:11px">◀ Left</button>
|
||
<button id="brush-tab-right" style="flex:1; background:#0a1a2a; border:1px solid #2a4a6a; color:#7ab; padding:5px; cursor:pointer; font-family:monospace; font-size:11px">Right ▶</button>
|
||
</div>
|
||
<div class="param" style="margin-top:6px">
|
||
<label>Color</label>
|
||
<div id="paint-color-btn" style="flex:1; height:26px; border:1px solid #1e3448; cursor:pointer; border-radius:2px;" title="Pick color…"></div>
|
||
</div>
|
||
<div id="sidebar-swatch-tabs" style="display:flex; gap:2px; margin-top:4px"></div>
|
||
<div id="sidebar-swatch-grid" style="display:flex; flex-wrap:wrap; gap:2px; margin-top:3px"></div>
|
||
<div class="param">
|
||
<label>Metallic</label>
|
||
<input type="range" id="metallic" min="0" max="1" step="0.01" value="0">
|
||
<span class="val" id="metallic-val">0.00</span>
|
||
</div>
|
||
<div class="param">
|
||
<label>Roughness</label>
|
||
<input type="range" id="roughness" min="0" max="1" step="0.01" value="0.6">
|
||
<span class="val" id="roughness-val">0.60</span>
|
||
</div>
|
||
<div style="display:flex; gap:3px; margin-top:6px; flex-wrap:wrap">
|
||
<button id="paint-btn" style="flex:1; background:#0a1a2a; border:1px solid #2a4a6a; color:#7ab; padding:5px 2px; cursor:pointer; font-family:monospace; font-size:11px">✏ Pen</button>
|
||
<button id="paint-brush-btn" style="flex:1; background:#0a1a2a; border:1px solid #2a4a6a; color:#7ab; padding:5px 2px; cursor:pointer; font-family:monospace; font-size:11px">⬤ Brush</button>
|
||
<button id="paint-circle-btn" style="flex:1; background:#0a1a2a; border:1px solid #2a4a6a; color:#7ab; padding:5px 2px; cursor:pointer; font-family:monospace; font-size:11px">○ Circle</button>
|
||
<button id="paint-fill-btn" style="flex:1; background:#0a1a2a; border:1px solid #2a4a6a; color:#7ab; padding:5px 2px; cursor:pointer; font-family:monospace; font-size:11px">⬛ Fill</button>
|
||
<button id="paint-line-btn" style="flex:1; background:#0a1a2a; border:1px solid #2a4a6a; color:#7ab; padding:5px 2px; cursor:pointer; font-family:monospace; font-size:11px">╱ Line</button>
|
||
<button id="paint-pick-btn" style="flex:1; background:#0a1a2a; border:1px solid #2a4a6a; color:#7ab; padding:5px 2px; cursor:pointer; font-family:monospace; font-size:11px">✦ Pick</button>
|
||
<button id="paint-clear" style="background:#1a0a0a; border:1px solid #4a2a2a; color:#a66; padding:5px 6px; cursor:pointer; font-family:monospace; font-size:11px">✕</button>
|
||
</div>
|
||
<div class="param" id="brush-params" style="display:none; margin-top:4px">
|
||
<label>Radius</label>
|
||
<input type="range" id="brush-radius" min="0" max="10" step="1" value="2">
|
||
<span class="val" id="brush-radius-val">2</span>
|
||
</div>
|
||
<div style="display:flex; gap:4px; margin-top:4px">
|
||
<button id="paint-save" style="flex:1; background:#0a1a2a; border:1px solid #2a4a6a; color:#7ab; padding:6px; cursor:pointer; font-family:monospace; font-size:12px">↓ Save</button>
|
||
<button id="paint-load" style="flex:1; background:#0a1a2a; border:1px solid #2a4a6a; color:#7ab; padding:6px; cursor:pointer; font-family:monospace; font-size:12px">↑ Load</button>
|
||
<input type="file" id="paint-load-input" accept=".json" style="display:none">
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<h3>Noise</h3>
|
||
<div style="display:flex; gap:3px; margin-top:6px; flex-wrap:wrap">
|
||
<button class="noise-preset" data-preset="flat" style="flex:1; background:#0a1a2a; border:1px solid #2a4a6a; color:#7ab; padding:4px 2px; cursor:pointer; font-family:monospace; font-size:10px">Flat</button>
|
||
<button class="noise-preset" data-preset="snow" style="flex:1; background:#0a1a2a; border:1px solid #2a4a6a; color:#7ab; padding:4px 2px; cursor:pointer; font-family:monospace; font-size:10px">Snow</button>
|
||
<button class="noise-preset" data-preset="water" style="flex:1; background:#0a1a2a; border:1px solid #2a4a6a; color:#7ab; padding:4px 2px; cursor:pointer; font-family:monospace; font-size:10px">Water</button>
|
||
<button class="noise-preset" data-preset="dunes" style="flex:1; background:#0a1a2a; border:1px solid #2a4a6a; color:#7ab; padding:4px 2px; cursor:pointer; font-family:monospace; font-size:10px">Dunes</button>
|
||
<button class="noise-preset" data-preset="rock" style="flex:1; background:#0a1a2a; border:1px solid #2a4a6a; color:#7ab; padding:4px 2px; cursor:pointer; font-family:monospace; font-size:10px">Rock</button>
|
||
</div>
|
||
<div class="param" style="margin-top:4px">
|
||
<label>Scale</label>
|
||
<input type="range" id="noise-scale" min="0" max="20" step="0.5" value="0">
|
||
<span class="val" id="noise-scale-val">0.0</span>
|
||
</div>
|
||
<div class="param">
|
||
<label>Strength</label>
|
||
<input type="range" id="noise-strength" min="0" max="3" step="0.05" value="0">
|
||
<span class="val" id="noise-strength-val">0.00</span>
|
||
</div>
|
||
<div class="param">
|
||
<label>Octaves</label>
|
||
<input type="range" id="noise-octaves" min="1" max="8" step="1" value="4">
|
||
<span class="val" id="noise-octaves-val">4</span>
|
||
</div>
|
||
<div class="param">
|
||
<label>Gain</label>
|
||
<input type="range" id="noise-gain" min="0.2" max="0.8" step="0.05" value="0.5">
|
||
<span class="val" id="noise-gain-val">0.50</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<h3>Environment</h3>
|
||
<div style="color:#5a8; font-size:10px; margin-top:4px; margin-bottom:2px; letter-spacing:0.5px">LIGHT 1</div>
|
||
<div class="param">
|
||
<label>Azimuth</label>
|
||
<input type="range" id="l1-azim" min="-180" max="180" step="1" value="27">
|
||
<span class="val" id="l1-azim-val">27°</span>
|
||
</div>
|
||
<div class="param">
|
||
<label>Elevation</label>
|
||
<input type="range" id="l1-elev" min="-90" max="90" step="1" value="34">
|
||
<span class="val" id="l1-elev-val">34°</span>
|
||
</div>
|
||
<div class="param">
|
||
<label>Color</label>
|
||
<div id="l1-color-btn" style="flex:1; height:22px; border:1px solid #1e3448; cursor:pointer; border-radius:2px;" title="Pick color…"></div>
|
||
<input type="range" id="l1-intensity" min="0" max="4" step="0.05" value="1.5" style="flex:1; max-width:60px">
|
||
<span class="val" id="l1-intensity-val">1.50</span>
|
||
</div>
|
||
<div style="color:#5a8; font-size:10px; margin-top:6px; margin-bottom:2px; letter-spacing:0.5px">LIGHT 2</div>
|
||
<div class="param">
|
||
<label>Azimuth</label>
|
||
<input type="range" id="l2-azim" min="-180" max="180" step="1" value="-58">
|
||
<span class="val" id="l2-azim-val">-58°</span>
|
||
</div>
|
||
<div class="param">
|
||
<label>Elevation</label>
|
||
<input type="range" id="l2-elev" min="-90" max="90" step="1" value="-28">
|
||
<span class="val" id="l2-elev-val">-28°</span>
|
||
</div>
|
||
<div class="param">
|
||
<label>Color</label>
|
||
<div id="l2-color-btn" style="flex:1; height:22px; border:1px solid #1e3448; cursor:pointer; border-radius:2px;" title="Pick color…"></div>
|
||
<input type="range" id="l2-intensity" min="0" max="4" step="0.05" value="0.5" style="flex:1; max-width:60px">
|
||
<span class="val" id="l2-intensity-val">0.50</span>
|
||
</div>
|
||
<div style="color:#5a8; font-size:10px; margin-top:6px; margin-bottom:2px; letter-spacing:0.5px">AMBIENT</div>
|
||
<div class="param">
|
||
<label>Color</label>
|
||
<div id="amb-color-btn" style="flex:1; height:22px; border:1px solid #1e3448; cursor:pointer; border-radius:2px;" title="Pick color…"></div>
|
||
<input type="range" id="amb-intensity" min="0" max="4" step="0.05" value="0.07" style="flex:1; max-width:60px">
|
||
<span class="val" id="amb-intensity-val">0.07</span>
|
||
</div>
|
||
</div>
|
||
|
||
<button id="spin-btn" style="background:#0a1a2a; border:1px solid #2a4a6a; color:#7ab; padding:7px; cursor:pointer; font-family:monospace; font-size:13px; width:100%">↻ Auto-spin</button>
|
||
|
||
<div>
|
||
<div id="status">Ready</div>
|
||
<div id="stats"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="canvas-wrap">
|
||
<canvas id="gl-canvas"></canvas>
|
||
</div>
|
||
|
||
<dialog id="color-dialog">
|
||
<div id="cd-preview" style="height:32px; border-radius:2px; margin-bottom:10px; border:1px solid #2a4a6a;"></div>
|
||
<div style="display:flex; gap:3px; margin-bottom:2px">
|
||
<button class="cd-model-tab active" data-model="hsl">HSL</button>
|
||
<button class="cd-model-tab" data-model="rgb">RGB</button>
|
||
<button class="cd-model-tab" data-model="hex">Hex</button>
|
||
</div>
|
||
<div id="cd-panel-hsl">
|
||
<div class="cd-row"><label>H</label><input type="range" id="cd-h" min="0" max="360" step="1"><span class="val" id="cd-h-val">0</span></div>
|
||
<div class="cd-row"><label>S</label><input type="range" id="cd-s" min="0" max="100" step="1"><span class="val" id="cd-s-val">0%</span></div>
|
||
<div class="cd-row"><label>L</label><input type="range" id="cd-l" min="0" max="100" step="1"><span class="val" id="cd-l-val">0%</span></div>
|
||
</div>
|
||
<div id="cd-panel-rgb" style="display:none">
|
||
<div class="cd-row"><label>R</label><input type="range" id="cd-r" min="0" max="255" step="1"><span class="val" id="cd-r-val">0</span></div>
|
||
<div class="cd-row"><label>G</label><input type="range" id="cd-g" min="0" max="255" step="1"><span class="val" id="cd-g-val">0</span></div>
|
||
<div class="cd-row"><label>B</label><input type="range" id="cd-b" min="0" max="255" step="1"><span class="val" id="cd-b-val">0</span></div>
|
||
</div>
|
||
<div id="cd-panel-hex" style="display:none; margin-top:6px">
|
||
<input type="text" id="cd-hex" maxlength="7" style="width:100%; background:#091520; border:1px solid #1e3448; color:#cdf; padding:5px; font-family:monospace; font-size:13px; text-align:center;">
|
||
</div>
|
||
<div style="margin-top:10px; border-top:1px solid #1a2a3a; padding-top:8px">
|
||
<div id="cd-swatch-tabs" style="display:flex; gap:2px; margin-bottom:4px"></div>
|
||
<div id="cd-swatch-grid" style="display:flex; flex-wrap:wrap; gap:2px;"></div>
|
||
</div>
|
||
<div style="display:flex; gap:6px; margin-top:10px">
|
||
<button id="cd-close" style="flex:1; background:#0a1a2a; border:1px solid #2a4a6a; color:#7ab; padding:6px; cursor:pointer; font-family:monospace; font-size:12px;">Close</button>
|
||
</div>
|
||
</dialog>
|
||
|
||
<script type="module" src="./playground_main.mjs"></script>
|
||
</body>
|
||
</html>
|