Compare commits
13 Commits
86e145515e
...
main
Author | SHA1 | Date | |
---|---|---|---|
4ccbd28c4f | |||
618e5b442e | |||
936a2fa1ee | |||
2165a63bfb | |||
2d294de69a | |||
7754370a70 | |||
2edf6b3f1f | |||
8b0996781e | |||
bb714e6fa2 | |||
75f516b408 | |||
8403a17636 | |||
44353988a1 | |||
1d3d684c8f |
311
README.md
311
README.md
@@ -1,92 +1,269 @@
|
||||
<img src="https://r2cdn.perplexity.ai/pplx-full-logo-primary-dark%402x.png" class="logo" width="120"/>
|
||||
# REMind - Dream Analysis & VR Experience Platform
|
||||
|
||||
# REMind (Mockup Demo)
|
||||

|
||||

|
||||

|
||||
|
||||
🚧 **This is a MOCKUP / DEMO APPLICATION for a university project – no real backend, AI features, or data storage.** 🚧
|
||||
> **🚧 IMPORTANT: This is a MOCKUP/DEMO APPLICATION for a university project**
|
||||
> **No real backend, AI features, or data storage. All functionality is simulated for demonstration purposes.**
|
||||
|
||||
REMind is an interactive, front-end prototype that showcases dream journaling, AI-powered analysis (simulated), and immersive VR demonstrations. Built with React, TypeScript, Vite, and Tailwind CSS, it’s designed for demos and concept validation only.
|
||||
## Project Overview
|
||||
|
||||
## Features (all mockup/demo only)
|
||||
REMind is an innovative interactive prototype that explores the intersection of dream analysis, artificial intelligence,
|
||||
and virtual reality. The application demonstrates how modern technology could revolutionize dream journaling and
|
||||
interpretation through immersive experiences and intelligent analysis.
|
||||
|
||||
- **(Mockup)** Dream Journal
|
||||
Record dreams as text entries, voice memos, or sketches—UI only, no persistence.
|
||||
- **(Mockup)** AI Analysis
|
||||
Simulated “Here the AI would analyze your dream…” step with instant, hard-coded interpretations, symbol recognition, and emotion tagging.
|
||||
- **(Mockup)** Personal Archive
|
||||
Browse and filter past mock entries by date, symbols, emotions, or tags.
|
||||
- **(Mockup)** Statistics \& Trends
|
||||
Visualize frequent symbols, emotional patterns, and simulated sleep-cycle correlations using dummy data.
|
||||
- **(Mockup)** VR Dreamscapes
|
||||
Enter 3D scenes built with React Three Fiber—explore thematic environments (labyrinths, dark rooms) in browser-based VR mode.
|
||||
- **Responsive Design**
|
||||
Works on desktop browsers(design optimized for mobile) and mobile devices. Ideal for QR-code launches at demos.
|
||||
**Project Aim:** To showcase the potential of combining dream journaling with AI-powered analysis and VR visualization,
|
||||
creating an engaging platform for understanding the subconscious mind and exploring the societal implications of dream
|
||||
research.
|
||||
|
||||
## Tech Stack
|
||||
## ✨ Key Features
|
||||
|
||||
- **Framework:** React + TypeScript
|
||||
- **Bundler:** Vite
|
||||
- **Styling:** Tailwind CSS
|
||||
- **Routing:** React Router Dom
|
||||
- **Icons:** React Icons
|
||||
- **3D \& VR:** @react-three/fiber
|
||||
- **Linting:** ESLint
|
||||
### 🌙 Multi-Modal Dream Recording
|
||||
|
||||
## Getting Started
|
||||
- **Text Input**: Write detailed dream descriptions with rich formatting
|
||||
- **Voice Recording**: Record dream narratives with audio transcription
|
||||
- **Image Upload**: Upload dream sketches, drawings, or related images
|
||||
- **Biometric Integration**: Simulate EEG, heart rate, and movement data from sleep tracking devices
|
||||
|
||||
> **Note:** This repository contains only a front-end prototype with hard-coded mock data. There is no production backend or AI service.
|
||||
### 🤖 AI-Powered Analysis (Simulated)
|
||||
|
||||
1. Clone the repo
|
||||
- **Symbol Recognition**: Identify and categorize dream symbols
|
||||
- **Emotion Detection**: Analyze emotional patterns and mood indicators
|
||||
- **Interpretation Engine**: Generate personalized dream interpretations
|
||||
- **Pattern Analysis**: Detect recurring themes and symbols across dreams
|
||||
|
||||
```bash
|
||||
git clone https://gitea.puchstein.bayern/mpuchstein/REMind.git
|
||||
cd REMind
|
||||
```
|
||||
### 🎮 VR Dreamscapes
|
||||
|
||||
2. Install dependencies
|
||||
- **3D Visualization**: Transform dreams into immersive 3D environments
|
||||
- **Neural Network Visualization**: Interactive neural network representations for biometric data
|
||||
- **VR Experience**: WebXR-enabled virtual reality exploration
|
||||
- **Responsive Controls**: Support for both VR headsets and desktop interaction
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
### 📊 Advanced Analytics
|
||||
|
||||
3. Run development server
|
||||
- **EEG Visualization**: Real-time brainwave pattern charts (Alpha, Beta, Theta, Delta)
|
||||
- **Vital Signs Monitoring**: Heart rate and HRV trend analysis
|
||||
- **Movement Tracking**: Sleep movement pattern visualization
|
||||
- **Statistical Dashboard**: Comprehensive dream pattern analysis
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
### 🗂️ Dream Archive & Management
|
||||
|
||||
Open `http://localhost:5173` in your browser. 4. Build for production (static demo)
|
||||
- **Personal Library**: Organized collection of all dream entries
|
||||
- **Search & Filter**: Advanced filtering by date, symbols, emotions, and tags
|
||||
- **Social Features**: Share dreams with friends and community
|
||||
- **Daily Highlights**: Curated dream insights and patterns
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
npm run preview
|
||||
```
|
||||
### 🌐 Community Features
|
||||
|
||||
## Project Structure
|
||||
- **Dream Archive**: Explore categorized dream collections
|
||||
- **Research Integration**: Live research data visualization
|
||||
- **Cultural Landscapes**: Dreams categorized by cultural themes
|
||||
- **Worldwide Events**: Global dream pattern analysis
|
||||
|
||||
## 🛠️ Technology Stack
|
||||
|
||||
### Frontend Framework
|
||||
|
||||
- **React 19.1.0** - Modern React with latest features
|
||||
- **TypeScript** - Type-safe development
|
||||
- **Vite 7.0.0** - Fast build tool and development server
|
||||
|
||||
### Styling & UI
|
||||
|
||||
- **Tailwind CSS 4.1.11** - Utility-first CSS framework
|
||||
- **CSS Variables** - Dynamic theming system
|
||||
- **Responsive Design** - Mobile-first approach
|
||||
|
||||
### 3D & VR
|
||||
|
||||
- **Three.js 0.178.0** - 3D graphics library
|
||||
- **@react-three/fiber 9.2.0** - React Three.js renderer
|
||||
- **@react-three/drei 10.5.1** - Three.js helpers
|
||||
- **@react-three/xr 6.6.19** - WebXR integration
|
||||
- **webxr-polyfill 2.0.3** - VR compatibility
|
||||
|
||||
### Data Visualization
|
||||
|
||||
- **D3.js 7.9.0** - Advanced data visualization
|
||||
- **Custom Chart Components** - EEG, vitals, and movement charts
|
||||
|
||||
### Navigation & Routing
|
||||
|
||||
- **React Router DOM 7.6.3** - Client-side routing
|
||||
- **React Icons 5.5.0** - Comprehensive icon library
|
||||
|
||||
### Development Tools
|
||||
|
||||
- **ESLint** - Code linting and formatting
|
||||
- **TypeScript ESLint** - TypeScript-specific linting
|
||||
- **Vite Plugin React** - React integration
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
.
|
||||
├── public/ # Static assets
|
||||
REMind/
|
||||
├── public/ # Static assets (favicon, etc.)
|
||||
├── src/
|
||||
│ ├── assets/ # Images, fonts
|
||||
│ ├── components/ # Reusable React components
|
||||
│ ├── data/ # Mock data (dream entries, analysis)
|
||||
│ ├── pages/ # Route-driven pages
|
||||
│ ├── types/ # TypeScript types/interfaces
|
||||
│ ├── App.tsx # Root component
|
||||
│ ├── index.css # Tailwind setup
|
||||
│ └── main.tsx # Entry point
|
||||
├── index.html # HTML template
|
||||
├── package.json # Scripts & dependencies
|
||||
├── tsconfig.app.json # TS config for app
|
||||
├── vite.config.ts # Vite config
|
||||
└── README.md # This file
|
||||
│ ├── assets/ # Images, logos, fonts
|
||||
│ ├── components/ # Reusable UI components
|
||||
│ │ ├── DreamCard.tsx # Dream display component
|
||||
│ │ ├── DreamRecord.tsx # Dream recording interface
|
||||
│ │ ├── DreamVR.tsx # VR visualization component
|
||||
│ │ ├── DreamCharts.tsx # Data visualization components
|
||||
│ │ ├── Navbar.tsx # Navigation component
|
||||
│ │ └── ...
|
||||
│ ├── data/ # Mock data for demonstration
|
||||
│ │ ├── MockDreams.ts # Sample dream entries
|
||||
│ │ ├── MockUsers.ts # User data
|
||||
│ │ └── ...
|
||||
│ ├── pages/ # Route-driven page components
|
||||
│ │ ├── Home.tsx # Main dashboard
|
||||
│ │ ├── Feed.tsx # Dream feed
|
||||
│ │ ├── DreamPage.tsx # Individual dream view
|
||||
│ │ ├── Overview.tsx # Landing page
|
||||
│ │ └── dreamarchive/ # Archive pages
|
||||
│ ├── types/ # TypeScript type definitions
|
||||
│ │ ├── Dream.ts # Dream data structures
|
||||
│ │ └── User.ts # User data structures
|
||||
│ ├── utils/ # Utility functions
|
||||
│ ├── styles/ # Styling utilities
|
||||
│ ├── App.tsx # Root component
|
||||
│ ├── main.tsx # Application entry point
|
||||
│ └── index.css # Global styles
|
||||
├── docs/ # Documentation
|
||||
│ └── poster.pdf # Project presentation poster
|
||||
├── package.json # Dependencies and scripts
|
||||
├── tsconfig.app.json # TypeScript configuration
|
||||
├── vite.config.ts # Vite configuration
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Usage \& Demo
|
||||
## 🚀 Getting Started
|
||||
|
||||
- **Start Dream Journal** — click “🌙 Lass die Magie beginnen” to open the mock dream entry form.
|
||||
- **AI Analysis** — submit an entry to see the “mock” interpretation card.
|
||||
- **Browse Archive** — filter mock entries by symbol, emotion, or date.
|
||||
- **VR Mode** — click VR Demo to load an immersive 3D scene in your browser.
|
||||
### Prerequisites
|
||||
|
||||
**Reminder:** REMind is a design-and-concept prototype only. Enjoy exploring the features—no real data or AI integration under the hood!
|
||||
- Node.js 18+
|
||||
- npm or yarn package manager
|
||||
|
||||
### Installation
|
||||
|
||||
1. **Clone the repository**
|
||||
```bash
|
||||
git clone https://gitea.puchstein.bayern/mpuchstein/REMind.git
|
||||
cd REMind
|
||||
```
|
||||
|
||||
2. **Install dependencies**
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. **Start development server**
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
4. **Open in browser**
|
||||
```
|
||||
http://localhost:5173
|
||||
```
|
||||
|
||||
### Available Scripts
|
||||
|
||||
- `npm run dev` - Start development server
|
||||
- `npm run build` - Build for production
|
||||
- `npm run preview` - Preview production build
|
||||
- `npm run lint` - Run ESLint
|
||||
|
||||
## 🎯 Usage Guide
|
||||
|
||||
### Getting Started
|
||||
|
||||
1. **Landing Page**: Visit the overview page to understand the application concept
|
||||
2. **Begin Journey**: Click "Lass die Magie beginnen" to start the dream recording experience
|
||||
3. **Record Dreams**: Use various input methods (text, voice, image, biometric)
|
||||
4. **AI Analysis**: View simulated AI interpretations and insights
|
||||
5. **VR Experience**: Explore dreams in virtual reality environments
|
||||
6. **Archive Exploration**: Browse and filter through dream collections
|
||||
|
||||
### Key Navigation
|
||||
|
||||
- **Home** (`/home`) - Personal dashboard with recent dreams
|
||||
- **Feed** (`/feed`) - Community dream sharing
|
||||
- **Record** (`/record`) - Dream input interface
|
||||
- **Dream Archive** (`/dreamarchive`) - Categorized dream collections
|
||||
- **Profile** (`/profile`) - User settings and statistics
|
||||
|
||||
### Input Types
|
||||
|
||||
- **Text**: Write detailed dream descriptions
|
||||
- **Audio**: Record voice memos with transcription
|
||||
- **Image**: Upload dream-related visuals
|
||||
- **Chip**: Biometric data from sleep tracking devices
|
||||
|
||||
## 🔮 Demo Features
|
||||
|
||||
### Simulated AI Analysis
|
||||
|
||||
- **Symbol Recognition**: Identifies dream symbols like "water," "flying," "animals"
|
||||
- **Emotion Tagging**: Categorizes emotions as "fear," "joy," "anxiety," "peace"
|
||||
- **Interpretation**: Generates psychological insights and meanings
|
||||
- **Pattern Detection**: Highlights recurring themes across dreams
|
||||
|
||||
### VR Visualization
|
||||
|
||||
- **Neural Networks**: Interactive 3D representations of brain activity
|
||||
- **Dreamscapes**: Immersive environments based on dream content
|
||||
- **Data Visualization**: 3D charts and graphs for biometric data
|
||||
- **WebXR Support**: Compatible with VR headsets and desktop browsers
|
||||
|
||||
### Data Analytics
|
||||
|
||||
- **EEG Patterns**: Brainwave analysis during sleep (Alpha, Beta, Theta, Delta)
|
||||
- **Vital Signs**: Heart rate and heart rate variability trends
|
||||
- **Movement Data**: Sleep movement and restlessness patterns
|
||||
- **Statistical Insights**: Long-term pattern analysis and correlations
|
||||
|
||||
## ⚠️ Important Notes
|
||||
|
||||
### Assets Not Included
|
||||
|
||||
- **Media Files**: Dream images, audio files, and videos are not included in the repository
|
||||
- **Mock Data**: All displayed content uses placeholder data for demonstration
|
||||
- **External Resources**: Some images and media are referenced but not stored in the repository
|
||||
|
||||
### Limitations
|
||||
|
||||
- **No Backend**: All data is stored locally in browser memory
|
||||
- **No AI Integration**: Analysis results are pre-generated mock responses
|
||||
- **No Data Persistence**: Data is lost when browser is refreshed
|
||||
- **Demo Purpose Only**: Not intended for production use
|
||||
|
||||
## 🎓 Educational Context
|
||||
|
||||
This project was developed as a university demonstration to explore:
|
||||
|
||||
- **Human-Computer Interaction**: Innovative interfaces for dream recording
|
||||
- **Data Visualization**: Advanced techniques for complex data representation
|
||||
- **Virtual Reality**: Immersive experiences for psychological content
|
||||
- **AI Integration**: Simulated machine learning for content analysis
|
||||
- **User Experience**: Intuitive design for sensitive personal data
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
This is a university project demo and not actively maintained for production use. However, feedback and suggestions are
|
||||
welcome for educational purposes.
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the AGPL-3.0 License - see the LICENSE file for details.
|
||||
|
||||
## 🔗 Repository
|
||||
|
||||
[View on Gitea](https://gitea.puchstein.bayern/mpuchstein/REMind)
|
||||
|
||||
---
|
||||
|
||||
**Disclaimer**: REMind is a prototype demonstration created for educational purposes. All AI features, data analysis,
|
||||
and interpretations are simulated and should not be used for actual psychological or medical analysis.
|
BIN
docs/poster.pdf
Normal file
BIN
docs/poster.pdf
Normal file
Binary file not shown.
557
package-lock.json
generated
557
package-lock.json
generated
@@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"@react-three/drei": "^10.5.1",
|
||||
"@react-three/fiber": "^9.2.0",
|
||||
"@react-three/xr": "^6.6.19",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"d3": "^7.9.0",
|
||||
"react": "^19.1.0",
|
||||
@@ -19,15 +20,18 @@
|
||||
"react-slick": "^0.30.3",
|
||||
"slick-carousel": "^1.8.1",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"three": "^0.178.0"
|
||||
"three": "^0.178.0",
|
||||
"webxr-polyfill": "^2.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.29.0",
|
||||
"@types/d3": "^7.4.3",
|
||||
"@types/node": "^24.0.14",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@types/react-slick": "^0.23.13",
|
||||
"@types/three": "^0.178.1",
|
||||
"@types/webxr": "^0.5.22",
|
||||
"@vitejs/plugin-react": "^4.5.2",
|
||||
"eslint": "^9.29.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
@@ -342,12 +346,39 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/protobuf": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.6.0.tgz",
|
||||
"integrity": "sha512-6cuonJVNOIL7lTj5zgo/Rc2bKAo4/GvN+rKCrUj7GdEHRzCk8zKOfFwUsL9nAVk5rSIsRmlgcpLzTRysopEeeg==",
|
||||
"license": "(Apache-2.0 AND BSD-3-Clause)"
|
||||
},
|
||||
"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.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
|
||||
@@ -915,6 +946,52 @@
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-common-types": {
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz",
|
||||
"integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-svg-core": {
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz",
|
||||
"integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/free-solid-svg-icons": {
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz",
|
||||
"integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==",
|
||||
"license": "(CC-BY-4.0 AND MIT)",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/react-fontawesome": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz",
|
||||
"integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "~1 || ~6",
|
||||
"react": ">=16.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||
@@ -993,6 +1070,51 @@
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@iwer/devui": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@iwer/devui/-/devui-1.1.2.tgz",
|
||||
"integrity": "sha512-ggF1lXSX14BTYP0QzB4xaurySr2PC+3+rtK/dpCR++giWquzFv2mBw3LW/PaCtdl5mqkZMrQ2GSwfUNg9ZoO+w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "6.6.0",
|
||||
"@fortawesome/free-solid-svg-icons": "6.6.0",
|
||||
"@fortawesome/react-fontawesome": "0.2.2",
|
||||
"@pmndrs/handle": "^6.6.17",
|
||||
"@pmndrs/pointer-events": "^6.6.17",
|
||||
"react": ">=18.3.1",
|
||||
"react-dom": ">=18.3.1",
|
||||
"styled-components": "^6.1.13",
|
||||
"three": "^0.165.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"iwer": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@iwer/devui/node_modules/three": {
|
||||
"version": "0.165.0",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.165.0.tgz",
|
||||
"integrity": "sha512-cc96IlVYGydeceu0e5xq70H8/yoVT/tXBxV/W8A/U6uOq7DXc4/s1Mkmnu6SqoYGhSRWWYFOhVwvq6V0VtbplA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@iwer/sem": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@iwer/sem/-/sem-0.2.5.tgz",
|
||||
"integrity": "sha512-vMCfpu/7Qqc+hkBiGD9pxjeObgrhXOrL0KX94CA3yzJaU0dq0y49HXZT6fC+6X/jOmjaM3hjyE1m2h7ZmLzzyA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"three": "^0.165.0",
|
||||
"ts-proto": "^2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"iwer": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@iwer/sem/node_modules/three": {
|
||||
"version": "0.165.0",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.165.0.tgz",
|
||||
"integrity": "sha512-cc96IlVYGydeceu0e5xq70H8/yoVT/tXBxV/W8A/U6uOq7DXc4/s1Mkmnu6SqoYGhSRWWYFOhVwvq6V0VtbplA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
|
||||
@@ -1097,6 +1219,95 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@pmndrs/handle": {
|
||||
"version": "6.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@pmndrs/handle/-/handle-6.6.19.tgz",
|
||||
"integrity": "sha512-sULA2N/3uxFbLBM/fX0W3mKpWRTQQvnqDMaLUya5gaGWcWioSRcNTVdyPIpnhMDHUcS17op7uci36yfpXca7PA==",
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"dependencies": {
|
||||
"@pmndrs/pointer-events": "~6.6.19",
|
||||
"zustand": "^4.5.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@pmndrs/handle/node_modules/zustand": {
|
||||
"version": "4.5.7",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
|
||||
"integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"use-sync-external-store": "^1.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.7.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=16.8",
|
||||
"immer": ">=9.0.6",
|
||||
"react": ">=16.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"immer": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@pmndrs/pointer-events": {
|
||||
"version": "6.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@pmndrs/pointer-events/-/pointer-events-6.6.19.tgz",
|
||||
"integrity": "sha512-w/i5LJnfPdDowRQ5HokGZFvtLx+VF3iO/jP3Mc7aJ5tuuJlNf/VmjesiyJsc0QRw/RW3pBgBfIFQQlqknXorww==",
|
||||
"license": "SEE LICENSE IN LICENSE"
|
||||
},
|
||||
"node_modules/@pmndrs/xr": {
|
||||
"version": "6.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@pmndrs/xr/-/xr-6.6.19.tgz",
|
||||
"integrity": "sha512-z47rhRfubHLlKmg8QtRpTt384cup5/zIG/7V030Ia7VaFWpxbEsqB6VkM4zWZfdvotmBgO0JgDrGNRdUBT3qJA==",
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"dependencies": {
|
||||
"@iwer/devui": "^1.1.1",
|
||||
"@iwer/sem": "~0.2.5",
|
||||
"@pmndrs/pointer-events": "~6.6.19",
|
||||
"iwer": "^2.0.1",
|
||||
"meshline": "^3.3.1",
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"three": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@pmndrs/xr/node_modules/zustand": {
|
||||
"version": "4.5.7",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
|
||||
"integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"use-sync-external-store": "^1.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.7.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=16.8",
|
||||
"immer": ">=9.0.6",
|
||||
"react": ">=16.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"immer": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@react-three/drei": {
|
||||
"version": "10.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.5.1.tgz",
|
||||
@@ -1193,6 +1404,53 @@
|
||||
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@react-three/xr": {
|
||||
"version": "6.6.19",
|
||||
"resolved": "https://registry.npmjs.org/@react-three/xr/-/xr-6.6.19.tgz",
|
||||
"integrity": "sha512-aNozIFFRQ5CAld7OiYHrFgFqZGWqmIKanE3Jlj+rwRfcngSG8NfdKGLddiG5W6OnPKdU9Apll1k+k1vNC3q8Lg==",
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"dependencies": {
|
||||
"@pmndrs/pointer-events": "~6.6.19",
|
||||
"@pmndrs/xr": "~6.6.19",
|
||||
"suspend-react": "^0.1.3",
|
||||
"tunnel-rat": "^0.1.2",
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-three/fiber": ">=8",
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18",
|
||||
"three": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-three/xr/node_modules/zustand": {
|
||||
"version": "4.5.7",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
|
||||
"integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"use-sync-external-store": "^1.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.7.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=16.8",
|
||||
"immer": ">=9.0.6",
|
||||
"react": ">=16.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"immer": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.19",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz",
|
||||
@@ -2083,6 +2341,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz",
|
||||
"integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/offscreencanvas": {
|
||||
"version": "2019.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
|
||||
@@ -2133,6 +2401,12 @@
|
||||
"integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"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.178.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.178.1.tgz",
|
||||
@@ -2659,6 +2933,15 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"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/camera-controls": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-3.1.0.tgz",
|
||||
@@ -2693,6 +2976,29 @@
|
||||
],
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/cardboard-vr-display": {
|
||||
"version": "1.0.19",
|
||||
"resolved": "https://registry.npmjs.org/cardboard-vr-display/-/cardboard-vr-display-1.0.19.tgz",
|
||||
"integrity": "sha512-+MjcnWKAkb95p68elqZLDPzoiF/dGncQilLGvPBM5ZorABp/ao3lCs7nnRcYBckmuNkg1V/5rdGDKoUaCVsHzQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"gl-preserve-state": "^1.0.0",
|
||||
"nosleep.js": "^0.7.0",
|
||||
"webvr-polyfill-dpdb": "^1.0.17"
|
||||
}
|
||||
},
|
||||
"node_modules/case-anything": {
|
||||
"version": "2.1.13",
|
||||
"resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.13.tgz",
|
||||
"integrity": "sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.13"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/mesqueeb"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
@@ -2809,6 +3115,26 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
@@ -3268,6 +3594,27 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dprint-node": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/dprint-node/-/dprint-node-1.0.8.tgz",
|
||||
"integrity": "sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"detect-libc": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/dprint-node/node_modules/detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"detect-libc": "bin/detect-libc.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/draco3d": {
|
||||
"version": "1.5.7",
|
||||
"resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz",
|
||||
@@ -3696,6 +4043,18 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gl-matrix": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
|
||||
"integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/gl-preserve-state": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/gl-preserve-state/-/gl-preserve-state-1.0.0.tgz",
|
||||
"integrity": "sha512-zQZ25l3haD4hvgJZ6C9+s0ebdkW9y+7U2qxvGu1uWOJh8a4RU+jURIKEQhf8elIlFpMH6CrAY2tH0mYrRjet3Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/glob-parent": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
@@ -3898,6 +4257,15 @@
|
||||
"react": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/iwer": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/iwer/-/iwer-2.0.1.tgz",
|
||||
"integrity": "sha512-kNjh8DDWGSnbiK6jKcVLYVRLFCMBQ4+sVyHZ3La4EJYTCTY+Trrnagf6z3oCdNpC4OTiwcPdakunTAqZhoWK+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"gl-matrix": "^3.4.3"
|
||||
}
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
|
||||
@@ -3918,7 +4286,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
@@ -4280,6 +4647,18 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"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",
|
||||
@@ -4436,6 +4815,21 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nosleep.js": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/nosleep.js/-/nosleep.js-0.7.0.tgz",
|
||||
"integrity": "sha512-Z4B1HgvzR+en62ghwZf6BwAR6x4/pjezsiMcbF9KMLh7xoscpoYhaSXfY3lLkqC68AtW+/qLJ1lzvBIj0FGaTA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
@@ -4565,6 +4959,12 @@
|
||||
"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/potpack": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
|
||||
@@ -4591,6 +4991,17 @@
|
||||
"lie": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
@@ -4652,6 +5063,12 @@
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-reconciler": {
|
||||
"version": "0.31.0",
|
||||
"resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.31.0.tgz",
|
||||
@@ -4892,6 +5309,12 @@
|
||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"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/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@@ -4976,6 +5399,74 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-components": {
|
||||
"version": "6.1.19",
|
||||
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.19.tgz",
|
||||
"integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==",
|
||||
"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/styled-components/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/styled-components/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/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/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
@@ -5175,6 +5666,39 @@
|
||||
"typescript": ">=4.8.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-poet": {
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-poet/-/ts-poet-6.12.0.tgz",
|
||||
"integrity": "sha512-xo+iRNMWqyvXpFTaOAvLPA5QAWO6TZrSUs5s4Odaya3epqofBu/fMLHEWl8jPmjhA0s9sgj9sNvF1BmaQlmQkA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"dprint-node": "^1.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-proto": {
|
||||
"version": "2.7.5",
|
||||
"resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-2.7.5.tgz",
|
||||
"integrity": "sha512-FoRxSaNW+P3m+GiXIZjUjhaHXT67Ah4zMGKzn4yklbGRQTS+PqpUhKo5AJnwfUDUByjEUG7ch36byFUYWRH9Nw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^2.0.0",
|
||||
"case-anything": "^2.1.13",
|
||||
"ts-poet": "^6.12.0",
|
||||
"ts-proto-descriptors": "2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"protoc-gen-ts_proto": "protoc-gen-ts_proto"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-proto-descriptors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-proto-descriptors/-/ts-proto-descriptors-2.0.0.tgz",
|
||||
"integrity": "sha512-wHcTH3xIv11jxgkX5OyCSFfw27agpInAd6yh89hKG6zqIXnjW9SYqSER2CVQxdPj4czeOhGagNvZBEbJPy7qkw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
@@ -5269,6 +5793,13 @@
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
|
||||
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
|
||||
"devOptional": 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",
|
||||
@@ -5439,6 +5970,28 @@
|
||||
"integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/webvr-polyfill-dpdb": {
|
||||
"version": "1.0.18",
|
||||
"resolved": "https://registry.npmjs.org/webvr-polyfill-dpdb/-/webvr-polyfill-dpdb-1.0.18.tgz",
|
||||
"integrity": "sha512-O0S1ZGEWyPvyZEkS2VbyV7mtir/NM9MNK3EuhbHPoJ8EHTky2pTXehjIl+IiDPr+Lldgx129QGt3NGly7rwRPw==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/webxr-polyfill": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/webxr-polyfill/-/webxr-polyfill-2.0.3.tgz",
|
||||
"integrity": "sha512-lgTKYVeD4HeTwWdJN+SeS6iflGx3epz/3dww9X4GyuuXmYGAV5p8l34jUM/HRGHn1jKS3oZNZRC/J9MlSk/Zhg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"cardboard-vr-display": "^1.0.19",
|
||||
"gl-matrix": "^2.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/webxr-polyfill/node_modules/gl-matrix": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-2.8.1.tgz",
|
||||
"integrity": "sha512-0YCjVpE3pS5XWlN3J4X7AiAx65+nqAI54LndtVFnQZB6G/FVLkZH8y8V6R3cIoOQR4pUdfwQGd1iwyoXHJ4Qfw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
@@ -12,6 +12,7 @@
|
||||
"dependencies": {
|
||||
"@react-three/drei": "^10.5.1",
|
||||
"@react-three/fiber": "^9.2.0",
|
||||
"@react-three/xr": "^6.6.19",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"d3": "^7.9.0",
|
||||
"react": "^19.1.0",
|
||||
@@ -21,15 +22,18 @@
|
||||
"react-slick": "^0.30.3",
|
||||
"slick-carousel": "^1.8.1",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"three": "^0.178.0"
|
||||
"three": "^0.178.0",
|
||||
"webxr-polyfill": "^2.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.29.0",
|
||||
"@types/d3": "^7.4.3",
|
||||
"@types/node": "^24.0.14",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@types/react-slick": "^0.23.13",
|
||||
"@types/three": "^0.178.1",
|
||||
"@types/webxr": "^0.5.22",
|
||||
"@vitejs/plugin-react": "^4.5.2",
|
||||
"eslint": "^9.29.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
@@ -24,7 +24,7 @@ export const DreamCardCompact = ({dream, index, showUser = false, user}: DreamCa
|
||||
border: '1px solid rgba(166, 77, 255, 0.1)'
|
||||
}}>
|
||||
{dream.input.inputType === 'chip' && (
|
||||
<div className="absolute top-6 right-10 w-10 h-10 flex items-center justify-center rounded-full"
|
||||
<div className="absolute bottom-2 right-2 w-10 h-10 flex items-center justify-center rounded-full"
|
||||
style={{background: 'var(--accent-gradient)', boxShadow: '0 0 5px var(--shadow)'}}>
|
||||
<FaMicrochip className="w-6 h-6 md:w-8 md:h-8 text-pink-300 opacity-90 hover:opacity-100"/>
|
||||
</div>
|
||||
|
@@ -16,96 +16,101 @@ export const EEGChart: React.FC<{ chipInput: ChipInput }> = ({chipInput}) => {
|
||||
{name: 'Delta', values: chipInput.eeg.delta}
|
||||
];
|
||||
|
||||
// Clear previous chart
|
||||
d3.select(chartRef.current).selectAll('*').remove();
|
||||
// Function to render the chart
|
||||
const renderChart = () => {
|
||||
if (!chartRef.current) return;
|
||||
|
||||
// Clear previous chart
|
||||
d3.select(chartRef.current).selectAll('*').remove();
|
||||
|
||||
const margin = {top: 20, right: 20, bottom: 30, left: 50};
|
||||
const width = chartRef.current.clientWidth - margin.left - margin.right;
|
||||
const height = chartRef.current.clientHeight - margin.top - margin.bottom;
|
||||
const margin = {top: 20, right: 20, bottom: 30, left: 50};
|
||||
const width = chartRef.current.clientWidth - margin.left - margin.right;
|
||||
const height = chartRef.current.clientHeight - margin.top - margin.bottom;
|
||||
|
||||
const svg = d3.select(chartRef.current)
|
||||
.append('svg')
|
||||
.attr('width', width + margin.left + margin.right)
|
||||
.attr('height', height + margin.top + margin.bottom)
|
||||
.append('g')
|
||||
.attr('transform', `translate(${margin.left},${margin.top})`);
|
||||
const svg = d3.select(chartRef.current)
|
||||
.append('svg')
|
||||
.attr('width', width + margin.left + margin.right)
|
||||
.attr('height', height + margin.top + margin.bottom)
|
||||
.append('g')
|
||||
.attr('transform', `translate(${margin.left},${margin.top})`);
|
||||
|
||||
// X scale
|
||||
const x = d3.scaleLinear()
|
||||
.domain([0, chipInput.eeg.alpha.length - 1])
|
||||
.range([0, width]);
|
||||
// X scale
|
||||
const x = d3.scaleLinear()
|
||||
.domain([0, chipInput.eeg.alpha.length - 1])
|
||||
.range([0, width]);
|
||||
|
||||
// Y scale
|
||||
const y = d3.scaleLinear()
|
||||
.domain([0, d3.max(eegData.flatMap(d => d.values)) || 50])
|
||||
.range([height, 0]);
|
||||
// Y scale
|
||||
const y = d3.scaleLinear()
|
||||
.domain([0, d3.max(eegData.flatMap(d => d.values)) || 50])
|
||||
.range([height, 0]);
|
||||
|
||||
// Line generator
|
||||
const line = d3.line<number>()
|
||||
.x((_d, i) => x(i))
|
||||
.y(d => y(d))
|
||||
.curve(d3.curveMonotoneX);
|
||||
// Line generator
|
||||
const line = d3.line<number>()
|
||||
.x((_d, i) => x(i))
|
||||
.y(d => y(d))
|
||||
.curve(d3.curveMonotoneX);
|
||||
|
||||
// Color scale
|
||||
const color = d3.scaleOrdinal<string>()
|
||||
.domain(eegData.map(d => d.name))
|
||||
.range(['#8884d8', '#82ca9d', '#ffc658', '#ff8042']);
|
||||
// Color scale
|
||||
const color = d3.scaleOrdinal<string>()
|
||||
.domain(eegData.map(d => d.name))
|
||||
.range(['#8884d8', '#82ca9d', '#ffc658', '#ff8042']);
|
||||
|
||||
// Add X axis
|
||||
svg.append('g')
|
||||
.attr('transform', `translate(0,${height})`)
|
||||
.call(d3.axisBottom(x))
|
||||
.selectAll('text')
|
||||
.style('fill', 'var(--text)');
|
||||
// Add X axis
|
||||
svg.append('g')
|
||||
.attr('transform', `translate(0,${height})`)
|
||||
.call(d3.axisBottom(x))
|
||||
.selectAll('text')
|
||||
.style('fill', 'var(--text)');
|
||||
|
||||
// Add Y axis
|
||||
svg.append('g')
|
||||
.call(d3.axisLeft(y))
|
||||
.selectAll('text')
|
||||
.style('fill', 'var(--text)');
|
||||
// Add Y axis
|
||||
svg.append('g')
|
||||
.call(d3.axisLeft(y))
|
||||
.selectAll('text')
|
||||
.style('fill', 'var(--text)');
|
||||
|
||||
// Add lines
|
||||
eegData.forEach(d => {
|
||||
svg.append('path')
|
||||
.datum(d.values)
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke', color(d.name))
|
||||
.attr('stroke-width', 1.5)
|
||||
.attr('d', line);
|
||||
});
|
||||
// Add lines
|
||||
eegData.forEach(d => {
|
||||
svg.append('path')
|
||||
.datum(d.values)
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke', color(d.name))
|
||||
.attr('stroke-width', 1.5)
|
||||
.attr('d', line);
|
||||
});
|
||||
|
||||
// Add legend at the top right
|
||||
const legendItemHeight = 20; // Height allocated for each legend item
|
||||
// Add legend at the top right
|
||||
const legendItemHeight = 20; // Height allocated for each legend item
|
||||
|
||||
const legend = svg.append('g')
|
||||
.attr('font-family', 'sans-serif')
|
||||
.attr('font-size', 10)
|
||||
.attr('text-anchor', 'end')
|
||||
.selectAll('g')
|
||||
.data(eegData)
|
||||
.enter().append('g')
|
||||
.attr('transform', (_d, i) => `translate(${width},${i * legendItemHeight + 10})`);
|
||||
const legend = svg.append('g')
|
||||
.attr('font-family', 'sans-serif')
|
||||
.attr('font-size', 10)
|
||||
.attr('text-anchor', 'end')
|
||||
.selectAll('g')
|
||||
.data(eegData)
|
||||
.enter().append('g')
|
||||
.attr('transform', (_d, i) => `translate(${width},${i * legendItemHeight + 10})`);
|
||||
|
||||
legend.append('rect')
|
||||
.attr('x', -15)
|
||||
.attr('width', 15)
|
||||
.attr('height', 15)
|
||||
.attr('fill', d => color(d.name));
|
||||
legend.append('rect')
|
||||
.attr('x', -15)
|
||||
.attr('width', 15)
|
||||
.attr('height', 15)
|
||||
.attr('fill', d => color(d.name));
|
||||
|
||||
legend.append('text')
|
||||
.attr('x', -20)
|
||||
.attr('y', 7.5)
|
||||
.attr('dy', '0.32em')
|
||||
.style('fill', 'var(--text)')
|
||||
.text(d => d.name);
|
||||
legend.append('text')
|
||||
.attr('x', -20)
|
||||
.attr('y', 7.5)
|
||||
.attr('dy', '0.32em')
|
||||
.style('fill', 'var(--text)')
|
||||
.text(d => d.name);
|
||||
};
|
||||
|
||||
// Initial render
|
||||
renderChart();
|
||||
|
||||
// Handle resize
|
||||
const handleResize = () => {
|
||||
if (!chartRef.current) return;
|
||||
|
||||
// Re-render chart on resize
|
||||
d3.select(chartRef.current).selectAll('*').remove();
|
||||
// We would re-render the chart here, but for simplicity we'll just reload the component
|
||||
renderChart();
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
@@ -127,98 +132,103 @@ export const VitalsChart: React.FC<{ chipInput: ChipInput }> = ({chipInput}) =>
|
||||
{name: 'HRV', values: chipInput.hrv}
|
||||
];
|
||||
|
||||
// Clear previous chart
|
||||
d3.select(chartRef.current).selectAll('*').remove();
|
||||
// Function to render the chart
|
||||
const renderChart = () => {
|
||||
if (!chartRef.current) return;
|
||||
|
||||
// Clear previous chart
|
||||
d3.select(chartRef.current).selectAll('*').remove();
|
||||
|
||||
const margin = {top: 20, right: 20, bottom: 30, left: 50};
|
||||
const width = chartRef.current.clientWidth - margin.left - margin.right;
|
||||
const height = chartRef.current.clientHeight - margin.top - margin.bottom;
|
||||
const margin = {top: 20, right: 20, bottom: 30, left: 50};
|
||||
const width = chartRef.current.clientWidth - margin.left - margin.right;
|
||||
const height = chartRef.current.clientHeight - margin.top - margin.bottom;
|
||||
|
||||
const svg = d3.select(chartRef.current)
|
||||
.append('svg')
|
||||
.attr('width', width + margin.left + margin.right)
|
||||
.attr('height', height + margin.top + margin.bottom)
|
||||
.append('g')
|
||||
.attr('transform', `translate(${margin.left},${margin.top})`);
|
||||
const svg = d3.select(chartRef.current)
|
||||
.append('svg')
|
||||
.attr('width', width + margin.left + margin.right)
|
||||
.attr('height', height + margin.top + margin.bottom)
|
||||
.append('g')
|
||||
.attr('transform', `translate(${margin.left},${margin.top})`);
|
||||
|
||||
// X scale
|
||||
const x = d3.scaleLinear()
|
||||
.domain([0, chipInput.puls.length - 1])
|
||||
.range([0, width]);
|
||||
// X scale
|
||||
const x = d3.scaleLinear()
|
||||
.domain([0, chipInput.puls.length - 1])
|
||||
.range([0, width]);
|
||||
|
||||
// Y scale
|
||||
const minValue = d3.min(vitalsData.flatMap(d => d.values)) || 0;
|
||||
const maxValue = d3.max(vitalsData.flatMap(d => d.values)) || 100;
|
||||
const y = d3.scaleLinear()
|
||||
.domain([Math.max(0, minValue - 10), maxValue + 10])
|
||||
.range([height, 0]);
|
||||
// Y scale
|
||||
const minValue = d3.min(vitalsData.flatMap(d => d.values)) || 0;
|
||||
const maxValue = d3.max(vitalsData.flatMap(d => d.values)) || 100;
|
||||
const y = d3.scaleLinear()
|
||||
.domain([Math.max(0, minValue - 10), maxValue + 10])
|
||||
.range([height, 0]);
|
||||
|
||||
// Line generator
|
||||
const line = d3.line<number>()
|
||||
.x((_d, i) => x(i))
|
||||
.y(d => y(d))
|
||||
.curve(d3.curveMonotoneX);
|
||||
// Line generator
|
||||
const line = d3.line<number>()
|
||||
.x((_d, i) => x(i))
|
||||
.y(d => y(d))
|
||||
.curve(d3.curveMonotoneX);
|
||||
|
||||
// Color scale
|
||||
const color = d3.scaleOrdinal<string>()
|
||||
.domain(vitalsData.map(d => d.name))
|
||||
.range(['#ff6b6b', '#48dbfb']);
|
||||
// Color scale
|
||||
const color = d3.scaleOrdinal<string>()
|
||||
.domain(vitalsData.map(d => d.name))
|
||||
.range(['#ff6b6b', '#48dbfb']);
|
||||
|
||||
// Add X axis
|
||||
svg.append('g')
|
||||
.attr('transform', `translate(0,${height})`)
|
||||
.call(d3.axisBottom(x))
|
||||
.selectAll('text')
|
||||
.style('fill', 'var(--text)');
|
||||
// Add X axis
|
||||
svg.append('g')
|
||||
.attr('transform', `translate(0,${height})`)
|
||||
.call(d3.axisBottom(x))
|
||||
.selectAll('text')
|
||||
.style('fill', 'var(--text)');
|
||||
|
||||
// Add Y axis
|
||||
svg.append('g')
|
||||
.call(d3.axisLeft(y))
|
||||
.selectAll('text')
|
||||
.style('fill', 'var(--text)');
|
||||
// Add Y axis
|
||||
svg.append('g')
|
||||
.call(d3.axisLeft(y))
|
||||
.selectAll('text')
|
||||
.style('fill', 'var(--text)');
|
||||
|
||||
// Add lines
|
||||
vitalsData.forEach(d => {
|
||||
svg.append('path')
|
||||
.datum(d.values)
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke', color(d.name))
|
||||
.attr('stroke-width', 1.5)
|
||||
.attr('d', line);
|
||||
});
|
||||
// Add lines
|
||||
vitalsData.forEach(d => {
|
||||
svg.append('path')
|
||||
.datum(d.values)
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke', color(d.name))
|
||||
.attr('stroke-width', 1.5)
|
||||
.attr('d', line);
|
||||
});
|
||||
|
||||
// Add legend at the top right
|
||||
const legendItemHeight = 20; // Height allocated for each legend item
|
||||
// Add legend at the top right
|
||||
const legendItemHeight = 20; // Height allocated for each legend item
|
||||
|
||||
const legend = svg.append('g')
|
||||
.attr('font-family', 'sans-serif')
|
||||
.attr('font-size', 10)
|
||||
.attr('text-anchor', 'end')
|
||||
.selectAll('g')
|
||||
.data(vitalsData)
|
||||
.enter().append('g')
|
||||
.attr('transform', (_d, i) => `translate(${width},${i * legendItemHeight + 10})`);
|
||||
const legend = svg.append('g')
|
||||
.attr('font-family', 'sans-serif')
|
||||
.attr('font-size', 10)
|
||||
.attr('text-anchor', 'end')
|
||||
.selectAll('g')
|
||||
.data(vitalsData)
|
||||
.enter().append('g')
|
||||
.attr('transform', (_d, i) => `translate(${width},${i * legendItemHeight + 10})`);
|
||||
|
||||
legend.append('rect')
|
||||
.attr('x', -15)
|
||||
.attr('width', 15)
|
||||
.attr('height', 15)
|
||||
.attr('fill', d => color(d.name));
|
||||
legend.append('rect')
|
||||
.attr('x', -15)
|
||||
.attr('width', 15)
|
||||
.attr('height', 15)
|
||||
.attr('fill', d => color(d.name));
|
||||
|
||||
legend.append('text')
|
||||
.attr('x', -20)
|
||||
.attr('y', 7.5)
|
||||
.attr('dy', '0.32em')
|
||||
.style('fill', 'var(--text)')
|
||||
.text(d => d.name);
|
||||
legend.append('text')
|
||||
.attr('x', -20)
|
||||
.attr('y', 7.5)
|
||||
.attr('dy', '0.32em')
|
||||
.style('fill', 'var(--text)')
|
||||
.text(d => d.name);
|
||||
};
|
||||
|
||||
// Initial render
|
||||
renderChart();
|
||||
|
||||
// Handle resize
|
||||
const handleResize = () => {
|
||||
if (!chartRef.current) return;
|
||||
|
||||
// Re-render chart on resize
|
||||
d3.select(chartRef.current).selectAll('*').remove();
|
||||
// We would re-render the chart here, but for simplicity we'll just reload the component
|
||||
renderChart();
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
@@ -239,92 +249,97 @@ export const MovementChart: React.FC<{ chipInput: ChipInput }> = ({chipInput}) =
|
||||
{name: 'Bewegung', values: chipInput.bewegung.map(v => v * 100)} // Scale movement for better visibility
|
||||
];
|
||||
|
||||
// Clear previous chart
|
||||
d3.select(chartRef.current).selectAll('*').remove();
|
||||
// Function to render the chart
|
||||
const renderChart = () => {
|
||||
if (!chartRef.current) return;
|
||||
|
||||
// Clear previous chart
|
||||
d3.select(chartRef.current).selectAll('*').remove();
|
||||
|
||||
const margin = {top: 20, right: 20, bottom: 30, left: 50};
|
||||
const width = chartRef.current.clientWidth - margin.left - margin.right;
|
||||
const height = chartRef.current.clientHeight - margin.top - margin.bottom;
|
||||
const margin = {top: 20, right: 20, bottom: 30, left: 50};
|
||||
const width = chartRef.current.clientWidth - margin.left - margin.right;
|
||||
const height = chartRef.current.clientHeight - margin.top - margin.bottom;
|
||||
|
||||
const svg = d3.select(chartRef.current)
|
||||
.append('svg')
|
||||
.attr('width', width + margin.left + margin.right)
|
||||
.attr('height', height + margin.top + margin.bottom)
|
||||
.append('g')
|
||||
.attr('transform', `translate(${margin.left},${margin.top})`);
|
||||
const svg = d3.select(chartRef.current)
|
||||
.append('svg')
|
||||
.attr('width', width + margin.left + margin.right)
|
||||
.attr('height', height + margin.top + margin.bottom)
|
||||
.append('g')
|
||||
.attr('transform', `translate(${margin.left},${margin.top})`);
|
||||
|
||||
// X scale
|
||||
const x = d3.scaleLinear()
|
||||
.domain([0, chipInput.bewegung.length - 1])
|
||||
.range([0, width]);
|
||||
// X scale
|
||||
const x = d3.scaleLinear()
|
||||
.domain([0, chipInput.bewegung.length - 1])
|
||||
.range([0, width]);
|
||||
|
||||
// Y scale
|
||||
const y = d3.scaleLinear()
|
||||
.domain([0, d3.max(bewegungData[0].values) || 100])
|
||||
.range([height, 0]);
|
||||
// Y scale
|
||||
const y = d3.scaleLinear()
|
||||
.domain([0, d3.max(bewegungData[0].values) || 100])
|
||||
.range([height, 0]);
|
||||
|
||||
// Line generator
|
||||
const line = d3.line<number>()
|
||||
.x((_d, i) => x(i))
|
||||
.y(d => y(d))
|
||||
.curve(d3.curveMonotoneX);
|
||||
// Line generator
|
||||
const line = d3.line<number>()
|
||||
.x((_d, i) => x(i))
|
||||
.y(d => y(d))
|
||||
.curve(d3.curveMonotoneX);
|
||||
|
||||
// Color scale
|
||||
const color = '#1dd1a1'; // Use the same color as before for consistency
|
||||
// Color scale
|
||||
const color = '#1dd1a1'; // Use the same color as before for consistency
|
||||
|
||||
// Add X axis
|
||||
svg.append('g')
|
||||
.attr('transform', `translate(0,${height})`)
|
||||
.call(d3.axisBottom(x))
|
||||
.selectAll('text')
|
||||
.style('fill', 'var(--text)');
|
||||
// Add X axis
|
||||
svg.append('g')
|
||||
.attr('transform', `translate(0,${height})`)
|
||||
.call(d3.axisBottom(x))
|
||||
.selectAll('text')
|
||||
.style('fill', 'var(--text)');
|
||||
|
||||
// Add Y axis
|
||||
svg.append('g')
|
||||
.call(d3.axisLeft(y))
|
||||
.selectAll('text')
|
||||
.style('fill', 'var(--text)');
|
||||
// Add Y axis
|
||||
svg.append('g')
|
||||
.call(d3.axisLeft(y))
|
||||
.selectAll('text')
|
||||
.style('fill', 'var(--text)');
|
||||
|
||||
// Add line
|
||||
svg.append('path')
|
||||
.datum(bewegungData[0].values)
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke', color)
|
||||
.attr('stroke-width', 1.5)
|
||||
.attr('d', line);
|
||||
// Add line
|
||||
svg.append('path')
|
||||
.datum(bewegungData[0].values)
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke', color)
|
||||
.attr('stroke-width', 1.5)
|
||||
.attr('d', line);
|
||||
|
||||
// Add legend at the top right
|
||||
const legendItemHeight = 20; // Height allocated for each legend item
|
||||
// Add legend at the top right
|
||||
const legendItemHeight = 20; // Height allocated for each legend item
|
||||
|
||||
const legend = svg.append('g')
|
||||
.attr('font-family', 'sans-serif')
|
||||
.attr('font-size', 10)
|
||||
.attr('text-anchor', 'end')
|
||||
.selectAll('g')
|
||||
.data(bewegungData)
|
||||
.enter().append('g')
|
||||
.attr('transform', (_d, i) => `translate(${width},${i * legendItemHeight + 10})`);
|
||||
const legend = svg.append('g')
|
||||
.attr('font-family', 'sans-serif')
|
||||
.attr('font-size', 10)
|
||||
.attr('text-anchor', 'end')
|
||||
.selectAll('g')
|
||||
.data(bewegungData)
|
||||
.enter().append('g')
|
||||
.attr('transform', (_d, i) => `translate(${width},${i * legendItemHeight + 10})`);
|
||||
|
||||
legend.append('rect')
|
||||
.attr('x', -15)
|
||||
.attr('width', 15)
|
||||
.attr('height', 15)
|
||||
.attr('fill', () => color);
|
||||
legend.append('rect')
|
||||
.attr('x', -15)
|
||||
.attr('width', 15)
|
||||
.attr('height', 15)
|
||||
.attr('fill', () => color);
|
||||
|
||||
legend.append('text')
|
||||
.attr('x', -20)
|
||||
.attr('y', 7.5)
|
||||
.attr('dy', '0.32em')
|
||||
.style('fill', 'var(--text)')
|
||||
.text(d => d.name);
|
||||
legend.append('text')
|
||||
.attr('x', -20)
|
||||
.attr('y', 7.5)
|
||||
.attr('dy', '0.32em')
|
||||
.style('fill', 'var(--text)')
|
||||
.text(d => d.name);
|
||||
};
|
||||
|
||||
// Initial render
|
||||
renderChart();
|
||||
|
||||
// Handle resize
|
||||
const handleResize = () => {
|
||||
if (!chartRef.current) return;
|
||||
|
||||
// Re-render chart on resize
|
||||
d3.select(chartRef.current).selectAll('*').remove();
|
||||
// We would re-render the chart here, but for simplicity we'll just reload the component
|
||||
renderChart();
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
@@ -1,26 +1,13 @@
|
||||
import React, {useEffect, useMemo, useRef, useState} from 'react';
|
||||
import {Canvas, useFrame, useThree} from '@react-three/fiber';
|
||||
import React, {useMemo, useRef} from 'react';
|
||||
import {Canvas, useFrame} from '@react-three/fiber';
|
||||
import {createXRStore, useXR, XR} from '@react-three/xr';
|
||||
import {OrbitControls, PerspectiveCamera} from '@react-three/drei';
|
||||
import * as THREE from 'three';
|
||||
import type Dream from '../types/Dream';
|
||||
import {DeviceOrientationControls} from 'three-stdlib';
|
||||
|
||||
// Extended window interface with DeviceOrientationEvent
|
||||
interface WindowWithDeviceOrientation extends Window {
|
||||
DeviceOrientationEvent: {
|
||||
prototype: DeviceOrientationEvent;
|
||||
new(type: string, eventInitDict?: DeviceOrientationEventInit): DeviceOrientationEvent;
|
||||
requestPermission?: () => Promise<string>;
|
||||
};
|
||||
}
|
||||
|
||||
// Neural Node component representing a synapse in the neural network
|
||||
const NeuralNode = ({position, color, scale, pulseSpeed, pulseIntensity}: {
|
||||
position: [number, number, number],
|
||||
color: string,
|
||||
scale: number,
|
||||
pulseSpeed: number,
|
||||
pulseIntensity: number
|
||||
position: [number, number, number], color: string, scale: number, pulseSpeed: number, pulseIntensity: number
|
||||
}) => {
|
||||
const nodeRef = useRef<THREE.Mesh>(null);
|
||||
const initialScale = scale;
|
||||
@@ -33,18 +20,10 @@ const NeuralNode = ({position, color, scale, pulseSpeed, pulseIntensity}: {
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<mesh ref={nodeRef} position={position}>
|
||||
<sphereGeometry args={[1, 32, 32]}/>
|
||||
<meshStandardMaterial
|
||||
color={color}
|
||||
emissive={color}
|
||||
emissiveIntensity={0.5}
|
||||
transparent
|
||||
opacity={0.8}
|
||||
/>
|
||||
</mesh>
|
||||
);
|
||||
return (<mesh ref={nodeRef} position={position}>
|
||||
<sphereGeometry args={[scale, 8, 8]}/>
|
||||
<meshStandardMaterial color={color} emissive={color} emissiveIntensity={0.2}/>
|
||||
</mesh>);
|
||||
};
|
||||
|
||||
// Neural Connection component representing connections between synapses
|
||||
@@ -60,60 +39,36 @@ const NeuralConnection = ({start, end, color, thickness, pulseSpeed, pulseIntens
|
||||
|
||||
// Create a cylinder between two points
|
||||
const direction = useMemo(() => {
|
||||
return new THREE.Vector3(
|
||||
end[0] - start[0],
|
||||
end[1] - start[1],
|
||||
end[2] - start[2]
|
||||
);
|
||||
return new THREE.Vector3(end[0] - start[0], end[1] - start[1], end[2] - start[2]);
|
||||
}, [start, end]);
|
||||
|
||||
const length = useMemo(() => direction.length(), [direction]);
|
||||
|
||||
// Calculate rotation to align cylinder with direction
|
||||
const rotationMatrix = useMemo(() => {
|
||||
useMemo(() => {
|
||||
const normalizedDirection = direction.clone().normalize();
|
||||
const quaternion = new THREE.Quaternion();
|
||||
quaternion.setFromUnitVectors(
|
||||
new THREE.Vector3(0, 1, 0), // Default cylinder orientation
|
||||
normalizedDirection
|
||||
);
|
||||
quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), // Default cylinder orientation
|
||||
normalizedDirection);
|
||||
return new THREE.Matrix4().makeRotationFromQuaternion(quaternion);
|
||||
}, [direction]);
|
||||
|
||||
// Calculate position (midpoint between start and end)
|
||||
const position = useMemo(() => {
|
||||
return [
|
||||
(start[0] + end[0]) / 2,
|
||||
(start[1] + end[1]) / 2,
|
||||
(start[2] + end[2]) / 2
|
||||
] as [number, number, number];
|
||||
return [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2, (start[2] + end[2]) / 2] as [number, number, number];
|
||||
}, [start, end]);
|
||||
|
||||
useFrame(({clock}) => {
|
||||
if (connectionRef.current) {
|
||||
// Create a pulsing effect for the connection
|
||||
const pulse = Math.sin(clock.getElapsedTime() * pulseSpeed +
|
||||
(start[0] + start[1] + start[2])) * pulseIntensity + 1;
|
||||
const pulse = Math.sin(clock.getElapsedTime() * pulseSpeed + (start[0] + start[1] + start[2])) * pulseIntensity + 1;
|
||||
connectionRef.current.scale.set(thickness * pulse, length / 2, thickness * pulse);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<mesh
|
||||
ref={connectionRef}
|
||||
position={position}
|
||||
matrix={rotationMatrix}
|
||||
>
|
||||
<cylinderGeometry args={[thickness, thickness, length / 2, 8]}/>
|
||||
<meshStandardMaterial
|
||||
color={color}
|
||||
emissive={color}
|
||||
emissiveIntensity={0.5}
|
||||
transparent
|
||||
opacity={0.6}
|
||||
/>
|
||||
</mesh>
|
||||
);
|
||||
return (<mesh ref={connectionRef} position={position}>
|
||||
<cylinderGeometry args={[thickness, thickness, length]}/>
|
||||
<meshStandardMaterial color={color} emissive={color} emissiveIntensity={0.1}/>
|
||||
</mesh>);
|
||||
};
|
||||
|
||||
// Neural Network component that generates nodes and connections
|
||||
@@ -156,11 +111,7 @@ const NeuralNetwork = ({dream}: { dream: Dream }) => {
|
||||
const pulseIntensity = 0.1 + (chipInput.bewegung[i % chipInput.bewegung.length] * 2);
|
||||
|
||||
nodes.push({
|
||||
position: [x, y, z] as [number, number, number],
|
||||
color,
|
||||
scale,
|
||||
pulseSpeed,
|
||||
pulseIntensity
|
||||
position: [x, y, z] as [number, number, number], color, scale, pulseSpeed, pulseIntensity
|
||||
});
|
||||
}
|
||||
|
||||
@@ -194,74 +145,50 @@ const NeuralNetwork = ({dream}: { dream: Dream }) => {
|
||||
return connections;
|
||||
}, [nodes, dream]);
|
||||
|
||||
return (
|
||||
<>
|
||||
return (<>
|
||||
{/* Render all nodes */}
|
||||
{nodes.map((node, index) => (
|
||||
<NeuralNode
|
||||
key={`node-${index}`}
|
||||
{nodes.map((node, index) => (<NeuralNode
|
||||
key={index}
|
||||
position={node.position}
|
||||
color={node.color}
|
||||
scale={node.scale}
|
||||
pulseSpeed={node.pulseSpeed}
|
||||
pulseIntensity={node.pulseIntensity}
|
||||
/>
|
||||
))}
|
||||
/>))}
|
||||
|
||||
{/* Render all connections */}
|
||||
{connections.map((connection, index) => (
|
||||
<NeuralConnection
|
||||
key={`connection-${index}`}
|
||||
{connections.map((connection, index) => (<NeuralConnection
|
||||
key={index}
|
||||
start={connection.start}
|
||||
end={connection.end}
|
||||
color={connection.color}
|
||||
thickness={connection.thickness}
|
||||
pulseSpeed={connection.pulseSpeed}
|
||||
pulseIntensity={connection.pulseIntensity}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
/>))}
|
||||
</>);
|
||||
};
|
||||
|
||||
// Custom controls component that switches between OrbitControls and DeviceOrientationControls
|
||||
const Controls = ({useDeviceOrientation}: { useDeviceOrientation: boolean }) => {
|
||||
const {camera} = useThree();
|
||||
const controlsRef = useRef<DeviceOrientationControls | null>(null);
|
||||
// Camera Controls component that handles XR and non-XR states
|
||||
const CameraControls = () => {
|
||||
const {session} = useXR();
|
||||
|
||||
useEffect(() => {
|
||||
if (useDeviceOrientation) {
|
||||
// Create DeviceOrientationControls
|
||||
controlsRef.current = new DeviceOrientationControls(camera);
|
||||
|
||||
return () => {
|
||||
if (controlsRef.current) {
|
||||
controlsRef.current.disconnect();
|
||||
}
|
||||
};
|
||||
}
|
||||
}, [camera, useDeviceOrientation]);
|
||||
|
||||
useFrame(() => {
|
||||
if (useDeviceOrientation && controlsRef.current) {
|
||||
controlsRef.current.update();
|
||||
}
|
||||
});
|
||||
|
||||
// If not using device orientation, use OrbitControls
|
||||
if (!useDeviceOrientation) {
|
||||
return (
|
||||
<OrbitControls
|
||||
enableZoom={true}
|
||||
enablePan={true}
|
||||
enableRotate={true}
|
||||
autoRotate={true}
|
||||
autoRotateSpeed={0.5}
|
||||
/>
|
||||
);
|
||||
// Only enable OrbitControls when NOT in VR session
|
||||
if (session) {
|
||||
return null; // Let XR handle camera in VR mode
|
||||
}
|
||||
|
||||
return null;
|
||||
return (<OrbitControls
|
||||
enablePan={true}
|
||||
enableZoom={true}
|
||||
enableRotate={true}
|
||||
enableDamping={true}
|
||||
dampingFactor={0.05}
|
||||
minDistance={5}
|
||||
maxDistance={50}
|
||||
maxPolarAngle={Math.PI}
|
||||
minPolarAngle={0}
|
||||
/>);
|
||||
};
|
||||
|
||||
// Main DreamVR component
|
||||
@@ -270,175 +197,53 @@ interface DreamVRProps {
|
||||
height?: string;
|
||||
}
|
||||
|
||||
// Create XR store outside the component to avoid recreation on each render
|
||||
const store = createXRStore();
|
||||
|
||||
const DreamVR: React.FC<DreamVRProps> = ({dream, height = '500px'}) => {
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
const [useDeviceOrientation, setUseDeviceOrientation] = useState(false);
|
||||
const [deviceOrientationPermission, setDeviceOrientationPermission] = useState<'granted' | 'denied' | 'unknown'>('unknown');
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Function to toggle fullscreen
|
||||
const toggleFullscreen = () => {
|
||||
if (!document.fullscreenElement) {
|
||||
// Enter fullscreen
|
||||
if (containerRef.current?.requestFullscreen) {
|
||||
containerRef.current.requestFullscreen()
|
||||
.then(() => setIsFullscreen(true))
|
||||
.catch(err => console.error(`Error attempting to enable fullscreen: ${err.message}`));
|
||||
}
|
||||
} else {
|
||||
// Exit fullscreen
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen()
|
||||
.then(() => setIsFullscreen(false))
|
||||
.catch(err => console.error(`Error attempting to exit fullscreen: ${err.message}`));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Function to request device orientation permission
|
||||
const requestDeviceOrientationPermission = () => {
|
||||
// Cast window to our extended interface
|
||||
const windowWithOrientation = window as unknown as WindowWithDeviceOrientation;
|
||||
|
||||
// Check if DeviceOrientationEvent is available and if requestPermission is a function
|
||||
if (typeof windowWithOrientation.DeviceOrientationEvent !== 'undefined' &&
|
||||
typeof windowWithOrientation.DeviceOrientationEvent.requestPermission === 'function') {
|
||||
// iOS 13+ requires permission
|
||||
windowWithOrientation.DeviceOrientationEvent.requestPermission()
|
||||
.then((permissionState: string) => {
|
||||
if (permissionState === 'granted') {
|
||||
setDeviceOrientationPermission('granted');
|
||||
setUseDeviceOrientation(true);
|
||||
} else {
|
||||
setDeviceOrientationPermission('denied');
|
||||
setUseDeviceOrientation(false);
|
||||
}
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
console.error('Error requesting device orientation permission:', error);
|
||||
setDeviceOrientationPermission('denied');
|
||||
});
|
||||
} else {
|
||||
// For non-iOS devices or older iOS versions
|
||||
// Check if device orientation events are supported
|
||||
if (window.DeviceOrientationEvent) {
|
||||
setDeviceOrientationPermission('granted');
|
||||
setUseDeviceOrientation(true);
|
||||
} else {
|
||||
setDeviceOrientationPermission('denied');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Effect to handle fullscreen change
|
||||
useEffect(() => {
|
||||
const handleFullscreenChange = () => {
|
||||
setIsFullscreen(!!document.fullscreenElement);
|
||||
};
|
||||
|
||||
document.addEventListener('fullscreenchange', handleFullscreenChange);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Check if device is mobile
|
||||
useEffect(() => {
|
||||
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
||||
if (isMobile) {
|
||||
// For mobile devices, we'll show the device orientation button
|
||||
setDeviceOrientationPermission('unknown');
|
||||
} else {
|
||||
// For desktop, we'll use orbit controls
|
||||
setDeviceOrientationPermission('denied');
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Only render VR for dream with chip input type
|
||||
if (dream.input.inputType !== 'chip') {
|
||||
return (
|
||||
<div className="flex items-center justify-center" style={{height}}>
|
||||
<p className="text-center text-gray-500">
|
||||
VR-Visualisierung ist nur für Träume mit Chip-Eingabe verfügbar.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
return (<div className="flex items-center justify-center h-full text-gray-500">
|
||||
VR-Visualisierung ist nur für Träume mit Chip-Eingabe verfügbar.
|
||||
</div>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={containerRef} style={{height, width: '100%', position: 'relative'}}>
|
||||
{/* Controls */}
|
||||
<div className="absolute top-2 right-2 z-10 flex space-x-2">
|
||||
<button
|
||||
onClick={toggleFullscreen}
|
||||
className="p-2 bg-white/20 backdrop-blur-sm rounded-full text-white hover:bg-white/30 transition-colors"
|
||||
aria-label={isFullscreen ? "Exit Fullscreen" : "Enter Fullscreen"}
|
||||
>
|
||||
{isFullscreen ? (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path
|
||||
d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path
|
||||
d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
return (<div ref={containerRef} className="relative w-full" style={{height}}>
|
||||
{/* VR Entry Button */}
|
||||
<button
|
||||
onClick={() => store.enterVR()}
|
||||
className="absolute top-4 right-4 z-10 p-2 bg-white/20 backdrop-blur-sm rounded-full text-white hover:bg-white/30 transition-colors"
|
||||
aria-label="Enter VR"
|
||||
>
|
||||
🥽 VR
|
||||
</button>
|
||||
|
||||
{deviceOrientationPermission === 'unknown' && (
|
||||
<button
|
||||
onClick={requestDeviceOrientationPermission}
|
||||
className="p-2 bg-white/20 backdrop-blur-sm rounded-full text-white hover:bg-white/30 transition-colors"
|
||||
aria-label="Enable Device Orientation"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<rect x="5" y="2" width="14" height="20" rx="2" ry="2"/>
|
||||
<line x1="12" y1="18" x2="12" y2="18"/>
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
<Canvas camera={{position: [0, 0, 10], fov: 60}}>
|
||||
<XR store={store}>
|
||||
{/* Camera setup */}
|
||||
<PerspectiveCamera makeDefault position={[0, 0, 10]}/>
|
||||
|
||||
{deviceOrientationPermission === 'granted' && (
|
||||
<button
|
||||
onClick={() => setUseDeviceOrientation(!useDeviceOrientation)}
|
||||
className={`p-2 backdrop-blur-sm rounded-full text-white transition-colors ${useDeviceOrientation ? 'bg-white/40' : 'bg-white/20 hover:bg-white/30'}`}
|
||||
aria-label={useDeviceOrientation ? "Disable Device Orientation" : "Enable Device Orientation"}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<rect x="5" y="2" width="14" height="20" rx="2" ry="2"/>
|
||||
<path d="M12 18h.01"/>
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Canvas shadows>
|
||||
{/* Camera setup */}
|
||||
<PerspectiveCamera makeDefault position={[0, 0, 30]}/>
|
||||
{/* Camera Controls - only active outside VR */}
|
||||
<CameraControls/>
|
||||
|
||||
{/* Controls - either OrbitControls or DeviceOrientationControls */}
|
||||
<Controls useDeviceOrientation={useDeviceOrientation}/>
|
||||
{/* Lighting */}
|
||||
<ambientLight intensity={0.2}/>
|
||||
<pointLight position={[10, 10, 10]} intensity={0.8}/>
|
||||
<pointLight position={[-10, -10, -10]} intensity={0.4}/>
|
||||
|
||||
{/* Lighting */}
|
||||
<ambientLight intensity={0.5}/>
|
||||
<pointLight position={[10, 10, 10]} intensity={1}/>
|
||||
<pointLight position={[-10, -10, -10]} intensity={0.5}/>
|
||||
{/* Neural network visualization */}
|
||||
<NeuralNetwork dream={dream}/>
|
||||
|
||||
{/* Neural network visualization */}
|
||||
<NeuralNetwork dream={dream}/>
|
||||
|
||||
{/* Background */}
|
||||
<color attach="background" args={['#000']}/>
|
||||
{/* Background */}
|
||||
<mesh scale={[100, 100, 100]}>
|
||||
<sphereGeometry args={[1, 32, 32]}/>
|
||||
<meshBasicMaterial color="#000012" side={THREE.BackSide}/>
|
||||
</mesh>
|
||||
</XR>
|
||||
</Canvas>
|
||||
</div>
|
||||
);
|
||||
</div>);
|
||||
};
|
||||
|
||||
export default DreamVR;
|
@@ -215,7 +215,7 @@ export const mockDreams: Dream[] = [
|
||||
inputType: 'image',
|
||||
img: 'in_11.png',
|
||||
imgAlt: 'Ein Garten aus leuchtenden Codezeilen und holografischen Pflanzen',
|
||||
description: 'Du hast von einem Garten geträumt, in dem digitale und natürliche Elemente harmonisch verschmelzen. Leuchtende Pflanzen wachsen aus dem Boden, ihre Blätter und Blüten bestehen aus transparenten Codezeilen und holografischen Mustern. Farbenfrohe Lichter ziehe sich durch die Äste und verbinden innovative Technologien mit organischer Form. Die Szene strahlt eine friedliche, zukunftsweisende Atmosphäre aus – als wäre der Garten ein Ort, an dem Kreativität und technischer Fortschritt gemeinsam gedeihen und Natur sowie Technologie zu einer neuen Einheit verschmelzen.'
|
||||
description: 'Leuchtende, halb-transparente Pflanzen mit holografischen Mustern wachsen in einem futuristischen Garten, in dem organische Formen und digitale Technologien harmonisch verschmelzen. Farbenfrohe Lichter ziehen sich an den Ästen entlang und schaffen eine friedliche, zukunftsweisende Atmosphäre.'
|
||||
} as ImageInput,
|
||||
ai: {
|
||||
interpretation: 'Der digitale Garten verkörpert deine kreative Verbindung von Natur und Technologie. Er zeigt, wie du Innovation als organischen, wachsenden Prozess betrachtest.',
|
||||
|
@@ -183,6 +183,7 @@ export default function DreamPage() {
|
||||
<div className="flex justify-center px-2">
|
||||
<video
|
||||
controls
|
||||
loop
|
||||
src={`/assets/dreams/videos/${dream.ai.video}`}
|
||||
className="max-w-full w-full rounded-lg shadow-lg object-contain mx-auto"
|
||||
style={{maxHeight: '70vh'}}
|
||||
@@ -219,6 +220,7 @@ export default function DreamPage() {
|
||||
<div className="flex justify-center">
|
||||
<video
|
||||
controls
|
||||
loop
|
||||
src={`/assets/dreams/videos/${dream.ai.video}`}
|
||||
className="max-w-full w-full rounded-lg shadow-lg object-contain mx-auto"
|
||||
style={{maxHeight: '70vh'}}
|
||||
|
@@ -13,57 +13,60 @@ const ProfilePage: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="page p-4 pb-20">
|
||||
{/* Profile Card */}
|
||||
<div
|
||||
className="dreamy-border rounded-xl p-6 max-w-md mx-auto overflow-hidden"
|
||||
className="dreamy-border rounded-xl p-6 max-w-md mx-auto overflow-hidden mb-10"
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, rgba(166, 77, 255, 0.25), rgba(213, 0, 249, 0.25))',
|
||||
backdropFilter: 'blur(10px)',
|
||||
boxShadow: '0 10px 30px var(--shadow), inset 0 0 15px rgba(166, 77, 255, 0.2)'
|
||||
}}>
|
||||
{/* Profile Picture */}
|
||||
{/* Profile Picture */}
|
||||
<div className="w-28 h-28 sm:w-36 sm:h-36 rounded-full overflow-hidden mb-6 mx-auto relative"
|
||||
style={{
|
||||
background: 'var(--accent-gradient)',
|
||||
padding: '4px',
|
||||
boxShadow: '0 0 15px var(--shadow)'
|
||||
}}>
|
||||
<img
|
||||
src={profileUser ? `/assets/profiles/${profileUser.profilePicture}` : `https://ui-avatars.com/api/?name=${defaultName}&background=random&color=fff&size=128`}
|
||||
alt={profileUser ? profileUser.name : defaultName}
|
||||
className="w-full h-full object-cover rounded-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* User Information */}
|
||||
<div className="text-center w-full">
|
||||
<h2 className="text-xl sm:text-2xl mb-2 break-words dreamy-text font-bold">{profileUser ? profileUser.name : defaultName}</h2>
|
||||
<p className="text-text-muted mb-8 break-words">{profileUser ? profileUser.email : defaultEmail}</p>
|
||||
|
||||
<div className="grid grid-cols-2 gap-6 mt-6 text-center">
|
||||
<div
|
||||
className="dreamy-border rounded-lg p-4"
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, rgba(166, 77, 255, 0.35), rgba(213, 0, 249, 0.35))',
|
||||
boxShadow: '0 0 15px var(--shadow)',
|
||||
backdropFilter: 'blur(5px)'
|
||||
}}
|
||||
>
|
||||
<p className="dreamy-text text-2xl sm:text-3xl font-bold">{profileUser ? profileUser.dreamCount : defaultDreamCount}</p>
|
||||
<p className="text-sm sm:text-base font-medium" style={{color: 'var(--text)'}}>Dreams</p>
|
||||
<img
|
||||
src={profileUser ? `/assets/profiles/${profileUser.profilePicture}` : `https://ui-avatars.com/api/?name=${defaultName}&background=random&color=fff&size=128`}
|
||||
alt={profileUser ? profileUser.name : defaultName}
|
||||
className="w-full h-full object-cover rounded-full"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="dreamy-border rounded-lg p-4"
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, rgba(166, 77, 255, 0.35), rgba(213, 0, 249, 0.35))',
|
||||
boxShadow: '0 0 15px var(--shadow)',
|
||||
backdropFilter: 'blur(5px)'
|
||||
}}
|
||||
>
|
||||
<p className="dreamy-text text-2xl sm:text-3xl font-bold">{profileUser ? profileUser.streakDays : defaultStreakDays}</p>
|
||||
<p className="text-sm sm:text-base font-medium" style={{color: 'var(--text)'}}>Days Streak</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* User Information */}
|
||||
<div className="text-center w-full">
|
||||
<h2 className="text-xl sm:text-2xl mb-2 break-words dreamy-text font-bold">{profileUser ? profileUser.name : defaultName}</h2>
|
||||
<p className="text-text-muted mb-8 break-words">{profileUser ? profileUser.email : defaultEmail}</p>
|
||||
|
||||
<div className="grid grid-cols-2 gap-6 mt-6 text-center">
|
||||
<div
|
||||
className="dreamy-border rounded-lg p-4"
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, rgba(166, 77, 255, 0.35), rgba(213, 0, 249, 0.35))',
|
||||
boxShadow: '0 0 15px var(--shadow)',
|
||||
backdropFilter: 'blur(5px)'
|
||||
}}
|
||||
>
|
||||
<p className="dreamy-text text-2xl sm:text-3xl font-bold">{profileUser ? profileUser.dreamCount : defaultDreamCount}</p>
|
||||
<p className="text-sm sm:text-base font-medium" style={{color: 'var(--text)'}}>Dreams</p>
|
||||
</div>
|
||||
<div
|
||||
className="dreamy-border rounded-lg p-4"
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, rgba(166, 77, 255, 0.35), rgba(213, 0, 249, 0.35))',
|
||||
boxShadow: '0 0 15px var(--shadow)',
|
||||
backdropFilter: 'blur(5px)'
|
||||
}}
|
||||
>
|
||||
<p className="dreamy-text text-2xl sm:text-3xl font-bold">{profileUser ? profileUser.streakDays : defaultStreakDays}</p>
|
||||
<p className="text-sm sm:text-base font-medium" style={{color: 'var(--text)'}}>Days Streak</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Account Settings (moved back into profile card) */}
|
||||
<div className="mt-10">
|
||||
<h3 className="dreamy-text text-lg mb-4 font-bold">Account Settings</h3>
|
||||
<div
|
||||
@@ -84,8 +87,8 @@ const ProfilePage: React.FC = () => {
|
||||
opacity: '0.9',
|
||||
boxShadow: 'inset 0 0 5px rgba(166, 77, 255, 0.5)'
|
||||
}}></span>
|
||||
</label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex justify-between items-center py-3 flex-wrap">
|
||||
<span className="mr-2 font-medium" style={{color: 'var(--text)'}}>Privacy</span>
|
||||
<label className="relative inline-block w-12 h-6">
|
||||
@@ -96,14 +99,58 @@ const ProfilePage: React.FC = () => {
|
||||
opacity: '0.9',
|
||||
boxShadow: 'inset 0 0 5px rgba(166, 77, 255, 0.5)'
|
||||
}}></span>
|
||||
</label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Project Information Card */}
|
||||
<div
|
||||
className="dreamy-border rounded-xl p-6 max-w-md mx-auto overflow-hidden"
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, rgba(166, 77, 255, 0.25), rgba(213, 0, 249, 0.25))',
|
||||
backdropFilter: 'blur(10px)',
|
||||
boxShadow: '0 10px 30px var(--shadow), inset 0 0 15px rgba(166, 77, 255, 0.2)'
|
||||
}}>
|
||||
<h3 className="dreamy-text text-lg mb-4 font-bold text-center">Project Information</h3>
|
||||
<div
|
||||
className="dreamy-border rounded-lg p-5 text-left"
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, rgba(166, 77, 255, 0.35), rgba(213, 0, 249, 0.35))',
|
||||
boxShadow: '0 0 15px var(--shadow)',
|
||||
backdropFilter: 'blur(5px)'
|
||||
}}
|
||||
>
|
||||
<div className="py-2 border-b border-purple-100/30">
|
||||
<p className="font-medium mb-1" style={{color: 'var(--text)'}}>Gitea Link:</p>
|
||||
<a
|
||||
href="https://gitea.puchstein.bayern/mpuchstein/REMind"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm break-words hover:underline"
|
||||
style={{color: 'var(--accent)'}}
|
||||
>
|
||||
https://gitea.puchstein.bayern/mpuchstein/REMind
|
||||
</a>
|
||||
</div>
|
||||
<div className="py-2 border-b border-purple-100/30">
|
||||
<p className="font-medium mb-1" style={{color: 'var(--text)'}}>Gruppenmitglieder:</p>
|
||||
<p className="text-sm">Kim Anhäuser, Matthias Puchstein, Anya Zell</p>
|
||||
</div>
|
||||
<div className="py-2 border-b border-purple-100/30">
|
||||
<p className="font-medium mb-1" style={{color: 'var(--text)'}}>Modul:</p>
|
||||
<p className="text-sm">Einführung in die Text und Medienanalyse bei Prof. Dr. Rettiner und Dr. phil.
|
||||
Ripoll</p>
|
||||
</div>
|
||||
<div className="py-2">
|
||||
<p className="font-medium mb-1" style={{color: 'var(--text)'}}>Genutzte AI:</p>
|
||||
<p className="text-sm">Perplexity, Junie, Veo, Imgen, Suno</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfilePage;
|
||||
export default ProfilePage;
|
@@ -11,8 +11,8 @@ import * as d3 from 'd3';
|
||||
const dreamTypesData = [
|
||||
{type: "Angstträume", percentage: 34, color: "#ef4444", darkColor: "#dc2626", note: "Global durchschnittlich"},
|
||||
{type: "Soziale Träume", percentage: 28, color: "#3b82f6", darkColor: "#2563eb", note: "Kulturelle Variationen"},
|
||||
{type: "Sexuelle Träume", percentage: 12, color: "#ec4899", darkColor: "#db2777", note: "Altersabhängig"},
|
||||
{type: "Flugträume", percentage: 18, color: "#a855f7", darkColor: "#9333ea", note: "Persönlichkeitsabhängig"},
|
||||
{type: "Sexuelle Träume", percentage: 12, color: "#ec4899", darkColor: "#db2777", note: "Altersabhängig"},
|
||||
{type: "Verfolgungsträume", percentage: 8, color: "#f97316", darkColor: "#ea580c", note: "Stresskorreliert"}
|
||||
];
|
||||
|
||||
@@ -392,23 +392,6 @@ export default function Technology() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<div className="w-4 h-4 bg-pink-500 dark:bg-pink-600 mr-2"></div>
|
||||
<div className="flex-1">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm font-bold">Sexuelle Träume</span>
|
||||
<span className="text-sm">12%</span>
|
||||
</div>
|
||||
<div
|
||||
className="relative h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="absolute top-0 left-0 h-full bg-pink-500 dark:bg-pink-600 rounded-full"
|
||||
style={{width: '12%'}}></div>
|
||||
</div>
|
||||
<p className="text-xs mt-1" style={getTextStyle('muted')}>Altersabhängig</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<div className="w-4 h-4 bg-purple-500 dark:bg-purple-600 mr-2"></div>
|
||||
<div className="flex-1">
|
||||
@@ -427,6 +410,23 @@ export default function Technology() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<div className="w-4 h-4 bg-pink-500 dark:bg-pink-600 mr-2"></div>
|
||||
<div className="flex-1">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm font-bold">Sexuelle Träume</span>
|
||||
<span className="text-sm">12%</span>
|
||||
</div>
|
||||
<div
|
||||
className="relative h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="absolute top-0 left-0 h-full bg-pink-500 dark:bg-pink-600 rounded-full"
|
||||
style={{width: '12%'}}></div>
|
||||
</div>
|
||||
<p className="text-xs mt-1" style={getTextStyle('muted')}>Altersabhängig</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<div className="w-4 h-4 bg-orange-500 dark:bg-orange-600 mr-2"></div>
|
||||
<div className="flex-1">
|
||||
|
Reference in New Issue
Block a user