-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhexagonal_ribbon_circuit_grid.html
More file actions
250 lines (214 loc) · 8.11 KB
/
Copy pathhexagonal_ribbon_circuit_grid.html
File metadata and controls
250 lines (214 loc) · 8.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Hexagonal Ribbon Circuit Grid</title>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #222;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
touch-action: none;
}
canvas {
display: block;
cursor: grab;
}
canvas:active {
cursor: grabbing;
}
#overlay {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 12px 24px;
border-radius: 30px;
pointer-events: none;
font-size: 14px;
letter-spacing: 0.5px;
opacity: 1;
transition: opacity 0.8s ease;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}
</style>
</head>
<body>
<canvas id="gridCanvas"></canvas>
<div id="overlay">Drag to explore the infinite labyrinth</div>
<script>
const canvas = document.getElementById('gridCanvas');
const ctx = canvas.getContext('2d');
const overlay = document.getElementById('overlay');
// Tile Dimensions
const R = 75;
const W = Math.sqrt(3) * R;
const H = 1.5 * R;
// Styling Configurations
const PATH_WIDTH = R * 0.35;
const OUTLINE_WIDTH = 2;
const TOTAL_WIDTH = PATH_WIDTH + OUTLINE_WIDTH * 2;
const COLOR_BG = '#f0f1ed';
const COLOR_MAIN = '#a0a7ab';
const COLOR_OUTLINE = '#4a4f54';
const COLOR_GROUT = '#b0b5b8';
// Scrolling state
let scrollX = 0;
let scrollY = 0;
let speedX = 0.4;
let speedY = 0.3;
let isDragging = false;
let lastX = 0;
let lastY = 0;
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();
// Deterministic pseudo-random number generator for grid coordinates
function getRotationHash(r, c) {
const hash = Math.sin(r * 12.9898 + c * 78.233) * 43758.5453123;
return Math.floor(Math.abs(hash) * 6); // Returns 0 to 5
}
// Draw a specific path sequence to achieve the desired layered strokes
function strokeLayeredPath(pathFn, isOver) {
if (isOver) {
// Background clear line to make it look like it's "over" the other
ctx.beginPath();
pathFn();
ctx.strokeStyle = COLOR_BG;
ctx.lineWidth = TOTAL_WIDTH + 8;
ctx.stroke();
}
// Dark Outer Edges
ctx.beginPath();
pathFn();
ctx.strokeStyle = COLOR_OUTLINE;
ctx.lineWidth = TOTAL_WIDTH;
ctx.stroke();
// Main Thick Line
ctx.beginPath();
pathFn();
ctx.strokeStyle = COLOR_MAIN;
ctx.lineWidth = PATH_WIDTH;
ctx.stroke();
// Central Thin Detail Line
ctx.beginPath();
pathFn();
ctx.strokeStyle = COLOR_OUTLINE;
ctx.lineWidth = 1.5;
ctx.stroke();
}
function drawHexagon(cx, cy, gridR, gridC) {
ctx.save();
ctx.translate(cx, cy);
// Apply deterministic random rotation (multiples of 60 degrees)
const rotIndex = getRotationHash(gridR, gridC);
ctx.rotate(rotIndex * Math.PI / 3);
// 1. Draw base hexagon
ctx.beginPath();
for (let i = 0; i < 6; i++) {
const angle = (i * 60 + 30) * Math.PI / 180;
ctx.lineTo(R * Math.cos(angle), R * Math.sin(angle));
}
ctx.closePath();
ctx.fillStyle = COLOR_BG;
ctx.fill();
// Draw grout lines early so they sit under the continuous ribbons
ctx.strokeStyle = COLOR_GROUT;
ctx.lineWidth = 2;
ctx.stroke();
// Setup edge midpoints
const M = R * Math.sqrt(3) / 2;
const mPt = (angleDeg) => {
const a = angleDeg * Math.PI / 180;
return { x: M * Math.cos(a), y: M * Math.sin(a) };
};
const m0 = mPt(60); // Side 1
const m1 = mPt(120); // Side 2
const m2 = mPt(180); // Side 3
const m3 = mPt(240); // Side 4
// m4 is 300, m5 is 0
// Factor for bezier control points to pull curves toward center safely
const f = 0.45;
// Arc 3: Adjacent connection (Sides 5 to 6)
// Perfectly centered at Vertex 5 (330 degrees)
const v5 = { x: R * Math.cos(330 * Math.PI/180), y: R * Math.sin(330 * Math.PI/180) };
const arc3Fn = () => {
ctx.arc(v5.x, v5.y, R/2, 210 * Math.PI/180, 90 * Math.PI/180, true);
};
// Arc 2: Crosses the center (Sides 2 to 4) - UNDER
const arc2Fn = () => {
ctx.moveTo(m1.x, m1.y);
ctx.bezierCurveTo(m1.x * f, m1.y * f, m3.x * f, m3.y * f, m3.x, m3.y);
};
// Arc 1: Crosses the center (Sides 1 to 3) - OVER
const arc1Fn = () => {
ctx.moveTo(m0.x, m0.y);
ctx.bezierCurveTo(m0.x * f, m0.y * f, m2.x * f, m2.y * f, m2.x, m2.y);
};
// Render sequentially
strokeLayeredPath(arc3Fn, false);
strokeLayeredPath(arc2Fn, false);
strokeLayeredPath(arc1Fn, true); // True triggers the erasing shadow
ctx.restore();
}
function animate() {
if (!isDragging) {
scrollX += speedX;
scrollY += speedY;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Determine visible grid bounds
const startCol = Math.floor(-scrollX / W) - 2;
const endCol = Math.floor((-scrollX + canvas.width) / W) + 2;
const startRow = Math.floor(-scrollY / H) - 2;
const endRow = Math.floor((-scrollY + canvas.height) / H) + 2;
// Render visible grid coordinates
for (let r = startRow; r <= endRow; r++) {
for (let c = startCol; c <= endCol; c++) {
const cx = c * W + (Math.abs(r) % 2 === 1 ? W / 2 : 0) + scrollX;
const cy = r * H + scrollY;
drawHexagon(cx, cy, r, c);
}
}
requestAnimationFrame(animate);
}
// --- Interaction Handlers ---
function startDrag(x, y) {
isDragging = true;
lastX = x;
lastY = y;
overlay.style.opacity = '0';
}
function handleDrag(x, y) {
if (!isDragging) return;
scrollX += (x - lastX);
scrollY += (y - lastY);
lastX = x;
lastY = y;
}
function endDrag() {
isDragging = false;
}
canvas.addEventListener('mousedown', (e) => startDrag(e.clientX, e.clientY));
window.addEventListener('mousemove', (e) => handleDrag(e.clientX, e.clientY));
window.addEventListener('mouseup', endDrag);
canvas.addEventListener('touchstart', (e) => startDrag(e.touches[0].clientX, e.touches[0].clientY), { passive: false });
window.addEventListener('touchmove', (e) => {
handleDrag(e.touches[0].clientX, e.touches[0].clientY);
e.preventDefault();
}, { passive: false });
window.addEventListener('touchend', endDrag);
// Initiate animation loop
animate();
// Fade overlay out
setTimeout(() => { overlay.style.opacity = '0'; }, 5000);
</script>
</body>
</html>