395 lines
11 KiB
HTML
395 lines
11 KiB
HTML
<!DOCTYPE html>
|
|
<!--
|
|
p5.js Interactive Viewer Template
|
|
=================================
|
|
USE THIS AS THE STARTING POINT for interactive generative art sketches.
|
|
|
|
FIXED (keep as-is):
|
|
✓ Layout structure (sidebar + canvas)
|
|
✓ Seed navigation (prev/next/random/jump)
|
|
✓ Action buttons (regenerate, reset, download PNG)
|
|
✓ Responsive canvas sizing
|
|
✓ Parameter update + regeneration wiring
|
|
|
|
VARIABLE (replace for each project):
|
|
✗ The p5.js algorithm (setup/draw/classes)
|
|
✗ The PARAMS object (define what your art needs)
|
|
✗ The parameter controls in the sidebar (sliders, pickers)
|
|
✗ The color palette
|
|
✗ The title and description
|
|
|
|
For headless export: add noLoop() and window._p5Ready=true in setup().
|
|
-->
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Generative Art Viewer</title>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.3/p5.min.js"></script>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body {
|
|
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
|
background: #0a0a0f;
|
|
color: #c8c8d0;
|
|
display: flex;
|
|
min-height: 100vh;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* --- Sidebar --- */
|
|
.sidebar {
|
|
width: 280px;
|
|
flex-shrink: 0;
|
|
background: #12121a;
|
|
border-right: 1px solid #1e1e2a;
|
|
padding: 20px;
|
|
overflow-y: auto;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 20px;
|
|
}
|
|
.sidebar h1 {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: #e8e8f0;
|
|
margin-bottom: 4px;
|
|
}
|
|
.sidebar .subtitle {
|
|
font-size: 12px;
|
|
color: #666;
|
|
margin-bottom: 8px;
|
|
}
|
|
.section-title {
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 1px;
|
|
color: #555;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
/* --- Seed Controls --- */
|
|
.seed-display {
|
|
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
color: #e8e8f0;
|
|
text-align: center;
|
|
padding: 8px;
|
|
background: #1a1a25;
|
|
border-radius: 6px;
|
|
margin-bottom: 8px;
|
|
}
|
|
.seed-nav {
|
|
display: flex;
|
|
gap: 6px;
|
|
margin-bottom: 6px;
|
|
}
|
|
.seed-nav button {
|
|
flex: 1;
|
|
padding: 6px;
|
|
font-size: 12px;
|
|
}
|
|
.seed-jump {
|
|
display: flex;
|
|
gap: 6px;
|
|
}
|
|
.seed-jump input {
|
|
flex: 1;
|
|
padding: 6px 8px;
|
|
background: #1a1a25;
|
|
border: 1px solid #2a2a35;
|
|
border-radius: 4px;
|
|
color: #c8c8d0;
|
|
font-size: 12px;
|
|
font-family: monospace;
|
|
}
|
|
.seed-jump button { padding: 6px 12px; font-size: 12px; }
|
|
|
|
/* --- Parameter Controls --- */
|
|
.control-group {
|
|
margin-bottom: 12px;
|
|
}
|
|
.control-group label {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
font-size: 12px;
|
|
color: #888;
|
|
margin-bottom: 4px;
|
|
}
|
|
.control-group .value {
|
|
color: #aaa;
|
|
font-family: monospace;
|
|
font-size: 11px;
|
|
}
|
|
.control-group input[type="range"] {
|
|
width: 100%;
|
|
height: 4px;
|
|
-webkit-appearance: none;
|
|
background: #2a2a35;
|
|
border-radius: 2px;
|
|
outline: none;
|
|
}
|
|
.control-group input[type="range"]::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
width: 14px; height: 14px;
|
|
border-radius: 50%;
|
|
background: #6a9bcc;
|
|
cursor: pointer;
|
|
}
|
|
.control-group input[type="color"] {
|
|
width: 100%;
|
|
height: 28px;
|
|
border: 1px solid #2a2a35;
|
|
border-radius: 4px;
|
|
background: #1a1a25;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* --- Buttons --- */
|
|
button {
|
|
padding: 8px 12px;
|
|
background: #1e1e2a;
|
|
border: 1px solid #2a2a35;
|
|
border-radius: 4px;
|
|
color: #c8c8d0;
|
|
font-size: 12px;
|
|
cursor: pointer;
|
|
transition: background 0.15s;
|
|
}
|
|
button:hover { background: #2a2a3a; }
|
|
button.primary { background: #2a4a6a; border-color: #3a5a7a; }
|
|
button.primary:hover { background: #3a5a7a; }
|
|
|
|
.actions { display: flex; flex-direction: column; gap: 6px; }
|
|
.actions button { width: 100%; }
|
|
|
|
/* --- Canvas Area --- */
|
|
.canvas-area {
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 20px;
|
|
background: #08080c;
|
|
}
|
|
canvas { display: block; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- === SIDEBAR === -->
|
|
<div class="sidebar">
|
|
<!-- FIXED: Title (customize text, keep structure) -->
|
|
<div>
|
|
<h1 id="art-title">Generative Sketch</h1>
|
|
<div class="subtitle" id="art-subtitle">p5.js generative art</div>
|
|
</div>
|
|
|
|
<!-- FIXED: Seed Navigation -->
|
|
<div>
|
|
<div class="section-title">Seed</div>
|
|
<div class="seed-display" id="seed-display">42</div>
|
|
<div class="seed-nav">
|
|
<button onclick="changeSeed(-1)">◀ Prev</button>
|
|
<button onclick="changeSeed(1)">Next ▶</button>
|
|
<button onclick="randomizeSeed()">Random</button>
|
|
</div>
|
|
<div class="seed-jump">
|
|
<input type="number" id="seed-input" placeholder="Seed #" min="0">
|
|
<button onclick="jumpToSeed()">Go</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- VARIABLE: Parameters (customize for each project) -->
|
|
<div id="params-section">
|
|
<div class="section-title">Parameters</div>
|
|
|
|
<!-- === REPLACE THESE WITH YOUR PARAMETERS === -->
|
|
<div class="control-group">
|
|
<label>Count <span class="value" id="count-val">500</span></label>
|
|
<input type="range" id="count" min="50" max="2000" step="50" value="500"
|
|
oninput="updateParam('count', +this.value)">
|
|
</div>
|
|
|
|
<div class="control-group">
|
|
<label>Scale <span class="value" id="scale-val">0.005</span></label>
|
|
<input type="range" id="scale" min="0.001" max="0.02" step="0.001" value="0.005"
|
|
oninput="updateParam('scale', +this.value)">
|
|
</div>
|
|
|
|
<div class="control-group">
|
|
<label>Speed <span class="value" id="speed-val">2.0</span></label>
|
|
<input type="range" id="speed" min="0.5" max="5" step="0.1" value="2.0"
|
|
oninput="updateParam('speed', +this.value)">
|
|
</div>
|
|
<!-- === END PARAMETER CONTROLS === -->
|
|
</div>
|
|
|
|
<!-- VARIABLE: Colors (optional — include if art needs adjustable palette) -->
|
|
<!--
|
|
<div>
|
|
<div class="section-title">Colors</div>
|
|
<div class="control-group">
|
|
<label>Background</label>
|
|
<input type="color" id="bg-color" value="#0a0a14"
|
|
oninput="updateParam('bgColor', this.value)">
|
|
</div>
|
|
<div class="control-group">
|
|
<label>Primary</label>
|
|
<input type="color" id="primary-color" value="#6a9bcc"
|
|
oninput="updateParam('primaryColor', this.value)">
|
|
</div>
|
|
</div>
|
|
-->
|
|
|
|
<!-- FIXED: Actions -->
|
|
<div class="actions">
|
|
<div class="section-title">Actions</div>
|
|
<button class="primary" onclick="regenerate()">Regenerate</button>
|
|
<button onclick="resetDefaults()">Reset Defaults</button>
|
|
<button onclick="downloadPNG()">Download PNG</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- === CANVAS === -->
|
|
<div class="canvas-area" id="canvas-container"></div>
|
|
|
|
<script>
|
|
// ====================================================================
|
|
// CONFIGURATION — REPLACE FOR EACH PROJECT
|
|
// ====================================================================
|
|
const DEFAULTS = {
|
|
seed: 42,
|
|
count: 500,
|
|
scale: 0.005,
|
|
speed: 2.0,
|
|
// Add your parameters here
|
|
};
|
|
|
|
let PARAMS = { ...DEFAULTS };
|
|
|
|
// ====================================================================
|
|
// SEED NAVIGATION — FIXED (do not modify)
|
|
// ====================================================================
|
|
function changeSeed(delta) {
|
|
PARAMS.seed = Math.max(0, PARAMS.seed + delta);
|
|
document.getElementById('seed-display').textContent = PARAMS.seed;
|
|
regenerate();
|
|
}
|
|
|
|
function randomizeSeed() {
|
|
PARAMS.seed = Math.floor(Math.random() * 99999);
|
|
document.getElementById('seed-display').textContent = PARAMS.seed;
|
|
regenerate();
|
|
}
|
|
|
|
function jumpToSeed() {
|
|
let v = parseInt(document.getElementById('seed-input').value);
|
|
if (!isNaN(v) && v >= 0) {
|
|
PARAMS.seed = v;
|
|
document.getElementById('seed-display').textContent = PARAMS.seed;
|
|
document.getElementById('seed-input').value = '';
|
|
regenerate();
|
|
}
|
|
}
|
|
|
|
// ====================================================================
|
|
// PARAMETER UPDATES — CUSTOMIZE updateParam body as needed
|
|
// ====================================================================
|
|
function updateParam(name, value) {
|
|
PARAMS[name] = value;
|
|
let el = document.getElementById(name + '-val');
|
|
if (el) el.textContent = typeof value === 'number' && value < 1 ? value.toFixed(3) : value;
|
|
regenerate();
|
|
}
|
|
|
|
function resetDefaults() {
|
|
PARAMS = { ...DEFAULTS };
|
|
// Reset all sliders to default values
|
|
for (let [key, val] of Object.entries(DEFAULTS)) {
|
|
let el = document.getElementById(key);
|
|
if (el) el.value = val;
|
|
let valEl = document.getElementById(key + '-val');
|
|
if (valEl) valEl.textContent = typeof val === 'number' && val < 1 ? val.toFixed(3) : val;
|
|
}
|
|
document.getElementById('seed-display').textContent = PARAMS.seed;
|
|
regenerate();
|
|
}
|
|
|
|
function regenerate() {
|
|
randomSeed(PARAMS.seed);
|
|
noiseSeed(PARAMS.seed);
|
|
// Clear and redraw
|
|
clear();
|
|
initializeArt();
|
|
redraw();
|
|
}
|
|
|
|
function downloadPNG() {
|
|
saveCanvas('generative-art-seed-' + PARAMS.seed, 'png');
|
|
}
|
|
|
|
// ====================================================================
|
|
// P5.JS SKETCH — REPLACE ENTIRELY FOR EACH PROJECT
|
|
// ====================================================================
|
|
|
|
// Your state variables
|
|
let particles = [];
|
|
|
|
function initializeArt() {
|
|
// Initialize your generative system using PARAMS
|
|
// This is called on every regenerate()
|
|
particles = [];
|
|
for (let i = 0; i < PARAMS.count; i++) {
|
|
particles.push({
|
|
x: random(width),
|
|
y: random(height),
|
|
vx: 0, vy: 0
|
|
});
|
|
}
|
|
}
|
|
|
|
function setup() {
|
|
// Size canvas to fit container
|
|
let container = document.getElementById('canvas-container');
|
|
let size = Math.min(container.clientWidth - 40, container.clientHeight - 40, 1080);
|
|
let cnv = createCanvas(size, size);
|
|
cnv.parent('canvas-container');
|
|
pixelDensity(1);
|
|
colorMode(HSB, 360, 100, 100, 100);
|
|
|
|
randomSeed(PARAMS.seed);
|
|
noiseSeed(PARAMS.seed);
|
|
initializeArt();
|
|
|
|
// For interactive/animated sketches: remove noLoop()
|
|
// For static generation: keep noLoop()
|
|
noLoop();
|
|
}
|
|
|
|
function draw() {
|
|
background(0, 0, 5);
|
|
|
|
// === YOUR ALGORITHM HERE ===
|
|
// Use PARAMS.count, PARAMS.scale, PARAMS.speed, etc.
|
|
noStroke();
|
|
for (let p of particles) {
|
|
let n = noise(p.x * PARAMS.scale, p.y * PARAMS.scale);
|
|
let hue = (n * 200 + PARAMS.seed * 0.1) % 360;
|
|
fill(hue, 70, 80, 60);
|
|
circle(p.x, p.y, n * 10 + 2);
|
|
}
|
|
// === END ALGORITHM ===
|
|
}
|
|
|
|
function windowResized() {
|
|
let container = document.getElementById('canvas-container');
|
|
let size = Math.min(container.clientWidth - 40, container.clientHeight - 40, 1080);
|
|
resizeCanvas(size, size);
|
|
regenerate();
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |