diff --git a/public/app.js b/public/app.js
index 11b5ea0..55ad155 100644
--- a/public/app.js
+++ b/public/app.js
@@ -651,6 +651,162 @@
const r = 0.76 + 0.24 * Math.cos(6 * (i / count) * Math.PI * 2);
verts.push({ x: Math.cos(theta) * r, y: Math.sin(theta) * r });
}
+ } else if (shape === 'lightning') {
+ verts = [
+ { x: 0.18, y: -1.0 },
+ { x: -0.42, y: -0.08 },
+ { x: -0.06, y: -0.08 },
+ { x: -0.32, y: 1.0 },
+ { x: 0.42, y: 0.08 },
+ { x: 0.06, y: 0.08 }
+ ];
+ } else if (shape === 'flame') {
+ const count = 64;
+ for (let i = 0; i < count; i++) {
+ const u = (i / count) * Math.PI * 2;
+ const px = 0.78 * Math.sin(u) * Math.pow(Math.sin(u / 2), 0.75);
+ const py = -Math.cos(u) * 0.95 - 0.05 * Math.cos(2 * u);
+ verts.push({ x: px, y: py });
+ }
+ } else if (shape === 'skull') {
+ verts = [
+ { x: 0, y: -0.95 },
+ { x: 0.38, y: -0.90 },
+ { x: 0.70, y: -0.68 },
+ { x: 0.84, y: -0.32 },
+ { x: 0.82, y: 0.0 },
+ { x: 0.62, y: 0.18 },
+ { x: 0.75, y: 0.38 },
+ { x: 0.48, y: 0.52 },
+ { x: 0.42, y: 0.88 },
+ { x: 0.20, y: 0.94 },
+ { x: 0, y: 0.95 },
+ { x: -0.20, y: 0.94 },
+ { x: -0.42, y: 0.88 },
+ { x: -0.48, y: 0.52 },
+ { x: -0.75, y: 0.38 },
+ { x: -0.62, y: 0.18 },
+ { x: -0.82, y: 0.0 },
+ { x: -0.84, y: -0.32 },
+ { x: -0.70, y: -0.68 },
+ { x: -0.38, y: -0.90 }
+ ];
+ } else if (shape === 'crown') {
+ verts = [
+ { x: 0, y: -0.95 },
+ { x: 0.24, y: -0.32 },
+ { x: 0.50, y: -0.80 },
+ { x: 0.66, y: -0.25 },
+ { x: 0.88, y: -0.65 },
+ { x: 0.82, y: 0.70 },
+ { x: 0.40, y: 0.75 },
+ { x: 0, y: 0.76 },
+ { x: -0.40, y: 0.75 },
+ { x: -0.82, y: 0.70 },
+ { x: -0.88, y: -0.65 },
+ { x: -0.66, y: -0.25 },
+ { x: -0.50, y: -0.80 },
+ { x: -0.24, y: -0.32 }
+ ];
+ } else if (shape === 'crescent') {
+ const count = 36;
+ for (let i = 0; i <= count; i++) {
+ const a = Math.PI * 1.5 - (i / count) * Math.PI;
+ verts.push({ x: 0.92 * Math.cos(a) + 0.42, y: 0.92 * Math.sin(a) });
+ }
+ for (let i = 1; i < count; i++) {
+ const phi = Math.PI * 0.5 - (i / count) * Math.PI;
+ verts.push({ x: -0.36 * Math.cos(phi) + 0.42, y: 0.92 * Math.sin(phi) });
+ }
+ } else if (shape === 'infinity') {
+ const count = 64;
+ for (let i = 0; i < count; i++) {
+ const theta = (i / count) * Math.PI * 2;
+ const r = 0.32 + 0.64 * Math.pow(Math.cos(theta), 2);
+ verts.push({ x: Math.cos(theta) * r, y: Math.sin(theta) * r });
+ }
+ } else if (shape === 'eye') {
+ const count = 32;
+ for (let i = 0; i <= count; i++) {
+ const t = i / count;
+ verts.push({ x: -0.95 + 1.90 * t, y: -Math.sin(t * Math.PI) * 0.62 });
+ }
+ for (let i = count - 1; i > 0; i--) {
+ const t = i / count;
+ verts.push({ x: -0.95 + 1.90 * t, y: Math.sin(t * Math.PI) * 0.62 });
+ }
+ } else if (shape === 'gem') {
+ verts = [
+ { x: -0.52, y: -0.85 },
+ { x: 0.52, y: -0.85 },
+ { x: 0.92, y: -0.32 },
+ { x: 0, y: 0.98 },
+ { x: -0.92, y: -0.32 }
+ ];
+ } else if (shape === 'pick') {
+ const count = 48;
+ for (let i = 0; i < count; i++) {
+ const a = (i / count) * Math.PI * 2;
+ const px = 0.80 * Math.cos(a) * (1.0 - 0.45 * Math.sin(a));
+ const py = 0.92 * Math.sin(a);
+ verts.push({ x: px, y: py });
+ }
+ } else if (shape === 'sun') {
+ const n = 16;
+ const step = Math.PI / n;
+ const start = -Math.PI / 2;
+ for (let i = 0; i < n * 2; i++) {
+ const r = (i % 2 === 0) ? 1.0 : 0.72;
+ const a = start + i * step;
+ verts.push({ x: Math.cos(a) * r, y: Math.sin(a) * r });
+ }
+ } else if (shape === 'droplet') {
+ const count = 48;
+ for (let i = 0; i < count; i++) {
+ const u = (i / count) * Math.PI * 2;
+ const px = 0.82 * Math.sin(u) * Math.sin(u / 2);
+ const py = -Math.cos(u) * 0.90 - 0.05 * Math.cos(2 * u) + 0.05;
+ verts.push({ x: px, y: py });
+ }
+ } else if (shape === 'hourglass') {
+ verts = [
+ { x: -0.80, y: -0.92 },
+ { x: 0.80, y: -0.92 },
+ { x: 0.18, y: 0.0 },
+ { x: 0.80, y: 0.92 },
+ { x: -0.80, y: 0.92 },
+ { x: -0.18, y: 0.0 }
+ ];
+ } else if (shape === 'butterfly') {
+ verts = [
+ { x: 0, y: -0.75 },
+ { x: 0.22, y: -0.68 },
+ { x: 0.62, y: -0.88 },
+ { x: 0.95, y: -0.48 },
+ { x: 0.72, y: -0.10 },
+ { x: 0.38, y: -0.02 },
+ { x: 0.78, y: 0.35 },
+ { x: 0.55, y: 0.72 },
+ { x: 0.20, y: 0.62 },
+ { x: 0, y: 0.82 },
+ { x: -0.20, y: 0.62 },
+ { x: -0.55, y: 0.72 },
+ { x: -0.78, y: 0.35 },
+ { x: -0.38, y: -0.02 },
+ { x: -0.72, y: -0.10 },
+ { x: -0.95, y: -0.48 },
+ { x: -0.62, y: -0.88 },
+ { x: -0.22, y: -0.68 }
+ ];
+ } else if (shape === 'hexagram') {
+ const n = 6;
+ const step = Math.PI / n;
+ const start = -Math.PI / 2;
+ for (let i = 0; i < n * 2; i++) {
+ const r = (i % 2 === 0) ? 1.0 : 0.577;
+ const a = start + i * step;
+ verts.push({ x: Math.cos(a) * r, y: Math.sin(a) * r });
+ }
}
SHAPE_VERTICES_CACHE[shape] = verts;
diff --git a/public/index.html b/public/index.html
index bfe4f73..43cdcc2 100644
--- a/public/index.html
+++ b/public/index.html
@@ -371,6 +371,104 @@
Blume
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/presets/metal_lightning.json b/public/presets/metal_lightning.json
new file mode 100644
index 0000000..0dedd9d
--- /dev/null
+++ b/public/presets/metal_lightning.json
@@ -0,0 +1,81 @@
+{
+ "name": "Heavy Metal Lightning",
+ "icon": "β‘",
+ "shapeType": "lightning",
+ "showCover": true,
+ "coverShape": "circle",
+ "circleRadius": 160,
+ "pulseIntensity": 0.65,
+ "pulseDecay": 0.82,
+ "showBorder": true,
+ "borderWidth": 4,
+ "borderColor": "#ffcc00",
+ "showProgressRing": true,
+ "progressRingColor": "#ff2200",
+ "progressRingWidth": 4,
+ "rotationSpeed": 0.8,
+ "beatSpinImpulse": true,
+ "showSpectrum": true,
+ "spectrumMode": "radial_bars",
+ "linearAlignment": "center_mirrored",
+ "barCount": 96,
+ "spectrumMaxHeight": 150,
+ "barWidth": 4,
+ "barSpacing": 8,
+ "barCornerRadius": 1,
+ "freqSensitivity": 1.4,
+ "freqFocus": "bass_heavy",
+ "mirrorSpectrum": true,
+ "stereoSplit": true,
+ "stereoMode": "split_hemisphere",
+ "routePulse": "sub_bass",
+ "routeParticles": "treble",
+ "routeShake": "sub_bass",
+ "bpmSync": false,
+ "bpmValue": 140,
+ "bpmSyncRotation": false,
+ "bpmSyncGrid": false,
+ "enableParticles": true,
+ "particleType": "sparks",
+ "particleCount": 120,
+ "particleSpeed": 1.4,
+ "particleWarp": 2.0,
+ "enableGrid": true,
+ "colorGrid": "#ff3300",
+ "gridPerspective": 0.7,
+ "gridAudioReactive": 0.6,
+ "enableShake": true,
+ "camShakeIntensity": 0.45,
+ "enableGlitch": true,
+ "rgbSplitIntensity": 0.25,
+ "beatFlash": true,
+ "enableBassZoom": true,
+ "enableVhs": false,
+ "vhsScanlineIntensity": 0.2,
+ "vhsNoiseIntensity": 0.1,
+ "vhsCrtCurvature": false,
+ "enableAfterglow": true,
+ "afterglowDecay": 0.75,
+ "afterglowMode": "fire_amber",
+ "colorMode": "gradient_3",
+ "colorGrad1": "#ffcc00",
+ "colorGrad2": "#ff3300",
+ "colorGrad3": "#990000",
+ "colorParticle": "#ffea88",
+ "gradientStyle": "angular",
+ "enableGlow": true,
+ "glowIntensity": 0.9,
+ "glowBlur": 28,
+ "laserCore": true,
+ "backgroundMode": "transparent",
+ "showTextOverlay": true,
+ "fontFamily": "'Outfit', sans-serif",
+ "textPosition": "below_circle",
+ "titleFontSize": 40,
+ "artistFontSize": 24,
+ "textPulseWithBeat": true,
+ "textGlow": true,
+ "titleColor": "#ffffff",
+ "artistColor": "#ffcc00",
+ "timecodeColor": "#ff4422"
+}
diff --git a/public/presets/midnight_crescent.json b/public/presets/midnight_crescent.json
new file mode 100644
index 0000000..7f8a7e5
--- /dev/null
+++ b/public/presets/midnight_crescent.json
@@ -0,0 +1,81 @@
+{
+ "name": "Midnight Crescent",
+ "icon": "π",
+ "shapeType": "crescent",
+ "showCover": true,
+ "coverShape": "circle",
+ "circleRadius": 155,
+ "pulseIntensity": 0.45,
+ "pulseDecay": 0.85,
+ "showBorder": true,
+ "borderWidth": 4,
+ "borderColor": "#00f0ff",
+ "showProgressRing": true,
+ "progressRingColor": "#bd00ff",
+ "progressRingWidth": 4,
+ "rotationSpeed": 0.6,
+ "beatSpinImpulse": false,
+ "showSpectrum": true,
+ "spectrumMode": "smooth_wave",
+ "linearAlignment": "center_mirrored",
+ "barCount": 120,
+ "spectrumMaxHeight": 120,
+ "barWidth": 3,
+ "barSpacing": 6,
+ "barCornerRadius": 2,
+ "freqSensitivity": 1.2,
+ "freqFocus": "flat",
+ "mirrorSpectrum": true,
+ "stereoSplit": true,
+ "stereoMode": "split_hemisphere",
+ "routePulse": "sub_bass",
+ "routeParticles": "treble",
+ "routeShake": "sub_bass",
+ "bpmSync": false,
+ "bpmValue": 90,
+ "bpmSyncRotation": false,
+ "bpmSyncGrid": false,
+ "enableParticles": true,
+ "particleType": "starfield",
+ "particleCount": 130,
+ "particleSpeed": 0.8,
+ "particleWarp": 1.8,
+ "enableGrid": true,
+ "colorGrid": "#6b11ff",
+ "gridPerspective": 0.6,
+ "gridAudioReactive": 0.4,
+ "enableShake": false,
+ "camShakeIntensity": 0.2,
+ "enableGlitch": false,
+ "rgbSplitIntensity": 0.15,
+ "beatFlash": false,
+ "enableBassZoom": true,
+ "enableVhs": false,
+ "vhsScanlineIntensity": 0.2,
+ "vhsNoiseIntensity": 0.1,
+ "vhsCrtCurvature": false,
+ "enableAfterglow": true,
+ "afterglowDecay": 0.82,
+ "afterglowMode": "magenta_dream",
+ "colorMode": "gradient_3",
+ "colorGrad1": "#00f0ff",
+ "colorGrad2": "#a020f0",
+ "colorGrad3": "#ff007f",
+ "colorParticle": "#aae7ff",
+ "gradientStyle": "angular",
+ "enableGlow": true,
+ "glowIntensity": 0.85,
+ "glowBlur": 26,
+ "laserCore": true,
+ "backgroundMode": "transparent",
+ "showTextOverlay": true,
+ "fontFamily": "'Outfit', sans-serif",
+ "textPosition": "below_circle",
+ "titleFontSize": 38,
+ "artistFontSize": 22,
+ "textPulseWithBeat": true,
+ "textGlow": true,
+ "titleColor": "#ffffff",
+ "artistColor": "#00f0ff",
+ "timecodeColor": "#ff007f"
+}