@viji-dev/sdk 1.0.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.
Files changed (55) hide show
  1. package/.gitignore +29 -0
  2. package/LICENSE +13 -0
  3. package/README.md +103 -0
  4. package/bin/viji.js +75 -0
  5. package/eslint.config.js +37 -0
  6. package/index.html +20 -0
  7. package/package.json +82 -0
  8. package/postcss.config.js +6 -0
  9. package/public/favicon.png +0 -0
  10. package/scenes/audio-visualizer/main.js +287 -0
  11. package/scenes/core-demo/main.js +532 -0
  12. package/scenes/demo-scene/main.js +619 -0
  13. package/scenes/global.d.ts +15 -0
  14. package/scenes/particle-system/main.js +349 -0
  15. package/scenes/tsconfig.json +12 -0
  16. package/scenes/video-mirror/main.ts +436 -0
  17. package/src/App.css +42 -0
  18. package/src/App.tsx +279 -0
  19. package/src/cli/commands/build.js +147 -0
  20. package/src/cli/commands/create.js +71 -0
  21. package/src/cli/commands/dev.js +108 -0
  22. package/src/cli/commands/init.js +262 -0
  23. package/src/cli/utils/cli-utils.js +208 -0
  24. package/src/cli/utils/scene-compiler.js +432 -0
  25. package/src/components/SDKPage.tsx +337 -0
  26. package/src/components/core/CoreContainer.tsx +126 -0
  27. package/src/components/ui/DeviceSelectionList.tsx +137 -0
  28. package/src/components/ui/FPSCounter.tsx +78 -0
  29. package/src/components/ui/FileDropzonePanel.tsx +120 -0
  30. package/src/components/ui/FileListPanel.tsx +285 -0
  31. package/src/components/ui/InputExpansionPanel.tsx +31 -0
  32. package/src/components/ui/MediaPlayerControls.tsx +191 -0
  33. package/src/components/ui/MenuContainer.tsx +71 -0
  34. package/src/components/ui/ParametersMenu.tsx +797 -0
  35. package/src/components/ui/ProjectSwitcherMenu.tsx +192 -0
  36. package/src/components/ui/QuickInputControls.tsx +542 -0
  37. package/src/components/ui/SDKMenuSystem.tsx +96 -0
  38. package/src/components/ui/SettingsMenu.tsx +346 -0
  39. package/src/components/ui/SimpleInputControls.tsx +137 -0
  40. package/src/index.css +68 -0
  41. package/src/main.tsx +10 -0
  42. package/src/scenes-hmr.ts +158 -0
  43. package/src/services/project-filesystem.ts +436 -0
  44. package/src/stores/scene-player/index.ts +3 -0
  45. package/src/stores/scene-player/input-manager.store.ts +1045 -0
  46. package/src/stores/scene-player/scene-session.store.ts +659 -0
  47. package/src/styles/globals.css +111 -0
  48. package/src/templates/minimal-template.js +11 -0
  49. package/src/utils/debounce.js +34 -0
  50. package/src/vite-env.d.ts +1 -0
  51. package/tailwind.config.js +18 -0
  52. package/tsconfig.app.json +27 -0
  53. package/tsconfig.json +27 -0
  54. package/tsconfig.node.json +27 -0
  55. package/vite.config.ts +54 -0
package/.gitignore ADDED
@@ -0,0 +1,29 @@
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
25
+
26
+ # Documentation for other project parts
27
+ /core_docs
28
+ /backend_docs
29
+ /sdk_docs
package/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2025 Artem Verkhovskiy and Dmitry Manoilenko.
2
+ All rights reserved.
3
+
4
+ Permission is granted to install and use this software for the purpose of
5
+ developing creative scenes or related content for the VIJI platform.
6
+
7
+ Artists may freely use the SDK to create, test, and share their own scenes
8
+ without restriction. Redistribution or modification of the SDK itself, or
9
+ use of the SDK for purposes unrelated to developing for the VIJI platform,
10
+ is prohibited without prior written permission from the copyright holders.
11
+
12
+ This license does not grant any rights to copy, sublicense, or commercialize
13
+ the SDK itself, nor to use the VIJI name or brand without permission.
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # Viji SDK
2
+
3
+ Professional toolkit for creating interactive Viji scenes. Includes a CLI, workspace management, local development UI (Vite-based), and a production build pipeline that outputs a single deployable `scene.js`. Uses `@viji-dev/core` under the hood.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @viji-dev/sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ # Create a new workspace
15
+ mkdir my-viji-workspace
16
+ cd my-viji-workspace
17
+ viji init .
18
+
19
+ # Create your first scene
20
+ viji create my-scene --lang=ts
21
+
22
+ # Start development server
23
+ viji dev
24
+ ```
25
+
26
+ ## CLI Commands
27
+
28
+ ### `viji init [workspace-name]`
29
+ Initialize a new Viji workspace with complete development environment.
30
+ - Use `viji init .` to initialize in current directory
31
+ - Use `viji init my-workspace` to create new directory
32
+
33
+ ### `viji create <scene-name> [--lang=js|ts]`
34
+ Create a new scene in the current workspace.
35
+ - Must be run inside a Viji workspace
36
+ - Creates scene in `scenes/<scene-name>/` folder
37
+
38
+ ### `viji dev [--port=5173] [--host=localhost] [--open]`
39
+ Start development server with hot reload.
40
+ - Must be run inside a Viji workspace
41
+ - Opens browser-based development UI
42
+
43
+ ### `viji build <scene-name> [--output=dist/scene.js]`
44
+ Build a scene for platform deployment.
45
+ - Compiles multi-file project to single JS file
46
+ - Ready for copy-paste to Viji platform
47
+
48
+ ## Workspace Structure
49
+
50
+ ```
51
+ my-workspace/
52
+ ├── src/ # Development UI (React + Vite)
53
+ ├── scenes/ # Your scenes
54
+ │ ├── my-scene/
55
+ │ │ └── main.ts # Scene entry point
56
+ │ └── another-scene/
57
+ ├── vite.config.ts # Vite configuration
58
+ ├── package.json # Dependencies
59
+ └── ... # TypeScript configs, etc.
60
+ ```
61
+
62
+ ## Scene Development
63
+
64
+ Each scene is a folder in `scenes/` with a `main.js` or `main.ts` entry point:
65
+
66
+ ```typescript
67
+ // scenes/my-scene/main.ts
68
+ export const parameters = [
69
+ // Parameter definitions
70
+ ];
71
+
72
+ export function init(canvas: HTMLCanvasElement, audioContext: AudioContext) {
73
+ // Initialization code
74
+ }
75
+
76
+ export function render(state: any, inputs: any, params: any) {
77
+ // Main render loop
78
+ }
79
+ ```
80
+
81
+ ## Features
82
+
83
+ - **🎨 Complete Development Environment**: Full React UI for scene development
84
+ - **🔥 Hot Reload**: Instant updates when you save scene files
85
+ - **📁 Workspace Management**: Organize multiple scenes in one project
86
+ - **🛠️ TypeScript Support**: Full type safety and IDE integration
87
+ - **📦 Single-File Build**: Compile to platform-ready scene.js
88
+ - **🎯 Platform Compatible**: Uses same Core runtime as Viji platform
89
+
90
+ ## Documentation
91
+
92
+ - SDK package: `docs/02-sdk-package.md`
93
+ - Implementation guide: `docs/06-cursor-implementation-guide.md`
94
+
95
+ ## 📄 License
96
+
97
+ Copyright (c) 2025 Artem Verkhovskiy and Dmitry Manoilenko.
98
+ All rights reserved - see the LICENSE file for details.
99
+
100
+ ## Contributor License Agreement
101
+
102
+ By contributing, you agree to the [CLA](./CLA.md).
103
+ Please also confirm your agreement by filling out this short [form](https://forms.gle/A49WTz8nj5b99Yev7).
package/bin/viji.js ADDED
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Viji SDK Command Line Interface
5
+ * Professional toolkit for creating interactive Viji scenes
6
+ */
7
+
8
+ import { createRequire } from 'module';
9
+ const require = createRequire(import.meta.url);
10
+ const { program } = require('commander');
11
+ import { createCommand } from '../src/cli/commands/create.js';
12
+ import { devCommand } from '../src/cli/commands/dev.js';
13
+ import { buildCommand } from '../src/cli/commands/build.js';
14
+ import { initCommand } from '../src/cli/commands/init.js';
15
+ import { fileURLToPath } from 'url';
16
+ import { dirname, join } from 'path';
17
+ import { readFileSync } from 'fs';
18
+
19
+ // Get package.json version
20
+ const __filename = fileURLToPath(import.meta.url);
21
+ const __dirname = dirname(__filename);
22
+ const packagePath = join(__dirname, '..', 'package.json');
23
+ const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
24
+
25
+ program
26
+ .name('viji')
27
+ .description('🎨 Viji SDK - Professional toolkit for creating interactive scenes')
28
+ .version(packageJson.version);
29
+
30
+ // Command: Initialize new workspace
31
+ program
32
+ .command('init')
33
+ .argument('[workspace-name]', 'Name of the workspace (use "." for current directory)', '.')
34
+ .description('Initialize a new Viji workspace')
35
+ .action(initCommand);
36
+
37
+ // Command: Create new scene
38
+ program
39
+ .command('create')
40
+ .argument('<scene-name>', 'Name of the new scene')
41
+ .option('--lang <lang>', 'Scene language (js|ts)', 'js')
42
+ .description('Create a new scene in the current workspace')
43
+ .action(createCommand);
44
+
45
+ // Command: Start development server
46
+ program
47
+ .command('dev')
48
+ .option('-p, --port <port>', 'Development server port', '5173')
49
+ .option('-h, --host <host>', 'Development server host', 'localhost')
50
+ .option('--open', 'Open browser automatically', false)
51
+ .description('Start development server with hot reload')
52
+ .action(devCommand);
53
+
54
+ // Command: Build for production
55
+ program
56
+ .command('build')
57
+ .argument('<scene-name>', 'Name of the scene to build')
58
+ .option('-o, --output <file>', 'Output file path', 'dist/scene.js')
59
+ .description('Build scene: bundle multi-file projects into one, no minify')
60
+ .action(buildCommand);
61
+
62
+
63
+ // Global error handling
64
+ process.on('uncaughtException', (error) => {
65
+ console.error('💥 Unexpected error:', error.message);
66
+ process.exit(1);
67
+ });
68
+
69
+ process.on('unhandledRejection', (reason) => {
70
+ console.error('💥 Unhandled promise rejection:', reason);
71
+ process.exit(1);
72
+ });
73
+
74
+ // Parse and execute
75
+ program.parse();
@@ -0,0 +1,37 @@
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+ import tseslint from 'typescript-eslint'
6
+ import { globalIgnores } from 'eslint/config'
7
+
8
+ export default tseslint.config([
9
+ globalIgnores(['dist']),
10
+ {
11
+ files: ['scenes/**/*.{js,ts}'],
12
+ languageOptions: {
13
+ ecmaVersion: 2020,
14
+ globals: {
15
+ ...globals.browser,
16
+ viji: 'readonly',
17
+ render: 'readonly',
18
+ },
19
+ },
20
+ rules: {
21
+ 'no-undef': 'off',
22
+ },
23
+ },
24
+ {
25
+ files: ['**/*.{ts,tsx}'],
26
+ extends: [
27
+ js.configs.recommended,
28
+ tseslint.configs.recommended,
29
+ reactHooks.configs['recommended-latest'],
30
+ reactRefresh.configs.vite,
31
+ ],
32
+ languageOptions: {
33
+ ecmaVersion: 2020,
34
+ globals: globals.browser,
35
+ },
36
+ },
37
+ ])
package/index.html ADDED
@@ -0,0 +1,20 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/png" href="/favicon.png" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>VIJI SDK - Creative Development Toolkit</title>
8
+
9
+ <!-- Security headers for @viji-dev/core workers and iframes -->
10
+ <meta http-equiv="Cross-Origin-Embedder-Policy" content="credentialless" />
11
+ <meta http-equiv="Cross-Origin-Opener-Policy" content="same-origin" />
12
+
13
+ <!-- Permissions for media access -->
14
+ <meta http-equiv="Feature-Policy" content="camera 'self'; microphone 'self'; display-capture 'self'" />
15
+ </head>
16
+ <body>
17
+ <div id="root"></div>
18
+ <script type="module" src="/src/main.tsx"></script>
19
+ </body>
20
+ </html>
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@viji-dev/sdk",
3
+ "private": false,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "description": "Professional toolkit for creating interactive Viji scenes",
7
+ "keywords": [
8
+ "viji",
9
+ "interactive",
10
+ "visualization",
11
+ "art",
12
+ "sdk",
13
+ "cli"
14
+ ],
15
+ "author": "Viji Team",
16
+ "license": "SEE LICENSE IN LICENSE",
17
+ "bin": {
18
+ "viji": "./bin/viji.js"
19
+ },
20
+ "scripts": {
21
+ "dev": "vite",
22
+ "build": "tsc -b && vite build",
23
+ "lint": "eslint .",
24
+ "preview": "vite preview",
25
+ "cli:dev": "node bin/viji.js dev",
26
+ "cli:build": "node bin/viji.js build",
27
+ "cli:create": "node bin/viji.js create"
28
+ },
29
+ "main": "src/main.tsx",
30
+ "files": [
31
+ "bin/",
32
+ "src/",
33
+ "scenes/",
34
+ "public/",
35
+ "index.html",
36
+ "vite.config.ts",
37
+ "tsconfig*.json",
38
+ "tailwind.config.js",
39
+ "postcss.config.js",
40
+ "eslint.config.js",
41
+ ".gitignore",
42
+ "README.md",
43
+ "LICENSE"
44
+ ],
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "https://github.com/viji-dev/sdk.git"
48
+ },
49
+ "homepage": "https://github.com/viji-dev/sdk#readme",
50
+ "bugs": {
51
+ "url": "https://github.com/viji-dev/sdk/issues"
52
+ },
53
+ "dependencies": {
54
+ "@heroicons/react": "^2.2.0",
55
+ "@heroui/react": "^2.7.11",
56
+ "@viji-dev/core": "^0.2.4",
57
+ "autoprefixer": "^10.4.21",
58
+ "chokidar": "^4.0.0",
59
+ "clsx": "^2.1.1",
60
+ "commander": "^12.0.0",
61
+ "framer-motion": "^12.18.1",
62
+ "react": "^19.1.1",
63
+ "react-dom": "^19.1.1",
64
+ "tailwind-merge": "^3.3.1",
65
+ "tailwindcss": "^3.4.17",
66
+ "zustand": "^5.0.6"
67
+ },
68
+ "devDependencies": {
69
+ "@eslint/js": "^9.33.0",
70
+ "@types/node": "^24.3.0",
71
+ "@types/react": "^19.1.10",
72
+ "@types/react-dom": "^19.1.7",
73
+ "@vitejs/plugin-react": "^4.5.2",
74
+ "eslint": "^9.33.0",
75
+ "eslint-plugin-react-hooks": "^5.2.0",
76
+ "eslint-plugin-react-refresh": "^0.4.20",
77
+ "globals": "^16.3.0",
78
+ "typescript": "~5.8.3",
79
+ "typescript-eslint": "^8.39.1",
80
+ "vite": "^6.3.5"
81
+ }
82
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
Binary file
@@ -0,0 +1,287 @@
1
+ // Audio Reactive Visualizer Scene - Parameter Object API
2
+ console.log('🎵 AUDIO VISUALIZER SCENE LOADED');
3
+
4
+ // Define parameters using helper functions (returns parameter objects)
5
+ const primaryColor = viji.color('#ff6b6b', {
6
+ label: 'Primary Color',
7
+ description: 'Main color for frequency bars',
8
+ group: 'colors'
9
+ });
10
+
11
+ const secondaryColor = viji.color('#4ecdc4', {
12
+ label: 'Secondary Color',
13
+ description: 'Background and accent color',
14
+ group: 'colors'
15
+ });
16
+
17
+ const backgroundColor = viji.color('#1a1a2e', {
18
+ label: 'Background Color',
19
+ description: 'Scene background color',
20
+ group: 'colors'
21
+ });
22
+
23
+ const audioSensitivity = viji.slider(2.0, {
24
+ min: 0.1,
25
+ max: 5.0,
26
+ step: 0.1,
27
+ label: 'Audio Sensitivity',
28
+ description: 'Overall sensitivity to audio input',
29
+ group: 'audio',
30
+ category: 'audio'
31
+ });
32
+
33
+ const bassBoost = viji.slider(1.5, {
34
+ min: 0.5,
35
+ max: 3.0,
36
+ step: 0.1,
37
+ label: 'Bass Boost',
38
+ description: 'Amplification for bass frequencies',
39
+ group: 'audio',
40
+ category: 'audio'
41
+ });
42
+
43
+ const trebleBoost = viji.slider(1.2, {
44
+ min: 0.5,
45
+ max: 3.0,
46
+ step: 0.1,
47
+ label: 'Treble Boost',
48
+ description: 'Amplification for treble frequencies',
49
+ group: 'audio',
50
+ category: 'audio'
51
+ });
52
+
53
+ const barCount = viji.slider(64, {
54
+ min: 8,
55
+ max: 128,
56
+ step: 8,
57
+ label: 'Bar Count',
58
+ description: 'Number of frequency analysis bars',
59
+ group: 'visual'
60
+ });
61
+
62
+ const barSpacing = viji.slider(2, {
63
+ min: 0,
64
+ max: 10,
65
+ step: 1,
66
+ label: 'Bar Spacing',
67
+ description: 'Space between frequency bars',
68
+ group: 'visual'
69
+ });
70
+
71
+ const showWaveform = viji.toggle(true, {
72
+ label: 'Show Waveform',
73
+ description: 'Display audio waveform overlay',
74
+ group: 'visual'
75
+ });
76
+
77
+ const showCenterCircle = viji.toggle(true, {
78
+ label: 'Show Center Circle',
79
+ description: 'Show reactive circle in center',
80
+ group: 'visual'
81
+ });
82
+
83
+ const rotationSpeed = viji.slider(0.5, {
84
+ min: -2.0,
85
+ max: 2.0,
86
+ step: 0.1,
87
+ label: 'Rotation Speed',
88
+ description: 'Speed of circular rotation',
89
+ group: 'animation'
90
+ });
91
+
92
+ const pulseIntensity = viji.slider(0.8, {
93
+ min: 0.0,
94
+ max: 2.0,
95
+ step: 0.1,
96
+ label: 'Pulse Intensity',
97
+ description: 'Intensity of audio-driven pulsing',
98
+ group: 'animation'
99
+ });
100
+
101
+ const smoothing = viji.slider(0.7, {
102
+ min: 0.0,
103
+ max: 0.95,
104
+ step: 0.05,
105
+ label: 'Smoothing',
106
+ description: 'Temporal smoothing of audio data',
107
+ group: 'animation'
108
+ });
109
+
110
+ const mouseInfluence = viji.toggle(true, {
111
+ label: 'Mouse Influence',
112
+ description: 'Allow mouse to affect visualization',
113
+ group: 'interaction',
114
+ category: 'interaction'
115
+ });
116
+
117
+ const mouseRadius = viji.slider(100, {
118
+ min: 50,
119
+ max: 300,
120
+ step: 10,
121
+ label: 'Mouse Radius',
122
+ description: 'Radius of mouse influence',
123
+ group: 'interaction',
124
+ category: 'interaction'
125
+ });
126
+
127
+ // Render function using parameter object API with interactions, audio, and video
128
+ function render(viji) {
129
+ const ctx = viji.useContext('2d');
130
+
131
+ // Get interaction state
132
+ const mouse = viji.mouse;
133
+ const keyboard = viji.keyboard;
134
+ const touches = viji.touches;
135
+
136
+ // Get audio state
137
+ const audio = viji.audio;
138
+
139
+ // Get video state
140
+ const video = viji.video;
141
+
142
+ // Clear background
143
+ ctx.fillStyle = backgroundColor.value;
144
+ ctx.fillRect(0, 0, viji.width, viji.height);
145
+
146
+ // Only proceed if audio is connected
147
+ if (!audio || !audio.isConnected) {
148
+ // Show instructions when no audio
149
+ ctx.fillStyle = primaryColor.value;
150
+ ctx.font = '24px Arial';
151
+ ctx.textAlign = 'center';
152
+ ctx.fillText('🎵 Connect audio to see visualization', viji.width / 2, viji.height / 2);
153
+
154
+ ctx.font = '16px Arial';
155
+ ctx.fillStyle = secondaryColor.value;
156
+ ctx.fillText('Use the audio controls to connect microphone, file, or screen audio', viji.width / 2, viji.height / 2 + 40);
157
+ return;
158
+ }
159
+
160
+ const centerX = viji.width / 2;
161
+ const centerY = viji.height / 2;
162
+
163
+ // Get audio data with parameters
164
+ const currentVolume = (((audio && audio.volume && audio.volume.rms) || 0)) * audioSensitivity.value;
165
+ const bassEnergy = ((((audio && audio.bands && audio.bands.bass) || 0) + ((audio && audio.bands && audio.bands.subBass) || 0)) / 2) * bassBoost.value;
166
+ const trebleEnergy = ((((audio && audio.bands && audio.bands.treble) || 0) + ((audio && audio.bands && audio.bands.presence) || 0)) / 2) * trebleBoost.value;
167
+
168
+ // Center reactive circle
169
+ if (showCenterCircle.value) {
170
+ const circleRadius = 50 + (currentVolume * pulseIntensity.value * 100);
171
+ const circleAlpha = Math.min(1, currentVolume * 2);
172
+
173
+ // Outer circle (bass reactive)
174
+ ctx.beginPath();
175
+ ctx.arc(centerX, centerY, circleRadius + (bassEnergy * 50), 0, Math.PI * 2);
176
+ ctx.strokeStyle = primaryColor.value + Math.floor(circleAlpha * 128).toString(16).padStart(2, '0');
177
+ ctx.lineWidth = 3;
178
+ ctx.stroke();
179
+
180
+ // Inner circle (treble reactive)
181
+ ctx.beginPath();
182
+ ctx.arc(centerX, centerY, circleRadius * 0.6 + (trebleEnergy * 30), 0, Math.PI * 2);
183
+ ctx.fillStyle = secondaryColor.value + Math.floor(circleAlpha * 64).toString(16).padStart(2, '0');
184
+ ctx.fill();
185
+ }
186
+
187
+ // Frequency bars in circular arrangement
188
+ const radius = Math.min(viji.width, viji.height) * 0.3;
189
+ const bands = ['subBass', 'bass', 'lowMid', 'mid', 'highMid', 'presence', 'brilliance', 'treble'];
190
+
191
+ for (let i = 0; i < barCount.value; i++) {
192
+ const angle = (i / barCount.value) * Math.PI * 2 + (viji.time * rotationSpeed.value);
193
+ const bandIndex = Math.floor((i / barCount.value) * bands.length);
194
+ const bandValue = (audio && audio.bands ? (audio.bands[bands[bandIndex]] || 0) : 0);
195
+
196
+ // Apply audio sensitivity
197
+ const barHeight = bandValue * audioSensitivity.value * 200;
198
+
199
+ // Mouse influence
200
+ let mouseInfluenceValue = 1;
201
+ if (mouseInfluence.value && mouse && mouse.isInCanvas) {
202
+ const barX = centerX + Math.cos(angle) * radius;
203
+ const barY = centerY + Math.sin(angle) * radius;
204
+ const distToMouse = Math.sqrt((barX - mouse.x) ** 2 + (barY - mouse.y) ** 2);
205
+
206
+ if (distToMouse < mouseRadius.value) {
207
+ mouseInfluenceValue = 1 + (1 - distToMouse / mouseRadius.value) * 2;
208
+ }
209
+ }
210
+
211
+ const finalBarHeight = barHeight * mouseInfluenceValue;
212
+
213
+ // Calculate bar position
214
+ const startX = centerX + Math.cos(angle) * radius;
215
+ const startY = centerY + Math.sin(angle) * radius;
216
+ const endX = centerX + Math.cos(angle) * (radius + finalBarHeight);
217
+ const endY = centerY + Math.sin(angle) * (radius + finalBarHeight);
218
+
219
+ // Color based on frequency band
220
+ const hue = (bandIndex / bands.length) * 360;
221
+ const intensity = Math.min(1, bandValue * audioSensitivity.value * 3);
222
+ ctx.strokeStyle = `hsla(${hue}, 80%, ${50 + intensity * 30}%, ${0.6 + intensity * 0.4})`;
223
+ ctx.lineWidth = Math.max(1, (viji.width / barCount.value) - barSpacing.value);
224
+
225
+ // Draw frequency bar
226
+ ctx.beginPath();
227
+ ctx.moveTo(startX, startY);
228
+ ctx.lineTo(endX, endY);
229
+ ctx.stroke();
230
+ }
231
+
232
+ // Waveform overlay
233
+ if (showWaveform.value && audio.isConnected) {
234
+ ctx.strokeStyle = primaryColor.value + '60';
235
+ ctx.lineWidth = 2;
236
+ ctx.beginPath();
237
+
238
+ // Simulate waveform
239
+ for (let x = 0; x < viji.width; x += 4) {
240
+ const waveY = centerY + Math.sin(x * 0.02 + viji.time * 3 + bassEnergy * 10) *
241
+ (currentVolume * 50) +
242
+ Math.sin(x * 0.05 + viji.time * 5 + trebleEnergy * 15) *
243
+ (currentVolume * 25);
244
+
245
+ if (x === 0) {
246
+ ctx.moveTo(x, waveY);
247
+ } else {
248
+ ctx.lineTo(x, waveY);
249
+ }
250
+ }
251
+ ctx.stroke();
252
+ }
253
+
254
+ // Audio info display
255
+ ctx.fillStyle = primaryColor.value + 'CC';
256
+ ctx.font = '14px monospace';
257
+ ctx.textAlign = 'left';
258
+
259
+ let infoY = 20;
260
+ ctx.fillText('🎵 AUDIO REACTIVE VISUALIZER', 20, infoY);
261
+ infoY += 20;
262
+ ctx.fillText(`Volume: ${(currentVolume * 100).toFixed(1)}%`, 20, infoY);
263
+ infoY += 16;
264
+ ctx.fillText(`Bass: ${(bassEnergy * 100).toFixed(1)}%`, 20, infoY);
265
+ infoY += 16;
266
+ ctx.fillText(`Treble: ${(trebleEnergy * 100).toFixed(1)}%`, 20, infoY);
267
+ infoY += 16;
268
+ ctx.fillText(`Bars: ${barCount.value}`, 20, infoY);
269
+
270
+ // Mouse interaction indicator
271
+ if (mouseInfluence.value && mouse && mouse.isInCanvas) {
272
+ ctx.strokeStyle = secondaryColor.value + '80';
273
+ ctx.lineWidth = 2;
274
+ ctx.setLineDash([5, 5]);
275
+ ctx.beginPath();
276
+ ctx.arc(mouse.x, mouse.y, mouseRadius.value, 0, Math.PI * 2);
277
+ ctx.stroke();
278
+ ctx.setLineDash([]);
279
+ }
280
+
281
+ // Performance info
282
+ ctx.fillStyle = secondaryColor.value + 'AA';
283
+ ctx.font = '12px monospace';
284
+ ctx.textAlign = 'right';
285
+ ctx.fillText(`Frame: ${viji.frameCount}`, viji.width - 20, viji.height - 25);
286
+ ctx.fillText(`Time: ${viji.time.toFixed(1)}s`, viji.width - 20, viji.height - 10);
287
+ }