Custom Code Experiment •
Tesseract 1
Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tesseract 1</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #000000;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
touch-action: manipulation;
}
#canvas {
display: block;
background: transparent;
cursor: grab;
}
#canvas:active {
cursor: grabbing;
}
.mobile-toggle {
display: none;
position: fixed;
top: 20px;
right: 20px;
width: 44px;
height: 44px;
border-radius: 50%;
background: #ffffff;
border: 1px solid rgba(255, 255, 255, 0.1);
color: #000000;
font-size: 20px;
cursor: pointer;
z-index: 1001;
transition: all 0.3s ease;
padding: 0;
line-height: 44px;
text-align: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.mobile-toggle:hover {
background: #f0f0f0;
transform: scale(1.05);
}
.mobile-toggle:active {
transform: scale(0.95);
}
.control-panel {
position: fixed;
top: 15px;
right: 15px;
background: rgba(0, 0, 0, 0.85);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 20px;
color: white;
font-size: 13px;
max-width: 280px;
z-index: 1000;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
}
.control-panel.mobile-hidden {
transform: translateX(120%);
opacity: 0;
pointer-events: none;
}
.control-panel.mobile-visible {
transform: translateX(0);
opacity: 1;
pointer-events: auto;
}
.control-group {
margin-bottom: 18px;
}
.control-group:last-child {
margin-bottom: 0;
}
label {
display: block;
margin-bottom: 6px;
font-weight: 500;
color: #ffffff;
font-size: 11px;
letter-spacing: 0.5px;
text-transform: uppercase;
}
input[type="range"] {
width: 100%;
height: 4px;
border-radius: 2px;
background: rgba(255, 255, 255, 0.1);
outline: none;
-webkit-appearance: none;
appearance: none;
}
input[type="range"]::-webkit-slider-thumb {
appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: #ffffff;
cursor: pointer;
border: 2px solid #000000;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
input[type="range"]::-moz-range-thumb {
width: 16px;
height: 16px;
border-radius: 50%;
background: #ffffff;
cursor: pointer;
border: 2px solid #000000;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
.button-group {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.button-group button:not(.mobile-toggle) {
flex: 1;
min-width: 60px;
padding: 10px 14px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
background: rgba(255, 255, 255, 0.1);
color: white;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
font-size: 12px;
letter-spacing: 0.3px;
}
.button-group button:not(.mobile-toggle):hover {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.3);
}
.button-group button:not(.mobile-toggle):active {
background: rgba(255, 255, 255, 0.2);
transform: translateY(0);
}
.value-display {
font-size: 11px;
color: rgba(255, 255, 255, 0.7);
text-align: right;
margin-top: 4px;
font-family: 'Monaco', 'Menlo', monospace;
}
.divider {
height: 1px;
background: rgba(255, 255, 255, 0.1);
margin: 15px 0;
}
.instructions {
position: fixed;
bottom: 20px;
left: 0;
right: 0;
text-align: center;
color: rgba(255, 255, 255, 0.5);
font-size: 11px;
padding: 0 10px;
z-index: 999;
pointer-events: none;
letter-spacing: 0.5px;
}
@media (min-width: 769px) {
.instructions {
display: none;
}
.control-panel {
transition: none;
transform: translateX(0);
opacity: 1;
pointer-events: auto;
}
}
@media (max-width: 768px) {
.mobile-toggle {
display: block;
}
.control-panel {
top: 10px;
right: 10px;
left: 10px;
max-width: none;
padding: 18px;
font-size: 12px;
max-height: 85vh;
overflow-y: auto;
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease;
}
.control-group {
margin-bottom: 16px;
}
.button-group button:not(.mobile-toggle) {
font-size: 11px;
padding: 9px 12px;
}
.instructions {
display: block;
}
#closeButton {
display: block !important;
}
}
@media (max-height: 600px) {
.control-panel {
max-height: 75vh;
overflow-y: auto;
}
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<button class="mobile-toggle" id="mobileToggle">⚙️</button>
<div class="control-panel" id="controlPanel">
<div class="control-group">
<label>Rotation Speed</label>
<input type="range" id="rotationSpeed" min="0.1" max="3" value="1" step="0.1">
<div class="value-display" id="rotationValue">1.0</div>
</div>
<div class="control-group">
<label>Pulse Intensity</label>
<input type="range" id="pulseIntensity" min="0.1" max="2" value="2" step="0.1">
<div class="value-display" id="pulseValue">2.0</div>
</div>
<div class="control-group">
<label>Tessellation Density</label>
<input type="range" id="tessellationDensity" min="4" max="20" value="8" step="2">
<div class="value-display" id="tessellationValue">8</div>
</div>
<div class="divider"></div>
<div class="control-group">
<label>Trail Length</label>
<input type="range" id="trailLength" min="0.8" max="0.99" value="0.95" step="0.01">
<div class="value-display" id="trailValue">0.95</div>
</div>
<div class="control-group">
<label>Color Shift Speed</label>
<input type="range" id="colorSpeed" min="0.1" max="5" value="2" step="0.1">
<div class="value-display" id="colorValue">2.0</div>
</div>
<div class="divider"></div>
<div class="control-group">
<div class="button-group">
<button onclick="resetRotation()">Reset</button>
<button onclick="randomizeColors()">Colors</button>
<button onclick="togglePause()">Pause</button>
<button onclick="closePanel()" id="closeButton" style="display: none;">Close</button>
</div>
</div>
</div>
<div class="instructions">
Drag to rotate • Tap ⚙️ for controls
</div>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const mobileToggle = document.getElementById('mobileToggle');
const controlPanel = document.getElementById('controlPanel');
const closeButton = document.getElementById('closeButton');
let isDragging = false;
let lastTouchX = 0;
let lastTouchY = 0;
let animationId;
let isPaused = false;
let time = 0;
let rotationSpeed = 1;
let pulseIntensity = 2;
let tessellationDensity = 8;
let trailLength = 0.95;
let colorSpeed = 2;
let rotXY = 0, rotXZ = 0, rotXW = 0, rotYZ = 0, rotYW = 0, rotZW = 0;
let colorOffset = 0;
const colorPalettes = [
['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57'],
['#e17055', '#00b894', '#0984e3', '#6c5ce7', '#fd79a8'],
['#ff7675', '#74b9ff', '#55a3ff', '#fd79a8', '#fdcb6e'],
['#a29bfe', '#fd79a8', '#fdcb6e', '#e17055', '#00b894']
];
let currentPalette = 0;
let isMobile = window.innerWidth <= 768;
let panelOpen = false;
function initUI() {
isMobile = window.innerWidth <= 768;
if (isMobile) {
controlPanel.classList.add('mobile-hidden');
controlPanel.classList.remove('mobile-visible');
mobileToggle.style.display = 'block';
panelOpen = false;
mobileToggle.textContent = '⚙️';
mobileToggle.style.background = '#ffffff';
mobileToggle.style.color = '#000000';
closeButton.style.display = 'block';
} else {
controlPanel.classList.remove('mobile-hidden');
controlPanel.classList.remove('mobile-visible');
mobileToggle.style.display = 'none';
panelOpen = true;
closeButton.style.display = 'none';
}
}
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
function toggleControlPanel() {
if (!isMobile) return;
panelOpen = !panelOpen;
if (panelOpen) {
controlPanel.classList.remove('mobile-hidden');
controlPanel.classList.add('mobile-visible');
mobileToggle.textContent = '✕';
mobileToggle.style.background = '#000000';
mobileToggle.style.color = '#ffffff';
mobileToggle.style.borderColor = 'rgba(255, 255, 255, 0.3)';
} else {
controlPanel.classList.remove('mobile-visible');
controlPanel.classList.add('mobile-hidden');
mobileToggle.textContent = '⚙️';
mobileToggle.style.background = '#ffffff';
mobileToggle.style.color = '#000000';
mobileToggle.style.borderColor = 'rgba(255, 255, 255, 0.1)';
}
}
function closePanel() {
if (!isMobile) return;
panelOpen = false;
controlPanel.classList.remove('mobile-visible');
controlPanel.classList.add('mobile-hidden');
mobileToggle.textContent = '⚙️';
mobileToggle.style.background = '#ffffff';
mobileToggle.style.color = '#000000';
mobileToggle.style.borderColor = 'rgba(255, 255, 255, 0.1)';
}
function point4D(x, y, z, w) {
return { x, y, z, w };
}
function rotateXY(p, angle) {
const cos = Math.cos(angle);
const sin = Math.sin(angle);
return point4D(
cos * p.x - sin * p.y,
sin * p.x + cos * p.y,
p.z, p.w
);
}
function rotateXZ(p, angle) {
const cos = Math.cos(angle);
const sin = Math.sin(angle);
return point4D(
cos * p.x - sin * p.z,
p.y,
sin * p.x + cos * p.z,
p.w
);
}
function rotateXW(p, angle) {
const cos = Math.cos(angle);
const sin = Math.sin(angle);
return point4D(
cos * p.x - sin * p.w,
p.y, p.z,
sin * p.x + cos * p.w
);
}
function project4Dto3D(p) {
const distance = 4;
const w = 1 / (distance - p.w);
return {
x: p.x * w,
y: p.y * w,
z: p.z * w
};
}
function project3Dto2D(p) {
const distance = 1.8;
const scale = Math.min(canvas.width, canvas.height) * 0.8;
const z = 1 / (distance - p.z);
return {
x: canvas.width/2 + p.x * scale * z,
y: canvas.height/2 + p.y * scale * z,
depth: p.z
};
}
function createTesseractVertices() {
const vertices = [];
for (let i = 0; i < 16; i++) {
vertices.push(point4D(
(i & 1) ? 1 : -1,
(i & 2) ? 1 : -1,
(i & 4) ? 1 : -1,
(i & 8) ? 1 : -1
));
}
return vertices;
}
function getColorFromPalette(index, alpha = 1) {
const colors = colorPalettes[currentPalette];
const colorIndex = Math.floor((index + colorOffset) % colors.length);
const color = colors[colorIndex];
const r = parseInt(color.slice(1, 3), 16);
const g = parseInt(color.slice(3, 5), 16);
const b = parseInt(color.slice(5, 7), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
function drawEscherPattern(p1, p2, p3, p4, colorIndex) {
const pulse = 1 + Math.sin(time * 3 + colorIndex) * 0.3 * pulseIntensity;
ctx.save();
const segments = tessellationDensity;
for (let i = 0; i < segments; i++) {
const t1 = i / segments;
const t2 = (i + 1) / segments;
const x1 = p1.x + (p2.x - p1.x) * t1;
const y1 = p1.y + (p2.y - p1.y) * t1;
const x2 = p3.x + (p4.x - p3.x) * t1;
const y2 = p3.y + (p4.y - p3.y) * t1;
const x3 = p1.x + (p2.x - p1.x) * t2;
const y3 = p1.y + (p2.y - p1.y) * t2;
const x4 = p3.x + (p4.x - p3.x) * t2;
const y4 = p3.y + (p4.y - p3.y) * t2;
ctx.beginPath();
ctx.moveTo(x1, y1);
const midX = (x1 + x2 + x3 + x4) / 4;
const midY = (y1 + y2 + y3 + y4) / 4;
const curve1X = midX + Math.sin(t1 * Math.PI * 4 + time) * 80 * pulse;
const curve1Y = midY + Math.cos(t1 * Math.PI * 4 + time) * 80 * pulse;
ctx.quadraticCurveTo(curve1X, curve1Y, x2, y2);
ctx.lineTo(x4, y4);
const curve2X = midX + Math.sin(t2 * Math.PI * 4 + time + Math.PI) * 20 * pulse;
const curve2Y = midY + Math.cos(t2 * Math.PI * 4 + time + Math.PI) * 20 * pulse;
ctx.quadraticCurveTo(curve2X, curve2Y, x3, y3);
ctx.closePath();
const alpha = Math.abs(Math.sin(t1 * Math.PI + time + colorIndex)) * 0.8 + 0.2;
ctx.fillStyle = getColorFromPalette(colorIndex + i, alpha);
ctx.fill();
ctx.strokeStyle = getColorFromPalette(colorIndex + i + 1, 0.6);
ctx.lineWidth = 1;
ctx.stroke();
}
ctx.restore();
}
function animate() {
if (isPaused) {
animationId = requestAnimationFrame(animate);
return;
}
time += 0.02 * rotationSpeed;
colorOffset += 0.01 * colorSpeed;
rotXY += 0.01 * rotationSpeed;
rotXZ += 0.007 * rotationSpeed;
rotXW += 0.013 * rotationSpeed;
rotYZ += 0.011 * rotationSpeed;
rotYW += 0.009 * rotationSpeed;
rotZW += 0.005 * rotationSpeed;
ctx.fillStyle = `rgba(0, 0, 0, ${1 - trailLength})`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
const vertices = createTesseractVertices();
const transformedVertices = vertices.map(v => {
let p = v;
const pulse = 1 + Math.sin(time * 2) * 0.2 * pulseIntensity;
p = point4D(p.x * pulse, p.y * pulse, p.z * pulse, p.w * pulse);
p = rotateXY(p, rotXY);
p = rotateXZ(p, rotXZ);
p = rotateXW(p, rotXW);
const p3d = project4Dto3D(p);
return project3Dto2D(p3d);
});
const edges = [
[0, 1], [1, 3], [3, 2], [2, 0],
[4, 5], [5, 7], [7, 6], [6, 4],
[0, 4], [1, 5], [2, 6], [3, 7],
[8, 9], [9, 11], [11, 10], [10, 8],
[12, 13], [13, 15], [15, 14], [14, 12],
[8, 12], [9, 13], [10, 14], [11, 15],
[0, 8], [1, 9], [2, 10], [3, 11],
[4, 12], [5, 13], [6, 14], [7, 15]
];
const edgesWithDepth = edges.map((edge, index) => ({
edge,
index,
depth: (transformedVertices[edge[0]].depth + transformedVertices[edge[1]].depth) / 2
})).sort((a, b) => a.depth - b.depth);
edgesWithDepth.forEach(({ edge, index }) => {
const v1 = transformedVertices[edge[0]];
const v2 = transformedVertices[edge[1]];
const dx = v2.x - v1.x;
const dy = v2.y - v1.y;
const length = Math.sqrt(dx * dx + dy * dy);
if (length > 5) {
const perpX = -dy / length * 60;
const perpY = dx / length * 60;
const p1 = { x: v1.x + perpX, y: v1.y + perpY };
const p2 = { x: v2.x + perpX, y: v2.y + perpY };
const p3 = { x: v1.x - perpX, y: v1.y - perpY };
const p4 = { x: v2.x - perpX, y: v2.y - perpY };
drawEscherPattern(p1, p2, p3, p4, index);
}
});
animationId = requestAnimationFrame(animate);
}
document.getElementById('rotationSpeed').addEventListener('input', (e) => {
rotationSpeed = parseFloat(e.target.value);
document.getElementById('rotationValue').textContent = rotationSpeed.toFixed(1);
});
document.getElementById('pulseIntensity').addEventListener('input', (e) => {
pulseIntensity = parseFloat(e.target.value);
document.getElementById('pulseValue').textContent = pulseIntensity.toFixed(1);
});
document.getElementById('tessellationDensity').addEventListener('input', (e) => {
tessellationDensity = parseInt(e.target.value);
document.getElementById('tessellationValue').textContent = tessellationDensity;
});
document.getElementById('trailLength').addEventListener('input', (e) => {
trailLength = parseFloat(e.target.value);
document.getElementById('trailValue').textContent = trailLength.toFixed(2);
});
document.getElementById('colorSpeed').addEventListener('input', (e) => {
colorSpeed = parseFloat(e.target.value);
document.getElementById('colorValue').textContent = colorSpeed.toFixed(1);
});
mobileToggle.addEventListener('click', toggleControlPanel);
document.addEventListener('click', (e) => {
if (isMobile && panelOpen &&
!controlPanel.contains(e.target) &&
e.target !== mobileToggle) {
closePanel();
}
});
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
isDragging = true;
const touch = e.touches[0];
lastTouchX = touch.clientX;
lastTouchY = touch.clientY;
if (isMobile && panelOpen) {
closePanel();
}
});
function resetRotation() {
rotXY = rotXZ = rotXW = rotYZ = rotYW = rotZW = 0;
time = 0;
}
function randomizeColors() {
currentPalette = Math.floor(Math.random() * colorPalettes.length);
colorOffset = Math.random() * 100;
}
function togglePause() {
isPaused = !isPaused;
const pauseButton = document.querySelector('button[onclick="togglePause()"]');
if (pauseButton) {
pauseButton.textContent = isPaused ? 'Play' : 'Pause';
}
}
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
if (!isDragging) return;
const touch = e.touches[0];
const deltaX = (touch.clientX - lastTouchX) * 0.01;
const deltaY = (touch.clientY - lastTouchY) * 0.01;
rotXY += deltaX;
rotXZ += deltaY;
lastTouchX = touch.clientX;
lastTouchY = touch.clientY;
});
canvas.addEventListener('touchend', (e) => {
e.preventDefault();
isDragging = false;
});
canvas.addEventListener('mousedown', (e) => {
isDragging = true;
lastTouchX = e.clientX;
lastTouchY = e.clientY;
});
canvas.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const deltaX = (e.clientX - lastTouchX) * 0.01;
const deltaY = (e.clientY - lastTouchY) * 0.01;
rotXY += deltaX;
rotXZ += deltaY;
lastTouchX = e.clientX;
lastTouchY = e.clientY;
});
canvas.addEventListener('mouseup', () => {
isDragging = false;
});
window.addEventListener('resize', () => {
resizeCanvas();
initUI();
});
resizeCanvas();
initUI();
animate();
if (isMobile) {
setTimeout(() => {
if (panelOpen) {
closePanel();
}
}, 5000);
}
</script>
</body>
</html>