Spaces:
Running
Running
<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> |