diff --git a/.gitignore b/.gitignore index c3a94a3..f2a65f1 100644 --- a/.gitignore +++ b/.gitignore @@ -39,11 +39,14 @@ jspm_packages # Optional for people who uses JetBrains products *.idea -dump.rdb - */target/ .env -Dockerfile -docker-compose.yml +prod + +.DS_Store + +dist/ + +.vite/ \ No newline at end of file diff --git a/README.md b/README.md index ae08df7..0ba6d6b 100644 --- a/README.md +++ b/README.md @@ -1,80 +1,164 @@ -GitHub Audio -============ +# GitHub Audio - React Edition -Listen to music generated by events happening across GitHub +A modern React + TypeScript + Vite reimplementation of the GitHub Audio project that converts real-time GitHub events into beautiful music and visualizations. -_Imitation is the sincerest form of flattery._ - __Oscar Wilde__ +## 🎵 What is GitHub Audio? -But seriously, [listen-to-wikipedia](https://github.com/hatnote/listen-to-wikipedia) by [@slaporte](https://github.com/slaporte) and [@mahmoud](https://github.com/mahmoud) is the hotness. +GitHub Audio listens to events happening across GitHub and converts them into music notes and visual effects. Each type of GitHub event (pushes, issues, pull requests, etc.) produces different sounds and visual elements, creating a unique musical experience from the world's largest code repository. -Use [`ULTIMATE_DREAM_KILLER`](https://github.com/debugger22/github-audio/commit/ed47067f5e56ab70d65fa31f72bf2dbc513f8f56) to filter all events except closed PRs. +## ✨ Features +- **Real-time GitHub Events**: Connects to live GitHub event streams via WebSocket +- **Audio Generation**: Different GitHub events trigger different musical notes + - Push events → Celesta sounds + - Issues/PRs → Clav sounds + - Random swell sounds for ambiance +- **Dual Visualization Modes**: + - **2D Mode**: D3.js powered animations with colored circles for each event + - **3D Mode**: Three.js powered 3D space with colorful floating spheres, a realistic fireball sun, gravitational effects, and a stunning starfield with 10,000 realistically twinkling stars +- **Interactive 3D Experience**: Orbit controls for zooming, panning, and rotating the 3D scene with realistic gravitational pull animations +- **Event Filtering**: Filter events by organization or repository name +- **Volume Control**: Adjustable audio volume +- **Responsive Design**: Works on desktop and mobile devices +- **Modern Architecture**: Built with React, TypeScript, styled-components, and Vite -Media Attention ------ -* [The Next Web](http://thenextweb.com/apps/2016/10/03/this-site-tracks-events-across-github-to-generate-calming-work-music/) -* [Product Hunt](https://www.producthunt.com/tech/github-audio) +## 🛠️ Tech Stack + +- **Frontend Framework**: React 18 with TypeScript +- **Build Tool**: Vite +- **Styling**: styled-components (CSS-in-JS) +- **Audio**: Howler.js +- **2D Visualizations**: D3.js +- **3D Visualizations**: Three.js (vanilla implementation for maximum compatibility) +- **State Management**: React Hooks +- **WebSockets**: Native WebSocket API +## 🚀 Getting Started -# Installing dependencies +### Prerequisites -This application requires: -* node -* npm -* redis +- Node.js 18+ +- npm or yarn -Dependency Installation on OSX -------------------- +### Installation +1. Clone the repository: ```bash -$ brew install nodejs -$ brew install npm -$ brew install redis +git clone https://github.com/debugger22/github-audio.git +cd github-audio/react-app ``` -Dependency Installation on Linux ---------------------- +2. Install dependencies: +```bash +npm install +``` +3. Start the development server: ```bash -$ sudo apt-get update -$ sudo apt-get install nodejs -$ sudo apt-get install npm -$ sudo apt-get install redis-server +npm run dev ``` -# Running the Application -Install node packages ---------------------- +4. Open your browser and navigate to `http://localhost:5173` -Navigate to the project directory and run +5. Click the play button to start listening to GitHub's musical symphony! + +### Building for Production ```bash -$ npm install +npm run build ``` -Environment variables ---------------------- +The built files will be in the `dist/` directory. + +## 🎮 How to Use + +1. **Start Listening**: Click the large play button to begin +2. **Choose Visualization Mode**: Use the 2D/3D toggle in the bottom right corner + - **2D Mode**: Classic flat visualization with animated circles + - **3D Mode**: Immersive 3D space with floating spheres that are gradually pulled into a realistic fireball sun +3. **Adjust Volume**: Use the volume slider in the top right +4. **Filter Events**: Enter organization or repository names to filter specific events +5. **3D Navigation** (3D mode only): + - **Mouse drag**: Rotate the camera around the scene + - **Mouse wheel**: Zoom in and out + - **Right-click drag**: Pan the camera +6. **Enjoy**: Watch the visual effects and listen to the music generated by GitHub activity + +## 🎨 Event Types & Sounds + +| GitHub Event | Sound | Visual Color | Description | +|--------------|-------|--------------|-------------| +| PushEvent | Celesta | Purple | Code pushes to repositories | +| CreateEvent | Celesta | Red | Repository/branch creation | +| IssuesEvent | Clav | Green | Issue activities | +| PullRequestEvent | Clav | Green | Pull request activities | +| IssueCommentEvent | Clav | Green | Comments on issues/PRs | +| WatchEvent | Celesta | Orange | Repository stars | +| ForkEvent | Celesta | Blue | Repository forks | +| ReleaseEvent | Celesta | Orange | New releases | +| DeleteEvent | Celesta | Red | Deletions | + +## 🔧 Development + +### Project Structure ``` -$ export GITHUB_OAUTH_KEY= +src/ +├── components/ # React components +│ └── Visualization.tsx +├── hooks/ # Custom React hooks +│ ├── useAudio.ts # Audio management +│ └── useWebSocket.ts # WebSocket connection +├── App.tsx # Main application component +├── main.tsx # Application entry point +└── index.css # Global styles ``` -Note: Without the GitHub oauth key the number of requests is throttled at 60 per hour. It can be increased to 5000 per hour by using an oauth key. +### Key Components -Run Redis and Server ----------- +- **App.tsx**: Main application component with layout and state management +- **Visualization.tsx**: D3.js-powered visual effects component +- **useAudio.ts**: Custom hook for managing Howler.js audio +- **useWebSocket.ts**: Custom hook for GitHub event streaming -If you are running locally then run local redis server +### Scripts -```bash -$ redis-server -``` +- `npm run dev` - Start development server +- `npm run build` - Build for production +- `npm run preview` - Preview production build +- `npm run lint` - Run ESLint -On heroku, you can set up `Heroku Redis` add-on and it sets `REDIS_URL` enviornment variable. +## 🌐 Deployment -In a separate window: -```bash -$ node server -``` +The app can be deployed to any static hosting service: + +- Vercel +- Netlify +- GitHub Pages +- AWS S3 + CloudFront + +## 🔗 Related Projects + +- [Original GitHub Audio](../app/) - The original vanilla JavaScript version +- [Rust Server](../rust-server/) - The backend WebSocket server + +## 🤝 Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## 📝 License + +This project is licensed under the MIT License - see the [LICENSE](../LICENSE) file for details. -Note: For production run `export NODE_ENV="production"` before starting the server. +## 👨‍💻 Author + +Created by [@debugger22](https://github.com/debugger22) + +## ⚠️ Disclaimer + +This project is not affiliated with GitHub Inc. in any way. + +Media Attention +----- +* [The Next Web](http://thenextweb.com/apps/2016/10/03/this-site-tracks-events-across-github-to-generate-calming-work-music/) +* [Product Hunt](https://www.producthunt.com/tech/github-audio) diff --git a/app/index.html b/app/index.html deleted file mode 100644 index ceb1a8e..0000000 --- a/app/index.html +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - GitHub Audio - - - - - - - - - - - - - - - -
- Click to play -
-
-

- Project Audio for GitHub  - offline -

-
-   - events remaining in queue  -
-
-
- -
-
-

people listening

-
-
-
-
- Enter your organization's or repository's names to filter events  -
-
-

Track events happening across GitHub and convert them to music notes.


-
-
- -
- - -
- - - - diff --git a/app/public/css/main.css b/app/public/css/main.css deleted file mode 100644 index 2917ea8..0000000 --- a/app/public/css/main.css +++ /dev/null @@ -1,290 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Inter&display=swap'); - -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; -} -/* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; -} -body { - line-height: 1; -} -ol, ul { - list-style: none; -} -blockquote, q { - quotes: none; -} -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; -} -table { - border-collapse: collapse; - border-spacing: 0; -} - -a{ - text-decoration: none; - color: #0091EA; -} - -html,body{ - background-color: #0288D1; - font-family: 'Inter', sans-serif; - font-weight: 400; - font-size: 16px; - overflow-x: hidden; - visibility: hidden; -} - -.click-to-play{ - position: fixed; - top: 0; - left: 0; - background-color: rgba(0, 0, 0, 0.7); - z-index: 1; - width: 100%; - height: 100%; - text-align: center; -} - -.play-button{ - width: 30rem; - margin-top: 20rem; -} - -.play-button:hover{ - cursor: pointer; -} - -header{ - position: relative;; - width: 100%; - height: 35px; - color: #fff; - font-family: 'Inter', sans-serif; - padding-left: 20px; - padding-top: 20px; - padding-bottom: 20px; -} - -.header-text{ - float: left; - font-size: 1.6em; - line-height: 1em; -} - -.offline-text{ - font-size: 0.4em; - visibility: hidden; -} - -.events-remaining{ - float:right; - margin-right: 5%; - margin-top: 30px; -} - -.events-remaining-text, .events-remaining-value{ - font-size: 0.8em; - visibility: hidden; -} - -#volumeSlider{ - cursor:pointer; - position: absolute; - top: 30px; - right: 40px; - width: 100px; - opacity: 0.3; - border-radius: 5px; -} - -#volumeSlider:hover{ - cursor:pointer; - opacity: 0.9; -} - -#area{ - width: 100%; - position: relative; - /*height: calc(100vh - 70px);*/ -} - -circle { - fill-opacity: 0.8; -} - -.label { - font: 1em 'Inter', sans-serif; - text-shadow:1px 1px 0 rgb(28, 39, 51), - -1px -1px 0 rgb(28, 39, 51), - 1px -1px 0 rgb(28, 39, 51), - -1px 1px 0 rgb(28, 39, 51), - 0px 1px 0 rgb(28, 39, 51), - 1px 0px 0 rgb(28, 39, 51), - 0px -1px 0 rgb(28, 39, 51), - -1px 0px 0 rgb(28, 39, 51); - color:#777777; - -} - -.article-label{ - font: 1em 'Inter', sans-serif; - text-shadow:1px 1px 0 rgb(28, 39, 51), - -1px -1px 0 rgb(28, 39, 51), - 1px -1px 0 rgb(28, 39, 51), - -1px 1px 0 rgb(28, 39, 51), - 0px 1px 0 rgb(28, 39, 51), - 1px 0px 0 rgb(28, 39, 51), - 0px -1px 0 rgb(28, 39, 51), - -1px 0px 0 rgb(28, 39, 51); -} - -.online-users-div{ - text-align: center; - position: absolute; - bottom: 60px; - width: 100%; - margin: 0 auto; - font-size: 0.9em; - z-index: 1; - opacity: 0.5; - visibility: hidden; -} - -.online-users-text{ - font-family: 'Inter', sans-serif; - font-size: 1em; - color: #E0E0E0; -} - -#config-area{ - width: 100%; - background: #FFFFFF; - min-height: 100px; - padding: 40px; - color: #555555; - font-family: 'Inter', sans-serif; - font-weight: 400; -} - -#org-repo-filter-div{ - width: 50%; - margin: 0 auto; - margin-bottom: 0px; -} - -#org-repo-filter-name{ - width: 20rem; - color: gray; - padding: 5px; - padding-left: 10px; - border: #555555; - background: #9EC5AB; - font-family: 'Inter', sans-serif; - font-size: 1em; - border-radius: 5px; -} - -.site-description{ - font-size: 1em; - line-height: 1.6em; - width: 50%; - margin: 0 auto; - margin-top: 50px; -} - -footer{ - width: 100%; - height: 200px; - padding: 20px; - background-color: #104F55; - color: #AAAAAA; - margin-top: -5px; - position: relative; - font-size: 0.9em; -} - -.footer-left-text-block{ - float:left; - line-height:1.3em; -} - -.footer-right-text-block{ - float:right; - margin-right:2%; - text-align:right; - line-height:1.3em; -} - -.social-buttons{ - position: absolute; - bottom:20px; - right:60px; - z-index: 2; -} - -.org-repo-filter-input{ - border: #555555; - background-color: #555555; -} - - -@media only screen and (max-device-width: 480px) { - - #config-area{ - padding: 0px; - padding-top: 50px; - min-height: 600px; - } - - .site-description{ - font-size: 1em; - line-height: 1.6em; - width: 50%; - margin: 0 auto; - } - - .footer-left-text-block{ - width: 100%; - margin: 0 auto; - line-height:1.3em; - } - - .footer-right-text-block{ - width: 100%; - margin-left: 0 auto; - line-height:1.3em; - float: left; - text-align: left; - margin-top: 20px; - } - - .online-users-div{ - margin-left: 20px; - text-align: left; - } - -} diff --git a/app/public/images/electric-guitar.png b/app/public/images/electric-guitar.png deleted file mode 100644 index ce83a12..0000000 Binary files a/app/public/images/electric-guitar.png and /dev/null differ diff --git a/app/public/images/favicon.png b/app/public/images/favicon.png deleted file mode 100644 index 50485b9..0000000 Binary files a/app/public/images/favicon.png and /dev/null differ diff --git a/app/public/js/main.js b/app/public/js/main.js deleted file mode 100644 index 1683fe1..0000000 --- a/app/public/js/main.js +++ /dev/null @@ -1,382 +0,0 @@ -var eventQueue = []; -var startConsuming = false; -var svg; -var element; -var drawingArea; -var width; -var height; -var volume = 0.5; -var ULTIMATE_DREAM_KILLER = false; // https://github.com/debugger22/github-audio/pull/19 -var orgRepoFilterNames = []; - -var scale_factor = 6, - note_overlap = 2, - note_timeout = 300, - current_notes = 0, - max_life = 20000; - -var svg_background_color_online = '#32746D', //'#0288D1', - svg_background_color_offline = '#E91E63', - svg_text_color = '#FFFFFF', - newuser_box_color = 'rgb(41, 128, 185)', - push_color = 'rgb(155, 89, 182)', - issue_color = 'rgb(46, 204, 113)', - pull_request_color = 'rgb(46, 204, 113)', - comment_color = 'rgb(46, 204, 113)', - edit_color = '#fff', - total_sounds = 51; - - var celesta = [], - clav = [], - swells = [], - all_loaded = false; - - -const ws = new WebSocket('ws://localhost:8000/events/'); - -ws.addEventListener('message', (event) => { - var events = JSON.parse(event.data); - console.log(events); - // $('.online-users-count').html(data.connected_users); - events.forEach(function(event){ - // Filter out events only specified by the user - if(orgRepoFilterNames != []){ - // Don't consider pushes to github.io repos when org filter is on - if(new RegExp(orgRepoFilterNames.join("|")).test(event.repo.name) - && event.repo.name.indexOf('github.io') == -1){ - eventQueue.push(event); - } - }else{ - eventQueue.push(event); - } - }); - // Don't let the eventQueue grow more than 128 - if (eventQueue.length > 128) eventQueue = eventQueue.slice(0, 128); -}); - -ws.addEventListener('open', (event) => { - if(svg != null){ - $('svg').css('background-color', svg_background_color_online); - $('header').css('background-color', svg_background_color_online); - $('.offline-text').css('visibility', 'hidden'); - $('.events-remaining-text').css('visibility', 'hidden'); - $('.events-remaining-value').css('visibility', 'hidden'); - } -}); - -ws.addEventListener('close', (event) => { - if(svg != null){ - $('svg').css('background-color', svg_background_color_offline); - $('header').css('background-color', svg_background_color_offline); - $('.offline-text').css('visibility', 'visible'); - $('.events-remaining-text').css('visibility', 'visible'); - $('.events-remaining-value').css('visibility', 'visible'); - - } -}); - -ws.addEventListener('error', (event) => { - if(svg != null){ - $('svg').css('background-color', svg_background_color_offline); - $('header').css('background-color', svg_background_color_offline); - $('.offline-text').css('visibility', 'visible'); - $('.events-remaining-text').css('visibility', 'visible'); - $('.events-remaining-value').css('visibility', 'visible'); - } -}); - -/** - * This function adds a filter for events that we don't want to hear. - * - * To extend this function, simply add return true for events that should be filtered. - */ -function shouldEventBeIgnored(event){ - // This adds an easter egg to only play closed PRs - if (!!ULTIMATE_DREAM_KILLER) - return (event.type !== "PullRequestEvent" || event.action !== "closed"); - - return false; -} - - -$(function(){ - element = document.documentElement; - drawingArea = document.getElementsByTagName('#area')[0]; - width = window.innerWidth || element.clientWidth || drawingArea.clientWidth; - height = (window.innerHeight - $('header').height())|| (element.clientHeight - $('header').height()) || (drawingArea.clientHeight - $('header').height()); - $('svg').css('background-color', svg_background_color_online); - $('header').css('background-color', svg_background_color_online); - $('svg text').css('color', svg_text_color); - $('#volumeSlider').slider({ - 'max': 100, - 'min': 0, - 'value': volume*100, - 'slide' : function(event, ui){ - volume = ui.value/100.0; - Howler.volume(volume); - }, - 'change' : function(event, ui){ - volume = ui.value/100.0; - Howler.volume(volume); - } - }); - - // Main drawing area - svg = d3.select("#area").append("svg"); - svg.attr({width: width, height: height}); - svg.style('background-color', svg_background_color_online); - - // For window resizes - var update_window = function() { - width = window.innerWidth || element.clientWidth || drawingArea.clientWidth; - height = (window.innerHeight - $('header').height())|| (element.clientHeight - $('header').height()) || (drawingArea.clientHeight - $('header').height()); - svg.attr("width", width).attr("height", height); - } - window.onresize = update_window; - update_window(); - - var loaded_sounds = 0; - var sound_load = function(r) { - loaded_sounds += 1; - if (loaded_sounds == total_sounds) { - all_loaded = true; - setTimeout(playFromQueue, Math.floor(Math.random() * 1000)); - } - } - - // Load sounds - for (var i = 1; i <= 24; i++) { - if (i > 9) { - fn = 'c0' + i; - } else { - fn = 'c00' + i; - } - celesta.push(new Howl({ - src : ['https://d1fz9d31zqor6x.cloudfront.net/sounds/celesta/' + fn + '.ogg', - 'https://d1fz9d31zqor6x.cloudfront.net/sounds/celesta/' + fn + '.mp3'], - volume : 0.7, - onload : sound_load(), - buffer: true, - })) - clav.push(new Howl({ - src : ['https://d1fz9d31zqor6x.cloudfront.net/sounds/clav/' + fn + '.ogg', - 'https://d1fz9d31zqor6x.cloudfront.net/sounds/clav/' + fn + '.mp3'], - volume : 0.4, - onload : sound_load(), - buffer: true, - })) - } - - for (var i = 1; i <= 3; i++) { - swells.push(new Howl({ - src : ['https://d1fz9d31zqor6x.cloudfront.net/sounds/swells/swell' + i + '.ogg', - 'https://d1fz9d31zqor6x.cloudfront.net/sounds/swells/swell' + i + '.mp3'], - volume : 1, - onload : sound_load(), - buffer: true, - })); - } - - Howler.volume(volume); - - // Make header and footer visible - $('body').css('visibility', 'visible'); - - $('#org-repo-filter-name').on('input', function() { - orgRepoFilterNames = $('#org-repo-filter-name').val().split(' '); - eventQueue = []; - }); - -}); - - -/** -* Randomly selects a swell sound and plays it -*/ -function playRandomSwell() { - var index = Math.round(Math.random() * (swells.length - 1)); - swells[index].play(); -} - - -/** -* Plays a sound(celesta and clav) based on passed parameters -*/ -function playSound(size, type) { - var max_pitch = 100.0; - var log_used = 1.0715307808111486871978099; - var pitch = 100 - Math.min(max_pitch, Math.log(size + log_used) / Math.log(log_used)); - var index = Math.floor(pitch / 100.0 * Object.keys(celesta).length); - var fuzz = Math.floor(Math.random() * 4) - 2; - index += fuzz; - index = Math.min(Object.keys(celesta).length - 1, index); - index = Math.max(1, index); - if (current_notes < note_overlap) { - current_notes++; - if (type == 'IssuesEvent' || type == 'IssueCommentEvent') { - clav[index].play(); - } else if(type == 'PushEvent') { - celesta[index].play(); - }else{ - playRandomSwell(); - } - setTimeout(function() { - current_notes--; - }, note_timeout); - } -} - -// Following are the n numbers of event consumers -// consuming n events each per second with a random delay between them - -function playFromQueue(){ - if (!startConsuming) { - setTimeout(playFromQueue, Math.floor(Math.random() * 1000) + 500); - return; - } - var event = eventQueue.shift(); - if(event != null && event.actor.display_login != null && !shouldEventBeIgnored(event) && svg != null){ - playSound(event.actor.display_login.length*1.1, event.type); - if(!document.hidden) - drawEvent(event, svg); - }else{ - console.log("Ignored ex 1"); - } - setTimeout(playFromQueue, Math.floor(Math.random() * 1000) + 500); - $('.events-remaining-value').html(eventQueue.length); -} - -// This method capitalizes the string in place -String.prototype.capitalize=function(all){ - if(all){ - return this.split(' ').map(function(e){ - return e.capitalize().join(' '); - }); - }else{ - return this.charAt(0).toUpperCase() + this.slice(1); - } -} - - -function drawEvent(data, svg_area) { - var starting_opacity = 1; - var opacity = 1 / (100 / data.actor.display_login.length); - if (opacity > 0.5) { - opacity = 0.5; - } - var size = data.actor.display_login.length; - var label_text; - var ring_radius = 80; - var ring_anim_duration = 3000; - svg_text_color = '#FFFFFF'; - switch(data.type){ - case "PushEvent": - label_text = data.actor.display_login.capitalize() + " pushed to " + data.repo.name; - edit_color = '#FFF9A5'; - break; - case "PullRequestEvent": - label_text = data.actor.display_login.capitalize() + " " + - data.action + " " + " a PR for " + data.repo.name; - edit_color = '#C6FF00'; - ring_anim_duration = 10000; - ring_radius = 600; - break; - case "IssuesEvent": - label_text = data.actor.display_login.capitalize() + " " + - data.action + " an issue in " + data.repo.name; - edit_color = '#DFEFCA'; - break; - case "IssueCommentEvent": - label_text = data.actor.display_login.capitalize() + " " + data.action + " in " + data.repo.name; - edit_color = '#CCDDD3'; - break; - } - var csize = size; - var no_label = false; - var type = data.type; - - var circle_id = 'd' + ((Math.random() * 100000) | 0); - var abs_size = Math.abs(size); - size = Math.max(Math.sqrt(abs_size) * scale_factor, 3); - - Math.seedrandom(data.event_url) - var x = Math.random() * (width - size) + size; - var y = Math.random() * (height - size) + size; - - - var circle_group = svg_area.append('g') - .attr('transform', 'translate(' + x + ', ' + y + ')') - .attr('fill', edit_color) - .style('opacity', starting_opacity) - - - var ring = circle_group.append('circle'); - ring.attr({r: size, stroke: 'none'}); - ring.transition() - .attr('r', size + ring_radius) - .style('opacity', 0) - .ease(Math.sqrt) - .duration(ring_anim_duration) - .remove(); - - var circle_container = circle_group.append('a'); - circle_container.attr('xlink:href', data.event_url); - circle_container.attr('target', '_blank'); - circle_container.attr('fill', svg_text_color); - - var circle = circle_container.append('circle'); - circle.classed(type, true); - circle.attr('r', size) - .attr('fill', edit_color) - .transition() - .duration(max_life) - .style('opacity', 0) - .remove(); - - - circle_container.on('mouseover', function() { - circle_container.append('text') - .text(label_text) - .classed('label', true) - .attr('text-anchor', 'middle') - .attr('y', '0.3em') - .transition() - .delay(10) - .style('opacity', 0) - .duration(200) - .each(function() { no_label = true; }) - .remove(); - }); - - circle_container.append('text') - .text(label_text) - .classed('article-label', true) - .attr('text-anchor', 'middle') - .attr('y', '0.3em') - .transition() - .delay(2000) - .style('opacity', 0) - .duration(5000) - .each(function() { no_label = true; }) - .remove(); - - // Remove HTML of decayed events - // Keep it less than 50 - if($('#area svg g').length > 50){ - $('#area svg g:lt(10)').remove(); - } -} - - -function playButtonHover(e){ - e.setAttribute('src', '/public/images/play-button-hover.svg'); -} - -function playButtonUnhover(e){ - e.setAttribute('src', '/public/images/play-button.svg'); -} - -function playButtonClick(e) { - startConsuming = true; - $('#clickToPlay').remove(); -} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..ec2b712 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,33 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' + +export default [ + { ignores: ['dist'] }, + { + files: ['**/*.{js,jsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...js.configs.recommended.rules, + ...reactHooks.configs.recommended.rules, + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +] diff --git a/index.html b/index.html new file mode 100644 index 0000000..c05925c --- /dev/null +++ b/index.html @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + GitHub Audio + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f425508 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2738 @@ +{ + "name": "github-audio-react", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "github-audio-react", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@types/three": "^0.176.0", + "d3": "^7.8.5", + "howler": "^2.2.4", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "seedrandom": "^3.0.5", + "styled-components": "^6.1.8", + "three": "^0.176.0" + }, + "devDependencies": { + "@types/d3": "^7.4.3", + "@types/howler": "^2.2.11", + "@types/node": "^20.12.7", + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@types/styled-components": "^5.1.34", + "@vitejs/plugin-react": "^4.2.1", + "terser": "^5.39.2", + "typescript": "^5.2.2", + "vite": "^5.2.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", + "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", + "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helpers": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", + "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.1", + "@babel/types": "^7.27.1", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", + "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", + "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", + "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@dimforge/rapier3d-compat": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz", + "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==", + "license": "Apache-2.0" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "license": "MIT" + }, + "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/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.9", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz", + "integrity": "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz", + "integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz", + "integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz", + "integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz", + "integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz", + "integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz", + "integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz", + "integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz", + "integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz", + "integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz", + "integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz", + "integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz", + "integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz", + "integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz", + "integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz", + "integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz", + "integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz", + "integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz", + "integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz", + "integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz", + "integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tweenjs/tween.js": { + "version": "23.1.3", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", + "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", + "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "node_modules/@types/howler": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@types/howler/-/howler-2.2.12.tgz", + "integrity": "sha512-hy769UICzOSdK0Kn1FBk4gN+lswcj1EKRkmiDtMkUGvFfYJzgaDXmVXkSShS2m89ERAatGIPnTUlp2HhfkVo5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.17.50", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.50.tgz", + "integrity": "sha512-Mxiq0ULv/zo1OzOhwPqOA13I81CV/W3nvd3ChtQZRT5Cwz3cr0FKo/wMSsbTqL3EXpaBAEQhva2B8ByRkOIh9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.22", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.22.tgz", + "integrity": "sha512-vUhG0YmQZ7kL/tmKLrD3g5zXbXXreZXB3pmROW8bg3CnLnpjkRVwUlLne7Ufa2r9yJ8+/6B73RzhAek5TBKh2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/stats.js": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", + "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==", + "license": "MIT" + }, + "node_modules/@types/styled-components": { + "version": "5.1.34", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.34.tgz", + "integrity": "sha512-mmiVvwpYklFIv9E8qfxuPyIt/OuyIrn6gMOAMOFUO3WJfSrSE+sGUoa4PiZj77Ut7bKZpaa6o1fBKS/4TOEvnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/stylis": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==", + "license": "MIT" + }, + "node_modules/@types/three": { + "version": "0.176.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.176.0.tgz", + "integrity": "sha512-FwfPXxCqOtP7EdYMagCFePNKoG1AGBDUEVKtluv2BTVRpSt7b+X27xNsirPCTCqY1pGYsPUzaM3jgWP7dXSxlw==", + "license": "MIT", + "dependencies": { + "@dimforge/rapier3d-compat": "^0.12.0", + "@tweenjs/tween.js": "~23.1.3", + "@types/stats.js": "*", + "@types/webxr": "*", + "@webgpu/types": "*", + "fflate": "~0.8.2", + "meshoptimizer": "~0.18.1" + } + }, + "node_modules/@types/webxr": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.22.tgz", + "integrity": "sha512-Vr6Stjv5jPRqH690f5I5GLjVk8GSsoQSYJ2FVd/3jJF7KaqfwPi3ehfBS96mlQ2kPCwZaX6U0rG2+NGHBKkA/A==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.0.tgz", + "integrity": "sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.10", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@rolldown/pluginutils": "1.0.0-beta.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/@webgpu/types": { + "version": "0.1.60", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.60.tgz", + "integrity": "sha512-8B/tdfRFKdrnejqmvq95ogp8tf52oZ51p3f4QD5m5Paey/qlX4Rhhy5Y8tgFMi7Ms70HzcMMw3EQjH/jdhTwlA==", + "license": "BSD-3-Clause" + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/browserslist": { + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001718", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", + "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.157", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.157.tgz", + "integrity": "sha512-/0ybgsQd1muo8QlnuTpKwtl0oX5YMlUGbm8xyqgDU00motRkKFFbUJySAQBWcY79rVqNLWIWa87BGVGClwAB2w==", + "dev": true, + "license": "ISC" + }, + "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/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, + "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/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/howler": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/howler/-/howler-2.2.4.tgz", + "integrity": "sha512-iARIBPgcQrwtEr+tALF+rapJ8qSc+Set2GJQl7xT1MQzWaVkFebdJhR3alVlSiUf5U7nAANKuj3aWpwerocD5w==", + "license": "MIT" + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/meshoptimizer": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", + "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "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.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, + "node_modules/rollup": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", + "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.41.1", + "@rollup/rollup-android-arm64": "4.41.1", + "@rollup/rollup-darwin-arm64": "4.41.1", + "@rollup/rollup-darwin-x64": "4.41.1", + "@rollup/rollup-freebsd-arm64": "4.41.1", + "@rollup/rollup-freebsd-x64": "4.41.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", + "@rollup/rollup-linux-arm-musleabihf": "4.41.1", + "@rollup/rollup-linux-arm64-gnu": "4.41.1", + "@rollup/rollup-linux-arm64-musl": "4.41.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", + "@rollup/rollup-linux-riscv64-gnu": "4.41.1", + "@rollup/rollup-linux-riscv64-musl": "4.41.1", + "@rollup/rollup-linux-s390x-gnu": "4.41.1", + "@rollup/rollup-linux-x64-gnu": "4.41.1", + "@rollup/rollup-linux-x64-musl": "4.41.1", + "@rollup/rollup-win32-arm64-msvc": "4.41.1", + "@rollup/rollup-win32-ia32-msvc": "4.41.1", + "@rollup/rollup-win32-x64-msvc": "4.41.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/styled-components": { + "version": "6.1.18", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.18.tgz", + "integrity": "sha512-Mvf3gJFzZCkhjY2Y/Fx9z1m3dxbza0uI9H1CbNZm/jSHCojzJhQ0R7bByrlFJINnMzz/gPulpoFFGymNwrsMcw==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.49", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", + "license": "MIT" + }, + "node_modules/terser": { + "version": "5.39.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.2.tgz", + "integrity": "sha512-yEPUmWve+VA78bI71BW70Dh0TuV4HHd+I5SHOAfS1+QBOmvmCiiffgjR8ryyEd3KIfvPGFqoADt8LdQ6XpXIvg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.14.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/three": { + "version": "0.176.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.176.0.tgz", + "integrity": "sha512-PWRKYWQo23ojf9oZSlRGH8K09q7nRSWx6LY/HF/UUrMdYgN9i1e2OwJYHoQjwc6HF/4lvvYLC5YC1X8UJL2ZpA==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "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 + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..641dcea --- /dev/null +++ b/package.json @@ -0,0 +1,42 @@ +{ + "name": "github-audio-react", + "private": true, + "version": "1.0.0", + "description": "GitHub Audio - Listen to music generated by events happening across GitHub (React + TypeScript + Vite)", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@types/three": "^0.176.0", + "d3": "^7.8.5", + "howler": "^2.2.4", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "seedrandom": "^3.0.5", + "styled-components": "^6.1.8", + "three": "^0.176.0" + }, + "devDependencies": { + "@types/d3": "^7.4.3", + "@types/howler": "^2.2.11", + "@types/node": "^20.12.7", + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@types/styled-components": "^5.1.34", + "@vitejs/plugin-react": "^4.2.1", + "terser": "^5.39.2", + "typescript": "^5.2.2", + "vite": "^5.2.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/debugger22/github-audio.git" + }, + "author": "Sudhanshu Mishra", + "license": "MIT", + "homepage": "https://github.com/debugger22/github-audio#readme" +} diff --git a/public/_headers b/public/_headers new file mode 100644 index 0000000..2b6d28c --- /dev/null +++ b/public/_headers @@ -0,0 +1,28 @@ +# Cache static assets for 1 year +/images/* + Cache-Control: public, max-age=31536000, immutable + +/assets/* + Cache-Control: public, max-age=31536000, immutable + +# Cache fonts for 1 year +*.woff2 + Cache-Control: public, max-age=31536000, immutable + +*.woff + Cache-Control: public, max-age=31536000, immutable + +# Cache JavaScript and CSS for 1 year (with versioning) +*.js + Cache-Control: public, max-age=31536000, immutable + +*.css + Cache-Control: public, max-age=31536000, immutable + +# Cache HTML for 1 hour (to allow for updates) +/*.html + Cache-Control: public, max-age=3600 + +# Root HTML +/ + Cache-Control: public, max-age=3600 \ No newline at end of file diff --git a/public/images/favicon.ico b/public/images/favicon.ico new file mode 100644 index 0000000..1241185 Binary files /dev/null and b/public/images/favicon.ico differ diff --git a/public/images/logo-60.avif b/public/images/logo-60.avif new file mode 100644 index 0000000..1ad2a4d Binary files /dev/null and b/public/images/logo-60.avif differ diff --git a/public/images/logo.png b/public/images/logo.png new file mode 100644 index 0000000..6156845 Binary files /dev/null and b/public/images/logo.png differ diff --git a/app/public/images/play-button-hover.svg b/public/images/play-button-hover.svg similarity index 100% rename from app/public/images/play-button-hover.svg rename to public/images/play-button-hover.svg diff --git a/app/public/images/play-button.svg b/public/images/play-button.svg similarity index 100% rename from app/public/images/play-button.svg rename to public/images/play-button.svg diff --git a/app/public/images/speaker-muted.svg b/public/images/speaker-muted.svg similarity index 100% rename from app/public/images/speaker-muted.svg rename to public/images/speaker-muted.svg diff --git a/app/public/images/speaker.svg b/public/images/speaker.svg similarity index 100% rename from app/public/images/speaker.svg rename to public/images/speaker.svg diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..bddb1c7 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,11 @@ +User-agent: * +Allow: / + +# Allow crawling of all content +Disallow: + +# Sitemap location +Sitemap: https://github.audio/sitemap.xml + +# Crawl delay (optional - helps prevent overwhelming the server) +Crawl-delay: 1 \ No newline at end of file diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..bdb3502 --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1,9 @@ + + + + https://github.audio/ + 2024-01-01 + weekly + 1.0 + + \ No newline at end of file diff --git a/app/public/sounds/celesta/c001.mp3 b/public/sounds/celesta/c001.mp3 similarity index 100% rename from app/public/sounds/celesta/c001.mp3 rename to public/sounds/celesta/c001.mp3 diff --git a/app/public/sounds/celesta/c001.ogg b/public/sounds/celesta/c001.ogg similarity index 100% rename from app/public/sounds/celesta/c001.ogg rename to public/sounds/celesta/c001.ogg diff --git a/app/public/sounds/celesta/c002.mp3 b/public/sounds/celesta/c002.mp3 similarity index 100% rename from app/public/sounds/celesta/c002.mp3 rename to public/sounds/celesta/c002.mp3 diff --git a/app/public/sounds/celesta/c002.ogg b/public/sounds/celesta/c002.ogg similarity index 100% rename from app/public/sounds/celesta/c002.ogg rename to public/sounds/celesta/c002.ogg diff --git a/app/public/sounds/celesta/c003.mp3 b/public/sounds/celesta/c003.mp3 similarity index 100% rename from app/public/sounds/celesta/c003.mp3 rename to public/sounds/celesta/c003.mp3 diff --git a/app/public/sounds/celesta/c003.ogg b/public/sounds/celesta/c003.ogg similarity index 100% rename from app/public/sounds/celesta/c003.ogg rename to public/sounds/celesta/c003.ogg diff --git a/app/public/sounds/celesta/c004.mp3 b/public/sounds/celesta/c004.mp3 similarity index 100% rename from app/public/sounds/celesta/c004.mp3 rename to public/sounds/celesta/c004.mp3 diff --git a/app/public/sounds/celesta/c004.ogg b/public/sounds/celesta/c004.ogg similarity index 100% rename from app/public/sounds/celesta/c004.ogg rename to public/sounds/celesta/c004.ogg diff --git a/app/public/sounds/celesta/c005.mp3 b/public/sounds/celesta/c005.mp3 similarity index 100% rename from app/public/sounds/celesta/c005.mp3 rename to public/sounds/celesta/c005.mp3 diff --git a/app/public/sounds/celesta/c005.ogg b/public/sounds/celesta/c005.ogg similarity index 100% rename from app/public/sounds/celesta/c005.ogg rename to public/sounds/celesta/c005.ogg diff --git a/app/public/sounds/celesta/c006.mp3 b/public/sounds/celesta/c006.mp3 similarity index 100% rename from app/public/sounds/celesta/c006.mp3 rename to public/sounds/celesta/c006.mp3 diff --git a/app/public/sounds/celesta/c006.ogg b/public/sounds/celesta/c006.ogg similarity index 100% rename from app/public/sounds/celesta/c006.ogg rename to public/sounds/celesta/c006.ogg diff --git a/app/public/sounds/celesta/c007.mp3 b/public/sounds/celesta/c007.mp3 similarity index 100% rename from app/public/sounds/celesta/c007.mp3 rename to public/sounds/celesta/c007.mp3 diff --git a/app/public/sounds/celesta/c007.ogg b/public/sounds/celesta/c007.ogg similarity index 100% rename from app/public/sounds/celesta/c007.ogg rename to public/sounds/celesta/c007.ogg diff --git a/app/public/sounds/celesta/c008.mp3 b/public/sounds/celesta/c008.mp3 similarity index 100% rename from app/public/sounds/celesta/c008.mp3 rename to public/sounds/celesta/c008.mp3 diff --git a/app/public/sounds/celesta/c008.ogg b/public/sounds/celesta/c008.ogg similarity index 100% rename from app/public/sounds/celesta/c008.ogg rename to public/sounds/celesta/c008.ogg diff --git a/app/public/sounds/celesta/c009.mp3 b/public/sounds/celesta/c009.mp3 similarity index 100% rename from app/public/sounds/celesta/c009.mp3 rename to public/sounds/celesta/c009.mp3 diff --git a/app/public/sounds/celesta/c009.ogg b/public/sounds/celesta/c009.ogg similarity index 100% rename from app/public/sounds/celesta/c009.ogg rename to public/sounds/celesta/c009.ogg diff --git a/app/public/sounds/celesta/c010.mp3 b/public/sounds/celesta/c010.mp3 similarity index 100% rename from app/public/sounds/celesta/c010.mp3 rename to public/sounds/celesta/c010.mp3 diff --git a/app/public/sounds/celesta/c010.ogg b/public/sounds/celesta/c010.ogg similarity index 100% rename from app/public/sounds/celesta/c010.ogg rename to public/sounds/celesta/c010.ogg diff --git a/app/public/sounds/celesta/c011.mp3 b/public/sounds/celesta/c011.mp3 similarity index 100% rename from app/public/sounds/celesta/c011.mp3 rename to public/sounds/celesta/c011.mp3 diff --git a/app/public/sounds/celesta/c011.ogg b/public/sounds/celesta/c011.ogg similarity index 100% rename from app/public/sounds/celesta/c011.ogg rename to public/sounds/celesta/c011.ogg diff --git a/app/public/sounds/celesta/c012.mp3 b/public/sounds/celesta/c012.mp3 similarity index 100% rename from app/public/sounds/celesta/c012.mp3 rename to public/sounds/celesta/c012.mp3 diff --git a/app/public/sounds/celesta/c012.ogg b/public/sounds/celesta/c012.ogg similarity index 100% rename from app/public/sounds/celesta/c012.ogg rename to public/sounds/celesta/c012.ogg diff --git a/app/public/sounds/celesta/c013.mp3 b/public/sounds/celesta/c013.mp3 similarity index 100% rename from app/public/sounds/celesta/c013.mp3 rename to public/sounds/celesta/c013.mp3 diff --git a/app/public/sounds/celesta/c013.ogg b/public/sounds/celesta/c013.ogg similarity index 100% rename from app/public/sounds/celesta/c013.ogg rename to public/sounds/celesta/c013.ogg diff --git a/app/public/sounds/celesta/c014.mp3 b/public/sounds/celesta/c014.mp3 similarity index 100% rename from app/public/sounds/celesta/c014.mp3 rename to public/sounds/celesta/c014.mp3 diff --git a/app/public/sounds/celesta/c014.ogg b/public/sounds/celesta/c014.ogg similarity index 100% rename from app/public/sounds/celesta/c014.ogg rename to public/sounds/celesta/c014.ogg diff --git a/app/public/sounds/celesta/c015.mp3 b/public/sounds/celesta/c015.mp3 similarity index 100% rename from app/public/sounds/celesta/c015.mp3 rename to public/sounds/celesta/c015.mp3 diff --git a/app/public/sounds/celesta/c015.ogg b/public/sounds/celesta/c015.ogg similarity index 100% rename from app/public/sounds/celesta/c015.ogg rename to public/sounds/celesta/c015.ogg diff --git a/app/public/sounds/celesta/c016.mp3 b/public/sounds/celesta/c016.mp3 similarity index 100% rename from app/public/sounds/celesta/c016.mp3 rename to public/sounds/celesta/c016.mp3 diff --git a/app/public/sounds/celesta/c016.ogg b/public/sounds/celesta/c016.ogg similarity index 100% rename from app/public/sounds/celesta/c016.ogg rename to public/sounds/celesta/c016.ogg diff --git a/app/public/sounds/celesta/c017.mp3 b/public/sounds/celesta/c017.mp3 similarity index 100% rename from app/public/sounds/celesta/c017.mp3 rename to public/sounds/celesta/c017.mp3 diff --git a/app/public/sounds/celesta/c017.ogg b/public/sounds/celesta/c017.ogg similarity index 100% rename from app/public/sounds/celesta/c017.ogg rename to public/sounds/celesta/c017.ogg diff --git a/app/public/sounds/celesta/c018.mp3 b/public/sounds/celesta/c018.mp3 similarity index 100% rename from app/public/sounds/celesta/c018.mp3 rename to public/sounds/celesta/c018.mp3 diff --git a/app/public/sounds/celesta/c018.ogg b/public/sounds/celesta/c018.ogg similarity index 100% rename from app/public/sounds/celesta/c018.ogg rename to public/sounds/celesta/c018.ogg diff --git a/app/public/sounds/celesta/c019.mp3 b/public/sounds/celesta/c019.mp3 similarity index 100% rename from app/public/sounds/celesta/c019.mp3 rename to public/sounds/celesta/c019.mp3 diff --git a/app/public/sounds/celesta/c019.ogg b/public/sounds/celesta/c019.ogg similarity index 100% rename from app/public/sounds/celesta/c019.ogg rename to public/sounds/celesta/c019.ogg diff --git a/app/public/sounds/celesta/c020.mp3 b/public/sounds/celesta/c020.mp3 similarity index 100% rename from app/public/sounds/celesta/c020.mp3 rename to public/sounds/celesta/c020.mp3 diff --git a/app/public/sounds/celesta/c020.ogg b/public/sounds/celesta/c020.ogg similarity index 100% rename from app/public/sounds/celesta/c020.ogg rename to public/sounds/celesta/c020.ogg diff --git a/app/public/sounds/celesta/c021.mp3 b/public/sounds/celesta/c021.mp3 similarity index 100% rename from app/public/sounds/celesta/c021.mp3 rename to public/sounds/celesta/c021.mp3 diff --git a/app/public/sounds/celesta/c021.ogg b/public/sounds/celesta/c021.ogg similarity index 100% rename from app/public/sounds/celesta/c021.ogg rename to public/sounds/celesta/c021.ogg diff --git a/app/public/sounds/celesta/c022.mp3 b/public/sounds/celesta/c022.mp3 similarity index 100% rename from app/public/sounds/celesta/c022.mp3 rename to public/sounds/celesta/c022.mp3 diff --git a/app/public/sounds/celesta/c022.ogg b/public/sounds/celesta/c022.ogg similarity index 100% rename from app/public/sounds/celesta/c022.ogg rename to public/sounds/celesta/c022.ogg diff --git a/app/public/sounds/celesta/c023.mp3 b/public/sounds/celesta/c023.mp3 similarity index 100% rename from app/public/sounds/celesta/c023.mp3 rename to public/sounds/celesta/c023.mp3 diff --git a/app/public/sounds/celesta/c023.ogg b/public/sounds/celesta/c023.ogg similarity index 100% rename from app/public/sounds/celesta/c023.ogg rename to public/sounds/celesta/c023.ogg diff --git a/app/public/sounds/celesta/c024.mp3 b/public/sounds/celesta/c024.mp3 similarity index 100% rename from app/public/sounds/celesta/c024.mp3 rename to public/sounds/celesta/c024.mp3 diff --git a/app/public/sounds/celesta/c024.ogg b/public/sounds/celesta/c024.ogg similarity index 100% rename from app/public/sounds/celesta/c024.ogg rename to public/sounds/celesta/c024.ogg diff --git a/app/public/sounds/celesta/c025.mp3 b/public/sounds/celesta/c025.mp3 similarity index 100% rename from app/public/sounds/celesta/c025.mp3 rename to public/sounds/celesta/c025.mp3 diff --git a/app/public/sounds/celesta/c025.ogg b/public/sounds/celesta/c025.ogg similarity index 100% rename from app/public/sounds/celesta/c025.ogg rename to public/sounds/celesta/c025.ogg diff --git a/app/public/sounds/celesta/c026.mp3 b/public/sounds/celesta/c026.mp3 similarity index 100% rename from app/public/sounds/celesta/c026.mp3 rename to public/sounds/celesta/c026.mp3 diff --git a/app/public/sounds/celesta/c026.ogg b/public/sounds/celesta/c026.ogg similarity index 100% rename from app/public/sounds/celesta/c026.ogg rename to public/sounds/celesta/c026.ogg diff --git a/app/public/sounds/celesta/c027.mp3 b/public/sounds/celesta/c027.mp3 similarity index 100% rename from app/public/sounds/celesta/c027.mp3 rename to public/sounds/celesta/c027.mp3 diff --git a/app/public/sounds/celesta/c027.ogg b/public/sounds/celesta/c027.ogg similarity index 100% rename from app/public/sounds/celesta/c027.ogg rename to public/sounds/celesta/c027.ogg diff --git a/app/public/sounds/clav/c001.mp3 b/public/sounds/clav/c001.mp3 similarity index 100% rename from app/public/sounds/clav/c001.mp3 rename to public/sounds/clav/c001.mp3 diff --git a/app/public/sounds/clav/c001.ogg b/public/sounds/clav/c001.ogg similarity index 100% rename from app/public/sounds/clav/c001.ogg rename to public/sounds/clav/c001.ogg diff --git a/app/public/sounds/clav/c002.mp3 b/public/sounds/clav/c002.mp3 similarity index 100% rename from app/public/sounds/clav/c002.mp3 rename to public/sounds/clav/c002.mp3 diff --git a/app/public/sounds/clav/c002.ogg b/public/sounds/clav/c002.ogg similarity index 100% rename from app/public/sounds/clav/c002.ogg rename to public/sounds/clav/c002.ogg diff --git a/app/public/sounds/clav/c003.mp3 b/public/sounds/clav/c003.mp3 similarity index 100% rename from app/public/sounds/clav/c003.mp3 rename to public/sounds/clav/c003.mp3 diff --git a/app/public/sounds/clav/c003.ogg b/public/sounds/clav/c003.ogg similarity index 100% rename from app/public/sounds/clav/c003.ogg rename to public/sounds/clav/c003.ogg diff --git a/app/public/sounds/clav/c004.mp3 b/public/sounds/clav/c004.mp3 similarity index 100% rename from app/public/sounds/clav/c004.mp3 rename to public/sounds/clav/c004.mp3 diff --git a/app/public/sounds/clav/c004.ogg b/public/sounds/clav/c004.ogg similarity index 100% rename from app/public/sounds/clav/c004.ogg rename to public/sounds/clav/c004.ogg diff --git a/app/public/sounds/clav/c005.mp3 b/public/sounds/clav/c005.mp3 similarity index 100% rename from app/public/sounds/clav/c005.mp3 rename to public/sounds/clav/c005.mp3 diff --git a/app/public/sounds/clav/c005.ogg b/public/sounds/clav/c005.ogg similarity index 100% rename from app/public/sounds/clav/c005.ogg rename to public/sounds/clav/c005.ogg diff --git a/app/public/sounds/clav/c006.mp3 b/public/sounds/clav/c006.mp3 similarity index 100% rename from app/public/sounds/clav/c006.mp3 rename to public/sounds/clav/c006.mp3 diff --git a/app/public/sounds/clav/c006.ogg b/public/sounds/clav/c006.ogg similarity index 100% rename from app/public/sounds/clav/c006.ogg rename to public/sounds/clav/c006.ogg diff --git a/app/public/sounds/clav/c007.mp3 b/public/sounds/clav/c007.mp3 similarity index 100% rename from app/public/sounds/clav/c007.mp3 rename to public/sounds/clav/c007.mp3 diff --git a/app/public/sounds/clav/c007.ogg b/public/sounds/clav/c007.ogg similarity index 100% rename from app/public/sounds/clav/c007.ogg rename to public/sounds/clav/c007.ogg diff --git a/app/public/sounds/clav/c008.mp3 b/public/sounds/clav/c008.mp3 similarity index 100% rename from app/public/sounds/clav/c008.mp3 rename to public/sounds/clav/c008.mp3 diff --git a/app/public/sounds/clav/c008.ogg b/public/sounds/clav/c008.ogg similarity index 100% rename from app/public/sounds/clav/c008.ogg rename to public/sounds/clav/c008.ogg diff --git a/app/public/sounds/clav/c009.mp3 b/public/sounds/clav/c009.mp3 similarity index 100% rename from app/public/sounds/clav/c009.mp3 rename to public/sounds/clav/c009.mp3 diff --git a/app/public/sounds/clav/c009.ogg b/public/sounds/clav/c009.ogg similarity index 100% rename from app/public/sounds/clav/c009.ogg rename to public/sounds/clav/c009.ogg diff --git a/app/public/sounds/clav/c010.mp3 b/public/sounds/clav/c010.mp3 similarity index 100% rename from app/public/sounds/clav/c010.mp3 rename to public/sounds/clav/c010.mp3 diff --git a/app/public/sounds/clav/c010.ogg b/public/sounds/clav/c010.ogg similarity index 100% rename from app/public/sounds/clav/c010.ogg rename to public/sounds/clav/c010.ogg diff --git a/app/public/sounds/clav/c011.mp3 b/public/sounds/clav/c011.mp3 similarity index 100% rename from app/public/sounds/clav/c011.mp3 rename to public/sounds/clav/c011.mp3 diff --git a/app/public/sounds/clav/c011.ogg b/public/sounds/clav/c011.ogg similarity index 100% rename from app/public/sounds/clav/c011.ogg rename to public/sounds/clav/c011.ogg diff --git a/app/public/sounds/clav/c012.mp3 b/public/sounds/clav/c012.mp3 similarity index 100% rename from app/public/sounds/clav/c012.mp3 rename to public/sounds/clav/c012.mp3 diff --git a/app/public/sounds/clav/c012.ogg b/public/sounds/clav/c012.ogg similarity index 100% rename from app/public/sounds/clav/c012.ogg rename to public/sounds/clav/c012.ogg diff --git a/app/public/sounds/clav/c013.mp3 b/public/sounds/clav/c013.mp3 similarity index 100% rename from app/public/sounds/clav/c013.mp3 rename to public/sounds/clav/c013.mp3 diff --git a/app/public/sounds/clav/c013.ogg b/public/sounds/clav/c013.ogg similarity index 100% rename from app/public/sounds/clav/c013.ogg rename to public/sounds/clav/c013.ogg diff --git a/app/public/sounds/clav/c014.mp3 b/public/sounds/clav/c014.mp3 similarity index 100% rename from app/public/sounds/clav/c014.mp3 rename to public/sounds/clav/c014.mp3 diff --git a/app/public/sounds/clav/c014.ogg b/public/sounds/clav/c014.ogg similarity index 100% rename from app/public/sounds/clav/c014.ogg rename to public/sounds/clav/c014.ogg diff --git a/app/public/sounds/clav/c015.mp3 b/public/sounds/clav/c015.mp3 similarity index 100% rename from app/public/sounds/clav/c015.mp3 rename to public/sounds/clav/c015.mp3 diff --git a/app/public/sounds/clav/c015.ogg b/public/sounds/clav/c015.ogg similarity index 100% rename from app/public/sounds/clav/c015.ogg rename to public/sounds/clav/c015.ogg diff --git a/app/public/sounds/clav/c016.mp3 b/public/sounds/clav/c016.mp3 similarity index 100% rename from app/public/sounds/clav/c016.mp3 rename to public/sounds/clav/c016.mp3 diff --git a/app/public/sounds/clav/c016.ogg b/public/sounds/clav/c016.ogg similarity index 100% rename from app/public/sounds/clav/c016.ogg rename to public/sounds/clav/c016.ogg diff --git a/app/public/sounds/clav/c017.mp3 b/public/sounds/clav/c017.mp3 similarity index 100% rename from app/public/sounds/clav/c017.mp3 rename to public/sounds/clav/c017.mp3 diff --git a/app/public/sounds/clav/c017.ogg b/public/sounds/clav/c017.ogg similarity index 100% rename from app/public/sounds/clav/c017.ogg rename to public/sounds/clav/c017.ogg diff --git a/app/public/sounds/clav/c018.mp3 b/public/sounds/clav/c018.mp3 similarity index 100% rename from app/public/sounds/clav/c018.mp3 rename to public/sounds/clav/c018.mp3 diff --git a/app/public/sounds/clav/c018.ogg b/public/sounds/clav/c018.ogg similarity index 100% rename from app/public/sounds/clav/c018.ogg rename to public/sounds/clav/c018.ogg diff --git a/app/public/sounds/clav/c019.mp3 b/public/sounds/clav/c019.mp3 similarity index 100% rename from app/public/sounds/clav/c019.mp3 rename to public/sounds/clav/c019.mp3 diff --git a/app/public/sounds/clav/c019.ogg b/public/sounds/clav/c019.ogg similarity index 100% rename from app/public/sounds/clav/c019.ogg rename to public/sounds/clav/c019.ogg diff --git a/app/public/sounds/clav/c020.mp3 b/public/sounds/clav/c020.mp3 similarity index 100% rename from app/public/sounds/clav/c020.mp3 rename to public/sounds/clav/c020.mp3 diff --git a/app/public/sounds/clav/c020.ogg b/public/sounds/clav/c020.ogg similarity index 100% rename from app/public/sounds/clav/c020.ogg rename to public/sounds/clav/c020.ogg diff --git a/app/public/sounds/clav/c021.mp3 b/public/sounds/clav/c021.mp3 similarity index 100% rename from app/public/sounds/clav/c021.mp3 rename to public/sounds/clav/c021.mp3 diff --git a/app/public/sounds/clav/c021.ogg b/public/sounds/clav/c021.ogg similarity index 100% rename from app/public/sounds/clav/c021.ogg rename to public/sounds/clav/c021.ogg diff --git a/app/public/sounds/clav/c022.mp3 b/public/sounds/clav/c022.mp3 similarity index 100% rename from app/public/sounds/clav/c022.mp3 rename to public/sounds/clav/c022.mp3 diff --git a/app/public/sounds/clav/c022.ogg b/public/sounds/clav/c022.ogg similarity index 100% rename from app/public/sounds/clav/c022.ogg rename to public/sounds/clav/c022.ogg diff --git a/app/public/sounds/clav/c023.mp3 b/public/sounds/clav/c023.mp3 similarity index 100% rename from app/public/sounds/clav/c023.mp3 rename to public/sounds/clav/c023.mp3 diff --git a/app/public/sounds/clav/c023.ogg b/public/sounds/clav/c023.ogg similarity index 100% rename from app/public/sounds/clav/c023.ogg rename to public/sounds/clav/c023.ogg diff --git a/app/public/sounds/clav/c024.mp3 b/public/sounds/clav/c024.mp3 similarity index 100% rename from app/public/sounds/clav/c024.mp3 rename to public/sounds/clav/c024.mp3 diff --git a/app/public/sounds/clav/c024.ogg b/public/sounds/clav/c024.ogg similarity index 100% rename from app/public/sounds/clav/c024.ogg rename to public/sounds/clav/c024.ogg diff --git a/app/public/sounds/clav/c025.mp3 b/public/sounds/clav/c025.mp3 similarity index 100% rename from app/public/sounds/clav/c025.mp3 rename to public/sounds/clav/c025.mp3 diff --git a/app/public/sounds/clav/c025.ogg b/public/sounds/clav/c025.ogg similarity index 100% rename from app/public/sounds/clav/c025.ogg rename to public/sounds/clav/c025.ogg diff --git a/app/public/sounds/clav/c026.mp3 b/public/sounds/clav/c026.mp3 similarity index 100% rename from app/public/sounds/clav/c026.mp3 rename to public/sounds/clav/c026.mp3 diff --git a/app/public/sounds/clav/c026.ogg b/public/sounds/clav/c026.ogg similarity index 100% rename from app/public/sounds/clav/c026.ogg rename to public/sounds/clav/c026.ogg diff --git a/app/public/sounds/clav/c027.mp3 b/public/sounds/clav/c027.mp3 similarity index 100% rename from app/public/sounds/clav/c027.mp3 rename to public/sounds/clav/c027.mp3 diff --git a/app/public/sounds/clav/c027.ogg b/public/sounds/clav/c027.ogg similarity index 100% rename from app/public/sounds/clav/c027.ogg rename to public/sounds/clav/c027.ogg diff --git a/app/public/sounds/swells/swell1.mp3 b/public/sounds/swells/swell1.mp3 similarity index 100% rename from app/public/sounds/swells/swell1.mp3 rename to public/sounds/swells/swell1.mp3 diff --git a/app/public/sounds/swells/swell1.ogg b/public/sounds/swells/swell1.ogg similarity index 100% rename from app/public/sounds/swells/swell1.ogg rename to public/sounds/swells/swell1.ogg diff --git a/app/public/sounds/swells/swell2.mp3 b/public/sounds/swells/swell2.mp3 similarity index 100% rename from app/public/sounds/swells/swell2.mp3 rename to public/sounds/swells/swell2.mp3 diff --git a/app/public/sounds/swells/swell2.ogg b/public/sounds/swells/swell2.ogg similarity index 100% rename from app/public/sounds/swells/swell2.ogg rename to public/sounds/swells/swell2.ogg diff --git a/app/public/sounds/swells/swell3.mp3 b/public/sounds/swells/swell3.mp3 similarity index 100% rename from app/public/sounds/swells/swell3.mp3 rename to public/sounds/swells/swell3.mp3 diff --git a/app/public/sounds/swells/swell3.ogg b/public/sounds/swells/swell3.ogg similarity index 100% rename from app/public/sounds/swells/swell3.ogg rename to public/sounds/swells/swell3.ogg diff --git a/rust-server/.dockerignore b/rust-server/.dockerignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/rust-server/.dockerignore @@ -0,0 +1 @@ +target diff --git a/rust-server/Cargo.lock b/rust-server/Cargo.lock index 273dc87..fff67be 100644 --- a/rust-server/Cargo.lock +++ b/rust-server/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "adler" @@ -45,7 +45,7 @@ checksum = "705339e0e4a9690e2908d2b3d049d85682cf19fbd5782494498fbf7003a6a282" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -63,7 +63,7 @@ dependencies = [ "async-trait", "axum-core", "base64 0.20.0", - "bitflags", + "bitflags 1.3.2", "bytes", "futures-util", "headers", @@ -126,6 +126,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + [[package]] name = "block-buffer" version = "0.10.3" @@ -176,9 +182,12 @@ checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "cc" -version = "1.0.78" +version = "1.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -360,7 +369,7 @@ checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -416,9 +425,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "66b91535aa35fea1523ad1b86cb6b53c28e0ae566ba4a460f4457e936cad7c6f" dependencies = [ "bytes", "fnv", @@ -446,7 +455,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ "base64 0.13.1", - "bitflags", + "bitflags 1.3.2", "bytes", "headers-core", "http", @@ -741,11 +750,11 @@ checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] name = "openssl" -version = "0.10.45" +version = "0.10.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" +checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" dependencies = [ - "bitflags", + "bitflags 2.9.1", "cfg-if", "foreign-types", "libc", @@ -756,13 +765,13 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -771,15 +780,24 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-src" +version = "300.5.0+3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8ce546f549326b0e6052b649198487d91320875da901e7bd11a06d1ee3f9c2f" +dependencies = [ + "cc", +] + [[package]] name = "openssl-sys" -version = "0.9.80" +version = "0.9.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" +checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" dependencies = [ - "autocfg", "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] @@ -830,7 +848,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -859,18 +877,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.49" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -911,7 +929,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -970,6 +988,7 @@ dependencies = [ "futures-util", "headers", "hyper", + "openssl", "reqwest", "serde", "serde_json", @@ -1011,7 +1030,7 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -1045,7 +1064,7 @@ checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1091,6 +1110,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -1136,6 +1161,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "sync_wrapper" version = "0.1.1" @@ -1173,7 +1209,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1193,9 +1229,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.24.1" +version = "1.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae" +checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb" dependencies = [ "autocfg", "bytes", @@ -1219,7 +1255,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1320,7 +1356,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" dependencies = [ - "bitflags", + "bitflags 1.3.2", "bytes", "futures-core", "futures-util", @@ -1493,7 +1529,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.107", "wasm-bindgen-shared", ] @@ -1527,7 +1563,7 @@ checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/rust-server/Cargo.toml b/rust-server/Cargo.toml index cdbdd20..9bdc338 100644 --- a/rust-server/Cargo.toml +++ b/rust-server/Cargo.toml @@ -6,9 +6,10 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +openssl = { version = "0.10.59", features = ["vendored"] } axum = { version = "0.6.2", features = ["headers", "ws"] } hyper = { version = "0.14.23", features = ["full"] } -tokio = { version = "1.24.1", features = ["full"] } +tokio = { version = "1.24.2", features = ["full"] } tower = "0.4.13" tower-http = { version = "0.1", features = ["full"] } headers = "0.3" diff --git a/rust-server/Dockerfile b/rust-server/Dockerfile new file mode 100644 index 0000000..12ba5a1 --- /dev/null +++ b/rust-server/Dockerfile @@ -0,0 +1,19 @@ +FROM rust:1.66 as build-env + +# Create working directory +RUN mkdir /rust-server + +COPY . /rust-server + +# Set working directory +WORKDIR /rust-server + +RUN cargo build --release + +FROM gcr.io/distroless/cc +COPY --from=build-env /rust-server/target/release/rust-server /app +COPY --from=build-env /rust-server/.env /.env + +EXPOSE 8000 + +CMD ["./app"] diff --git a/rust-server/fly.toml b/rust-server/fly.toml new file mode 100644 index 0000000..833238a --- /dev/null +++ b/rust-server/fly.toml @@ -0,0 +1,37 @@ +# fly.toml file generated for github-audio on 2023-01-18T19:43:54+05:30 + +app = "github-audio" +kill_signal = "SIGINT" +kill_timeout = 5 +processes = [] + +[env] + +[experimental] + auto_rollback = true + +[[services]] + http_checks = [] + internal_port = 8000 + processes = ["app"] + protocol = "tcp" + script_checks = [] + [services.concurrency] + hard_limit = 25 + soft_limit = 20 + type = "connections" + + [[services.ports]] + force_https = true + handlers = ["http"] + port = 80 + + [[services.ports]] + handlers = ["tls", "http"] + port = 443 + + [[services.tcp_checks]] + grace_period = "1s" + interval = "15s" + restart_limit = 0 + timeout = "2s" diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..c92a6df --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,534 @@ +import React, { useState, useEffect, useMemo, useRef, useCallback, Suspense } from 'react'; +import styled from 'styled-components'; +import { useWebSocket } from './hooks/useWebSocket'; +import { useAudio } from './hooks/useAudio'; +import Visualization, { VisualizationRef } from './components/Visualization'; +import VisualizationToggle from './components/VisualizationToggle'; + +// Lazy load the 3D visualization component +const Visualization3D = React.lazy(() => import('./components/Visualization3D')); + +// Import the type separately for TypeScript +import type { Visualization3DRef } from './components/Visualization3D'; + +// Styled Components - Optimized to reduce DOM elements +const AppContainer = styled.div` + margin: 0; + padding: 0; + background-color: #000; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; + font-weight: 400; + font-size: 16px; + overflow-x: hidden; + min-height: 100vh; +`; + +const ClickToPlay = styled.div<{ $show: boolean }>` + position: fixed; + top: 0; + left: 0; + background-color: rgba(0, 0, 0, 0.7); + z-index: 1000; + width: 100%; + height: 100%; + text-align: center; + display: ${props => props.$show ? 'flex' : 'none'}; + align-items: center; + justify-content: center; +`; + +const PlayButton = styled.img` + width: 300px; + cursor: pointer; + transition: transform 0.2s ease; + + &:hover { + transform: scale(1.05); + } +`; + +// Combined Header with all header elements +const Header = styled.header<{ $isOnline: boolean }>` + position: relative; + width: 100%; + padding: 20px 40px; + z-index: 1000; + transition: background-color 0.3s ease; + display: flex; + flex-direction: row-reverse; + align-items: center; + + @media (max-width: 768px) { + padding: 10px 20px; + } + + .logo { + height: 60px; + width: auto; + } + + input[type="range"] { + width: 100px; + opacity: 0.3; + transition: opacity 0.2s ease; + cursor: pointer; + + &:hover { + opacity: 0.9; + } + } +`; + +// Combined MainArea with blob background +const MainArea = styled.div<{ $isOnline: boolean }>` + width: 100%; + position: relative; + min-height: 100vh; + background-color: transparent; + transition: background-color 0.3s ease; + overflow: hidden; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 80%; + height: 80%; + z-index: 0; + filter: blur(100px); + border-radius: 99999px; + margin: auto; + background: conic-gradient( + from 0deg, + #FE7743 0%, + #F1BA88 20%, + #6C4E4E 30%, + #3A59D1 40%, + #3D90D7 50%, + #0118D8 60%, + #7965C1 70%, + #483AA0 80%, + #CF0F47 90%, + #EA98DA 100% + ); + animation: spinAndPulseBlob 30s cubic-bezier(0.77, 0, 0.175, 1) infinite, + fadeBlob 15s ease-in-out infinite; + } + + @keyframes spinAndPulseBlob { + 0% { + transform: rotate(0deg) scale(1.95); + } + 50% { + transform: rotate(180deg) scale(2.05); + } + 100% { + transform: rotate(360deg) scale(1.95); + } + } + + @keyframes fadeBlob { + 0%, 100% { + opacity: 0.85; + } + 50% { + opacity: 1; + } + } +`; + +// Combined ConfigArea with all config elements +const ConfigArea = styled.div` + width: 100%; + background: #FFFFFF; + min-height: 100px; + padding: 40px; + color: #555555; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; + font-weight: 400; + + .filter-section { + width: 50%; + margin: 0 auto 20px auto; + text-align: center; + + input { + width: 320px; + padding: 8px 12px; + border: 1px solid #ccc; + border-radius: 4px; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; + } + } + + .description { + text-align: center; + margin-bottom: 20px; + } +`; + +const Footer = styled.footer` + background: #f5f5f5; + padding: 20px; + text-align: center; + color: #666; + font-size: 0.9em; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + gap: 10px; +`; + +// Beautiful 3D Loading Fallback UI +const LoadingContainer = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(135deg, #0c0c0c 0%, #1a1a2e 50%, #16213e 100%); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + z-index: 100; +`; + +const LoadingContent = styled.div` + text-align: center; + color: white; + font-family: 'Inter', sans-serif; +`; + +const LoadingTitle = styled.h2` + font-size: 2.5rem; + font-weight: 300; + margin-bottom: 1rem; + background: linear-gradient(45deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4, #ffeaa7); + background-size: 300% 300%; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + animation: gradientShift 3s ease-in-out infinite; + + @keyframes gradientShift { + 0%, 100% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + } +`; + +const LoadingSubtitle = styled.p` + font-size: 1.1rem; + color: #a0a0a0; + margin-bottom: 3rem; + font-weight: 300; +`; + +const LoadingSpinner = styled.div` + position: relative; + width: 80px; + height: 80px; + margin: 0 auto 2rem; +`; + +const SpinnerRing = styled.div<{ delay?: number }>` + position: absolute; + width: 100%; + height: 100%; + border: 2px solid transparent; + border-top: 2px solid #4ecdc4; + border-radius: 50%; + animation: spin 2s linear infinite; + animation-delay: ${props => props.delay || 0}s; + + @keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } + } +`; + +const SpinnerCore = styled.div` + position: absolute; + top: 50%; + left: 50%; + width: 12px; + height: 12px; + background: radial-gradient(circle, #ff6b6b, #4ecdc4); + border-radius: 50%; + transform: translate(-50%, -50%); + animation: pulse 1.5s ease-in-out infinite; + + @keyframes pulse { + 0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 1; } + 50% { transform: translate(-50%, -50%) scale(1.5); opacity: 0.7; } + } +`; + +const LoadingStars = styled.div` + position: absolute; + width: 100%; + height: 100%; + overflow: hidden; + + &::before, &::after { + content: ''; + position: absolute; + width: 2px; + height: 2px; + background: white; + border-radius: 50%; + animation: twinkle 3s ease-in-out infinite; + } + + &::before { + top: 20%; + left: 15%; + animation-delay: 0s; + } + + &::after { + top: 70%; + right: 20%; + animation-delay: 1.5s; + } + + @keyframes twinkle { + 0%, 100% { opacity: 0.3; transform: scale(1); } + 50% { opacity: 1; transform: scale(1.5); } + } +`; + +const LoadingProgress = styled.div` + font-size: 0.9rem; + color: #666; + font-weight: 300; + animation: fadeInOut 2s ease-in-out infinite; + + @keyframes fadeInOut { + 0%, 100% { opacity: 0.5; } + 50% { opacity: 1; } + } +`; + +// Loading Fallback Component +const Loading3DFallback: React.FC = () => ( + + + + Initializing 3D Universe + Preparing cosmic visualization... + + + + + + Loading Three.js components + + +); + +const App: React.FC = () => { + const [showClickToPlay, setShowClickToPlay] = useState(true); + const [volume, setVolumeState] = useState(() => { + const savedVolume = localStorage.getItem('github-audio-volume'); + return savedVolume ? parseFloat(savedVolume) : 0.5; + }); + const [orgRepoFilter, setOrgRepoFilter] = useState(''); + const [is3DMode, setIs3DMode] = useState(false); + const processedEventIdsRef = useRef>(new Set()); + const processingTimeoutRef = useRef(null); + const visualizationRef = useRef(null); + const visualization3DRef = useRef(null); + + // Parse org/repo filter into array + const orgRepoFilterArray = useMemo(() => { + return orgRepoFilter.split(' ').filter(item => item.trim() !== ''); + }, [orgRepoFilter]); + + // Custom hooks + const { isOnline, eventQueue, clearEventQueue } = useWebSocket( + 'wss://github-audio.fly.dev/events/', + orgRepoFilterArray + ); + + const { allSoundsLoaded, playSound, setVolume } = useAudio(); + + // Process events one at a time with proper delays (matching original implementation) + const processNextEvent = useCallback(() => { + if (!showClickToPlay && allSoundsLoaded && eventQueue.length > 0) { + // Find the next unprocessed event using event_url as unique identifier + const nextEvent = eventQueue.find(event => + event.event_url && !processedEventIdsRef.current.has(event.event_url) + ); + + if (nextEvent && nextEvent.actor?.display_login && nextEvent.event_url) { + console.log(`Processing event: ${nextEvent.type} by ${nextEvent.actor.display_login} (URL: ${nextEvent.event_url.slice(-8)})`); + + // Mark as processed immediately + processedEventIdsRef.current.add(nextEvent.event_url); + + // Play sound + const size = nextEvent.actor.display_login.length * 1.1; + playSound(size, nextEvent.type); + + // Draw event in the appropriate visualization mode + if (is3DMode && visualization3DRef.current) { + visualization3DRef.current.drawEvent(nextEvent); + } else if (!is3DMode && visualizationRef.current) { + visualizationRef.current.drawEvent(nextEvent); + } + + console.log(`Processed count: ${processedEventIdsRef.current.size}, Queue length: ${eventQueue.length}`); + } else if (eventQueue.length > 0) { + console.log(`No new events to process (queue: ${eventQueue.length}, processed: ${processedEventIdsRef.current.size})`); + } + + // Clean up the processed IDs set if it gets too large + if (processedEventIdsRef.current.size > 200) { + console.log(`Cleaning up processed URLs set (was ${processedEventIdsRef.current.size})`); + // Keep only the most recent event URLs from the current queue + const recentUrls = new Set(); + eventQueue.slice(-100).forEach(event => { + if (event.event_url) recentUrls.add(event.event_url); + }); + processedEventIdsRef.current = recentUrls; + console.log(`Cleaned up to ${processedEventIdsRef.current.size} URLs`); + } + } + + // Schedule next processing cycle with original timing: 500-1500ms delay + const delay = Math.floor(Math.random() * 1000) + 500; + processingTimeoutRef.current = setTimeout(processNextEvent, delay); + }, [showClickToPlay, allSoundsLoaded, eventQueue, playSound, is3DMode]); + + // Start the processing loop when conditions are met + useEffect(() => { + if (!showClickToPlay && allSoundsLoaded) { + // Clear any existing timeout + if (processingTimeoutRef.current) { + clearTimeout(processingTimeoutRef.current); + } + + // Start processing with initial delay + const initialDelay = Math.floor(Math.random() * 1000) + 500; + processingTimeoutRef.current = setTimeout(processNextEvent, initialDelay); + + return () => { + if (processingTimeoutRef.current) { + clearTimeout(processingTimeoutRef.current); + } + }; + } + }, [showClickToPlay, allSoundsLoaded, processNextEvent]); + + // Clear processed events when filter changes + useEffect(() => { + processedEventIdsRef.current.clear(); + }, [orgRepoFilter]); + + const handlePlayButtonClick = () => { + setShowClickToPlay(false); + }; + + const handleVolumeChange = (e: React.ChangeEvent) => { + const newVolume = parseFloat(e.target.value) / 100; + setVolumeState(newVolume); + setVolume(newVolume); + localStorage.setItem('github-audio-volume', newVolume.toString()); + }; + + const handleFilterChange = (e: React.ChangeEvent) => { + setOrgRepoFilter(e.target.value); + clearEventQueue(); + processedEventIdsRef.current.clear(); + }; + + const handleToggle3DMode = (newIs3D: boolean) => { + setIs3DMode(newIs3D); + }; + + return ( + + + + + + +
+ +
+ {!is3DMode && ( + + )} + {is3DMode && ( + }> + + + )} +
+ + +
+ Enter your organization's or repository's names to filter events  + +
+
+

Track events happening across GitHub and convert them to music notes.

+
+
+ +
+ GitHub Audio Logo + This project is not in any way affiliated with GitHub. + + + Source Code + + + + Developed by{' '} @debugger22 + + + ProTip: It's actually kind of nice to leave on the background + +
+ + +
+ ); +}; + +export default App; \ No newline at end of file diff --git a/src/components/Visualization.tsx b/src/components/Visualization.tsx new file mode 100644 index 0000000..e9d6251 --- /dev/null +++ b/src/components/Visualization.tsx @@ -0,0 +1,215 @@ +import { useEffect, useRef, useCallback, useImperativeHandle, forwardRef } from 'react'; +import * as d3 from 'd3'; +import styled from 'styled-components'; +import { GitHubEvent } from '../hooks/useWebSocket'; + +// Combined container and SVG - reduces one DOM element per visualization +const SVGContainer = styled.svg` + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + overflow: hidden; + background-color: transparent; + display: block; +`; + +interface VisualizationProps { + // No props needed since we're using ref-based drawing +} + +export interface VisualizationRef { + drawEvent: (event: GitHubEvent) => void; +} + +const Visualization = forwardRef((_, ref) => { + const svgRef = useRef(null); + + // Function to draw a single event immediately - optimized to reduce DOM elements + const drawEvent = useCallback((event: GitHubEvent) => { + if (!svgRef.current) return; + + const svg = d3.select(svgRef.current); + const width = svgRef.current.clientWidth; + const height = svgRef.current.clientHeight; + + // Constants + const scaleFactor = 6; + const maxLife = 20000; + const svgTextColor = '#FFFFFF'; + + // Calculate opacity + const startingOpacity = 1; + let opacity = 1 / (100 / event.actor.display_login.length); + if (opacity > 0.5) { + opacity = 0.5; + } + + // Size and event-specific settings + let size = event.actor.display_login.length; + let labelText = ''; + let ringRadius = 80; + let ringAnimDuration = 3000; + let editColor = ''; + + // Event-specific colors and settings + switch (event.type) { + case 'PushEvent': + labelText = `${event.actor.display_login} pushed to ${event.repo.name}`; + editColor = '#FFF9A5'; + break; + case 'PullRequestEvent': + labelText = `${event.actor.display_login} ${event.action || 'updated'} a PR for ${event.repo.name}`; + editColor = '#C6FF00'; + ringAnimDuration = 10000; + ringRadius = 600; + break; + case 'IssuesEvent': + labelText = `${event.actor.display_login} ${event.action || 'updated'} an issue in ${event.repo.name}`; + editColor = '#DFEFCA'; + break; + case 'IssueCommentEvent': + labelText = `${event.actor.display_login} ${event.action || 'commented'} in ${event.repo.name}`; + editColor = '#CCDDD3'; + break; + default: + labelText = `${event.actor.display_login} updated ${event.repo.name}`; + editColor = '#BDC3C7'; + } + + const absSize = Math.abs(size); + size = Math.max(Math.sqrt(absSize) * scaleFactor, 3); + + // Seeded random positioning + const hashCode = (str: string) => { + let hash = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; + } + return Math.abs(hash); + }; + + const seed = hashCode(event.event_url || event.id); + const seededRandom1 = (seed % 10000) / 10000; + const seededRandom2 = ((seed * 9301 + 49297) % 233280) / 233280; + + const x = seededRandom1 * (width - size) + size; + const y = seededRandom2 * (height - size) + size; + + // Optimized: Create single group instead of nested groups + const eventGroup = svg.append('g') + .attr('transform', `translate(${x}, ${y})`) + .attr('fill', editColor) + .style('opacity', startingOpacity); + + // Ring animation (outer expanding ring) + eventGroup.append('circle') + .attr('r', size) + .attr('stroke', 'none') + .attr('fill', editColor) + .style('opacity', opacity) + .transition() + .attr('r', size + ringRadius) + .style('opacity', 0) + .ease(d3.easePoly.exponent(0.5)) + .duration(ringAnimDuration) + .remove(); + + // Main circle with click handler - reduced nesting + const mainCircle = eventGroup.append('circle') + .classed(event.type, true) + .attr('r', size) + .attr('fill', editColor) + .style('opacity', opacity) + .style('cursor', event.event_url ? 'pointer' : 'default'); + + // Add click handler if URL exists (before transition) + if (event.event_url) { + mainCircle.on('click', () => { + window.open(event.event_url, '_blank'); + }); + } + + // Optimized: Single text element that changes on hover instead of creating new ones + const textElement = eventGroup.append('text') + .text(labelText) + .attr('text-anchor', 'middle') + .attr('y', '0.3em') + .attr('fill', svgTextColor) + .attr('font-size', '13px') + .attr('letter-spacing', '0.05em') + .attr('font-family', 'Inter, sans-serif') + .attr('font-weight', '500') + .style('pointer-events', 'none'); + + // Hover effects on the main circle (before transition) + mainCircle + .on('mouseover', function() { + textElement.style('opacity', 1).attr('font-size', '12px'); + }) + .on('mouseout', function() { + textElement.style('opacity', 0.7).attr('font-size', '11px'); + }); + + // Apply transition after event handlers are set + mainCircle.transition() + .duration(maxLife) + .style('opacity', 0) + .remove(); + + // Text fade out animation + textElement.transition() + .delay(2000) + .style('opacity', 0) + .duration(5000) + .remove(); + + // More aggressive cleanup - keep fewer elements (matching original: keep < 50) + const allGroups = svg.selectAll('g'); + if (allGroups.size() > 50) { + // Remove the first 10 oldest groups (matching original cleanup pattern) + allGroups.nodes().slice(0, 10).forEach(node => { + d3.select(node).remove(); + }); + } + }, []); + + // Expose drawEvent function to parent component + useImperativeHandle(ref, () => ({ + drawEvent + }), [drawEvent]); + + // Handle window resize + useEffect(() => { + const handleResize = () => { + if (!svgRef.current) return; + const newWidth = svgRef.current.clientWidth; + const newHeight = svgRef.current.clientHeight; + const svg = d3.select(svgRef.current); + svg.attr('width', newWidth).attr('height', newHeight); + }; + + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + + // Initialize SVG dimensions + useEffect(() => { + if (!svgRef.current) return; + const svg = d3.select(svgRef.current); + const width = svgRef.current.clientWidth; + const height = svgRef.current.clientHeight; + svg.attr('width', width).attr('height', height); + }, []); + + return ( + + ); +}); + +Visualization.displayName = 'Visualization'; + +export default Visualization; \ No newline at end of file diff --git a/src/components/Visualization3D.tsx b/src/components/Visualization3D.tsx new file mode 100644 index 0000000..fb38a83 --- /dev/null +++ b/src/components/Visualization3D.tsx @@ -0,0 +1,759 @@ +import { useEffect, useRef, useCallback, useImperativeHandle, forwardRef } from 'react'; +import * as THREE from 'three'; +import styled from 'styled-components'; +import { GitHubEvent } from '../hooks/useWebSocket'; + +const Container3D = styled.div` + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + background: #000; +`; + +interface EventSphere { + id: string; + mesh: THREE.Mesh; + createdAt: number; + event: GitHubEvent; + velocity: THREE.Vector3; + startTime: number; +} + +interface ShootingStar { + id: string; + line: THREE.Mesh; + startTime: number; + startPosition: THREE.Vector3; + endPosition: THREE.Vector3; + duration: number; +} + +interface Visualization3DProps { + // No props needed since we're using ref-based drawing +} + +export interface Visualization3DRef { + drawEvent: (event: GitHubEvent) => void; +} + +const Visualization3D = forwardRef((_, ref) => { + const containerRef = useRef(null); + const sceneRef = useRef(null); + const rendererRef = useRef(null); + const cameraRef = useRef(null); + const spheresRef = useRef([]); + const shootingStarsRef = useRef([]); + const animationIdRef = useRef(null); + const mouseRef = useRef({ x: 0, y: 0 }); + const isMouseDownRef = useRef(false); + const cameraControlsRef = useRef({ + phi: 0, + theta: 0, + radius: 100, + targetPhi: 0, + targetTheta: 0, + targetRadius: 100 + }); + + // Function to get event-specific color and settings + const getEventSettings = useCallback((event: GitHubEvent) => { + let color = '#BDC3C7'; + let size = Math.max(Math.sqrt(event.actor.display_login.length) * 0.8, 0.5); + + switch (event.type) { + case 'PushEvent': + color = '#4E71FF'; + break; + case 'PullRequestEvent': + color = '#C6FF00'; + size *= 1.5; + break; + case 'IssuesEvent': + color = '#DFEFCA'; + break; + case 'IssueCommentEvent': + color = '#CCDDD3'; + break; + case 'CreateEvent': + color = '#FF6B6B'; + break; + case 'DeleteEvent': + color = '#FF4757'; + break; + case 'ForkEvent': + color = '#5F27CD'; + break; + case 'WatchEvent': + color = '#00D2D3'; + break; + case 'ReleaseEvent': + color = '#FF9FF3'; + size *= 1.3; + break; + default: + color = '#BDC3C7'; + } + + return { color, size }; + }, []); + + // Function to create a shooting star with trail + const createShootingStar = useCallback(() => { + if (!sceneRef.current) return; + + // Create shooting star closer to the camera (distance 150-250) + const distance = 150 + Math.random() * 100; + const startTheta = Math.random() * Math.PI * 2; + const startPhi = Math.random() * Math.PI; + + const startPosition = new THREE.Vector3( + distance * Math.sin(startPhi) * Math.cos(startTheta), + distance * Math.cos(startPhi), + distance * Math.sin(startPhi) * Math.sin(startTheta) + ); + + // Create end position for the trail (50-100 units away from start) + const trailLength = 50 + Math.random() * 50; + const direction = new THREE.Vector3( + (Math.random() - 0.5) * 2, + (Math.random() - 0.5) * 2, + (Math.random() - 0.5) * 2 + ).normalize(); + + const endPosition = startPosition.clone().add(direction.multiplyScalar(trailLength)); + + // Create a bright head sphere for the shooting star + const headGeometry = new THREE.SphereGeometry(0.5, 8, 8); + const headMaterial = new THREE.MeshBasicMaterial({ + color: 0xFFFFFF, + transparent: true, + opacity: 1.0 + }); + const head = new THREE.Mesh(headGeometry, headMaterial); + head.position.copy(startPosition); + sceneRef.current.add(head); + + // Create trail using multiple small spheres for better visibility + const trailSpheres: THREE.Mesh[] = []; + const numTrailPoints = 15; + + for (let i = 0; i < numTrailPoints; i++) { + const progress = i / (numTrailPoints - 1); + const trailGeometry = new THREE.SphereGeometry(0.2 * (1 - progress * 0.8), 6, 6); + const opacity = (1 - progress) * 0.8; + const trailMaterial = new THREE.MeshBasicMaterial({ + color: 0xFFFFFF, + transparent: true, + opacity: opacity + }); + const trailSphere = new THREE.Mesh(trailGeometry, trailMaterial); + trailSphere.position.copy(startPosition); + sceneRef.current.add(trailSphere); + trailSpheres.push(trailSphere); + } + + const shootingStar: ShootingStar = { + id: `shooting-star-${Date.now()}-${Math.random()}`, + line: head, // Store the head mesh + startTime: Date.now(), + startPosition: startPosition.clone(), + endPosition: endPosition.clone(), + duration: 5000 // 5 seconds + }; + + // Store trail spheres in userData for cleanup + shootingStar.line.userData = { trailSpheres, head }; + + shootingStarsRef.current.push(shootingStar); + }, []); + + // Initialize Three.js scene + useEffect(() => { + if (!containerRef.current) return; + + const container = containerRef.current; + const width = container.clientWidth; + const height = container.clientHeight; + + // Scene + const scene = new THREE.Scene(); + sceneRef.current = scene; + + // Camera + const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000); + camera.position.set(0, 0, 100); + cameraRef.current = camera; + + // Renderer + const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false }); + renderer.setSize(width, height); + renderer.setClearColor(0x000000); + container.appendChild(renderer.domElement); + rendererRef.current = renderer; + + // Create the central sun - realistic fireball + const sunGeometry = new THREE.SphereGeometry(4, 64, 64); + const sunMaterial = new THREE.MeshStandardMaterial({ + color: 0xFF4500, // Deep orange-red core + emissive: 0xFF6600, + emissiveIntensity: 1.2, + roughness: 0.8, + metalness: 0.1 + }); + const sun = new THREE.Mesh(sunGeometry, sunMaterial); + sun.position.set(0, 0, 0); + scene.add(sun); + + // Inner fire layer + const innerFireGeometry = new THREE.SphereGeometry(3.5, 32, 32); + const innerFireMaterial = new THREE.MeshStandardMaterial({ + color: 0xFFFF00, // Bright yellow inner fire + emissive: 0xFFAA00, + emissiveIntensity: 1.5, + transparent: true, + opacity: 0.8, + roughness: 0.9 + }); + const innerFire = new THREE.Mesh(innerFireGeometry, innerFireMaterial); + innerFire.position.set(0, 0, 0); + scene.add(innerFire); + + // Outer fire corona + const coronaGeometry = new THREE.SphereGeometry(5.5, 32, 32); + const coronaMaterial = new THREE.MeshStandardMaterial({ + color: 0xFF8C00, // Dark orange corona + emissive: 0xFF4500, + emissiveIntensity: 0.8, + transparent: true, + opacity: 0.4, + roughness: 1.0, + side: THREE.DoubleSide + }); + const corona = new THREE.Mesh(coronaGeometry, coronaMaterial); + corona.position.set(0, 0, 0); + scene.add(corona); + + // Plasma flares (outer wispy layer) + const flareGeometry = new THREE.SphereGeometry(6.5, 16, 16); + const flareMaterial = new THREE.MeshStandardMaterial({ + color: 0xFF6600, + emissive: 0xFF3300, + emissiveIntensity: 0.5, + transparent: true, + opacity: 0.2, + roughness: 1.0, + side: THREE.DoubleSide + }); + const flares = new THREE.Mesh(flareGeometry, flareMaterial); + flares.position.set(0, 0, 0); + scene.add(flares); + + // Lighting + const ambientLight = new THREE.AmbientLight(0xffffff, 0.3); + scene.add(ambientLight); + + const pointLight1 = new THREE.PointLight(0xffffff, 1, 0); + pointLight1.position.set(10, 10, 10); + scene.add(pointLight1); + + const pointLight2 = new THREE.PointLight(0xffffff, 0.5, 0); + pointLight2.position.set(-10, -10, -10); + scene.add(pointLight2); + + // Central sun light with fire colors + const sunLight = new THREE.PointLight(0xFF6600, 3, 0); + sunLight.position.set(0, 0, 0); + scene.add(sunLight); + + // Create realistic starfield like the solar system project + const createStarfield = () => { + const starGeometry = new THREE.BufferGeometry(); + const starCount = 4000; + + const positions = new Float32Array(starCount * 3); + const colors = new Float32Array(starCount * 3); + const originalColors = new Float32Array(starCount * 3); // Store original colors + const sizes = new Float32Array(starCount); + const originalSizes = new Float32Array(starCount); // Store original sizes for twinkling + const twinkleFrequencies = new Float32Array(starCount); // Individual twinkling speeds + const twinklePhases = new Float32Array(starCount); // Phase offsets for variety + const twinkleIntensities = new Float32Array(starCount); // How much each star twinkles + + // Create star colors and positions + for (let i = 0; i < starCount; i++) { + // Random spherical distribution + const radius = 400 + Math.random() * 200; + const theta = Math.random() * Math.PI * 2; + const phi = Math.acos(2 * Math.random() - 1); + + positions[i * 3] = radius * Math.sin(phi) * Math.cos(theta); + positions[i * 3 + 1] = radius * Math.sin(phi) * Math.sin(theta); + positions[i * 3 + 2] = radius * Math.cos(phi); + + // Varied star colors (white, blue-white, yellow-white, red) + const starType = Math.random(); + let r, g, b; + if (starType < 0.7) { + // White stars (most common) + r = g = b = 1; + } else if (starType < 0.85) { + // Blue-white stars + r = 0.8; g = 0.9; b = 1; + } else if (starType < 0.95) { + // Yellow-white stars + r = 1; g = 1; b = 0.8; + } else { + // Red stars + r = 1; g = 0.7; b = 0.6; + } + + colors[i * 3] = r; + colors[i * 3 + 1] = g; + colors[i * 3 + 2] = b; + + // Store original colors + originalColors[i * 3] = r; + originalColors[i * 3 + 1] = g; + originalColors[i * 3 + 2] = b; + + // Varied star sizes with realistic distribution + const brightness = Math.random(); + let starSize; + if (brightness < 0.8) { + starSize = 0.5 + Math.random() * 1; // Small stars + } else if (brightness < 0.95) { + starSize = 1.5 + Math.random() * 2; // Medium stars + } else { + starSize = 3 + Math.random() * 3; // Bright stars + } + + sizes[i] = starSize; + originalSizes[i] = starSize; // Store original size + + // Twinkling properties for more realistic effect + twinkleFrequencies[i] = 0.5 + Math.random() * 2; // Varied twinkling speeds (0.5-2.5) + twinklePhases[i] = Math.random() * Math.PI * 2; // Random phase offset + + // Larger stars twinkle more noticeably + if (starSize > 3) { + twinkleIntensities[i] = 0.4 + Math.random() * 0.4; // Bright stars twinkle more (0.4-0.8) + } else if (starSize > 1.5) { + twinkleIntensities[i] = 0.2 + Math.random() * 0.3; // Medium stars (0.2-0.5) + } else { + twinkleIntensities[i] = 0.1 + Math.random() * 0.2; // Small stars twinkle less (0.1-0.3) + } + } + + starGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); + starGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); + starGeometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1)); + + // Create star material with proper blending + const starMaterial = new THREE.PointsMaterial({ + size: 2, + sizeAttenuation: true, + vertexColors: true, + transparent: true, + opacity: 0.9, + blending: THREE.AdditiveBlending, + depthWrite: false + }); + + const starField = new THREE.Points(starGeometry, starMaterial); + starField.userData = { + originalSizes, + originalColors, + twinkleFrequencies, + twinklePhases, + twinkleIntensities + }; // Store twinkling data + return starField; + }; + + const stars = createStarfield(); + scene.add(stars); + + // Mouse controls + const handleMouseDown = (event: MouseEvent) => { + isMouseDownRef.current = true; + mouseRef.current.x = event.clientX; + mouseRef.current.y = event.clientY; + }; + + const handleMouseMove = (event: MouseEvent) => { + if (!isMouseDownRef.current) return; + + const deltaX = event.clientX - mouseRef.current.x; + const deltaY = event.clientY - mouseRef.current.y; + + cameraControlsRef.current.targetTheta -= deltaX * 0.01; + cameraControlsRef.current.targetPhi += deltaY * 0.01; + cameraControlsRef.current.targetPhi = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, cameraControlsRef.current.targetPhi)); + + mouseRef.current.x = event.clientX; + mouseRef.current.y = event.clientY; + }; + + const handleMouseUp = () => { + isMouseDownRef.current = false; + }; + + const handleWheel = (event: WheelEvent) => { + event.preventDefault(); + cameraControlsRef.current.targetRadius += event.deltaY * 0.1; + cameraControlsRef.current.targetRadius = Math.max(20, Math.min(300, cameraControlsRef.current.targetRadius)); + }; + + const handleClick = (event: MouseEvent) => { + const rect = renderer.domElement.getBoundingClientRect(); + const mouse = new THREE.Vector2(); + mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; + mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; + + const raycaster = new THREE.Raycaster(); + raycaster.setFromCamera(mouse, camera); + + const sphereMeshes = spheresRef.current.map(sphere => sphere.mesh); + const intersects = raycaster.intersectObjects(sphereMeshes); + + if (intersects.length > 0) { + const clickedSphere = spheresRef.current.find(sphere => sphere.mesh === intersects[0].object); + if (clickedSphere && clickedSphere.event.event_url) { + window.open(clickedSphere.event.event_url, '_blank'); + } + } + }; + + renderer.domElement.addEventListener('mousedown', handleMouseDown); + renderer.domElement.addEventListener('mousemove', handleMouseMove); + renderer.domElement.addEventListener('mouseup', handleMouseUp); + renderer.domElement.addEventListener('wheel', handleWheel); + renderer.domElement.addEventListener('click', handleClick); + + // Animation loop + const animate = () => { + const now = Date.now(); + + // Update camera controls + const controls = cameraControlsRef.current; + controls.theta += (controls.targetTheta - controls.theta) * 0.1; + controls.phi += (controls.targetPhi - controls.phi) * 0.1; + controls.radius += (controls.targetRadius - controls.radius) * 0.1; + + camera.position.x = controls.radius * Math.sin(controls.theta) * Math.cos(controls.phi); + camera.position.y = controls.radius * Math.sin(controls.phi); + camera.position.z = controls.radius * Math.cos(controls.theta) * Math.cos(controls.phi); + camera.lookAt(0, 0, 0); + + // Animate sun with realistic fire effects + sun.rotation.y += 0.005; + sun.rotation.x += 0.002; + + // Animate fire layers with different speeds for realistic effect + innerFire.rotation.y -= 0.008; + innerFire.rotation.z += 0.003; + + corona.rotation.y += 0.004; + corona.rotation.x -= 0.002; + + flares.rotation.y -= 0.006; + flares.rotation.z += 0.004; + + // Dynamic fire intensity pulsing + const fireIntensity = 1 + Math.sin(now * 0.003) * 0.3; + const coronaIntensity = 1 + Math.sin(now * 0.004 + 1) * 0.2; + const flareIntensity = 1 + Math.sin(now * 0.002 + 2) * 0.4; + + innerFire.material.emissiveIntensity = 1.5 * fireIntensity; + corona.material.emissiveIntensity = 0.8 * coronaIntensity; + flares.material.emissiveIntensity = 0.5 * flareIntensity; + + // Update spheres with gravitational pull + spheresRef.current = spheresRef.current.filter(sphere => { + const elapsed = now - sphere.startTime; + const maxLife = 60000; + const progress = elapsed / maxLife; + + if (progress >= 1) { + scene.remove(sphere.mesh); + sphere.mesh.geometry.dispose(); + (sphere.mesh.material as THREE.Material).dispose(); + return false; + } + + // Calculate gravitational pull towards the sun (center) + const sunPosition = new THREE.Vector3(0, 0, 0); + const spherePosition = sphere.mesh.position.clone(); + const directionToSun = sunPosition.sub(spherePosition).normalize(); + + // Increase pull strength over time (ease-in effect) + const pullStrength = progress * progress * 0.3; // Quadratic ease-in + const pullForce = directionToSun.multiplyScalar(pullStrength); + + // Apply both original velocity and gravitational pull + const dampedVelocity = sphere.velocity.clone().multiplyScalar(1 - progress * 0.8); + sphere.mesh.position.add(dampedVelocity.add(pullForce)); + + // Rotation + sphere.mesh.rotation.x += 0.01; + sphere.mesh.rotation.y += 0.01; + + // Scale down as it approaches the sun + const scaleProgress = Math.max(0.1, 1 - progress * 0.7); + sphere.mesh.scale.setScalar(scaleProgress); + + // Maintain opacity until very close to the sun + const opacityProgress = Math.max(0, 1 - Math.pow(progress, 3)); + (sphere.mesh.material as THREE.MeshStandardMaterial).opacity = 1 * opacityProgress; + + return true; + }); + + // Animate stars with enhanced twinkling and rotation + stars.rotation.y += 0.0002; + stars.rotation.x += 0.0001; + + // Enhanced star twinkling effect + const starSizes = stars.geometry.attributes.size.array; + const starColors = stars.geometry.attributes.color.array; + const { originalSizes, originalColors, twinkleFrequencies, twinklePhases, twinkleIntensities } = stars.userData; + const time = now * 0.001; + + for (let i = 0; i < starSizes.length; i++) { + const baseSize = originalSizes[i]; + const frequency = twinkleFrequencies[i]; + const phase = twinklePhases[i]; + const intensity = twinkleIntensities[i]; + + // Multi-layered twinkling with different wave patterns + const primaryTwinkle = Math.sin(time * frequency + phase); + const secondaryTwinkle = Math.sin(time * frequency * 1.7 + phase * 0.3) * 0.5; + const tertiaryTwinkle = Math.sin(time * frequency * 0.3 + phase * 1.8) * 0.3; + + // Combine waves for complex twinkling pattern + const combinedTwinkle = (primaryTwinkle + secondaryTwinkle + tertiaryTwinkle) / 1.8; + + // Apply size variation + const sizeMultiplier = 1 + combinedTwinkle * intensity; + starSizes[i] = baseSize * Math.max(0.3, sizeMultiplier); + + // Add subtle color intensity variation for brighter stars + if (baseSize > 2) { + const colorIntensity = 1 + combinedTwinkle * intensity * 0.3; + const colorIndex = i * 3; + + // Get original color values + const originalR = originalColors[colorIndex]; + const originalG = originalColors[colorIndex + 1]; + const originalB = originalColors[colorIndex + 2]; + + // Apply intensity variation while preserving color ratios + starColors[colorIndex] = Math.min(1, originalR * colorIntensity); + starColors[colorIndex + 1] = Math.min(1, originalG * colorIntensity); + starColors[colorIndex + 2] = Math.min(1, originalB * colorIntensity); + } + } + + stars.geometry.attributes.size.needsUpdate = true; + stars.geometry.attributes.color.needsUpdate = true; + + // Randomly create shooting stars (about every 3-8 seconds) + if (Math.random() < 0.002) { // Increased probability for testing (roughly every 8 seconds) + createShootingStar(); + } + + // Update and cleanup shooting stars + shootingStarsRef.current = shootingStarsRef.current.filter(star => { + const elapsed = now - star.startTime; + const progress = elapsed / star.duration; + + if (progress >= 1) { + // Remove completed shooting star + const { trailSpheres, head } = star.line.userData; + scene.remove(head); + head.geometry.dispose(); + head.material.dispose(); + + trailSpheres.forEach((sphere: THREE.Mesh) => { + if (sceneRef.current) { + sceneRef.current.remove(sphere); + } + sphere.geometry.dispose(); + (sphere.material as THREE.Material).dispose(); + }); + return false; + } + + // Animate shooting star movement + const currentPosition = star.startPosition.clone().lerp(star.endPosition, progress); + const { trailSpheres, head } = star.line.userData; + + // Update head position + head.position.copy(currentPosition); + + // Update head opacity + const fadeProgress = Math.min(1, progress * 1.5); + const headOpacity = Math.max(0, 1 - fadeProgress); + (head.material as THREE.MeshBasicMaterial).opacity = headOpacity; + + // Update trail spheres with staggered positions + trailSpheres.forEach((sphere: THREE.Mesh, index: number) => { + const trailProgress = Math.max(0, progress - (index * 0.05)); // Staggered trail + const trailPosition = star.startPosition.clone().lerp(star.endPosition, trailProgress); + sphere.position.copy(trailPosition); + + // Update trail opacity + const baseOpacity = (1 - index / trailSpheres.length) * 0.8; + const trailFade = Math.max(0, 1 - progress * 1.2); + (sphere.material as THREE.MeshBasicMaterial).opacity = baseOpacity * trailFade; + }); + + return true; + }); + + renderer.render(scene, camera); + animationIdRef.current = requestAnimationFrame(animate); + }; + + animate(); + + // Handle resize + const handleResize = () => { + const newWidth = container.clientWidth; + const newHeight = container.clientHeight; + + camera.aspect = newWidth / newHeight; + camera.updateProjectionMatrix(); + renderer.setSize(newWidth, newHeight); + }; + + window.addEventListener('resize', handleResize); + + return () => { + if (animationIdRef.current) { + cancelAnimationFrame(animationIdRef.current); + } + + // Cleanup shooting stars + shootingStarsRef.current.forEach(star => { + if (sceneRef.current) { + const { trailSpheres, head } = star.line.userData; + sceneRef.current.remove(head); + head.geometry.dispose(); + head.material.dispose(); + + trailSpheres.forEach((sphere: THREE.Mesh) => { + if (sceneRef.current) { + sceneRef.current.remove(sphere); + } + sphere.geometry.dispose(); + (sphere.material as THREE.Material).dispose(); + }); + } + }); + shootingStarsRef.current = []; + + renderer.domElement.removeEventListener('mousedown', handleMouseDown); + renderer.domElement.removeEventListener('mousemove', handleMouseMove); + renderer.domElement.removeEventListener('mouseup', handleMouseUp); + renderer.domElement.removeEventListener('wheel', handleWheel); + renderer.domElement.removeEventListener('click', handleClick); + window.removeEventListener('resize', handleResize); + + container.removeChild(renderer.domElement); + renderer.dispose(); + }; + }, []); + + // Function to draw a single event as a 3D sphere + const drawEvent = useCallback((event: GitHubEvent) => { + if (!sceneRef.current) return; + + const { color, size } = getEventSettings(event); + + // Generate seeded random position + const hashCode = (str: string) => { + let hash = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; + } + return Math.abs(hash); + }; + + const seed = hashCode(event.event_url || event.id); + const seededRandom1 = (seed % 10000) / 10000; + const seededRandom2 = ((seed * 9301 + 49297) % 233280) / 233280; + const seededRandom3 = ((seed * 1103515245 + 12345) % 2147483647) / 2147483647; + + // Create sphere geometry and material + const geometry = new THREE.SphereGeometry(size, 32, 32); + const material = new THREE.MeshStandardMaterial({ + color: new THREE.Color(color), + transparent: true, + opacity: 1, + emissive: new THREE.Color(color), + emissiveIntensity: 0.5 + }); + + const mesh = new THREE.Mesh(geometry, material); + + // Position spheres at the edge of the visible screen area + // Use a distance range (80-120 units) to start them at screen edge + const distance = 80 + seededRandom1 * 40; // Distance between 80-120 units + const theta = seededRandom2 * Math.PI * 2; // Random angle around Y axis + const phi = seededRandom3 * Math.PI; // Random angle for vertical distribution + + mesh.position.set( + distance * Math.sin(phi) * Math.cos(theta), + distance * Math.cos(phi), + distance * Math.sin(phi) * Math.sin(theta) + ); + + // Random velocity for gentle movement (reduced since they start farther) + const velocity = new THREE.Vector3( + (Math.random() - 0.5) * 0.005, + (Math.random() - 0.5) * 0.004, + (Math.random() - 0.5) * 0.003 + ); + + sceneRef.current.add(mesh); + + const newSphere: EventSphere = { + id: event.event_url || `${event.id}-${Date.now()}`, + mesh, + createdAt: Date.now(), + event, + velocity, + startTime: Date.now() + }; + + spheresRef.current.push(newSphere); + + // Clean up old spheres (keep last 100) + if (spheresRef.current.length > 100) { + const oldSpheres = spheresRef.current.splice(0, spheresRef.current.length - 100); + oldSpheres.forEach(sphere => { + if (sceneRef.current) { + sceneRef.current.remove(sphere.mesh); + sphere.mesh.geometry.dispose(); + (sphere.mesh.material as THREE.Material).dispose(); + } + }); + } + }, [getEventSettings]); + + // Expose drawEvent function to parent component + useImperativeHandle(ref, () => ({ + drawEvent + }), [drawEvent]); + + return ; +}); + +Visualization3D.displayName = 'Visualization3D'; + +export default Visualization3D; \ No newline at end of file diff --git a/src/components/VisualizationToggle.tsx b/src/components/VisualizationToggle.tsx new file mode 100644 index 0000000..c62490b --- /dev/null +++ b/src/components/VisualizationToggle.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import styled from 'styled-components'; + +const ToggleContainer = styled.div` + position: fixed; + bottom: 20px; + right: 20px; + z-index: 1000; + background: rgba(0, 0, 0, 0.8); + border-radius: 25px; + padding: 8px; + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.1); +`; + +const ToggleButton = styled.button<{ $isActive: boolean }>` + background: ${props => props.$isActive ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' : 'transparent'}; + color: ${props => props.$isActive ? '#fff' : '#ccc'}; + border: none; + padding: 10px 16px; + margin: 0 2px; + border-radius: 18px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + font-family: 'Inter', sans-serif; + transition: all 0.3s ease; + + &:hover { + background: ${props => props.$isActive + ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' + : 'rgba(255, 255, 255, 0.1)'}; + color: #fff; + transform: translateY(-1px); + } + + &:active { + transform: translateY(0); + } +`; + +interface VisualizationToggleProps { + is3D: boolean; + onToggle: (is3D: boolean) => void; +} + +const VisualizationToggle: React.FC = ({ is3D, onToggle }) => { + return ( + + onToggle(false)} + title="Switch to 2D visualization" + > + 2D + + onToggle(true)} + title="Switch to 3D visualization" + > + 3D + + + ); +}; + +export default VisualizationToggle; \ No newline at end of file diff --git a/src/hooks/useAudio.ts b/src/hooks/useAudio.ts new file mode 100644 index 0000000..d1600f8 --- /dev/null +++ b/src/hooks/useAudio.ts @@ -0,0 +1,150 @@ +import { useRef, useCallback, useEffect, useState } from 'react'; +import { Howl, Howler } from 'howler'; + +interface UseAudioReturn { + allSoundsLoaded: boolean; + playRandomSwell: () => void; + playSound: (size: number, type: string) => void; + setVolume: (volume: number) => void; +} + +export const useAudio = (): UseAudioReturn => { + const [allSoundsLoaded, setAllSoundsLoaded] = useState(false); + const celestaRef = useRef([]); + const clavRef = useRef([]); + const swellsRef = useRef([]); + + // Throttling variables to match original + const currentNotesRef = useRef(0); + const noteOverlap = 2; + const noteTimeout = 300; + + useEffect(() => { + const loadSounds = () => { + const celesta: Howl[] = []; + const clav: Howl[] = []; + const swells: Howl[] = []; + + let loadedSounds = 0; + const totalSounds = 51; // 24 celesta + 24 clav + 3 swells + + const onSoundLoad = () => { + loadedSounds++; + if (loadedSounds === totalSounds) { + setAllSoundsLoaded(true); + } + }; + + // Load celesta and clav sounds + for (let i = 1; i <= 24; i++) { + const fn = i > 9 ? `c0${i}` : `c00${i}`; + + celesta.push(new Howl({ + src: [ + `https://d1fz9d31zqor6x.cloudfront.net/sounds/celesta/${fn}.ogg`, + `https://d1fz9d31zqor6x.cloudfront.net/sounds/celesta/${fn}.mp3` + ], + volume: 0.7, + onload: onSoundLoad, + onloaderror: (_id, error) => console.error(`Failed to load celesta/${fn}:`, error), + })); + + clav.push(new Howl({ + src: [ + `https://d1fz9d31zqor6x.cloudfront.net/sounds/clav/${fn}.ogg`, + `https://d1fz9d31zqor6x.cloudfront.net/sounds/clav/${fn}.mp3` + ], + volume: 0.4, + onload: onSoundLoad, + onloaderror: (_id, error) => console.error(`Failed to load clav/${fn}:`, error), + })); + } + + // Load swell sounds + for (let i = 1; i <= 3; i++) { + swells.push(new Howl({ + src: [ + `https://d1fz9d31zqor6x.cloudfront.net/sounds/swells/swell${i}.ogg`, + `https://d1fz9d31zqor6x.cloudfront.net/sounds/swells/swell${i}.mp3` + ], + volume: 1, + onload: onSoundLoad, + onloaderror: (_id, error) => console.error(`Failed to load swells/swell${i}:`, error), + })); + } + + celestaRef.current = celesta; + clavRef.current = clav; + swellsRef.current = swells; + }; + + loadSounds(); + }, []); + + const playRandomSwell = useCallback(() => { + if (swellsRef.current.length > 0) { + const index = Math.round(Math.random() * (swellsRef.current.length - 1)); + swellsRef.current[index].play(); + } else { + console.error('No swell sounds available'); + } + }, []); + + const playSound = useCallback((size: number, type: string) => { + if (!allSoundsLoaded) { + return; + } + + // Exact original logic + const maxPitch = 100.0; + const logUsed = 1.0715307808111486871978099; + const pitch = 100 - Math.min(maxPitch, Math.log(size + logUsed) / Math.log(logUsed)); + let index = Math.floor((pitch / 100.0) * Object.keys(celestaRef.current).length); + + // Add random fuzz for musicality + const fuzz = Math.floor(Math.random() * 4) - 2; + index += fuzz; + + // Clamp index to valid range + index = Math.min(Object.keys(celestaRef.current).length - 1, index); + index = Math.max(1, index); + + // Throttling - only play if we haven't exceeded max concurrent notes + if (currentNotesRef.current < noteOverlap) { + currentNotesRef.current++; + + // Sound selection logic matching original + if (type === 'IssuesEvent' || type === 'IssueCommentEvent') { + if (clavRef.current[index]) { + clavRef.current[index].play(); + } else { + console.error('Clav sound not found at index:', index); + } + } else if (type === 'PushEvent') { + if (celestaRef.current[index]) { + celestaRef.current[index].play(); + } else { + console.error('Celesta sound not found at index:', index); + } + } else { + playRandomSwell(); + } + + // Reset note count after timeout + setTimeout(() => { + currentNotesRef.current--; + }, noteTimeout); + } + }, [allSoundsLoaded, playRandomSwell]); + + const setVolume = useCallback((volume: number) => { + Howler.volume(volume); + }, []); + + return { + allSoundsLoaded, + playRandomSwell, + playSound, + setVolume, + }; +}; diff --git a/src/hooks/useWebSocket.ts b/src/hooks/useWebSocket.ts new file mode 100644 index 0000000..2b6ddb0 --- /dev/null +++ b/src/hooks/useWebSocket.ts @@ -0,0 +1,105 @@ +import { useEffect, useRef, useState, useCallback } from 'react'; + +export interface GitHubEvent { + id: string; + type: string; + action?: string; + repo: { + name: string; + }; + actor: { + login: string; + display_login: string; + }; + payload?: any; + created_at: string; + event_url?: string; +} + +interface UseWebSocketReturn { + isOnline: boolean; + eventQueue: GitHubEvent[]; + clearEventQueue: () => void; +} + +export const useWebSocket = ( + url: string, + orgRepoFilter: string[] +): UseWebSocketReturn => { + const [isOnline, setIsOnline] = useState(false); + const [eventQueue, setEventQueue] = useState([]); + const wsRef = useRef(null); + + const clearEventQueue = useCallback(() => { + setEventQueue([]); + }, []); + + useEffect(() => { + const connectWebSocket = () => { + const ws = new WebSocket(url); + + ws.addEventListener('open', () => { + setIsOnline(true); + console.log('WebSocket connected'); + }); + + ws.addEventListener('close', () => { + setIsOnline(false); + console.log('WebSocket disconnected'); + }); + + ws.addEventListener('error', (_error) => { + setIsOnline(false); + // Suppress error logging since server is always available and this is likely a dev environment issue + console.log('WebSocket connection attempt failed, retrying...'); + }); + + ws.addEventListener('message', (event) => { + try { + const events = JSON.parse(event.data); + if (Array.isArray(events)) { + setEventQueue(prev => { + let filteredEvents = events; + + // Apply organization/repository filter + if (orgRepoFilter.length > 0 && orgRepoFilter[0] !== '') { + const filterRegex = new RegExp(orgRepoFilter.join('|'), 'i'); + filteredEvents = events.filter((event: GitHubEvent) => + filterRegex.test(event.repo.name) && + !event.repo.name.includes('github.io') + ); + } + + const newQueue = [...prev, ...filteredEvents]; + // Keep only last 128 events + return newQueue.slice(-128); + }); + } + } catch (error) { + console.error('Error parsing WebSocket message:', error); + } + }); + + wsRef.current = ws; + }; + + connectWebSocket(); + + return () => { + if (wsRef.current) { + wsRef.current.close(); + } + }; + }, [url]); + + // Clear queue when filter changes + useEffect(() => { + setEventQueue([]); + }, [orgRepoFilter]); + + return { + isOnline, + eventQueue, + clearEventQueue, + }; +}; \ No newline at end of file diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..fe6bfcc --- /dev/null +++ b/src/index.css @@ -0,0 +1,39 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body { + margin: 0; + padding: 0; + font-family: 'Inter', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + margin: 0; + font-family: 'Inter', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} + +#root { + margin: 0; + padding: 0; +} + +a { + text-decoration: none; + color: #0091EA; +} + +a:hover { + text-decoration: underline; +} \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..10ece32 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8883f8d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} \ No newline at end of file diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..a99b0fa --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true, + "noEmit": true + }, + "include": ["vite.config.ts"] +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..d200ccc --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,61 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], + build: { + // Enable gzip compression + rollupOptions: { + output: { + manualChunks: { + vendor: ['react', 'react-dom'], + styled: ['styled-components'] + } + } + }, + // Optimize chunk size + chunkSizeWarningLimit: 1000, + // Enable source maps for production debugging + sourcemap: false, + // Use Terser for better compression + minify: 'terser', + terserOptions: { + compress: { + // Drop console logs in production + drop_console: true, + drop_debugger: true, + // Additional optimizations + pure_funcs: ['console.log', 'console.info', 'console.debug', 'console.warn'], + // Remove unused code + dead_code: true, + // Optimize conditionals + conditionals: true, + // Optimize comparisons + comparisons: true, + // Optimize sequences + sequences: true, + // Optimize booleans + booleans: true + }, + mangle: { + // Mangle variable names for smaller size + safari10: true + }, + format: { + // Remove comments + comments: false + } + } + }, + server: { + // Add cache headers for development + headers: { + 'Cache-Control': 'public, max-age=31536000' + } + }, + // Optimize dependencies + optimizeDeps: { + include: ['react', 'react-dom', 'styled-components'] + } +})