claude-code-skill-power/demos/nebula-genesis.html

868 lines
32 KiB
HTML
Raw Permalink Normal View History

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nebula Genesis - Generative Art</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>
: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%, #0a0a0f 100%);
min-height: 100vh;
color: var(--anthropic-light);
}
.container {
display: flex;
min-height: 100vh;
padding: 20px;
gap: 20px;
}
.sidebar {
width: 320px;
flex-shrink: 0;
background: rgba(20, 20, 25, 0.95);
backdrop-filter: blur(10px);
padding: 24px;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
overflow-y: auto;
overflow-x: hidden;
border: 1px solid rgba(255,255,255,0.1);
}
.sidebar h1 {
font-family: 'Lora', serif;
font-size: 24px;
font-weight: 500;
color: var(--anthropic-light);
margin-bottom: 8px;
}
.sidebar .subtitle {
color: var(--anthropic-mid-gray);
font-size: 14px;
margin-bottom: 32px;
line-height: 1.4;
}
.control-section {
margin-bottom: 32px;
}
.control-section h3 {
font-size: 16px;
font-weight: 600;
color: var(--anthropic-light);
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.control-section h3::before {
content: '•';
color: #ff6b4a;
font-weight: bold;
}
.seed-input {
width: 100%;
background: rgba(255,255,255,0.1);
padding: 12px;
border-radius: 8px;
font-family: 'Courier New', monospace;
font-size: 14px;
margin-bottom: 12px;
border: 1px solid rgba(255,255,255,0.2);
text-align: center;
color: var(--anthropic-light);
}
.seed-input:focus {
outline: none;
border-color: #ff6b4a;
box-shadow: 0 0 0 2px rgba(255, 107, 74, 0.2);
background: rgba(255,255,255,0.15);
}
.seed-controls {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-bottom: 8px;
}
.button {
background: linear-gradient(135deg, #ff6b4a 0%, #d97757 100%);
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: linear-gradient(135deg, #ff8570 0%, #e98868 100%);
transform: translateY(-1px);
}
.button:active {
transform: translateY(0);
}
.button.secondary {
background: linear-gradient(135deg, #4a90c2 0%, #6a9bcc 100%);
}
.button.secondary:hover {
background: linear-gradient(135deg, #5aa0d2 0%, #7aabdc 100%);
}
.button.tertiary {
background: linear-gradient(135deg, #5d7c5d 0%, #788c5d 100%);
}
.button.tertiary:hover {
background: linear-gradient(135deg, #6d8c6d 0%, #889c6d 100%);
}
.control-group {
margin-bottom: 20px;
}
.control-group label {
display: block;
font-size: 14px;
font-weight: 500;
color: var(--anthropic-light);
margin-bottom: 8px;
}
.slider-container {
display: flex;
align-items: center;
gap: 12px;
}
.slider-container input[type="range"] {
flex: 1;
height: 4px;
background: rgba(255,255,255,0.2);
border-radius: 2px;
outline: none;
-webkit-appearance: none;
}
.slider-container input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
background: #ff6b4a;
border-radius: 50%;
cursor: pointer;
transition: all 0.2s ease;
}
.slider-container input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.1);
background: #ff8570;
}
.slider-container input[type="range"]::-moz-range-thumb {
width: 16px;
height: 16px;
background: #ff6b4a;
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-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);
}
.button-row {
display: flex;
gap: 8px;
}
.button-row .button {
flex: 1;
}
.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 60px rgba(0, 0, 0, 0.5);
background: #000;
}
#canvas-container canvas {
display: block;
width: 100% !important;
height: auto !important;
}
.loading {
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
color: var(--anthropic-mid-gray);
min-height: 400px;
}
@media (max-width: 600px) {
.container {
flex-direction: column;
}
.sidebar {
width: 100%;
}
.canvas-area {
padding: 20px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="sidebar">
<h1>Nebula Genesis</h1>
<div class="subtitle">Stellar nursery in algorithmic form. Witness gas clouds collapse into stellar embryos.</div>
<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" onclick="randomSeedAndUpdate()">↻ Random</button>
</div>
<div class="control-section">
<h3>Particles</h3>
<div class="control-group">
<label>Particle Count</label>
<div class="slider-container">
<input type="range" id="particleCount" min="1000" max="8000" step="500" value="4000" oninput="updateParam('particleCount', this.value)">
<span class="value-display" id="particleCount-value">4000</span>
</div>
</div>
<div class="control-group">
<label>Flow Speed</label>
<div class="slider-container">
<input type="range" id="flowSpeed" min="0.1" max="3.0" step="0.1" value="1.0" oninput="updateParam('flowSpeed', this.value)">
<span class="value-display" id="flowSpeed-value">1.0</span>
</div>
</div>
<div class="control-group">
<label>Particle Life</label>
<div class="slider-container">
<input type="range" id="particleLife" min="50" max="500" step="25" value="200" oninput="updateParam('particleLife', this.value)">
<span class="value-display" id="particleLife-value">200</span>
</div>
</div>
</div>
<div class="control-section">
<h3>Gravity & Structure</h3>
<div class="control-group">
<label>Gravity Strength</label>
<div class="slider-container">
<input type="range" id="gravityStrength" min="0.1" max="2.0" step="0.1" value="0.8" oninput="updateParam('gravityStrength', this.value)">
<span class="value-display" id="gravityStrength-value">0.8</span>
</div>
</div>
<div class="control-group">
<label>Noise Scale</label>
<div class="slider-container">
<input type="range" id="noiseScale" min="0.002" max="0.02" step="0.001" value="0.008" oninput="updateParam('noiseScale', this.value)">
<span class="value-display" id="noiseScale-value">0.008</span>
</div>
</div>
<div class="control-group">
<label>Turbulence</label>
<div class="slider-container">
<input type="range" id="turbulence" min="0.1" max="2.0" step="0.1" value="1.0" oninput="updateParam('turbulence', this.value)">
<span class="value-display" id="turbulence-value">1.0</span>
</div>
</div>
<div class="control-group">
<label>Star Density</label>
<div class="slider-container">
<input type="range" id="starDensity" min="1" max="8" step="1" value="4" oninput="updateParam('starDensity', this.value)">
<span class="value-display" id="starDensity-value">4</span>
</div>
</div>
</div>
<div class="control-section">
<h3>Visuals</h3>
<div class="control-group">
<label>Trail Opacity</label>
<div class="slider-container">
<input type="range" id="trailOpacity" min="2" max="25" step="1" value="8" oninput="updateParam('trailOpacity', this.value)">
<span class="value-display" id="trailOpacity-value">8</span>
</div>
</div>
<div class="control-group">
<label>Core Brightness</label>
<div class="slider-container">
<input type="range" id="coreBrightness" min="50" max="200" step="10" value="120" oninput="updateParam('coreBrightness', this.value)">
<span class="value-display" id="coreBrightness-value">120</span>
</div>
</div>
</div>
<div class="control-section">
<h3>Colors</h3>
<div class="color-group">
<label>Core Color (Hydrogen)</label>
<div class="color-picker-container">
<input type="color" id="colorCore" value="#ff4532" onchange="updateColor('colorCore', this.value)">
<span class="color-value" id="colorCore-value">#ff4532</span>
</div>
</div>
<div class="color-group">
<label>Mid Layer (Sulfur/Oxygen)</label>
<div class="color-picker-container">
<input type="color" id="colorMid" value="#9b59b6" onchange="updateColor('colorMid', this.value)">
<span class="color-value" id="colorMid-value">#9b59b6</span>
</div>
</div>
<div class="color-group">
<label>Edge Color (Reflection)</label>
<div class="color-picker-container">
<input type="color" id="colorEdge" value="#3498db" onchange="updateColor('colorEdge', this.value)">
<span class="color-value" id="colorEdge-value">#3498db</span>
</div>
</div>
<div class="color-group">
<label>Star Color</label>
<div class="color-picker-container">
<input type="color" id="colorStar" value="#fff8e7" onchange="updateColor('colorStar', this.value)">
<span class="color-value" id="colorStar-value">#fff8e7</span>
</div>
</div>
</div>
<div class="control-section">
<h3>Actions</h3>
<div class="button-row">
<button class="button" onclick="resetParameters()">Reset</button>
<button class="button tertiary" onclick="downloadCanvas()">↓ PNG</button>
</div>
</div>
</div>
<div class="canvas-area">
<div id="canvas-container">
<div class="loading">Initializing stellar nursery...</div>
</div>
</div>
</div>
<script>
// ═══════════════════════════════════════════════════════════════════════
// NEBULA GENESIS - Generative Art Algorithm
// A meticulously crafted algorithm simulating stellar formation
// ═══════════════════════════════════════════════════════════════════════
let params = {
seed: 12345,
particleCount: 4000,
flowSpeed: 1.0,
particleLife: 200,
gravityStrength: 0.8,
noiseScale: 0.008,
turbulence: 1.0,
starDensity: 4,
trailOpacity: 8,
coreBrightness: 120,
colors: {
core: '#ff4532',
mid: '#9b59b6',
edge: '#3498db',
star: '#fff8e7'
}
};
let defaultParams = {...params};
// ═══════════════════════════════════════════════════════════════════════
// PARTICLE SYSTEM
// ═══════════════════════════════════════════════════════════════════════
let particles = [];
let gravityWells = [];
let cols, rows;
let scl = 8;
function setup() {
let canvas = createCanvas(1200, 1200);
canvas.parent('canvas-container');
initializeSystem();
document.querySelector('.loading').style.display = 'none';
}
function initializeSystem() {
randomSeed(params.seed);
noiseSeed(params.seed);
particles = [];
gravityWells = [];
// Initialize gravity wells at strategic positions
initializeGravityWells();
// Initialize particles
for (let i = 0; i < params.particleCount; i++) {
particles.push(new NebulaParticle());
}
// Calculate flow field dimensions
cols = floor(width / scl);
rows = floor(height / scl);
// Clear background
background(5, 5, 15);
}
function initializeGravityWells() {
let numWells = params.starDensity;
let centerX = width / 2;
let centerY = height / 2;
let maxRadius = min(width, height) * 0.35;
// Main central well
gravityWells.push({
x: centerX + random(-50, 50),
y: centerY + random(-50, 50),
strength: random(0.8, 1.2),
radius: random(maxRadius * 0.15, maxRadius * 0.25)
});
// Additional wells in spiral pattern
for (let i = 1; i < numWells; i++) {
let angle = (i / (numWells - 1)) * TWO_PI * random(1.5, 3);
let radius = maxRadius * random(0.3, 0.9);
let spiralFactor = 1 - (i / numWells);
gravityWells.push({
x: centerX + cos(angle) * radius * spiralFactor,
y: centerY + sin(angle) * radius * spiralFactor,
strength: random(0.4, 0.9),
radius: random(maxRadius * 0.08, maxRadius * 0.15)
});
}
}
function generateFlowField(x, y, scale, time) {
// Multi-layered noise for complex flow patterns
let n1 = noise(x * scale, y * scale, time * 0.1);
let n2 = noise(x * scale * 2.3 + 100, y * scale * 2.3 + 100, time * 0.15);
let n3 = noise(x * scale * 0.4 + 200, y * scale * 0.4 + 200, time * 0.08);
let combined = (n1 + n2 * 0.5 + n3 * 0.25) / 1.75;
return combined * TWO_PI * params.turbulence;
}
function draw() {
// Very subtle fade for accumulation effect
noStroke();
// Update and draw particles
for (let p of particles) {
p.update();
p.display();
}
// Occasionally respawn particles that have died
let deadCount = particles.filter(p => p.isDead()).length;
if (deadCount > particles.length * 0.1) {
for (let i = 0; i < min(deadCount, 50); i++) {
let deadIndex = particles.findIndex(p => p.isDead());
if (deadIndex !== -1) {
particles[deadIndex] = new NebulaParticle();
}
}
}
}
// ═══════════════════════════════════════════════════════════════════════
// NEBULA PARTICLE CLASS
// ═══════════════════════════════════════════════════════════════════════
class NebulaParticle {
constructor() {
this.reset();
}
reset() {
// Spawn at edges or randomly
if (random() < 0.4) {
// Spawn at edges
let edge = floor(random(4));
switch(edge) {
case 0: this.pos = createVector(random(width), random(5)); break;
case 1: this.pos = createVector(random(width), height - 5); break;
case 2: this.pos = createVector(random(5), random(height)); break;
case 3: this.pos = createVector(width - 5, random(height)); break;
}
} else {
// Spawn randomly with bias towards edges
let angle = random(TWO_PI);
let r = random(width * 0.6);
this.pos = createVector(width/2 + cos(angle) * r, height/2 + sin(angle) * r);
}
this.vel = createVector(0, 0);
this.acc = createVector(0, 0);
this.life = params.particleLife;
this.maxLife = params.particleLife;
this.history = [];
this.maxHistory = 8;
// Individual particle variation
this.noiseOffset = random(1000);
this.sizeFactor = random(0.5, 1.5);
}
update() {
let scale = params.noiseScale;
let time = frameCount * 0.01;
// Calculate flow field force
let flowAngle = generateFlowField(this.pos.x, this.pos.y, scale, time);
let flowForce = p5.Vector.fromAngle(flowAngle);
flowForce.mult(params.flowSpeed * 0.3);
// Calculate gravity well forces
let gravityForce = createVector(0, 0);
for (let well of gravityWells) {
let d = dist(this.pos.x, this.pos.y, well.x, well.y);
if (d < well.radius * 3 && d > 5) {
let forceDir = createVector(well.x - this.pos.x, well.y - this.pos.y);
forceDir.normalize();
let forceMag = well.strength * params.gravityStrength * 50 / (d * d + 100);
forceDir.mult(forceMag);
gravityForce.add(forceDir);
}
}
// Add subtle turbulence
let turbAngle = noise(this.pos.x * 0.01 + time, this.pos.y * 0.01 + time) * TWO_PI;
let turbForce = p5.Vector.fromAngle(turbAngle);
turbForce.mult(0.1);
this.acc.add(flowForce);
this.acc.add(gravityForce);
this.acc.add(turbForce);
this.vel.add(this.acc);
this.vel.limit(4);
this.pos.add(this.vel);
this.acc.mult(0);
// Store history for trail effect
this.history.push(this.pos.copy());
if (this.history.length > this.maxHistory) {
this.history.shift();
}
this.life--;
// Respawn if out of bounds or dead
if (this.isOutOfBounds() || this.isDead()) {
this.reset();
}
}
isOutOfBounds() {
return this.pos.x < -50 || this.pos.x > width + 50 ||
this.pos.y < -50 || this.pos.y > height + 50;
}
isDead() {
return this.life <= 0;
}
display() {
// Calculate velocity magnitude for color mapping
let speed = this.vel.mag();
let lifeRatio = this.life / this.maxLife;
// Determine which color to use based on speed and position
let color = this.selectColor(speed);
// Calculate opacity based on life and position
let alpha = map(this.trailOpacity / 255 * lifeRatio, 0, 1, 3, 20);
// Draw trail
noFill();
beginShape();
for (let i = 0; i < this.history.length; i++) {
let pos = this.history[i];
let t = i / this.history.length;
let trailAlpha = alpha * t;
// Blend color along trail
let c = color;
c.setAlpha(trailAlpha);
stroke(c);
strokeWeight(1.5 * this.sizeFactor * t);
vertex(pos.x, pos.y);
}
endShape();
// Draw current position with glow effect
noStroke();
// Outer glow
let glowSize = map(speed, 0, 4, 3, 8) * this.sizeFactor;
let glowColor = color;
glowColor.setAlpha(alpha * 0.3);
fill(glowColor);
ellipse(this.pos.x, this.pos.y, glowSize * 2, glowSize * 2);
// Core
let coreColor = this.selectCoreColor(speed);
coreColor.setAlpha(alpha * 0.8);
fill(coreColor);
ellipse(this.pos.x, this.pos.y, glowSize * 0.6, glowSize * 0.6);
}
selectColor(speed) {
// Calculate distance to nearest gravity well
let minDist = Infinity;
for (let well of gravityWells) {
let d = dist(this.pos.x, this.pos.y, well.x, well.y);
minDist = min(minDist, d);
}
// Map to color palette
let color1 = color(params.colors.core);
let color2 = color(params.colors.mid);
let color3 = color(params.colors.edge);
let maxDist = min(width, height) * 0.4;
let normalizedDist = constrain(minDist / maxDist, 0, 1);
let normalizedSpeed = constrain(speed / 3, 0, 1);
// Blend based on distance and speed
if (normalizedDist < 0.3) {
return lerpColor(color1, color2, normalizedDist * 3);
} else {
return lerpColor(color2, color3, (normalizedDist - 0.3) * 1.4);
}
}
selectCoreColor(speed) {
let starColor = color(params.colors.star);
let midColor = color(params.colors.mid);
let normalizedSpeed = constrain(speed / 3, 0, 1);
return lerpColor(starColor, midColor, normalizedSpeed * 0.7);
}
}
// ═══════════════════════════════════════════════════════════════════════
// UI CONTROL HANDLERS
// ═══════════════════════════════════════════════════════════════════════
function updateParam(paramName, value) {
value = parseFloat(value);
params[paramName] = value;
document.getElementById(paramName + '-value').textContent = value;
// Some params need reinitialization
if (paramName === 'particleCount' || paramName === 'starDensity') {
initializeSystem();
}
}
function updateColor(colorId, value) {
let paramName = colorId.replace('color', '').toLowerCase();
params.colors[paramName] = value;
document.getElementById(colorId + '-value').textContent = value;
}
// ═══════════════════════════════════════════════════════════════════════
// SEED CONTROL FUNCTIONS
// ═══════════════════════════════════════════════════════════════════════
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 {
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('particleLife').value = params.particleLife;
document.getElementById('particleLife-value').textContent = params.particleLife;
document.getElementById('gravityStrength').value = params.gravityStrength;
document.getElementById('gravityStrength-value').textContent = params.gravityStrength;
document.getElementById('noiseScale').value = params.noiseScale;
document.getElementById('noiseScale-value').textContent = params.noiseScale;
document.getElementById('turbulence').value = params.turbulence;
document.getElementById('turbulence-value').textContent = params.turbulence;
document.getElementById('starDensity').value = params.starDensity;
document.getElementById('starDensity-value').textContent = params.starDensity;
document.getElementById('trailOpacity').value = params.trailOpacity;
document.getElementById('trailOpacity-value').textContent = params.trailOpacity;
document.getElementById('coreBrightness').value = params.coreBrightness;
document.getElementById('coreBrightness-value').textContent = params.coreBrightness;
// Reset colors
document.getElementById('colorCore').value = params.colors.core;
document.getElementById('colorCore-value').textContent = params.colors.core;
document.getElementById('colorMid').value = params.colors.mid;
document.getElementById('colorMid-value').textContent = params.colors.mid;
document.getElementById('colorEdge').value = params.colors.edge;
document.getElementById('colorEdge-value').textContent = params.colors.edge;
document.getElementById('colorStar').value = params.colors.star;
document.getElementById('colorStar-value').textContent = params.colors.star;
updateSeedDisplay();
initializeSystem();
}
function downloadCanvas() {
saveCanvas('nebula-genesis-' + params.seed, 'png');
}
// Initialize UI on load
window.addEventListener('load', function() {
updateSeedDisplay();
});
</script>
</body>
</html>