599 lines
20 KiB
HTML
599 lines
20 KiB
HTML
<!DOCTYPE html>
|
|
<!--
|
|
THIS IS A TEMPLATE THAT SHOULD BE USED EVERY TIME AND MODIFIED.
|
|
WHAT TO KEEP:
|
|
✓ Overall structure (header, sidebar, main content)
|
|
✓ Anthropic branding (colors, fonts, layout)
|
|
✓ Seed navigation section (always include this)
|
|
✓ Self-contained artifact (everything inline)
|
|
|
|
WHAT TO CREATIVELY EDIT:
|
|
✗ The p5.js algorithm (implement YOUR vision)
|
|
✗ The parameters (define what YOUR art needs)
|
|
✗ The UI controls (match YOUR parameters)
|
|
|
|
Let your philosophy guide the implementation.
|
|
The world is your oyster - be creative!
|
|
-->
|
|
<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.7.0/p5.min.js"></script>
|
|
<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=Poppins:wght@400;500;600&family=Lora:wght@400;500&display=swap" rel="stylesheet">
|
|
<style>
|
|
/* Anthropic Brand Colors */
|
|
:root {
|
|
--anthropic-dark: #141413;
|
|
--anthropic-light: #faf9f5;
|
|
--anthropic-mid-gray: #b0aea5;
|
|
--anthropic-light-gray: #e8e6dc;
|
|
--anthropic-orange: #d97757;
|
|
--anthropic-blue: #6a9bcc;
|
|
--anthropic-green: #788c5d;
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Poppins', sans-serif;
|
|
background: linear-gradient(135deg, var(--anthropic-light) 0%, #f5f3ee 100%);
|
|
min-height: 100vh;
|
|
color: var(--anthropic-dark);
|
|
}
|
|
|
|
.container {
|
|
display: flex;
|
|
min-height: 100vh;
|
|
padding: 20px;
|
|
gap: 20px;
|
|
}
|
|
|
|
/* Sidebar */
|
|
.sidebar {
|
|
width: 320px;
|
|
flex-shrink: 0;
|
|
background: rgba(255, 255, 255, 0.95);
|
|
backdrop-filter: blur(10px);
|
|
padding: 24px;
|
|
border-radius: 12px;
|
|
box-shadow: 0 10px 30px rgba(20, 20, 19, 0.1);
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
.sidebar h1 {
|
|
font-family: 'Lora', serif;
|
|
font-size: 24px;
|
|
font-weight: 500;
|
|
color: var(--anthropic-dark);
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.sidebar .subtitle {
|
|
color: var(--anthropic-mid-gray);
|
|
font-size: 14px;
|
|
margin-bottom: 32px;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
/* Control Sections */
|
|
.control-section {
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
.control-section h3 {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: var(--anthropic-dark);
|
|
margin-bottom: 16px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.control-section h3::before {
|
|
content: '•';
|
|
color: var(--anthropic-orange);
|
|
font-weight: bold;
|
|
}
|
|
|
|
/* Seed Controls */
|
|
.seed-input {
|
|
width: 100%;
|
|
background: var(--anthropic-light);
|
|
padding: 12px;
|
|
border-radius: 8px;
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 14px;
|
|
margin-bottom: 12px;
|
|
border: 1px solid var(--anthropic-light-gray);
|
|
text-align: center;
|
|
}
|
|
|
|
.seed-input:focus {
|
|
outline: none;
|
|
border-color: var(--anthropic-orange);
|
|
box-shadow: 0 0 0 2px rgba(217, 119, 87, 0.1);
|
|
background: white;
|
|
}
|
|
|
|
.seed-controls {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 8px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.regen-button {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
/* Parameter Controls */
|
|
.control-group {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.control-group label {
|
|
display: block;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: var(--anthropic-dark);
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.slider-container {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.slider-container input[type="range"] {
|
|
flex: 1;
|
|
height: 4px;
|
|
background: var(--anthropic-light-gray);
|
|
border-radius: 2px;
|
|
outline: none;
|
|
-webkit-appearance: none;
|
|
}
|
|
|
|
.slider-container input[type="range"]::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
width: 16px;
|
|
height: 16px;
|
|
background: var(--anthropic-orange);
|
|
border-radius: 50%;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.slider-container input[type="range"]::-webkit-slider-thumb:hover {
|
|
transform: scale(1.1);
|
|
background: #c86641;
|
|
}
|
|
|
|
.slider-container input[type="range"]::-moz-range-thumb {
|
|
width: 16px;
|
|
height: 16px;
|
|
background: var(--anthropic-orange);
|
|
border-radius: 50%;
|
|
border: none;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.value-display {
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 12px;
|
|
color: var(--anthropic-mid-gray);
|
|
min-width: 60px;
|
|
text-align: right;
|
|
}
|
|
|
|
/* Color Pickers */
|
|
.color-group {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.color-group label {
|
|
display: block;
|
|
font-size: 12px;
|
|
color: var(--anthropic-mid-gray);
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.color-picker-container {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.color-picker-container input[type="color"] {
|
|
width: 32px;
|
|
height: 32px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
background: none;
|
|
padding: 0;
|
|
}
|
|
|
|
.color-value {
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 12px;
|
|
color: var(--anthropic-mid-gray);
|
|
}
|
|
|
|
/* Buttons */
|
|
.button {
|
|
background: var(--anthropic-orange);
|
|
color: white;
|
|
border: none;
|
|
padding: 10px 16px;
|
|
border-radius: 6px;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
width: 100%;
|
|
}
|
|
|
|
.button:hover {
|
|
background: #c86641;
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.button:active {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.button.secondary {
|
|
background: var(--anthropic-blue);
|
|
}
|
|
|
|
.button.secondary:hover {
|
|
background: #5a8bb8;
|
|
}
|
|
|
|
.button.tertiary {
|
|
background: var(--anthropic-green);
|
|
}
|
|
|
|
.button.tertiary:hover {
|
|
background: #6b7b52;
|
|
}
|
|
|
|
.button-row {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.button-row .button {
|
|
flex: 1;
|
|
}
|
|
|
|
/* Canvas Area */
|
|
.canvas-area {
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 0;
|
|
}
|
|
|
|
#canvas-container {
|
|
width: 100%;
|
|
max-width: 1000px;
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
box-shadow: 0 20px 40px rgba(20, 20, 19, 0.1);
|
|
background: white;
|
|
}
|
|
|
|
#canvas-container canvas {
|
|
display: block;
|
|
width: 100% !important;
|
|
height: auto !important;
|
|
}
|
|
|
|
/* Loading State */
|
|
.loading {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 18px;
|
|
color: var(--anthropic-mid-gray);
|
|
}
|
|
|
|
/* Responsive - Stack on mobile */
|
|
@media (max-width: 600px) {
|
|
.container {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.sidebar {
|
|
width: 100%;
|
|
}
|
|
|
|
.canvas-area {
|
|
padding: 20px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<!-- Control Sidebar -->
|
|
<div class="sidebar">
|
|
<!-- Headers (CUSTOMIZE THIS FOR YOUR ART) -->
|
|
<h1>TITLE - EDIT</h1>
|
|
<div class="subtitle">SUBHEADER - EDIT</div>
|
|
|
|
<!-- Seed Section (ALWAYS KEEP THIS) -->
|
|
<div class="control-section">
|
|
<h3>Seed</h3>
|
|
<input type="number" id="seed-input" class="seed-input" value="12345" onchange="updateSeed()">
|
|
<div class="seed-controls">
|
|
<button class="button secondary" onclick="previousSeed()">← Prev</button>
|
|
<button class="button secondary" onclick="nextSeed()">Next →</button>
|
|
</div>
|
|
<button class="button tertiary regen-button" onclick="randomSeedAndUpdate()">↻ Random</button>
|
|
</div>
|
|
|
|
<!-- Parameters Section (CUSTOMIZE THIS FOR YOUR ART) -->
|
|
<div class="control-section">
|
|
<h3>Parameters</h3>
|
|
|
|
<!-- Particle Count -->
|
|
<div class="control-group">
|
|
<label>Particle Count</label>
|
|
<div class="slider-container">
|
|
<input type="range" id="particleCount" min="1000" max="10000" step="500" value="5000" oninput="updateParam('particleCount', this.value)">
|
|
<span class="value-display" id="particleCount-value">5000</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Flow Speed -->
|
|
<div class="control-group">
|
|
<label>Flow Speed</label>
|
|
<div class="slider-container">
|
|
<input type="range" id="flowSpeed" min="0.1" max="2.0" step="0.1" value="0.5" oninput="updateParam('flowSpeed', this.value)">
|
|
<span class="value-display" id="flowSpeed-value">0.5</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Noise Scale -->
|
|
<div class="control-group">
|
|
<label>Noise Scale</label>
|
|
<div class="slider-container">
|
|
<input type="range" id="noiseScale" min="0.001" max="0.02" step="0.001" value="0.005" oninput="updateParam('noiseScale', this.value)">
|
|
<span class="value-display" id="noiseScale-value">0.005</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Trail Length -->
|
|
<div class="control-group">
|
|
<label>Trail Length</label>
|
|
<div class="slider-container">
|
|
<input type="range" id="trailLength" min="2" max="20" step="1" value="8" oninput="updateParam('trailLength', this.value)">
|
|
<span class="value-display" id="trailLength-value">8</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Colors Section (OPTIONAL - CUSTOMIZE OR REMOVE) -->
|
|
<div class="control-section">
|
|
<h3>Colors</h3>
|
|
|
|
<!-- Color 1 -->
|
|
<div class="color-group">
|
|
<label>Primary Color</label>
|
|
<div class="color-picker-container">
|
|
<input type="color" id="color1" value="#d97757" onchange="updateColor('color1', this.value)">
|
|
<span class="color-value" id="color1-value">#d97757</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Color 2 -->
|
|
<div class="color-group">
|
|
<label>Secondary Color</label>
|
|
<div class="color-picker-container">
|
|
<input type="color" id="color2" value="#6a9bcc" onchange="updateColor('color2', this.value)">
|
|
<span class="color-value" id="color2-value">#6a9bcc</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Color 3 -->
|
|
<div class="color-group">
|
|
<label>Accent Color</label>
|
|
<div class="color-picker-container">
|
|
<input type="color" id="color3" value="#788c5d" onchange="updateColor('color3', this.value)">
|
|
<span class="color-value" id="color3-value">#788c5d</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Actions Section (ALWAYS KEEP THIS) -->
|
|
<div class="control-section">
|
|
<h3>Actions</h3>
|
|
<div class="button-row">
|
|
<button class="button" onclick="resetParameters()">Reset</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Canvas Area -->
|
|
<div class="canvas-area">
|
|
<div id="canvas-container">
|
|
<div class="loading">Initializing generative art...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// ═══════════════════════════════════════════════════════════════════════
|
|
// GENERATIVE ART PARAMETERS - CUSTOMIZE FOR YOUR ALGORITHM
|
|
// ═══════════════════════════════════════════════════════════════════════
|
|
|
|
let params = {
|
|
seed: 12345,
|
|
particleCount: 5000,
|
|
flowSpeed: 0.5,
|
|
noiseScale: 0.005,
|
|
trailLength: 8,
|
|
colorPalette: ['#d97757', '#6a9bcc', '#788c5d']
|
|
};
|
|
|
|
let defaultParams = {...params}; // Store defaults for reset
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════
|
|
// P5.JS GENERATIVE ART ALGORITHM - REPLACE WITH YOUR VISION
|
|
// ═══════════════════════════════════════════════════════════════════════
|
|
|
|
let particles = [];
|
|
let flowField = [];
|
|
let cols, rows;
|
|
let scl = 10; // Flow field resolution
|
|
|
|
function setup() {
|
|
let canvas = createCanvas(1200, 1200);
|
|
canvas.parent('canvas-container');
|
|
|
|
initializeSystem();
|
|
|
|
// Remove loading message
|
|
document.querySelector('.loading').style.display = 'none';
|
|
}
|
|
|
|
function initializeSystem() {
|
|
// Seed the randomness for reproducibility
|
|
randomSeed(params.seed);
|
|
noiseSeed(params.seed);
|
|
|
|
// Clear particles and recreate
|
|
particles = [];
|
|
|
|
// Initialize particles
|
|
for (let i = 0; i < params.particleCount; i++) {
|
|
particles.push(new Particle());
|
|
}
|
|
|
|
// Calculate flow field dimensions
|
|
cols = floor(width / scl);
|
|
rows = floor(height / scl);
|
|
|
|
// Generate flow field
|
|
generateFlowField();
|
|
|
|
// Clear background
|
|
background(250, 249, 245); // Anthropic light background
|
|
}
|
|
|
|
function generateFlowField() {
|
|
// fill this in
|
|
}
|
|
|
|
function draw() {
|
|
// fill this in
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════
|
|
// PARTICLE SYSTEM - CUSTOMIZE FOR YOUR ALGORITHM
|
|
// ═══════════════════════════════════════════════════════════════════════
|
|
|
|
class Particle {
|
|
constructor() {
|
|
// fill this in
|
|
}
|
|
// fill this in
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════
|
|
// UI CONTROL HANDLERS - CUSTOMIZE FOR YOUR PARAMETERS
|
|
// ═══════════════════════════════════════════════════════════════════════
|
|
|
|
function updateParam(paramName, value) {
|
|
// fill this in
|
|
}
|
|
|
|
function updateColor(colorId, value) {
|
|
// fill this in
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════
|
|
// SEED CONTROL FUNCTIONS - ALWAYS KEEP THESE
|
|
// ═══════════════════════════════════════════════════════════════════════
|
|
|
|
function updateSeedDisplay() {
|
|
document.getElementById('seed-input').value = params.seed;
|
|
}
|
|
|
|
function updateSeed() {
|
|
let input = document.getElementById('seed-input');
|
|
let newSeed = parseInt(input.value);
|
|
if (newSeed && newSeed > 0) {
|
|
params.seed = newSeed;
|
|
initializeSystem();
|
|
} else {
|
|
// Reset to current seed if invalid
|
|
updateSeedDisplay();
|
|
}
|
|
}
|
|
|
|
function previousSeed() {
|
|
params.seed = Math.max(1, params.seed - 1);
|
|
updateSeedDisplay();
|
|
initializeSystem();
|
|
}
|
|
|
|
function nextSeed() {
|
|
params.seed = params.seed + 1;
|
|
updateSeedDisplay();
|
|
initializeSystem();
|
|
}
|
|
|
|
function randomSeedAndUpdate() {
|
|
params.seed = Math.floor(Math.random() * 999999) + 1;
|
|
updateSeedDisplay();
|
|
initializeSystem();
|
|
}
|
|
|
|
function resetParameters() {
|
|
params = {...defaultParams};
|
|
|
|
// Update UI elements
|
|
document.getElementById('particleCount').value = params.particleCount;
|
|
document.getElementById('particleCount-value').textContent = params.particleCount;
|
|
document.getElementById('flowSpeed').value = params.flowSpeed;
|
|
document.getElementById('flowSpeed-value').textContent = params.flowSpeed;
|
|
document.getElementById('noiseScale').value = params.noiseScale;
|
|
document.getElementById('noiseScale-value').textContent = params.noiseScale;
|
|
document.getElementById('trailLength').value = params.trailLength;
|
|
document.getElementById('trailLength-value').textContent = params.trailLength;
|
|
|
|
// Reset colors
|
|
document.getElementById('color1').value = params.colorPalette[0];
|
|
document.getElementById('color1-value').textContent = params.colorPalette[0];
|
|
document.getElementById('color2').value = params.colorPalette[1];
|
|
document.getElementById('color2-value').textContent = params.colorPalette[1];
|
|
document.getElementById('color3').value = params.colorPalette[2];
|
|
document.getElementById('color3-value').textContent = params.colorPalette[2];
|
|
|
|
updateSeedDisplay();
|
|
initializeSystem();
|
|
}
|
|
|
|
// Initialize UI on load
|
|
window.addEventListener('load', function() {
|
|
updateSeedDisplay();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |