@wavegrid/canvas 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/server.js +5 -2
- package/esm/ui.js +20 -96
- package/package.json +2 -2
- package/server.js +5 -2
- package/ui.d.ts +1 -1
- package/ui.js +20 -96
package/esm/server.js
CHANGED
|
@@ -3,6 +3,8 @@ import { WebSocket, WebSocketServer } from 'ws';
|
|
|
3
3
|
import { getCanvasHTML } from './ui';
|
|
4
4
|
const PORT = parseInt(process.env.PORT || '3001', 10);
|
|
5
5
|
const SIMULATOR_URL = process.env.SIMULATOR_URL || 'ws://localhost:3000';
|
|
6
|
+
const NUM_CANNONS = process.env.NUM_CANNONS ? parseInt(process.env.NUM_CANNONS, 10) : 49;
|
|
7
|
+
const GRID_COLUMNS = process.env.GRID_COLUMNS ? parseInt(process.env.GRID_COLUMNS, 10) : 7;
|
|
6
8
|
// constructive.io brand mark — served as the favicon
|
|
7
9
|
const FAVICON_SVG = `<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
8
10
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3315 21.7046V28.9348L26.2354 27.2232L29.8206 25.1157L36.9909 29.3307V37.761L30.1657 41.7731V41.785L22.9955 46L19.4102 43.8925L15.8343 41.7848V41.7749L12.5759 39.8595L9 37.7518V21.2924L12.5759 19.1847L15.8343 17.2694V9.25722L19.4102 7.14956L22.6685 5.23418V5.21521L26.2445 3.10755L29.8297 1L37 5.21499V13.6453L30.1657 17.6628V17.6873L23.3315 21.7046ZM16.16 17.8789L12.9168 19.7854L10.0443 21.4784L16.0542 25.0113L22.2948 21.4903L19.4101 19.7945L16.16 17.8789ZM23.6598 5.43249L29.7813 9.0309L35.955 5.40169L33.0743 3.70829L29.8297 1.80095L26.5853 3.70818L23.6598 5.43249ZM22.5139 38.2327L16.8333 41.5721L19.7511 43.2918L22.5185 44.9187L22.5196 38.2427L22.5139 38.2327ZM29.0399 33.6349L29.0153 33.5916L29.1105 33.5357L26.24 31.8482L23.3405 30.1438V33.546V36.9854L29.0399 33.6349ZM29.0998 9.43154L26.24 7.75041L22.9953 5.84307L19.7509 7.7503L16.8486 9.461L22.97 13.0595L29.0348 9.49437L29.0244 9.47595L29.0998 9.43154ZM16.5153 10.0661V13.4722V17.2854L22.5224 20.8167L22.5236 13.598L16.5153 10.0661ZM35.9458 29.5176L33.0651 27.8242L29.8205 25.9168L26.5761 27.8241L23.6705 29.5367L29.7919 33.1352L35.9458 29.5176ZM15.794 33.7218L12.5758 31.8299L9.68105 30.1237V33.5369V37.3517L12.9167 39.2589L15.7928 40.9496L15.794 33.7218ZM15.7954 25.7332L9.68116 22.1389V25.5074V29.3222L12.9168 31.2293L15.7943 32.9208L15.7954 25.7332Z" fill="#01A1FF"/>
|
|
@@ -14,7 +16,7 @@ const server = http.createServer((req, res) => {
|
|
|
14
16
|
return;
|
|
15
17
|
}
|
|
16
18
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
17
|
-
res.end(getCanvasHTML());
|
|
19
|
+
res.end(getCanvasHTML(NUM_CANNONS, GRID_COLUMNS));
|
|
18
20
|
});
|
|
19
21
|
const wss = new WebSocketServer({ server });
|
|
20
22
|
let simulatorWs = null;
|
|
@@ -65,12 +67,13 @@ wss.on('connection', (ws) => {
|
|
|
65
67
|
server.listen(PORT, '0.0.0.0', () => {
|
|
66
68
|
console.log('');
|
|
67
69
|
console.log(' ╭──────────────────────────────────────╮');
|
|
68
|
-
console.log(' │
|
|
70
|
+
console.log(' │ Wavegrid · Canvas │');
|
|
69
71
|
console.log(' │ painting the sky with light │');
|
|
70
72
|
console.log(' ╰──────────────────────────────────────╯');
|
|
71
73
|
console.log('');
|
|
72
74
|
console.log(` → http://localhost:${PORT}`);
|
|
73
75
|
console.log(` → Simulator: ${SIMULATOR_URL}`);
|
|
76
|
+
console.log(` → Grid: ${NUM_CANNONS} cannons (${GRID_COLUMNS} columns)`);
|
|
74
77
|
console.log('');
|
|
75
78
|
connectToSimulator();
|
|
76
79
|
});
|
package/esm/ui.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export function getCanvasHTML() {
|
|
1
|
+
export function getCanvasHTML(numCannons = 49, gridColumns = 7) {
|
|
2
2
|
return `<!DOCTYPE html>
|
|
3
3
|
<html>
|
|
4
4
|
<head>
|
|
@@ -239,19 +239,6 @@ export function getCanvasHTML() {
|
|
|
239
239
|
display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text2);
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
/* ─── Symmetry Tools ─── */
|
|
243
|
-
.symmetry-tools {
|
|
244
|
-
display: flex; gap: 6px;
|
|
245
|
-
}
|
|
246
|
-
.sym-btn {
|
|
247
|
-
width: 48px; height: 48px; border-radius: 12px; font-size: 18px;
|
|
248
|
-
display: flex; align-items: center; justify-content: center;
|
|
249
|
-
background: var(--surface2); border: 1px solid var(--border);
|
|
250
|
-
color: var(--text2); cursor: pointer; transition: all 0.2s;
|
|
251
|
-
}
|
|
252
|
-
.sym-btn:hover { border-color: var(--text2); }
|
|
253
|
-
.sym-btn.active { border-color: var(--accent); color: var(--accent); background: rgba(74,124,255,0.1); }
|
|
254
|
-
|
|
255
242
|
/* ─── Drops Mode ─── */
|
|
256
243
|
.drops-controls {
|
|
257
244
|
display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
|
|
@@ -332,13 +319,12 @@ export function getCanvasHTML() {
|
|
|
332
319
|
<div class="mode-tabs" id="mode-tabs">
|
|
333
320
|
<div class="mode-tab active" data-mode="paint">Paint</div>
|
|
334
321
|
<div class="mode-tab" data-mode="gradient">Gradient</div>
|
|
335
|
-
<div class="mode-tab" data-mode="brush">Brush</div>
|
|
336
322
|
<div class="mode-tab" data-mode="energy">Energy</div>
|
|
337
323
|
<div class="mode-tab" data-mode="scenes">Scenes</div>
|
|
338
324
|
<div class="mode-tab" data-mode="animations">Animations</div>
|
|
339
325
|
<div class="mode-tab" data-mode="motion">Motion</div>
|
|
340
326
|
<div class="mode-tab" data-mode="drops">Drops</div>
|
|
341
|
-
|
|
327
|
+
|
|
342
328
|
</div>
|
|
343
329
|
<div class="tool-area">
|
|
344
330
|
|
|
@@ -353,6 +339,16 @@ export function getCanvasHTML() {
|
|
|
353
339
|
<div class="brightness-thumb" id="bright-thumb" style="bottom:80%"></div>
|
|
354
340
|
</div>
|
|
355
341
|
<div class="color-preview" id="color-preview" style="background:#4a7cff"></div>
|
|
342
|
+
<div class="brush-controls" style="margin-left:8px">
|
|
343
|
+
<div class="brush-size-wrap">
|
|
344
|
+
<div class="brush-preview">
|
|
345
|
+
<div class="brush-dot" id="brush-dot" style="width:20px;height:20px"></div>
|
|
346
|
+
</div>
|
|
347
|
+
<span class="brush-label">Size</span>
|
|
348
|
+
<input type="range" class="brush-slider" id="brush-size" min="1" max="5" value="1">
|
|
349
|
+
</div>
|
|
350
|
+
<div class="toggle-pill" id="brush-falloff">Soft edge</div>
|
|
351
|
+
</div>
|
|
356
352
|
</div>
|
|
357
353
|
</div>
|
|
358
354
|
|
|
@@ -366,26 +362,6 @@ export function getCanvasHTML() {
|
|
|
366
362
|
</div>
|
|
367
363
|
</div>
|
|
368
364
|
|
|
369
|
-
<!-- Brush Mode -->
|
|
370
|
-
<div class="tool-panel" id="panel-brush">
|
|
371
|
-
<div class="color-section">
|
|
372
|
-
<div id="brush-color-wheel-wrap" style="position:relative;width:80px;height:80px;flex-shrink:0">
|
|
373
|
-
<canvas id="brush-color-wheel" width="80" height="80" style="width:80px;height:80px;border-radius:50%;cursor:crosshair;touch-action:none"></canvas>
|
|
374
|
-
<div id="brush-wheel-cursor" style="position:absolute;width:12px;height:12px;border:2px solid #fff;border-radius:50%;pointer-events:none;transform:translate(-50%,-50%);box-shadow:0 0 6px rgba(0,0,0,0.5);left:40px;top:40px"></div>
|
|
375
|
-
</div>
|
|
376
|
-
<div class="brush-controls">
|
|
377
|
-
<div class="brush-size-wrap">
|
|
378
|
-
<div class="brush-preview">
|
|
379
|
-
<div class="brush-dot" id="brush-dot" style="width:20px;height:20px"></div>
|
|
380
|
-
</div>
|
|
381
|
-
<span class="brush-label">Size</span>
|
|
382
|
-
<input type="range" class="brush-slider" id="brush-size" min="1" max="5" value="1">
|
|
383
|
-
</div>
|
|
384
|
-
<div class="toggle-pill" id="brush-falloff">Soft edge</div>
|
|
385
|
-
</div>
|
|
386
|
-
</div>
|
|
387
|
-
</div>
|
|
388
|
-
|
|
389
365
|
<!-- Energy Mode -->
|
|
390
366
|
<div class="tool-panel" id="panel-energy">
|
|
391
367
|
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:8px">
|
|
@@ -445,15 +421,7 @@ export function getCanvasHTML() {
|
|
|
445
421
|
</div>
|
|
446
422
|
</div>
|
|
447
423
|
|
|
448
|
-
|
|
449
|
-
<div class="tool-panel" id="panel-symmetry">
|
|
450
|
-
<div class="symmetry-tools">
|
|
451
|
-
<div class="sym-btn" data-sym="h" title="Mirror left/right">↔</div>
|
|
452
|
-
<div class="sym-btn" data-sym="v" title="Mirror top/bottom">↕</div>
|
|
453
|
-
<div class="sym-btn" data-sym="radial" title="Radial symmetry">✦</div>
|
|
454
|
-
<div class="sym-btn" data-sym="kaleidoscope" title="Kaleidoscope">❋</div>
|
|
455
|
-
</div>
|
|
456
|
-
</div>
|
|
424
|
+
|
|
457
425
|
</div>
|
|
458
426
|
</div>
|
|
459
427
|
</div>
|
|
@@ -462,8 +430,8 @@ export function getCanvasHTML() {
|
|
|
462
430
|
// ═══════════════════════════════════════════════════
|
|
463
431
|
// State
|
|
464
432
|
// ═══════════════════════════════════════════════════
|
|
465
|
-
const NUM =
|
|
466
|
-
const GRID =
|
|
433
|
+
const NUM = ${numCannons};
|
|
434
|
+
const GRID = ${gridColumns};
|
|
467
435
|
const grid = Array.from({length: NUM}, () => ({ h: 220, s: 90, b: 80 }));
|
|
468
436
|
|
|
469
437
|
let currentMode = 'paint';
|
|
@@ -474,7 +442,7 @@ let brushSize = 1;
|
|
|
474
442
|
let brushFalloff = false;
|
|
475
443
|
let activeScene = 'civic';
|
|
476
444
|
let activeAnimation = null;
|
|
477
|
-
|
|
445
|
+
|
|
478
446
|
|
|
479
447
|
// Motion painter state
|
|
480
448
|
let motionPath = [];
|
|
@@ -649,7 +617,7 @@ function getAffectedCannons(centerIdx) {
|
|
|
649
617
|
const result = [{ idx: centerIdx, falloff: 1 }];
|
|
650
618
|
|
|
651
619
|
// Brush size > 1: include neighbors
|
|
652
|
-
if (brushSize > 1 &&
|
|
620
|
+
if (brushSize > 1 && currentMode === 'paint') {
|
|
653
621
|
const reach = brushSize - 1;
|
|
654
622
|
for (let dr = -reach; dr <= reach; dr++) {
|
|
655
623
|
for (let dc = -reach; dc <= reach; dc++) {
|
|
@@ -664,34 +632,9 @@ function getAffectedCannons(centerIdx) {
|
|
|
664
632
|
}
|
|
665
633
|
}
|
|
666
634
|
|
|
667
|
-
// Symmetry: mirror all affected cannons
|
|
668
|
-
const mirrored = [];
|
|
669
|
-
for (const { idx, falloff } of result) {
|
|
670
|
-
mirrored.push({ idx, falloff });
|
|
671
|
-
const r = Math.floor(idx / GRID), c = idx % GRID;
|
|
672
|
-
if (symmetry.h) mirrored.push({ idx: r * GRID + (GRID - 1 - c), falloff });
|
|
673
|
-
if (symmetry.v) mirrored.push({ idx: (GRID - 1 - r) * GRID + c, falloff });
|
|
674
|
-
if (symmetry.h && symmetry.v) mirrored.push({ idx: (GRID - 1 - r) * GRID + (GRID - 1 - c), falloff });
|
|
675
|
-
if (symmetry.radial) {
|
|
676
|
-
// 4-fold rotational
|
|
677
|
-
mirrored.push({ idx: c * GRID + (GRID - 1 - r), falloff });
|
|
678
|
-
mirrored.push({ idx: (GRID - 1 - c) * GRID + r, falloff });
|
|
679
|
-
}
|
|
680
|
-
if (symmetry.kaleidoscope) {
|
|
681
|
-
// 8-fold
|
|
682
|
-
mirrored.push({ idx: r * GRID + (GRID - 1 - c), falloff });
|
|
683
|
-
mirrored.push({ idx: (GRID - 1 - r) * GRID + c, falloff });
|
|
684
|
-
mirrored.push({ idx: (GRID - 1 - r) * GRID + (GRID - 1 - c), falloff });
|
|
685
|
-
mirrored.push({ idx: c * GRID + r, falloff });
|
|
686
|
-
mirrored.push({ idx: c * GRID + (GRID - 1 - r), falloff });
|
|
687
|
-
mirrored.push({ idx: (GRID - 1 - c) * GRID + r, falloff });
|
|
688
|
-
mirrored.push({ idx: (GRID - 1 - c) * GRID + (GRID - 1 - r), falloff });
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
|
|
692
635
|
// Deduplicate
|
|
693
636
|
const seen = new Set();
|
|
694
|
-
return
|
|
637
|
+
return result.filter(m => {
|
|
695
638
|
if (m.idx < 0 || m.idx >= NUM || seen.has(m.idx)) return false;
|
|
696
639
|
seen.add(m.idx);
|
|
697
640
|
return true;
|
|
@@ -759,7 +702,7 @@ function handleSculptureStart(e) {
|
|
|
759
702
|
return;
|
|
760
703
|
}
|
|
761
704
|
|
|
762
|
-
if (idx >= 0 &&
|
|
705
|
+
if (idx >= 0 && currentMode === 'paint') {
|
|
763
706
|
const affected = getAffectedCannons(idx);
|
|
764
707
|
affected.forEach(a => paintCannon(a.idx, a.falloff));
|
|
765
708
|
lastPaintedIdx = idx;
|
|
@@ -809,7 +752,7 @@ function handleSculptureMove(e) {
|
|
|
809
752
|
return;
|
|
810
753
|
}
|
|
811
754
|
|
|
812
|
-
if (currentMode === 'paint'
|
|
755
|
+
if (currentMode === 'paint') {
|
|
813
756
|
const affected = getAffectedCannons(idx);
|
|
814
757
|
affected.forEach(a => paintCannon(a.idx, a.falloff));
|
|
815
758
|
lastPaintedIdx = idx;
|
|
@@ -896,14 +839,6 @@ setupColorWheel('color-wheel', 'wheel-cursor', (h, s) => {
|
|
|
896
839
|
updateColorPreview();
|
|
897
840
|
});
|
|
898
841
|
|
|
899
|
-
setupColorWheel('brush-color-wheel', 'brush-wheel-cursor', (h, s) => {
|
|
900
|
-
currentHue = h;
|
|
901
|
-
currentSat = s;
|
|
902
|
-
updateColorPreview();
|
|
903
|
-
const dot = document.getElementById('brush-dot');
|
|
904
|
-
dot.style.background = hsl(h, s, 50);
|
|
905
|
-
});
|
|
906
|
-
|
|
907
842
|
// Brightness bar
|
|
908
843
|
function setupBrightnessBar() {
|
|
909
844
|
const bar = document.getElementById('bright-bar');
|
|
@@ -1119,17 +1054,6 @@ function playMotionStep() {
|
|
|
1119
1054
|
motionTimer = setTimeout(playMotionStep, 300 - speed * 25);
|
|
1120
1055
|
}
|
|
1121
1056
|
|
|
1122
|
-
// ═══════════════════════════════════════════════════
|
|
1123
|
-
// Symmetry
|
|
1124
|
-
// ═══════════════════════════════════════════════════
|
|
1125
|
-
document.querySelectorAll('.sym-btn').forEach(btn => {
|
|
1126
|
-
btn.addEventListener('click', function() {
|
|
1127
|
-
const key = this.dataset.sym;
|
|
1128
|
-
symmetry[key] = !symmetry[key];
|
|
1129
|
-
this.classList.toggle('active', symmetry[key]);
|
|
1130
|
-
});
|
|
1131
|
-
});
|
|
1132
|
-
|
|
1133
1057
|
// ═══════════════════════════════════════════════════
|
|
1134
1058
|
// Gradient bar rendering
|
|
1135
1059
|
// ═══════════════════════════════════════════════════
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wavegrid/canvas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"author": "Dan Lynch <pyramation@gmail.com>",
|
|
5
5
|
"description": "Artist-facing creative canvas for painting with light on the 7×7 grid",
|
|
6
6
|
"main": "index.js",
|
|
@@ -43,5 +43,5 @@
|
|
|
43
43
|
"@types/ws": "^8.5.13",
|
|
44
44
|
"makage": "^0.3.0"
|
|
45
45
|
},
|
|
46
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "169d6edc27c6869d60729127a3b820ba60469240"
|
|
47
47
|
}
|
package/server.js
CHANGED
|
@@ -9,6 +9,8 @@ const ws_1 = require("ws");
|
|
|
9
9
|
const ui_1 = require("./ui");
|
|
10
10
|
const PORT = parseInt(process.env.PORT || '3001', 10);
|
|
11
11
|
const SIMULATOR_URL = process.env.SIMULATOR_URL || 'ws://localhost:3000';
|
|
12
|
+
const NUM_CANNONS = process.env.NUM_CANNONS ? parseInt(process.env.NUM_CANNONS, 10) : 49;
|
|
13
|
+
const GRID_COLUMNS = process.env.GRID_COLUMNS ? parseInt(process.env.GRID_COLUMNS, 10) : 7;
|
|
12
14
|
// constructive.io brand mark — served as the favicon
|
|
13
15
|
const FAVICON_SVG = `<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
14
16
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3315 21.7046V28.9348L26.2354 27.2232L29.8206 25.1157L36.9909 29.3307V37.761L30.1657 41.7731V41.785L22.9955 46L19.4102 43.8925L15.8343 41.7848V41.7749L12.5759 39.8595L9 37.7518V21.2924L12.5759 19.1847L15.8343 17.2694V9.25722L19.4102 7.14956L22.6685 5.23418V5.21521L26.2445 3.10755L29.8297 1L37 5.21499V13.6453L30.1657 17.6628V17.6873L23.3315 21.7046ZM16.16 17.8789L12.9168 19.7854L10.0443 21.4784L16.0542 25.0113L22.2948 21.4903L19.4101 19.7945L16.16 17.8789ZM23.6598 5.43249L29.7813 9.0309L35.955 5.40169L33.0743 3.70829L29.8297 1.80095L26.5853 3.70818L23.6598 5.43249ZM22.5139 38.2327L16.8333 41.5721L19.7511 43.2918L22.5185 44.9187L22.5196 38.2427L22.5139 38.2327ZM29.0399 33.6349L29.0153 33.5916L29.1105 33.5357L26.24 31.8482L23.3405 30.1438V33.546V36.9854L29.0399 33.6349ZM29.0998 9.43154L26.24 7.75041L22.9953 5.84307L19.7509 7.7503L16.8486 9.461L22.97 13.0595L29.0348 9.49437L29.0244 9.47595L29.0998 9.43154ZM16.5153 10.0661V13.4722V17.2854L22.5224 20.8167L22.5236 13.598L16.5153 10.0661ZM35.9458 29.5176L33.0651 27.8242L29.8205 25.9168L26.5761 27.8241L23.6705 29.5367L29.7919 33.1352L35.9458 29.5176ZM15.794 33.7218L12.5758 31.8299L9.68105 30.1237V33.5369V37.3517L12.9167 39.2589L15.7928 40.9496L15.794 33.7218ZM15.7954 25.7332L9.68116 22.1389V25.5074V29.3222L12.9168 31.2293L15.7943 32.9208L15.7954 25.7332Z" fill="#01A1FF"/>
|
|
@@ -20,7 +22,7 @@ const server = http_1.default.createServer((req, res) => {
|
|
|
20
22
|
return;
|
|
21
23
|
}
|
|
22
24
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
23
|
-
res.end((0, ui_1.getCanvasHTML)());
|
|
25
|
+
res.end((0, ui_1.getCanvasHTML)(NUM_CANNONS, GRID_COLUMNS));
|
|
24
26
|
});
|
|
25
27
|
exports.server = server;
|
|
26
28
|
const wss = new ws_1.WebSocketServer({ server });
|
|
@@ -72,12 +74,13 @@ wss.on('connection', (ws) => {
|
|
|
72
74
|
server.listen(PORT, '0.0.0.0', () => {
|
|
73
75
|
console.log('');
|
|
74
76
|
console.log(' ╭──────────────────────────────────────╮');
|
|
75
|
-
console.log(' │
|
|
77
|
+
console.log(' │ Wavegrid · Canvas │');
|
|
76
78
|
console.log(' │ painting the sky with light │');
|
|
77
79
|
console.log(' ╰──────────────────────────────────────╯');
|
|
78
80
|
console.log('');
|
|
79
81
|
console.log(` → http://localhost:${PORT}`);
|
|
80
82
|
console.log(` → Simulator: ${SIMULATOR_URL}`);
|
|
83
|
+
console.log(` → Grid: ${NUM_CANNONS} cannons (${GRID_COLUMNS} columns)`);
|
|
81
84
|
console.log('');
|
|
82
85
|
connectToSimulator();
|
|
83
86
|
});
|
package/ui.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function getCanvasHTML(): string;
|
|
1
|
+
export declare function getCanvasHTML(numCannons?: number, gridColumns?: number): string;
|
package/ui.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getCanvasHTML = getCanvasHTML;
|
|
4
|
-
function getCanvasHTML() {
|
|
4
|
+
function getCanvasHTML(numCannons = 49, gridColumns = 7) {
|
|
5
5
|
return `<!DOCTYPE html>
|
|
6
6
|
<html>
|
|
7
7
|
<head>
|
|
@@ -242,19 +242,6 @@ function getCanvasHTML() {
|
|
|
242
242
|
display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text2);
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
/* ─── Symmetry Tools ─── */
|
|
246
|
-
.symmetry-tools {
|
|
247
|
-
display: flex; gap: 6px;
|
|
248
|
-
}
|
|
249
|
-
.sym-btn {
|
|
250
|
-
width: 48px; height: 48px; border-radius: 12px; font-size: 18px;
|
|
251
|
-
display: flex; align-items: center; justify-content: center;
|
|
252
|
-
background: var(--surface2); border: 1px solid var(--border);
|
|
253
|
-
color: var(--text2); cursor: pointer; transition: all 0.2s;
|
|
254
|
-
}
|
|
255
|
-
.sym-btn:hover { border-color: var(--text2); }
|
|
256
|
-
.sym-btn.active { border-color: var(--accent); color: var(--accent); background: rgba(74,124,255,0.1); }
|
|
257
|
-
|
|
258
245
|
/* ─── Drops Mode ─── */
|
|
259
246
|
.drops-controls {
|
|
260
247
|
display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
|
|
@@ -335,13 +322,12 @@ function getCanvasHTML() {
|
|
|
335
322
|
<div class="mode-tabs" id="mode-tabs">
|
|
336
323
|
<div class="mode-tab active" data-mode="paint">Paint</div>
|
|
337
324
|
<div class="mode-tab" data-mode="gradient">Gradient</div>
|
|
338
|
-
<div class="mode-tab" data-mode="brush">Brush</div>
|
|
339
325
|
<div class="mode-tab" data-mode="energy">Energy</div>
|
|
340
326
|
<div class="mode-tab" data-mode="scenes">Scenes</div>
|
|
341
327
|
<div class="mode-tab" data-mode="animations">Animations</div>
|
|
342
328
|
<div class="mode-tab" data-mode="motion">Motion</div>
|
|
343
329
|
<div class="mode-tab" data-mode="drops">Drops</div>
|
|
344
|
-
|
|
330
|
+
|
|
345
331
|
</div>
|
|
346
332
|
<div class="tool-area">
|
|
347
333
|
|
|
@@ -356,6 +342,16 @@ function getCanvasHTML() {
|
|
|
356
342
|
<div class="brightness-thumb" id="bright-thumb" style="bottom:80%"></div>
|
|
357
343
|
</div>
|
|
358
344
|
<div class="color-preview" id="color-preview" style="background:#4a7cff"></div>
|
|
345
|
+
<div class="brush-controls" style="margin-left:8px">
|
|
346
|
+
<div class="brush-size-wrap">
|
|
347
|
+
<div class="brush-preview">
|
|
348
|
+
<div class="brush-dot" id="brush-dot" style="width:20px;height:20px"></div>
|
|
349
|
+
</div>
|
|
350
|
+
<span class="brush-label">Size</span>
|
|
351
|
+
<input type="range" class="brush-slider" id="brush-size" min="1" max="5" value="1">
|
|
352
|
+
</div>
|
|
353
|
+
<div class="toggle-pill" id="brush-falloff">Soft edge</div>
|
|
354
|
+
</div>
|
|
359
355
|
</div>
|
|
360
356
|
</div>
|
|
361
357
|
|
|
@@ -369,26 +365,6 @@ function getCanvasHTML() {
|
|
|
369
365
|
</div>
|
|
370
366
|
</div>
|
|
371
367
|
|
|
372
|
-
<!-- Brush Mode -->
|
|
373
|
-
<div class="tool-panel" id="panel-brush">
|
|
374
|
-
<div class="color-section">
|
|
375
|
-
<div id="brush-color-wheel-wrap" style="position:relative;width:80px;height:80px;flex-shrink:0">
|
|
376
|
-
<canvas id="brush-color-wheel" width="80" height="80" style="width:80px;height:80px;border-radius:50%;cursor:crosshair;touch-action:none"></canvas>
|
|
377
|
-
<div id="brush-wheel-cursor" style="position:absolute;width:12px;height:12px;border:2px solid #fff;border-radius:50%;pointer-events:none;transform:translate(-50%,-50%);box-shadow:0 0 6px rgba(0,0,0,0.5);left:40px;top:40px"></div>
|
|
378
|
-
</div>
|
|
379
|
-
<div class="brush-controls">
|
|
380
|
-
<div class="brush-size-wrap">
|
|
381
|
-
<div class="brush-preview">
|
|
382
|
-
<div class="brush-dot" id="brush-dot" style="width:20px;height:20px"></div>
|
|
383
|
-
</div>
|
|
384
|
-
<span class="brush-label">Size</span>
|
|
385
|
-
<input type="range" class="brush-slider" id="brush-size" min="1" max="5" value="1">
|
|
386
|
-
</div>
|
|
387
|
-
<div class="toggle-pill" id="brush-falloff">Soft edge</div>
|
|
388
|
-
</div>
|
|
389
|
-
</div>
|
|
390
|
-
</div>
|
|
391
|
-
|
|
392
368
|
<!-- Energy Mode -->
|
|
393
369
|
<div class="tool-panel" id="panel-energy">
|
|
394
370
|
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:8px">
|
|
@@ -448,15 +424,7 @@ function getCanvasHTML() {
|
|
|
448
424
|
</div>
|
|
449
425
|
</div>
|
|
450
426
|
|
|
451
|
-
|
|
452
|
-
<div class="tool-panel" id="panel-symmetry">
|
|
453
|
-
<div class="symmetry-tools">
|
|
454
|
-
<div class="sym-btn" data-sym="h" title="Mirror left/right">↔</div>
|
|
455
|
-
<div class="sym-btn" data-sym="v" title="Mirror top/bottom">↕</div>
|
|
456
|
-
<div class="sym-btn" data-sym="radial" title="Radial symmetry">✦</div>
|
|
457
|
-
<div class="sym-btn" data-sym="kaleidoscope" title="Kaleidoscope">❋</div>
|
|
458
|
-
</div>
|
|
459
|
-
</div>
|
|
427
|
+
|
|
460
428
|
</div>
|
|
461
429
|
</div>
|
|
462
430
|
</div>
|
|
@@ -465,8 +433,8 @@ function getCanvasHTML() {
|
|
|
465
433
|
// ═══════════════════════════════════════════════════
|
|
466
434
|
// State
|
|
467
435
|
// ═══════════════════════════════════════════════════
|
|
468
|
-
const NUM =
|
|
469
|
-
const GRID =
|
|
436
|
+
const NUM = ${numCannons};
|
|
437
|
+
const GRID = ${gridColumns};
|
|
470
438
|
const grid = Array.from({length: NUM}, () => ({ h: 220, s: 90, b: 80 }));
|
|
471
439
|
|
|
472
440
|
let currentMode = 'paint';
|
|
@@ -477,7 +445,7 @@ let brushSize = 1;
|
|
|
477
445
|
let brushFalloff = false;
|
|
478
446
|
let activeScene = 'civic';
|
|
479
447
|
let activeAnimation = null;
|
|
480
|
-
|
|
448
|
+
|
|
481
449
|
|
|
482
450
|
// Motion painter state
|
|
483
451
|
let motionPath = [];
|
|
@@ -652,7 +620,7 @@ function getAffectedCannons(centerIdx) {
|
|
|
652
620
|
const result = [{ idx: centerIdx, falloff: 1 }];
|
|
653
621
|
|
|
654
622
|
// Brush size > 1: include neighbors
|
|
655
|
-
if (brushSize > 1 &&
|
|
623
|
+
if (brushSize > 1 && currentMode === 'paint') {
|
|
656
624
|
const reach = brushSize - 1;
|
|
657
625
|
for (let dr = -reach; dr <= reach; dr++) {
|
|
658
626
|
for (let dc = -reach; dc <= reach; dc++) {
|
|
@@ -667,34 +635,9 @@ function getAffectedCannons(centerIdx) {
|
|
|
667
635
|
}
|
|
668
636
|
}
|
|
669
637
|
|
|
670
|
-
// Symmetry: mirror all affected cannons
|
|
671
|
-
const mirrored = [];
|
|
672
|
-
for (const { idx, falloff } of result) {
|
|
673
|
-
mirrored.push({ idx, falloff });
|
|
674
|
-
const r = Math.floor(idx / GRID), c = idx % GRID;
|
|
675
|
-
if (symmetry.h) mirrored.push({ idx: r * GRID + (GRID - 1 - c), falloff });
|
|
676
|
-
if (symmetry.v) mirrored.push({ idx: (GRID - 1 - r) * GRID + c, falloff });
|
|
677
|
-
if (symmetry.h && symmetry.v) mirrored.push({ idx: (GRID - 1 - r) * GRID + (GRID - 1 - c), falloff });
|
|
678
|
-
if (symmetry.radial) {
|
|
679
|
-
// 4-fold rotational
|
|
680
|
-
mirrored.push({ idx: c * GRID + (GRID - 1 - r), falloff });
|
|
681
|
-
mirrored.push({ idx: (GRID - 1 - c) * GRID + r, falloff });
|
|
682
|
-
}
|
|
683
|
-
if (symmetry.kaleidoscope) {
|
|
684
|
-
// 8-fold
|
|
685
|
-
mirrored.push({ idx: r * GRID + (GRID - 1 - c), falloff });
|
|
686
|
-
mirrored.push({ idx: (GRID - 1 - r) * GRID + c, falloff });
|
|
687
|
-
mirrored.push({ idx: (GRID - 1 - r) * GRID + (GRID - 1 - c), falloff });
|
|
688
|
-
mirrored.push({ idx: c * GRID + r, falloff });
|
|
689
|
-
mirrored.push({ idx: c * GRID + (GRID - 1 - r), falloff });
|
|
690
|
-
mirrored.push({ idx: (GRID - 1 - c) * GRID + r, falloff });
|
|
691
|
-
mirrored.push({ idx: (GRID - 1 - c) * GRID + (GRID - 1 - r), falloff });
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
|
|
695
638
|
// Deduplicate
|
|
696
639
|
const seen = new Set();
|
|
697
|
-
return
|
|
640
|
+
return result.filter(m => {
|
|
698
641
|
if (m.idx < 0 || m.idx >= NUM || seen.has(m.idx)) return false;
|
|
699
642
|
seen.add(m.idx);
|
|
700
643
|
return true;
|
|
@@ -762,7 +705,7 @@ function handleSculptureStart(e) {
|
|
|
762
705
|
return;
|
|
763
706
|
}
|
|
764
707
|
|
|
765
|
-
if (idx >= 0 &&
|
|
708
|
+
if (idx >= 0 && currentMode === 'paint') {
|
|
766
709
|
const affected = getAffectedCannons(idx);
|
|
767
710
|
affected.forEach(a => paintCannon(a.idx, a.falloff));
|
|
768
711
|
lastPaintedIdx = idx;
|
|
@@ -812,7 +755,7 @@ function handleSculptureMove(e) {
|
|
|
812
755
|
return;
|
|
813
756
|
}
|
|
814
757
|
|
|
815
|
-
if (currentMode === 'paint'
|
|
758
|
+
if (currentMode === 'paint') {
|
|
816
759
|
const affected = getAffectedCannons(idx);
|
|
817
760
|
affected.forEach(a => paintCannon(a.idx, a.falloff));
|
|
818
761
|
lastPaintedIdx = idx;
|
|
@@ -899,14 +842,6 @@ setupColorWheel('color-wheel', 'wheel-cursor', (h, s) => {
|
|
|
899
842
|
updateColorPreview();
|
|
900
843
|
});
|
|
901
844
|
|
|
902
|
-
setupColorWheel('brush-color-wheel', 'brush-wheel-cursor', (h, s) => {
|
|
903
|
-
currentHue = h;
|
|
904
|
-
currentSat = s;
|
|
905
|
-
updateColorPreview();
|
|
906
|
-
const dot = document.getElementById('brush-dot');
|
|
907
|
-
dot.style.background = hsl(h, s, 50);
|
|
908
|
-
});
|
|
909
|
-
|
|
910
845
|
// Brightness bar
|
|
911
846
|
function setupBrightnessBar() {
|
|
912
847
|
const bar = document.getElementById('bright-bar');
|
|
@@ -1122,17 +1057,6 @@ function playMotionStep() {
|
|
|
1122
1057
|
motionTimer = setTimeout(playMotionStep, 300 - speed * 25);
|
|
1123
1058
|
}
|
|
1124
1059
|
|
|
1125
|
-
// ═══════════════════════════════════════════════════
|
|
1126
|
-
// Symmetry
|
|
1127
|
-
// ═══════════════════════════════════════════════════
|
|
1128
|
-
document.querySelectorAll('.sym-btn').forEach(btn => {
|
|
1129
|
-
btn.addEventListener('click', function() {
|
|
1130
|
-
const key = this.dataset.sym;
|
|
1131
|
-
symmetry[key] = !symmetry[key];
|
|
1132
|
-
this.classList.toggle('active', symmetry[key]);
|
|
1133
|
-
});
|
|
1134
|
-
});
|
|
1135
|
-
|
|
1136
1060
|
// ═══════════════════════════════════════════════════
|
|
1137
1061
|
// Gradient bar rendering
|
|
1138
1062
|
// ═══════════════════════════════════════════════════
|