1
0

3 کامیت‌ها 1ae817c4f6 ... 8e0506456e

نویسنده SHA1 پیام تاریخ
  Billie Hilton 8e0506456e Add gamepad-controlled synthesizer with debug interface 4 ماه پیش
  Billie Hilton 20f1437dc6 Setup project 4 ماه پیش
  Billie Hilton bf05e9dbec Initialize memory bank 4 ماه پیش
7فایلهای تغییر یافته به همراه1642 افزوده شده و 0 حذف شده
  1. 154 0
      index.html
  2. 32 0
      memory-bank/context.md
  3. 68 0
      memory-bank/current.md
  4. 110 0
      memory-bank/patterns.md
  5. 980 0
      package-lock.json
  6. 27 0
      package.json
  7. 271 0
      src/index.js

+ 154 - 0
index.html

@@ -0,0 +1,154 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Dawdlehorn - Gamepad DAW</title>
+    <style>
+        * {
+            margin: 0;
+            padding: 0;
+            box-sizing: border-box;
+        }
+        
+        body {
+            font-family: 'Courier New', monospace;
+            background: #1a1a1a;
+            color: #00ff00;
+            overflow: hidden;
+        }
+        
+        .container {
+            display: flex;
+            flex-direction: column;
+            height: 100vh;
+            padding: 20px;
+        }
+        
+        .header {
+            text-align: center;
+            margin-bottom: 20px;
+        }
+        
+        .status {
+            display: flex;
+            justify-content: space-between;
+            margin-bottom: 20px;
+            padding: 10px;
+            background: #2a2a2a;
+            border-radius: 5px;
+        }
+        
+        .gamepad-status {
+            color: #ff6600;
+        }
+        
+        .audio-status {
+            color: #0066ff;
+        }
+        
+        .controls {
+            flex: 1;
+            display: grid;
+            grid-template-columns: 1fr 1fr;
+            gap: 20px;
+        }
+        
+        .synth-controls, .sequencer-controls {
+            background: #2a2a2a;
+            padding: 20px;
+            border-radius: 10px;
+            border: 2px solid #444;
+        }
+        
+        .control-group {
+            margin-bottom: 15px;
+        }
+        
+        .control-label {
+            display: block;
+            margin-bottom: 5px;
+            font-size: 12px;
+            text-transform: uppercase;
+        }
+        
+        .visualizer {
+            height: 100px;
+            background: #000;
+            border: 1px solid #444;
+            margin-top: 20px;
+        }
+        
+        .instructions {
+            position: absolute;
+            bottom: 20px;
+            left: 20px;
+            font-size: 12px;
+            color: #666;
+        }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <div class="header">
+            <h1>🎮 DAWDLEHORN 🎵</h1>
+            <p>Gamepad-Controlled Digital Audio Workstation</p>
+        </div>
+        
+        <div class="status">
+            <div class="gamepad-status">
+                Gamepad: <span id="gamepad-status">Not Connected</span>
+            </div>
+            <div class="audio-status">
+                Audio: <span id="audio-status">Initializing...</span>
+            </div>
+        </div>
+        
+        <div class="controls">
+            <div class="synth-controls">
+                <h3>Synthesizer</h3>
+                <div class="control-group">
+                    <label class="control-label">Frequency</label>
+                    <div id="freq-display">440 Hz</div>
+                </div>
+                <div class="control-group">
+                    <label class="control-label">Volume</label>
+                    <div id="volume-display">50%</div>
+                </div>
+                <div class="control-group">
+                    <label class="control-label">Filter</label>
+                    <div id="filter-display">1000 Hz</div>
+                </div>
+            </div>
+            
+            <div class="sequencer-controls">
+                <h3>Gamepad Debug</h3>
+                <div class="control-group">
+                    <label class="control-label">Left Stick</label>
+                    <div id="left-stick-display">0.00, 0.00</div>
+                </div>
+                <div class="control-group">
+                    <label class="control-label">Right Stick</label>
+                    <div id="right-stick-display">0.00, 0.00</div>
+                </div>
+                <div class="control-group">
+                    <label class="control-label">Triggers</label>
+                    <div id="triggers-display">L: 0.00 R: 0.00</div>
+                </div>
+                <div class="control-group">
+                    <label class="control-label">Buttons</label>
+                    <div id="buttons-display">A B X Y</div>
+                </div>
+            </div>
+        </div>
+        
+        <canvas id="visualizer" class="visualizer"></canvas>
+        
+        <div class="instructions">
+            Connect a gamepad and press any button to start • Left stick: Frequency • Right stick: Filter • Triggers: Volume • Face buttons: Notes
+        </div>
+    </div>
+    
+    <script type="module" src="src/index.js"></script>
+</body>
+</html>

+ 32 - 0
memory-bank/context.md

@@ -0,0 +1,32 @@
+# Dawdlehorn - Digital Audio Workstation Context
+
+## Project Overview
+Dawdlehorn is a web-based Digital Audio Workstation (DAW) / Groovebox / musical instrument controlled via gamepad input. It's designed to provide an intuitive, game-like interface for music creation and performance.
+
+## Core Requirements
+- **Gamepad Control**: Primary interface using Web Gamepad API
+- **Audio Processing**: Real-time audio synthesis and effects using Web Audio API
+- **MIDI Integration**: External MIDI device support via Web MIDI API
+- **Web-based**: Runs in modern browsers with no installation required
+- **Real-time Performance**: Low-latency audio for live performance use
+
+## Target User Experience
+- Intuitive gamepad-based music creation
+- Real-time audio feedback and manipulation
+- Groovebox-style workflow (pattern-based sequencing)
+- Accessible to both musicians and non-musicians
+- Performance-oriented interface
+
+## Technical Constraints
+- Must work in modern web browsers
+- Low audio latency requirements
+- Gamepad compatibility across different controllers
+- Responsive real-time interface
+- No server-side processing (client-side only)
+
+## Success Criteria
+- Responsive gamepad input with minimal latency
+- High-quality audio output
+- Intuitive user interface
+- Stable performance during extended use
+- Cross-browser compatibility

+ 68 - 0
memory-bank/current.md

@@ -0,0 +1,68 @@
+# Dawdlehorn - Current State
+
+## Current Task: Project Initialization & Technical Planning
+Building a web-based Digital Audio Workstation controlled via gamepad with real-time audio processing capabilities.
+
+## Current Task Checklist
+- [x] Read existing project files to understand requirements
+- [x] Initialize memory bank structure
+- [x] Draft technical plan with libraries and architecture
+- [x] Document current state and next steps
+- [x] Set up project structure and package.json
+- [x] Install Tone.js and set up basic audio context
+- [x] Implement gamepad detection and input polling
+- [x] Build simple synthesizer with gamepad control
+- [x] Add visual debugging for input latency
+- [ ] Create basic Web Components for UI controls
+- [ ] Add pattern sequencer functionality
+
+## Project Status
+- **Phase**: Core functionality implemented and testing
+- **Repository**: Working gamepad-controlled synthesizer with debug interface
+- **Next Priority**: Optimize latency and add sequencer functionality
+- **Current Issue**: Some latency between button press and audio response
+
+## Key Decisions Needed
+1. Audio synthesis approach (Web Audio API patterns)
+2. Gamepad input handling strategy
+3. UI framework selection
+4. State management approach
+5. Audio worklet vs main thread processing
+
+## Current Understanding
+- Target: Gamepad-controlled groovebox/DAW
+- Core APIs: Web Gamepad, Web Audio, Web MIDI
+- Environment: Browser-based, no server required
+- Focus: Real-time performance and low latency
+
+## Technical Plan Summary
+**Selected Stack:**
+- **Audio**: Tone.js + Audio Worklets for synthesis and effects
+- **Input**: Web Gamepad API + custom input manager
+- **UI**: Vanilla JavaScript + Web Components + Canvas
+- **State**: Custom lightweight state manager with audio/UI separation
+
+**Architecture**: Event-driven, audio-first design with separate processing threads
+
+## Current Implementation Status
+**Working Features:**
+- Gamepad detection and connection status
+- Real-time input polling with visual debug feedback
+- Synthesizer with frequency, filter, and volume control
+- Face button note triggering (C4, D4, E4, F4)
+- Audio visualization
+- Click-to-start audio context
+
+**Debug Interface Added:**
+- Real-time stick position display
+- Trigger pressure values
+- Button press indicators with brackets [A] [B] [X] [Y]
+- Updates immediately in polling loop to isolate latency source
+
+## Immediate Next Steps
+1. Analyze latency source using debug interface
+2. Optimize audio parameter updates for lower latency
+3. Add pattern sequencer with step-based recording
+4. Implement multiple instrument tracks
+5. Add effects chain controls
+6. Create preset saving/loading system

+ 110 - 0
memory-bank/patterns.md

@@ -0,0 +1,110 @@
+# Dawdlehorn - Architecture & Patterns
+
+## Technical Architecture Plan
+
+### Core Technology Stack
+
+#### Audio Processing
+- **Web Audio API**: Native browser audio processing
+- **Audio Worklets**: For low-latency, real-time audio processing
+- **Tone.js**: High-level audio synthesis and effects library
+  - Provides instruments, effects, and scheduling
+  - Built on Web Audio API with better abstractions
+  - Excellent for groovebox-style synthesis
+
+#### Input Handling
+- **Web Gamepad API**: Native gamepad support
+- **Custom Input Manager**: Abstraction layer for different controller types
+- **Event-driven architecture**: Responsive input handling
+
+#### UI Framework
+- **Vanilla JavaScript + Web Components**: For maximum performance
+- **Alternative**: Svelte (minimal runtime overhead)
+- **Canvas API**: For real-time visualizations and waveforms
+
+#### State Management
+- **Custom State Manager**: Lightweight, audio-focused
+- **Immutable updates**: Prevent audio glitches from state changes
+- **Separate audio and UI state**: Different update cycles
+
+### Application Architecture
+
+```
+┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
+│   Input Layer   │    │   Audio Engine  │    │   UI Layer      │
+│                 │    │                 │    │                 │
+│ • Gamepad API   │───▶│ • Tone.js       │───▶│ • Web Components│
+│ • MIDI API      │    │ • Audio Worklets│    │ • Canvas        │
+│ • Input Manager │    │ • Synthesis     │    │ • Visualizers   │
+└─────────────────┘    └─────────────────┘    └─────────────────┘
+         │                       │                       │
+         └───────────────────────┼───────────────────────┘
+                                 │
+                    ┌─────────────────┐
+                    │  State Manager  │
+                    │                 │
+                    │ • Pattern Data  │
+                    │ • Audio Params  │
+                    │ • UI State      │
+                    └─────────────────┘
+```
+
+### Key Design Patterns
+
+#### 1. Audio-First Architecture
+- Audio processing runs in separate thread (Audio Worklets)
+- UI updates never block audio processing
+- State changes are batched and scheduled
+
+#### 2. Component-Based UI
+- Modular components for different controls
+- Each component handles its own gamepad mappings
+- Reusable across different views/modes
+
+#### 3. Event-Driven Input
+- Gamepad events trigger audio parameter changes
+- Debounced input for smooth parameter transitions
+- Configurable input mappings
+
+#### 4. Pattern-Based Sequencing
+- Step sequencer with pattern storage
+- Real-time pattern switching
+- Quantized timing for musical accuracy
+
+### Library Selection Rationale
+
+#### Tone.js Benefits
+- Mature Web Audio abstraction
+- Built-in instruments and effects
+- Transport and timing utilities
+- Active community and documentation
+
+#### Web Components Benefits
+- Native browser support
+- Encapsulated styling and behavior
+- Framework-agnostic
+- Excellent for audio UI controls
+
+#### Audio Worklets Benefits
+- True real-time audio processing
+- Separate thread from main UI
+- Low-latency parameter updates
+- Future-proof Web Audio approach
+
+### Performance Considerations
+
+#### Audio Performance
+- Use Audio Worklets for synthesis
+- Minimize garbage collection in audio thread
+- Pre-allocate audio buffers
+- Batch parameter updates
+
+#### Input Performance
+- Poll gamepad at 60fps
+- Debounce rapid input changes
+- Use requestAnimationFrame for smooth updates
+
+#### UI Performance
+- Canvas for real-time visualizations
+- Virtual scrolling for large pattern lists
+- Efficient DOM updates with Web Components

+ 980 - 0
package-lock.json

@@ -0,0 +1,980 @@
+{
+    "name": "dawdlehorn",
+    "version": "0.1.0",
+    "lockfileVersion": 3,
+    "requires": true,
+    "packages": {
+        "": {
+            "name": "dawdlehorn",
+            "version": "0.1.0",
+            "license": "MIT",
+            "dependencies": {
+                "tone": "^14.7.77"
+            },
+            "devDependencies": {
+                "vite": "^5.0.0"
+            }
+        },
+        "node_modules/@babel/runtime": {
+            "version": "7.28.3",
+            "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz",
+            "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==",
+            "license": "MIT",
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@esbuild/aix-ppc64": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+            "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+            "cpu": [
+                "ppc64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "aix"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/android-arm": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+            "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+            "cpu": [
+                "arm"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "android"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/android-arm64": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+            "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+            "cpu": [
+                "arm64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "android"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/android-x64": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+            "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "android"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/darwin-arm64": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+            "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+            "cpu": [
+                "arm64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "darwin"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/darwin-x64": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+            "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "darwin"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/freebsd-arm64": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+            "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+            "cpu": [
+                "arm64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "freebsd"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/freebsd-x64": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+            "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "freebsd"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/linux-arm": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+            "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+            "cpu": [
+                "arm"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/linux-arm64": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+            "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+            "cpu": [
+                "arm64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/linux-ia32": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+            "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+            "cpu": [
+                "ia32"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/linux-loong64": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+            "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+            "cpu": [
+                "loong64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/linux-mips64el": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+            "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+            "cpu": [
+                "mips64el"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/linux-ppc64": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+            "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+            "cpu": [
+                "ppc64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/linux-riscv64": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+            "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+            "cpu": [
+                "riscv64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/linux-s390x": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+            "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+            "cpu": [
+                "s390x"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/linux-x64": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+            "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/netbsd-x64": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+            "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "netbsd"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/openbsd-x64": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+            "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "openbsd"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/sunos-x64": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+            "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "sunos"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/win32-arm64": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+            "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+            "cpu": [
+                "arm64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "win32"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/win32-ia32": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+            "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+            "cpu": [
+                "ia32"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "win32"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@esbuild/win32-x64": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+            "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "win32"
+            ],
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@rollup/rollup-android-arm-eabi": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.0.tgz",
+            "integrity": "sha512-lVgpeQyy4fWN5QYebtW4buT/4kn4p4IJ+kDNB4uYNT5b8c8DLJDg6titg20NIg7E8RWwdWZORW6vUFfrLyG3KQ==",
+            "cpu": [
+                "arm"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "android"
+            ]
+        },
+        "node_modules/@rollup/rollup-android-arm64": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.0.tgz",
+            "integrity": "sha512-2O73dR4Dc9bp+wSYhviP6sDziurB5/HCym7xILKifWdE9UsOe2FtNcM+I4xZjKrfLJnq5UR8k9riB87gauiQtw==",
+            "cpu": [
+                "arm64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "android"
+            ]
+        },
+        "node_modules/@rollup/rollup-darwin-arm64": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.0.tgz",
+            "integrity": "sha512-vwSXQN8T4sKf1RHr1F0s98Pf8UPz7pS6P3LG9NSmuw0TVh7EmaE+5Ny7hJOZ0M2yuTctEsHHRTMi2wuHkdS6Hg==",
+            "cpu": [
+                "arm64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "darwin"
+            ]
+        },
+        "node_modules/@rollup/rollup-darwin-x64": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.0.tgz",
+            "integrity": "sha512-cQp/WG8HE7BCGyFVuzUg0FNmupxC+EPZEwWu2FCGGw5WDT1o2/YlENbm5e9SMvfDFR6FRhVCBePLqj0o8MN7Vw==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "darwin"
+            ]
+        },
+        "node_modules/@rollup/rollup-freebsd-arm64": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.0.tgz",
+            "integrity": "sha512-UR1uTJFU/p801DvvBbtDD7z9mQL8J80xB0bR7DqW7UGQHRm/OaKzp4is7sQSdbt2pjjSS72eAtRh43hNduTnnQ==",
+            "cpu": [
+                "arm64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "freebsd"
+            ]
+        },
+        "node_modules/@rollup/rollup-freebsd-x64": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.0.tgz",
+            "integrity": "sha512-G/DKyS6PK0dD0+VEzH/6n/hWDNPDZSMBmqsElWnCRGrYOb2jC0VSupp7UAHHQ4+QILwkxSMaYIbQ72dktp8pKA==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "freebsd"
+            ]
+        },
+        "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.0.tgz",
+            "integrity": "sha512-u72Mzc6jyJwKjJbZZcIYmd9bumJu7KNmHYdue43vT1rXPm2rITwmPWF0mmPzLm9/vJWxIRbao/jrQmxTO0Sm9w==",
+            "cpu": [
+                "arm"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ]
+        },
+        "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.0.tgz",
+            "integrity": "sha512-S4UefYdV0tnynDJV1mdkNawp0E5Qm2MtSs330IyHgaccOFrwqsvgigUD29uT+B/70PDY1eQ3t40+xf6wIvXJyg==",
+            "cpu": [
+                "arm"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ]
+        },
+        "node_modules/@rollup/rollup-linux-arm64-gnu": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.0.tgz",
+            "integrity": "sha512-1EhkSvUQXJsIhk4msxP5nNAUWoB4MFDHhtc4gAYvnqoHlaL9V3F37pNHabndawsfy/Tp7BPiy/aSa6XBYbaD1g==",
+            "cpu": [
+                "arm64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ]
+        },
+        "node_modules/@rollup/rollup-linux-arm64-musl": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.0.tgz",
+            "integrity": "sha512-EtBDIZuDtVg75xIPIK1l5vCXNNCIRM0OBPUG+tbApDuJAy9mKago6QxX+tfMzbCI6tXEhMuZuN1+CU8iDW+0UQ==",
+            "cpu": [
+                "arm64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ]
+        },
+        "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.50.0.tgz",
+            "integrity": "sha512-BGYSwJdMP0hT5CCmljuSNx7+k+0upweM2M4YGfFBjnFSZMHOLYR0gEEj/dxyYJ6Zc6AiSeaBY8dWOa11GF/ppQ==",
+            "cpu": [
+                "loong64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ]
+        },
+        "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.0.tgz",
+            "integrity": "sha512-I1gSMzkVe1KzAxKAroCJL30hA4DqSi+wGc5gviD0y3IL/VkvcnAqwBf4RHXHyvH66YVHxpKO8ojrgc4SrWAnLg==",
+            "cpu": [
+                "ppc64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ]
+        },
+        "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.0.tgz",
+            "integrity": "sha512-bSbWlY3jZo7molh4tc5dKfeSxkqnf48UsLqYbUhnkdnfgZjgufLS/NTA8PcP/dnvct5CCdNkABJ56CbclMRYCA==",
+            "cpu": [
+                "riscv64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ]
+        },
+        "node_modules/@rollup/rollup-linux-riscv64-musl": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.0.tgz",
+            "integrity": "sha512-LSXSGumSURzEQLT2e4sFqFOv3LWZsEF8FK7AAv9zHZNDdMnUPYH3t8ZlaeYYZyTXnsob3htwTKeWtBIkPV27iQ==",
+            "cpu": [
+                "riscv64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ]
+        },
+        "node_modules/@rollup/rollup-linux-s390x-gnu": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.0.tgz",
+            "integrity": "sha512-CxRKyakfDrsLXiCyucVfVWVoaPA4oFSpPpDwlMcDFQvrv3XY6KEzMtMZrA+e/goC8xxp2WSOxHQubP8fPmmjOQ==",
+            "cpu": [
+                "s390x"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ]
+        },
+        "node_modules/@rollup/rollup-linux-x64-gnu": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.0.tgz",
+            "integrity": "sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ]
+        },
+        "node_modules/@rollup/rollup-linux-x64-musl": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.0.tgz",
+            "integrity": "sha512-SkE6YQp+CzpyOrbw7Oc4MgXFvTw2UIBElvAvLCo230pyxOLmYwRPwZ/L5lBe/VW/qT1ZgND9wJfOsdy0XptRvw==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ]
+        },
+        "node_modules/@rollup/rollup-openharmony-arm64": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.0.tgz",
+            "integrity": "sha512-PZkNLPfvXeIOgJWA804zjSFH7fARBBCpCXxgkGDRjjAhRLOR8o0IGS01ykh5GYfod4c2yiiREuDM8iZ+pVsT+Q==",
+            "cpu": [
+                "arm64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "openharmony"
+            ]
+        },
+        "node_modules/@rollup/rollup-win32-arm64-msvc": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.0.tgz",
+            "integrity": "sha512-q7cIIdFvWQoaCbLDUyUc8YfR3Jh2xx3unO8Dn6/TTogKjfwrax9SyfmGGK6cQhKtjePI7jRfd7iRYcxYs93esg==",
+            "cpu": [
+                "arm64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "win32"
+            ]
+        },
+        "node_modules/@rollup/rollup-win32-ia32-msvc": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.0.tgz",
+            "integrity": "sha512-XzNOVg/YnDOmFdDKcxxK410PrcbcqZkBmz+0FicpW5jtjKQxcW1BZJEQOF0NJa6JO7CZhett8GEtRN/wYLYJuw==",
+            "cpu": [
+                "ia32"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "win32"
+            ]
+        },
+        "node_modules/@rollup/rollup-win32-x64-msvc": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.0.tgz",
+            "integrity": "sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "win32"
+            ]
+        },
+        "node_modules/@types/estree": {
+            "version": "1.0.8",
+            "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+            "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+            "dev": true,
+            "license": "MIT"
+        },
+        "node_modules/automation-events": {
+            "version": "7.1.12",
+            "resolved": "https://registry.npmjs.org/automation-events/-/automation-events-7.1.12.tgz",
+            "integrity": "sha512-JDdPQoV58WPm15/L3ABtIEiqyxLoW+yTYIEqYtrKZ7VizLSRXhMKRZbQ8CYc2mFq/lMRKUvqOj0OcT3zANFiXA==",
+            "license": "MIT",
+            "dependencies": {
+                "@babel/runtime": "^7.28.3",
+                "tslib": "^2.8.1"
+            },
+            "engines": {
+                "node": ">=18.2.0"
+            }
+        },
+        "node_modules/esbuild": {
+            "version": "0.21.5",
+            "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+            "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+            "dev": true,
+            "hasInstallScript": true,
+            "license": "MIT",
+            "bin": {
+                "esbuild": "bin/esbuild"
+            },
+            "engines": {
+                "node": ">=12"
+            },
+            "optionalDependencies": {
+                "@esbuild/aix-ppc64": "0.21.5",
+                "@esbuild/android-arm": "0.21.5",
+                "@esbuild/android-arm64": "0.21.5",
+                "@esbuild/android-x64": "0.21.5",
+                "@esbuild/darwin-arm64": "0.21.5",
+                "@esbuild/darwin-x64": "0.21.5",
+                "@esbuild/freebsd-arm64": "0.21.5",
+                "@esbuild/freebsd-x64": "0.21.5",
+                "@esbuild/linux-arm": "0.21.5",
+                "@esbuild/linux-arm64": "0.21.5",
+                "@esbuild/linux-ia32": "0.21.5",
+                "@esbuild/linux-loong64": "0.21.5",
+                "@esbuild/linux-mips64el": "0.21.5",
+                "@esbuild/linux-ppc64": "0.21.5",
+                "@esbuild/linux-riscv64": "0.21.5",
+                "@esbuild/linux-s390x": "0.21.5",
+                "@esbuild/linux-x64": "0.21.5",
+                "@esbuild/netbsd-x64": "0.21.5",
+                "@esbuild/openbsd-x64": "0.21.5",
+                "@esbuild/sunos-x64": "0.21.5",
+                "@esbuild/win32-arm64": "0.21.5",
+                "@esbuild/win32-ia32": "0.21.5",
+                "@esbuild/win32-x64": "0.21.5"
+            }
+        },
+        "node_modules/fsevents": {
+            "version": "2.3.3",
+            "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+            "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+            "dev": true,
+            "hasInstallScript": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "darwin"
+            ],
+            "engines": {
+                "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+            }
+        },
+        "node_modules/nanoid": {
+            "version": "3.3.11",
+            "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+            "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "github",
+                    "url": "https://github.com/sponsors/ai"
+                }
+            ],
+            "license": "MIT",
+            "bin": {
+                "nanoid": "bin/nanoid.cjs"
+            },
+            "engines": {
+                "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+            }
+        },
+        "node_modules/picocolors": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+            "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+            "dev": true,
+            "license": "ISC"
+        },
+        "node_modules/postcss": {
+            "version": "8.5.6",
+            "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+            "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "opencollective",
+                    "url": "https://opencollective.com/postcss/"
+                },
+                {
+                    "type": "tidelift",
+                    "url": "https://tidelift.com/funding/github/npm/postcss"
+                },
+                {
+                    "type": "github",
+                    "url": "https://github.com/sponsors/ai"
+                }
+            ],
+            "license": "MIT",
+            "dependencies": {
+                "nanoid": "^3.3.11",
+                "picocolors": "^1.1.1",
+                "source-map-js": "^1.2.1"
+            },
+            "engines": {
+                "node": "^10 || ^12 || >=14"
+            }
+        },
+        "node_modules/rollup": {
+            "version": "4.50.0",
+            "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.0.tgz",
+            "integrity": "sha512-/Zl4D8zPifNmyGzJS+3kVoyXeDeT/GrsJM94sACNg9RtUE0hrHa1bNPtRSrfHTMH5HjRzce6K7rlTh3Khiw+pw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@types/estree": "1.0.8"
+            },
+            "bin": {
+                "rollup": "dist/bin/rollup"
+            },
+            "engines": {
+                "node": ">=18.0.0",
+                "npm": ">=8.0.0"
+            },
+            "optionalDependencies": {
+                "@rollup/rollup-android-arm-eabi": "4.50.0",
+                "@rollup/rollup-android-arm64": "4.50.0",
+                "@rollup/rollup-darwin-arm64": "4.50.0",
+                "@rollup/rollup-darwin-x64": "4.50.0",
+                "@rollup/rollup-freebsd-arm64": "4.50.0",
+                "@rollup/rollup-freebsd-x64": "4.50.0",
+                "@rollup/rollup-linux-arm-gnueabihf": "4.50.0",
+                "@rollup/rollup-linux-arm-musleabihf": "4.50.0",
+                "@rollup/rollup-linux-arm64-gnu": "4.50.0",
+                "@rollup/rollup-linux-arm64-musl": "4.50.0",
+                "@rollup/rollup-linux-loongarch64-gnu": "4.50.0",
+                "@rollup/rollup-linux-ppc64-gnu": "4.50.0",
+                "@rollup/rollup-linux-riscv64-gnu": "4.50.0",
+                "@rollup/rollup-linux-riscv64-musl": "4.50.0",
+                "@rollup/rollup-linux-s390x-gnu": "4.50.0",
+                "@rollup/rollup-linux-x64-gnu": "4.50.0",
+                "@rollup/rollup-linux-x64-musl": "4.50.0",
+                "@rollup/rollup-openharmony-arm64": "4.50.0",
+                "@rollup/rollup-win32-arm64-msvc": "4.50.0",
+                "@rollup/rollup-win32-ia32-msvc": "4.50.0",
+                "@rollup/rollup-win32-x64-msvc": "4.50.0",
+                "fsevents": "~2.3.2"
+            }
+        },
+        "node_modules/source-map-js": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+            "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+            "dev": true,
+            "license": "BSD-3-Clause",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/standardized-audio-context": {
+            "version": "25.3.77",
+            "resolved": "https://registry.npmjs.org/standardized-audio-context/-/standardized-audio-context-25.3.77.tgz",
+            "integrity": "sha512-Ki9zNz6pKcC5Pi+QPjPyVsD9GwJIJWgryji0XL9cAJXMGyn+dPOf6Qik1AHei0+UNVcc4BOCa0hWLBzlwqsW/A==",
+            "license": "MIT",
+            "dependencies": {
+                "@babel/runtime": "^7.25.6",
+                "automation-events": "^7.0.9",
+                "tslib": "^2.7.0"
+            }
+        },
+        "node_modules/tone": {
+            "version": "14.9.17",
+            "resolved": "https://registry.npmjs.org/tone/-/tone-14.9.17.tgz",
+            "integrity": "sha512-+Qb7M4NMua+tb5Z52+MEVmjye0fjJuIFBePx423pqr9E6/lHDqZAG+fUAvo+Ujm48q0s9bVLRAyT1ETJJglNtg==",
+            "license": "MIT",
+            "dependencies": {
+                "standardized-audio-context": "^25.3.70",
+                "tslib": "^2.3.1"
+            }
+        },
+        "node_modules/tslib": {
+            "version": "2.8.1",
+            "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+            "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+            "license": "0BSD"
+        },
+        "node_modules/vite": {
+            "version": "5.4.19",
+            "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz",
+            "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "esbuild": "^0.21.3",
+                "postcss": "^8.4.43",
+                "rollup": "^4.20.0"
+            },
+            "bin": {
+                "vite": "bin/vite.js"
+            },
+            "engines": {
+                "node": "^18.0.0 || >=20.0.0"
+            },
+            "funding": {
+                "url": "https://github.com/vitejs/vite?sponsor=1"
+            },
+            "optionalDependencies": {
+                "fsevents": "~2.3.3"
+            },
+            "peerDependencies": {
+                "@types/node": "^18.0.0 || >=20.0.0",
+                "less": "*",
+                "lightningcss": "^1.21.0",
+                "sass": "*",
+                "sass-embedded": "*",
+                "stylus": "*",
+                "sugarss": "*",
+                "terser": "^5.4.0"
+            },
+            "peerDependenciesMeta": {
+                "@types/node": {
+                    "optional": true
+                },
+                "less": {
+                    "optional": true
+                },
+                "lightningcss": {
+                    "optional": true
+                },
+                "sass": {
+                    "optional": true
+                },
+                "sass-embedded": {
+                    "optional": true
+                },
+                "stylus": {
+                    "optional": true
+                },
+                "sugarss": {
+                    "optional": true
+                },
+                "terser": {
+                    "optional": true
+                }
+            }
+        }
+    }
+}

+ 27 - 0
package.json

@@ -0,0 +1,27 @@
+{
+    "name": "dawdlehorn",
+    "version": "0.1.0",
+    "description": "A gamepad-controlled Digital Audio Workstation / Groovebox",
+    "main": "src/index.js",
+    "scripts": {
+        "dev": "vite",
+        "build": "vite build",
+        "preview": "vite preview",
+        "serve": "python3 -m http.server 8000"
+    },
+    "keywords": [
+        "daw",
+        "groovebox",
+        "gamepad",
+        "web-audio",
+        "music"
+    ],
+    "author": "",
+    "license": "MIT",
+    "dependencies": {
+        "tone": "^14.7.77"
+    },
+    "devDependencies": {
+        "vite": "^5.0.0"
+    }
+}

+ 271 - 0
src/index.js

@@ -0,0 +1,271 @@
+import * as Tone from 'tone';
+
+class DawdlehornApp {
+    constructor() {
+        this.audioInitialized = false;
+        this.gamepadConnected = false;
+        this.gamepadIndex = null;
+
+        // Audio components
+        this.synth = null;
+        this.filter = null;
+        this.volume = null;
+
+        // State
+        this.currentFreq = 440;
+        this.currentVolume = 0.5;
+        this.currentFilter = 1000;
+        this.isPlaying = false;
+
+        // UI elements
+        this.elements = {
+            gamepadStatus: document.getElementById('gamepad-status'),
+            audioStatus: document.getElementById('audio-status'),
+            freqDisplay: document.getElementById('freq-display'),
+            volumeDisplay: document.getElementById('volume-display'),
+            filterDisplay: document.getElementById('filter-display'),
+            leftStickDisplay: document.getElementById('left-stick-display'),
+            rightStickDisplay: document.getElementById('right-stick-display'),
+            triggersDisplay: document.getElementById('triggers-display'),
+            buttonsDisplay: document.getElementById('buttons-display'),
+            visualizer: document.getElementById('visualizer')
+        };
+
+        this.init();
+    }
+
+    async init() {
+        console.log('🎮 Initializing Dawdlehorn...');
+
+        // Set up gamepad detection
+        this.setupGamepadDetection();
+
+        // Set up audio (will be initialized on first user interaction)
+        this.setupAudioComponents();
+
+        // Start gamepad polling
+        this.startGamepadPolling();
+
+        // Set up visualizer
+        this.setupVisualizer();
+
+        console.log('✅ Dawdlehorn initialized');
+    }
+
+    setupGamepadDetection() {
+        window.addEventListener('gamepadconnected', (e) => {
+            console.log('🎮 Gamepad connected:', e.gamepad.id);
+            this.gamepadConnected = true;
+            this.gamepadIndex = e.gamepad.index;
+            this.elements.gamepadStatus.textContent = `Connected: ${e.gamepad.id}`;
+            this.elements.gamepadStatus.style.color = '#00ff00';
+        });
+
+        window.addEventListener('gamepaddisconnected', (e) => {
+            console.log('🎮 Gamepad disconnected');
+            this.gamepadConnected = false;
+            this.gamepadIndex = null;
+            this.elements.gamepadStatus.textContent = 'Not Connected';
+            this.elements.gamepadStatus.style.color = '#ff6600';
+        });
+    }
+
+    async setupAudioComponents() {
+        try {
+            // Create audio chain: Synth -> Filter -> Volume -> Destination
+            this.synth = new Tone.Oscillator(440, 'sawtooth');
+            this.filter = new Tone.Filter(1000, 'lowpass');
+            this.volume = new Tone.Volume(-20);
+
+            // Connect the audio chain
+            this.synth.connect(this.filter);
+            this.filter.connect(this.volume);
+            this.volume.toDestination();
+
+            this.elements.audioStatus.textContent = 'Ready (Click to start)';
+            this.elements.audioStatus.style.color = '#ffff00';
+
+            // Set up click-to-start audio
+            document.addEventListener('click', this.initializeAudio.bind(this), { once: true });
+
+        } catch (error) {
+            console.error('❌ Audio setup failed:', error);
+            this.elements.audioStatus.textContent = 'Setup Failed';
+            this.elements.audioStatus.style.color = '#ff0000';
+        }
+    }
+
+    async initializeAudio() {
+        try {
+            await Tone.start();
+            console.log('🔊 Audio context started');
+            this.audioInitialized = true;
+            this.elements.audioStatus.textContent = 'Active';
+            this.elements.audioStatus.style.color = '#00ff00';
+        } catch (error) {
+            console.error('❌ Audio initialization failed:', error);
+            this.elements.audioStatus.textContent = 'Failed';
+            this.elements.audioStatus.style.color = '#ff0000';
+        }
+    }
+
+    startGamepadPolling() {
+        const pollGamepad = () => {
+            if (this.gamepadConnected && this.gamepadIndex !== null) {
+                const gamepad = navigator.getGamepads()[this.gamepadIndex];
+                if (gamepad) {
+                    this.handleGamepadInput(gamepad);
+                }
+            }
+            requestAnimationFrame(pollGamepad);
+        };
+        pollGamepad();
+    }
+
+    handleGamepadInput(gamepad) {
+        try {
+            // Update debug displays FIRST - this shows raw input immediately
+            // Standard gamepad mapping: axes[0]=left stick X, axes[1]=left stick Y, axes[2]=right stick X, axes[3]=right stick Y
+            const leftStickX = gamepad.axes[0] || 0;
+            const leftStickY = gamepad.axes[1] || 0;
+            const rightStickX = gamepad.axes[2] || 0;
+            const rightStickY = gamepad.axes[3] || 0;
+
+            this.elements.leftStickDisplay.textContent = `${leftStickX.toFixed(2)}, ${leftStickY.toFixed(2)}`;
+            this.elements.rightStickDisplay.textContent = `${rightStickX.toFixed(2)}, ${rightStickY.toFixed(2)}`;
+
+            // Triggers: L2=buttons[6], R2=buttons[7] (analog values)
+            const leftTrigger = gamepad.buttons[6] ? gamepad.buttons[6].value : 0;
+            const rightTrigger = gamepad.buttons[7] ? gamepad.buttons[7].value : 0;
+            this.elements.triggersDisplay.textContent = `L: ${leftTrigger.toFixed(2)} R: ${rightTrigger.toFixed(2)}`;
+
+            // Face buttons: A=0, B=1, X=2, Y=3
+            const buttonA = gamepad.buttons[0] && gamepad.buttons[0].pressed;
+            const buttonB = gamepad.buttons[1] && gamepad.buttons[1].pressed;
+            const buttonX = gamepad.buttons[2] && gamepad.buttons[2].pressed;
+            const buttonY = gamepad.buttons[3] && gamepad.buttons[3].pressed;
+
+            let buttonDisplay = '';
+            buttonDisplay += buttonA ? '[A]' : 'A';
+            buttonDisplay += ' ';
+            buttonDisplay += buttonB ? '[B]' : 'B';
+            buttonDisplay += ' ';
+            buttonDisplay += buttonX ? '[X]' : 'X';
+            buttonDisplay += ' ';
+            buttonDisplay += buttonY ? '[Y]' : 'Y';
+            this.elements.buttonsDisplay.textContent = buttonDisplay;
+
+            // Early return if audio not ready
+            if (!this.audioInitialized) return;
+
+            // Left stick X-axis controls frequency (200Hz - 2000Hz)
+            const newFreq = 440 + (leftStickX * 560); // 440Hz ± 560Hz
+            if (Math.abs(newFreq - this.currentFreq) > 5) {
+                this.currentFreq = newFreq;
+                this.synth.frequency.setValueAtTime(this.currentFreq, Tone.now());
+                this.elements.freqDisplay.textContent = `${Math.round(this.currentFreq)} Hz`;
+            }
+
+            // Right stick Y-axis controls filter frequency (100Hz - 5000Hz)
+            const newFilter = 1000 + (rightStickY * -2000); // Inverted Y, 1000Hz ± 2000Hz
+            if (Math.abs(newFilter - this.currentFilter) > 50) {
+                this.currentFilter = Math.max(100, Math.min(5000, newFilter));
+                this.filter.frequency.setValueAtTime(this.currentFilter, Tone.now());
+                this.elements.filterDisplay.textContent = `${Math.round(this.currentFilter)} Hz`;
+            }
+
+            // Triggers control volume
+            const triggerVolume = (leftTrigger + rightTrigger) / 2;
+            if (Math.abs(triggerVolume - this.currentVolume) > 0.05) {
+                this.currentVolume = triggerVolume;
+                const dbVolume = -40 + (this.currentVolume * 40); // -40dB to 0dB
+                this.volume.volume.setValueAtTime(dbVolume, Tone.now());
+                this.elements.volumeDisplay.textContent = `${Math.round(this.currentVolume * 100)}%`;
+            }
+
+            // Face buttons control note playing
+            const anyButtonPressed = buttonA || buttonB || buttonX || buttonY;
+
+            if (anyButtonPressed && !this.isPlaying) {
+                this.synth.start();
+                this.isPlaying = true;
+            } else if (!anyButtonPressed && this.isPlaying) {
+                this.synth.stop();
+                this.isPlaying = false;
+            }
+
+            // Different buttons trigger different note frequencies
+            if (buttonA) this.synth.frequency.setValueAtTime(261.63, Tone.now()); // C4
+            if (buttonB) this.synth.frequency.setValueAtTime(293.66, Tone.now()); // D4
+            if (buttonX) this.synth.frequency.setValueAtTime(329.63, Tone.now()); // E4
+            if (buttonY) this.synth.frequency.setValueAtTime(349.23, Tone.now()); // F4
+
+        } catch (error) {
+            console.error('❌ Gamepad input error:', error);
+
+            // Emergency stop audio on any error
+            if (this.isPlaying && this.synth) {
+                try {
+                    this.synth.stop();
+                    this.isPlaying = false;
+                } catch (stopError) {
+                    console.error('❌ Failed to stop synth:', stopError);
+                }
+            }
+
+            // Update UI to show error state
+            this.elements.buttonsDisplay.textContent = 'ERROR - Audio Stopped';
+            this.elements.buttonsDisplay.style.color = '#ff0000';
+
+            // Reset error state after 2 seconds
+            setTimeout(() => {
+                this.elements.buttonsDisplay.style.color = '#00ff00';
+            }, 2000);
+        }
+    }
+
+    setupVisualizer() {
+        const canvas = this.elements.visualizer;
+        const ctx = canvas.getContext('2d');
+
+        // Set canvas size
+        const resizeCanvas = () => {
+            canvas.width = canvas.offsetWidth;
+            canvas.height = canvas.offsetHeight;
+        };
+        resizeCanvas();
+        window.addEventListener('resize', resizeCanvas);
+
+        // Simple visualizer animation
+        const animate = () => {
+            ctx.fillStyle = '#000';
+            ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+            if (this.isPlaying) {
+                const time = Date.now() * 0.01;
+                const centerY = canvas.height / 2;
+
+                ctx.strokeStyle = '#00ff00';
+                ctx.lineWidth = 2;
+                ctx.beginPath();
+
+                for (let x = 0; x < canvas.width; x += 2) {
+                    const freq = this.currentFreq / 1000;
+                    const y = centerY + Math.sin((x * 0.02) + time * freq) * (this.currentVolume * 30);
+                    if (x === 0) ctx.moveTo(x, y);
+                    else ctx.lineTo(x, y);
+                }
+
+                ctx.stroke();
+            }
+
+            requestAnimationFrame(animate);
+        };
+        animate();
+    }
+}
+
+// Initialize the app when the page loads
+document.addEventListener('DOMContentLoaded', () => {
+    new DawdlehornApp();
+});