fractal-generator / index.html
LuciferDS's picture
Add 2 files
461c732 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fractal Universe Explorer</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&family=Ubuntu+Mono&display=swap');
:root {
--primary: #6c5ce7;
--secondary: #0984e3;
--dark: #2d3436;
--light: #dfe6e9;
--accent: #e17055;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Montserrat', sans-serif;
background: linear-gradient(135deg, var(--dark), #000);
color: var(--light);
min-height: 100vh;
overflow-x: hidden;
}
.header {
text-align: center;
padding: 2rem;
position: relative;
}
.title {
font-size: 3.5rem;
margin-bottom: 1rem;
background: linear-gradient(to right, var(--primary), var(--secondary));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: 0 0 10px rgba(108, 92, 231, 0.3);
}
.subtitle {
font-size: 1.2rem;
opacity: 0.8;
max-width: 800px;
margin: 0 auto;
line-height: 1.6;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
padding: 2rem;
}
.controls {
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: center;
margin-bottom: 2rem;
width: 100%;
max-width: 900px;
}
.control-group {
display: flex;
flex-direction: column;
min-width: 150px;
}
label {
margin-bottom: 0.5rem;
font-size: 0.9rem;
opacity: 0.8;
}
select, input, button {
padding: 0.8rem 1rem;
border-radius: 8px;
border: 2px solid rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.05);
color: var(--light);
font-family: 'Ubuntu Mono', monospace;
transition: all 0.3s ease;
}
select:focus, input:focus, button:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(108, 92, 231, 0.3);
}
button {
cursor: pointer;
background: linear-gradient(135deg, var(--primary), var(--secondary));
color: white;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
border: none;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.canvas-container {
position: relative;
width: 800px;
height: 600px;
margin: 0 auto;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
border: 2px solid rgba(255, 255, 255, 0.1);
}
canvas {
width: 100%;
height: 100%;
display: block;
}
.info-panel {
margin-top: 2rem;
max-width: 800px;
padding: 1.5rem;
background: rgba(0, 0, 0, 0.3);
border-radius: 16px;
line-height: 1.6;
}
.fractal-info {
margin-bottom: 1rem;
}
.fractal-title {
font-size: 1.5rem;
margin-bottom: 0.5rem;
color: var(--primary);
}
.fractal-description {
font-size: 0.95rem;
opacity: 0.9;
}
.loading {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.7);
z-index: 10;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
}
.loading.active {
opacity: 1;
pointer-events: all;
}
.spinner {
width: 50px;
height: 50px;
border: 5px solid rgba(255, 255, 255, 0.1);
border-radius: 50%;
border-top-color: var(--primary);
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@media (max-width: 900px) {
.canvas-container {
width: 100%;
height: 500px;
}
}
@media (max-width: 600px) {
.title {
font-size: 2.5rem;
}
.subtitle {
font-size: 1rem;
}
.canvas-container {
height: 400px;
}
}
</style>
</head>
<body>
<div class="header">
<h1 class="title">Fractal Universe Explorer</h1>
<p class="subtitle">Explore the infinite complexity of mathematical fractals. Each fractal is generated in real-time using JavaScript, simulating what you might create with Python.</p>
</div>
<div class="container">
<div class="controls">
<div class="control-group">
<label for="fractal-type">Fractal Type</label>
<select id="fractal-type">
<option value="mandelbrot">Mandelbrot Set</option>
<option value="julia">Julia Set</option>
<option value="burning-ship">Burning Ship</option>
<option value="sierpinski">Sierpinski Triangle</option>
<option value="koch">Koch Snowflake</option>
</select>
</div>
<div class="control-group">
<label for="color-scheme">Color Scheme</label>
<select id="color-scheme">
<option value="rainbow">Rainbow</option>
<option value="fire">Fire</option>
<option value="ocean">Ocean</option>
<option value="monochrome">Monochrome</option>
<option value="pastel">Pastel</option>
</select>
</div>
<div class="control-group">
<label for="iterations">Iterations</label>
<input type="range" id="iterations" min="10" max="200" value="100">
<span id="iterations-value">100</span>
</div>
<div class="control-group">
<label for="zoom">Zoom Level</label>
<input type="range" id="zoom" min="1" max="20" value="1">
<span id="zoom-value">1x</span>
</div>
<button id="generate-btn">Generate Fractal</button>
</div>
<div class="canvas-container">
<canvas id="fractal-canvas"></canvas>
<div class="loading" id="loading">
<div class="spinner"></div>
</div>
</div>
<div class="info-panel">
<div class="fractal-info">
<h3 class="fractal-title">Mandelbrot Set</h3>
<p class="fractal-description" id="fractal-description">
The Mandelbrot set is the set of complex numbers c for which the function f(z) = z² + c does not diverge when iterated from z = 0.
It's one of the most famous fractals, exhibiting infinite complexity at every scale.
</p>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// DOM elements
const canvas = document.getElementById('fractal-canvas');
const ctx = canvas.getContext('2d');
const loading = document.getElementById('loading');
const generateBtn = document.getElementById('generate-btn');
const fractalType = document.getElementById('fractal-type');
const colorScheme = document.getElementById('color-scheme');
const iterations = document.getElementById('iterations');
const iterationsValue = document.getElementById('iterations-value');
const zoom = document.getElementById('zoom');
const zoomValue = document.getElementById('zoom-value');
const fractalDescription = document.getElementById('fractal-description');
// Canvas setup
function setupCanvas() {
const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
ctx.scale(dpr, dpr);
}
setupCanvas();
window.addEventListener('resize', setupCanvas);
// Update displayed values
iterations.addEventListener('input', function() {
iterationsValue.textContent = this.value;
});
zoom.addEventListener('input', function() {
zoomValue.textContent = `${this.value}x`;
});
// Fractal descriptions
const fractalDescriptions = {
'mandelbrot': 'The Mandelbrot set is the set of complex numbers c for which the function f(z) = z² + c does not diverge when iterated from z = 0. It\'s one of the most famous fractals, exhibiting infinite complexity at every scale.',
'julia': 'Julia sets are mathematically defined shapes that are closely related to the Mandelbrot set. Each Julia set corresponds to a different complex number c, determining the shape and complexity of the fractal pattern.',
'burning-ship': 'The Burning Ship fractal is a variation of the Mandelbrot set that uses a slightly different iteration formula. It reveals ship-like structures that appear to be burning, hence the name.',
'sierpinski': 'The Sierpinski triangle is a fractal that can be created by recursively subdividing an equilateral triangle into smaller equilateral triangles. It demonstrates perfect self-similarity at all scales.',
'koch': 'The Koch snowflake is a fractal constructed by starting with an equilateral triangle, then recursively altering each line segment to add a triangular bump. It has an infinite perimeter but finite area.'
};
// Color palettes
const palettes = {
'rainbow': (t) => {
const r = Math.floor((1 - t) * 255);
const g = Math.floor((Math.sin(t * Math.PI * 2) * 0.5 + 0.5) * 255);
const b = Math.floor(t * 255);
return `rgb(${r}, ${g}, ${b})`;
},
'fire': (t) => {
const r = 255;
const g = Math.floor(t * 128 + 127);
const b = Math.floor(t * 50);
return `rgb(${r}, ${g}, ${b})`;
},
'ocean': (t) => {
const r = 0;
const g = Math.floor(t * 100 + 50);
const b = Math.floor(t * 200 + 55);
return `rgb(${r}, ${g}, ${b})`;
},
'monochrome': (t) => {
const c = Math.floor(t * 255);
return `rgb(${c}, ${c}, ${c})`;
},
'pastel': (t) => {
const r = Math.floor((1 - t) * 200);
const g = Math.floor((t * 0.5 + 0.5) * 200);
const b = Math.floor((Math.sin(t * Math.PI) + 1) * 100);
return `rgb(${r}, ${g}, ${b})`;
}
};
// Generate fractal
function generateFractal() {
loading.classList.add('active');
// Get selected values
const type = fractalType.value;
const palette = palettes[colorScheme.value];
const maxIter = parseInt(iterations.value);
const zoomLevel = parseInt(zoom.value);
// Update description
document.querySelector('.fractal-title').textContent =
fractalType.options[fractalType.selectedIndex].text;
fractalDescription.textContent = fractalDescriptions[type];
// Clear and prepare canvas
const width = canvas.width;
const height = canvas.height;
// Use setTimeout to prevent UI freeze during heavy computation
setTimeout(() => {
// Draw selected fractal
switch(type) {
case 'mandelbrot':
drawMandelbrot(width, height, palette, maxIter, zoomLevel);
break;
case 'julia':
drawJulia(width, height, palette, maxIter, zoomLevel);
break;
case 'burning-ship':
drawBurningShip(width, height, palette, maxIter, zoomLevel);
break;
case 'sierpinski':
drawSierpinski(width, height, palette, maxIter);
break;
case 'koch':
drawKochSnowflake(width, height, palette, Math.floor(maxIter/15));
break;
}
loading.classList.remove('active');
}, 100);
}
// Mandelbrot set
function drawMandelbrot(width, height, palette, maxIter, zoomLevel) {
const zoomFactor = Math.pow(2, zoomLevel);
const centerX = -0.5;
const centerY = 0;
const scale = 2 / (Math.min(width, height) / 2) / zoomFactor;
const imageData = ctx.createImageData(width, height);
const data = imageData.data;
for (let px = 0; px < width; px++) {
for (let py = 0; py < height; py++) {
// Convert pixel coordinates to complex plane
const x0 = (px - width/2) * scale + centerX;
const y0 = (py - height/2) * scale + centerY;
let x = 0;
let y = 0;
let iter = 0;
// Iterate until escape or max iterations
while (x*x + y*y <= 4 && iter < maxIter) {
const x_new = x*x - y*y + x0;
y = 2*x*y + y0;
x = x_new;
iter++;
}
// Color based on iterations
const color = palette(iter/maxIter);
const [r, g, b] = color.match(/\d+/g);
const idx = (px + py * width) * 4;
// Set pixel color
if (iter === maxIter) {
// Inside the set (black)
data[idx] = 0;
data[idx+1] = 0;
data[idx+2] = 0;
} else {
// Outside (colored by iterations)
data[idx] = r;
data[idx+1] = g;
data[idx+2] = b;
}
data[idx+3] = 255; // Alpha
}
}
ctx.putImageData(imageData, 0, 0);
}
// Julia set (similar to Mandelbrot but with fixed c)
function drawJulia(width, height, palette, maxIter, zoomLevel) {
const zoomFactor = Math.pow(2, zoomLevel);
const centerX = 0;
const centerY = 0;
const scale = 2.5 / (Math.min(width, height) / 2) / zoomFactor;
// Julia parameters (can be randomized)
const cRe = -0.7;
const cIm = 0.27;
const imageData = ctx.createImageData(width, height);
const data = imageData.data;
for (let px = 0; px < width; px++) {
for (let py = 0; py < height; py++) {
const zx = (px - width/2) * scale + centerX;
const zy = (py - height/2) * scale + centerY;
let x = zx;
let y = zy;
let iter = 0;
while (x*x + y*y <= 4 && iter < maxIter) {
const x_new = x*x - y*y + cRe;
y = 2*x*y + cIm;
x = x_new;
iter++;
}
const color = palette(iter/maxIter);
const [r, g, b] = color.match(/\d+/g);
const idx = (px + py * width) * 4;
data[idx] = (iter === maxIter) ? 0 : r;
data[idx+1] = (iter === maxIter) ? 0 : g;
data[idx+2] = (iter === maxIter) ? 0 : b;
data[idx+3] = 255;
}
}
ctx.putImageData(imageData, 0, 0);
}
// Burning Ship fractal
function drawBurningShip(width, height, palette, maxIter, zoomLevel) {
const zoomFactor = Math.pow(2, zoomLevel);
const centerX = -0.5;
const centerY = -0.5;
const scale = 2.8 / (Math.min(width, height) / 2) / zoomFactor;
const imageData = ctx.createImageData(width, height);
const data = imageData.data;
for (let px = 0; px < width; px++) {
for (let py = 0; py < height; py++) {
const x0 = (px - width/2) * scale + centerX;
const y0 = (py - height/2) * scale + centerY;
let x = 0;
let y = 0;
let iter = 0;
while (x*x + y*y <= 4 && iter < maxIter) {
const x_new = x*x - y*y + x0;
y = Math.abs(2*x*y) + y0;
x = Math.abs(x_new);
iter++;
}
const color = palette(iter/maxIter);
const [r, g, b] = color.match(/\d+/g);
const idx = (px + py * width) * 4;
data[idx] = (iter === maxIter) ? 0 : r;
data[idx+1] = (iter === maxIter) ? 0 : g;
data[idx+2] = (iter === maxIter) ? 0 : b;
data[idx+3] = 255;
}
}
ctx.putImageData(imageData, 0, 0);
}
// Sierpinski Triangle (geometric fractal)
function drawSierpinski(width, height, palette, maxIter) {
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, width, height);
const size = Math.min(width, height) * 0.9;
const marginX = (width - size) / 2;
const marginY = (height - size) / 2;
// Starting points (triangle)
let points = [
{x: marginX + size/2, y: marginY},
{x: marginX, y: marginY + size},
{x: marginX + size, y: marginY + size}
];
// Random starting point
let px = Math.random() * width;
let py = Math.random() * height;
// Draw many iterations
for (let i = 0; i < maxIter * 300; i++) {
// Choose a random corner
const corner = Math.floor(Math.random() * 3);
// Move halfway toward that corner
px = (px + points[corner].x) / 2;
py = (py + points[corner].y) / 2;
// Draw point
ctx.fillStyle = palette(i/(maxIter*300));
ctx.fillRect(px, py, 1, 1);
}
}
// Koch Snowflake (recursive fractal)
function drawKochSnowflake(width, height, palette, depth) {
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, width, height);
const size = Math.min(width, height) * 0.7;
const centerX = width / 2;
const centerY = height / 2;
// Starting equilateral triangle
let points = [
{x: centerX, y: centerY - size/2},
{x: centerX - size/2, y: centerY + size/2},
{x: centerX + size/2, y: centerY + size/2}
];
// Draw the snowflake
ctx.strokeStyle = palette(1.0);
ctx.lineWidth = 1;
function koch(p1, p2, level) {
if (level === 0) {
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.strokeStyle = palette(1 - level/depth);
ctx.stroke();
} else {
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
// Calculate intermediate points
const a = {
x: p1.x + dx / 3,
y: p1.y + dy / 3
};
const b = {
x: p1.x + dx / 2 - dy * Math.sqrt(3)/6,
y: p1.y + dy / 2 + dx * Math.sqrt(3)/6
};
const c = {
x: p1.x + 2*dx/3,
y: p1.y + 2*dy/3
};
// Recursively draw the 4 segments
koch(p1, a, level - 1);
koch(a, b, level - 1);
koch(b, c, level - 1);
koch(c, p2, level - 1);
}
}
// Draw the three initial sides
koch(points[0], points[1], depth);
koch(points[1], points[2], depth);
koch(points[2], points[0], depth);
}
// Events
generateBtn.addEventListener('click', generateFractal);
fractalType.addEventListener('change', function() {
document.querySelector('.fractal-title').textContent =
this.options[this.selectedIndex].text;
fractalDescription.textContent = fractalDescriptions[this.value];
});
// Generate initial fractal
generateFractal();
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body>
</html>