Compare commits
10 Commits
8403a17636
...
main
Author | SHA1 | Date | |
---|---|---|---|
4ccbd28c4f | |||
618e5b442e | |||
936a2fa1ee | |||
2165a63bfb | |||
2d294de69a | |||
7754370a70 | |||
2edf6b3f1f | |||
8b0996781e | |||
bb714e6fa2 | |||
75f516b408 |
301
README.md
301
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
|
**Project Aim:** To showcase the potential of combining dream journaling with AI-powered analysis and VR visualization,
|
||||||
Record dreams as text entries, voice memos, or sketches—UI only, no persistence.
|
creating an engaging platform for understanding the subconscious mind and exploring the societal implications of dream
|
||||||
- **(Mockup)** AI Analysis
|
research.
|
||||||
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.
|
|
||||||
|
|
||||||
## Tech Stack
|
## ✨ Key Features
|
||||||
|
|
||||||
- **Framework:** React + TypeScript
|
### 🌙 Multi-Modal Dream Recording
|
||||||
- **Bundler:** Vite
|
|
||||||
- **Styling:** Tailwind CSS
|
|
||||||
- **Routing:** React Router Dom
|
|
||||||
- **Icons:** React Icons
|
|
||||||
- **3D \& VR:** @react-three/fiber
|
|
||||||
- **Linting:** ESLint
|
|
||||||
|
|
||||||
## 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
|
||||||
|
|
||||||
|
### 🎮 VR Dreamscapes
|
||||||
|
|
||||||
|
- **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
|
||||||
|
|
||||||
|
### 📊 Advanced Analytics
|
||||||
|
|
||||||
|
- **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
|
||||||
|
|
||||||
|
### 🗂️ Dream Archive & Management
|
||||||
|
|
||||||
|
- **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
|
||||||
|
|
||||||
|
### 🌐 Community Features
|
||||||
|
|
||||||
|
- **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
|
||||||
|
|
||||||
|
```
|
||||||
|
REMind/
|
||||||
|
├── public/ # Static assets (favicon, etc.)
|
||||||
|
├── src/
|
||||||
|
│ ├── 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
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Node.js 18+
|
||||||
|
- npm or yarn package manager
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
1. **Clone the repository**
|
||||||
```bash
|
```bash
|
||||||
git clone https://gitea.puchstein.bayern/mpuchstein/REMind.git
|
git clone https://gitea.puchstein.bayern/mpuchstein/REMind.git
|
||||||
cd REMind
|
cd REMind
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Install dependencies
|
2. **Install dependencies**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Run development server
|
3. **Start development server**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Open `http://localhost:5173` in your browser. 4. Build for production (static demo)
|
4. **Open in browser**
|
||||||
|
```
|
||||||
```bash
|
http://localhost:5173
|
||||||
npm run build
|
|
||||||
npm run preview
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Project Structure
|
### Available Scripts
|
||||||
|
|
||||||
```
|
- `npm run dev` - Start development server
|
||||||
.
|
- `npm run build` - Build for production
|
||||||
├── public/ # Static assets
|
- `npm run preview` - Preview production build
|
||||||
├── src/
|
- `npm run lint` - Run ESLint
|
||||||
│ ├── 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
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage \& Demo
|
## 🎯 Usage Guide
|
||||||
|
|
||||||
- **Start Dream Journal** — click “🌙 Lass die Magie beginnen” to open the mock dream entry form.
|
### Getting Started
|
||||||
- **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.
|
|
||||||
|
|
||||||
**Reminder:** REMind is a design-and-concept prototype only. Enjoy exploring the features—no real data or AI integration under the hood!
|
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.
67
package-lock.json
generated
67
package-lock.json
generated
@@ -20,15 +20,18 @@
|
|||||||
"react-slick": "^0.30.3",
|
"react-slick": "^0.30.3",
|
||||||
"slick-carousel": "^1.8.1",
|
"slick-carousel": "^1.8.1",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
"three": "^0.178.0"
|
"three": "^0.178.0",
|
||||||
|
"webxr-polyfill": "^2.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.29.0",
|
"@eslint/js": "^9.29.0",
|
||||||
"@types/d3": "^7.4.3",
|
"@types/d3": "^7.4.3",
|
||||||
|
"@types/node": "^24.0.14",
|
||||||
"@types/react": "^19.1.8",
|
"@types/react": "^19.1.8",
|
||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
"@types/react-slick": "^0.23.13",
|
"@types/react-slick": "^0.23.13",
|
||||||
"@types/three": "^0.178.1",
|
"@types/three": "^0.178.1",
|
||||||
|
"@types/webxr": "^0.5.22",
|
||||||
"@vitejs/plugin-react": "^4.5.2",
|
"@vitejs/plugin-react": "^4.5.2",
|
||||||
"eslint": "^9.29.0",
|
"eslint": "^9.29.0",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
@@ -2338,6 +2341,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@types/offscreencanvas": {
|
||||||
"version": "2019.7.3",
|
"version": "2019.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
|
||||||
@@ -2963,6 +2976,17 @@
|
|||||||
],
|
],
|
||||||
"license": "CC-BY-4.0"
|
"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": {
|
"node_modules/case-anything": {
|
||||||
"version": "2.1.13",
|
"version": "2.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.13.tgz",
|
||||||
@@ -4025,6 +4049,12 @@
|
|||||||
"integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==",
|
"integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/glob-parent": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
@@ -4785,6 +4815,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
@@ -5757,6 +5793,13 @@
|
|||||||
"typescript": ">=4.8.4 <5.9.0"
|
"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": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
||||||
@@ -5927,6 +5970,28 @@
|
|||||||
"integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==",
|
"integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
@@ -22,15 +22,18 @@
|
|||||||
"react-slick": "^0.30.3",
|
"react-slick": "^0.30.3",
|
||||||
"slick-carousel": "^1.8.1",
|
"slick-carousel": "^1.8.1",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
"three": "^0.178.0"
|
"three": "^0.178.0",
|
||||||
|
"webxr-polyfill": "^2.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.29.0",
|
"@eslint/js": "^9.29.0",
|
||||||
"@types/d3": "^7.4.3",
|
"@types/d3": "^7.4.3",
|
||||||
|
"@types/node": "^24.0.14",
|
||||||
"@types/react": "^19.1.8",
|
"@types/react": "^19.1.8",
|
||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
"@types/react-slick": "^0.23.13",
|
"@types/react-slick": "^0.23.13",
|
||||||
"@types/three": "^0.178.1",
|
"@types/three": "^0.178.1",
|
||||||
|
"@types/webxr": "^0.5.22",
|
||||||
"@vitejs/plugin-react": "^4.5.2",
|
"@vitejs/plugin-react": "^4.5.2",
|
||||||
"eslint": "^9.29.0",
|
"eslint": "^9.29.0",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
|
@@ -24,7 +24,7 @@ export const DreamCardCompact = ({dream, index, showUser = false, user}: DreamCa
|
|||||||
border: '1px solid rgba(166, 77, 255, 0.1)'
|
border: '1px solid rgba(166, 77, 255, 0.1)'
|
||||||
}}>
|
}}>
|
||||||
{dream.input.inputType === 'chip' && (
|
{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)'}}>
|
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"/>
|
<FaMicrochip className="w-6 h-6 md:w-8 md:h-8 text-pink-300 opacity-90 hover:opacity-100"/>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -16,6 +16,10 @@ export const EEGChart: React.FC<{ chipInput: ChipInput }> = ({chipInput}) => {
|
|||||||
{name: 'Delta', values: chipInput.eeg.delta}
|
{name: 'Delta', values: chipInput.eeg.delta}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Function to render the chart
|
||||||
|
const renderChart = () => {
|
||||||
|
if (!chartRef.current) return;
|
||||||
|
|
||||||
// Clear previous chart
|
// Clear previous chart
|
||||||
d3.select(chartRef.current).selectAll('*').remove();
|
d3.select(chartRef.current).selectAll('*').remove();
|
||||||
|
|
||||||
@@ -98,14 +102,15 @@ export const EEGChart: React.FC<{ chipInput: ChipInput }> = ({chipInput}) => {
|
|||||||
.attr('dy', '0.32em')
|
.attr('dy', '0.32em')
|
||||||
.style('fill', 'var(--text)')
|
.style('fill', 'var(--text)')
|
||||||
.text(d => d.name);
|
.text(d => d.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial render
|
||||||
|
renderChart();
|
||||||
|
|
||||||
// Handle resize
|
// Handle resize
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
if (!chartRef.current) return;
|
if (!chartRef.current) return;
|
||||||
|
renderChart();
|
||||||
// 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
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
@@ -127,6 +132,10 @@ export const VitalsChart: React.FC<{ chipInput: ChipInput }> = ({chipInput}) =>
|
|||||||
{name: 'HRV', values: chipInput.hrv}
|
{name: 'HRV', values: chipInput.hrv}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Function to render the chart
|
||||||
|
const renderChart = () => {
|
||||||
|
if (!chartRef.current) return;
|
||||||
|
|
||||||
// Clear previous chart
|
// Clear previous chart
|
||||||
d3.select(chartRef.current).selectAll('*').remove();
|
d3.select(chartRef.current).selectAll('*').remove();
|
||||||
|
|
||||||
@@ -211,14 +220,15 @@ export const VitalsChart: React.FC<{ chipInput: ChipInput }> = ({chipInput}) =>
|
|||||||
.attr('dy', '0.32em')
|
.attr('dy', '0.32em')
|
||||||
.style('fill', 'var(--text)')
|
.style('fill', 'var(--text)')
|
||||||
.text(d => d.name);
|
.text(d => d.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial render
|
||||||
|
renderChart();
|
||||||
|
|
||||||
// Handle resize
|
// Handle resize
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
if (!chartRef.current) return;
|
if (!chartRef.current) return;
|
||||||
|
renderChart();
|
||||||
// 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
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
@@ -239,6 +249,10 @@ export const MovementChart: React.FC<{ chipInput: ChipInput }> = ({chipInput}) =
|
|||||||
{name: 'Bewegung', values: chipInput.bewegung.map(v => v * 100)} // Scale movement for better visibility
|
{name: 'Bewegung', values: chipInput.bewegung.map(v => v * 100)} // Scale movement for better visibility
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Function to render the chart
|
||||||
|
const renderChart = () => {
|
||||||
|
if (!chartRef.current) return;
|
||||||
|
|
||||||
// Clear previous chart
|
// Clear previous chart
|
||||||
d3.select(chartRef.current).selectAll('*').remove();
|
d3.select(chartRef.current).selectAll('*').remove();
|
||||||
|
|
||||||
@@ -317,14 +331,15 @@ export const MovementChart: React.FC<{ chipInput: ChipInput }> = ({chipInput}) =
|
|||||||
.attr('dy', '0.32em')
|
.attr('dy', '0.32em')
|
||||||
.style('fill', 'var(--text)')
|
.style('fill', 'var(--text)')
|
||||||
.text(d => d.name);
|
.text(d => d.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial render
|
||||||
|
renderChart();
|
||||||
|
|
||||||
// Handle resize
|
// Handle resize
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
if (!chartRef.current) return;
|
if (!chartRef.current) return;
|
||||||
|
renderChart();
|
||||||
// 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
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
|
@@ -1,26 +1,13 @@
|
|||||||
import React, {useEffect, useMemo, useRef, useState} from 'react';
|
import React, {useMemo, useRef} from 'react';
|
||||||
import {Canvas, useFrame, useThree} from '@react-three/fiber';
|
import {Canvas, useFrame} from '@react-three/fiber';
|
||||||
|
import {createXRStore, useXR, XR} from '@react-three/xr';
|
||||||
import {OrbitControls, PerspectiveCamera} from '@react-three/drei';
|
import {OrbitControls, PerspectiveCamera} from '@react-three/drei';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import type Dream from '../types/Dream';
|
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
|
// Neural Node component representing a synapse in the neural network
|
||||||
const NeuralNode = ({position, color, scale, pulseSpeed, pulseIntensity}: {
|
const NeuralNode = ({position, color, scale, pulseSpeed, pulseIntensity}: {
|
||||||
position: [number, number, number],
|
position: [number, number, number], color: string, scale: number, pulseSpeed: number, pulseIntensity: number
|
||||||
color: string,
|
|
||||||
scale: number,
|
|
||||||
pulseSpeed: number,
|
|
||||||
pulseIntensity: number
|
|
||||||
}) => {
|
}) => {
|
||||||
const nodeRef = useRef<THREE.Mesh>(null);
|
const nodeRef = useRef<THREE.Mesh>(null);
|
||||||
const initialScale = scale;
|
const initialScale = scale;
|
||||||
@@ -33,18 +20,10 @@ const NeuralNode = ({position, color, scale, pulseSpeed, pulseIntensity}: {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (<mesh ref={nodeRef} position={position}>
|
||||||
<mesh ref={nodeRef} position={position}>
|
<sphereGeometry args={[scale, 8, 8]}/>
|
||||||
<sphereGeometry args={[1, 32, 32]}/>
|
<meshStandardMaterial color={color} emissive={color} emissiveIntensity={0.2}/>
|
||||||
<meshStandardMaterial
|
</mesh>);
|
||||||
color={color}
|
|
||||||
emissive={color}
|
|
||||||
emissiveIntensity={0.5}
|
|
||||||
transparent
|
|
||||||
opacity={0.8}
|
|
||||||
/>
|
|
||||||
</mesh>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Neural Connection component representing connections between synapses
|
// 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
|
// Create a cylinder between two points
|
||||||
const direction = useMemo(() => {
|
const direction = useMemo(() => {
|
||||||
return new THREE.Vector3(
|
return new THREE.Vector3(end[0] - start[0], end[1] - start[1], end[2] - start[2]);
|
||||||
end[0] - start[0],
|
|
||||||
end[1] - start[1],
|
|
||||||
end[2] - start[2]
|
|
||||||
);
|
|
||||||
}, [start, end]);
|
}, [start, end]);
|
||||||
|
|
||||||
const length = useMemo(() => direction.length(), [direction]);
|
const length = useMemo(() => direction.length(), [direction]);
|
||||||
|
|
||||||
// Calculate rotation to align cylinder with direction
|
// Calculate rotation to align cylinder with direction
|
||||||
const rotationMatrix = useMemo(() => {
|
useMemo(() => {
|
||||||
const normalizedDirection = direction.clone().normalize();
|
const normalizedDirection = direction.clone().normalize();
|
||||||
const quaternion = new THREE.Quaternion();
|
const quaternion = new THREE.Quaternion();
|
||||||
quaternion.setFromUnitVectors(
|
quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), // Default cylinder orientation
|
||||||
new THREE.Vector3(0, 1, 0), // Default cylinder orientation
|
normalizedDirection);
|
||||||
normalizedDirection
|
|
||||||
);
|
|
||||||
return new THREE.Matrix4().makeRotationFromQuaternion(quaternion);
|
return new THREE.Matrix4().makeRotationFromQuaternion(quaternion);
|
||||||
}, [direction]);
|
}, [direction]);
|
||||||
|
|
||||||
// Calculate position (midpoint between start and end)
|
// Calculate position (midpoint between start and end)
|
||||||
const position = useMemo(() => {
|
const position = useMemo(() => {
|
||||||
return [
|
return [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2, (start[2] + end[2]) / 2] as [number, number, number];
|
||||||
(start[0] + end[0]) / 2,
|
|
||||||
(start[1] + end[1]) / 2,
|
|
||||||
(start[2] + end[2]) / 2
|
|
||||||
] as [number, number, number];
|
|
||||||
}, [start, end]);
|
}, [start, end]);
|
||||||
|
|
||||||
useFrame(({clock}) => {
|
useFrame(({clock}) => {
|
||||||
if (connectionRef.current) {
|
if (connectionRef.current) {
|
||||||
// Create a pulsing effect for the connection
|
// Create a pulsing effect for the connection
|
||||||
const pulse = Math.sin(clock.getElapsedTime() * pulseSpeed +
|
const pulse = Math.sin(clock.getElapsedTime() * pulseSpeed + (start[0] + start[1] + start[2])) * pulseIntensity + 1;
|
||||||
(start[0] + start[1] + start[2])) * pulseIntensity + 1;
|
|
||||||
connectionRef.current.scale.set(thickness * pulse, length / 2, thickness * pulse);
|
connectionRef.current.scale.set(thickness * pulse, length / 2, thickness * pulse);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (<mesh ref={connectionRef} position={position}>
|
||||||
<mesh
|
<cylinderGeometry args={[thickness, thickness, length]}/>
|
||||||
ref={connectionRef}
|
<meshStandardMaterial color={color} emissive={color} emissiveIntensity={0.1}/>
|
||||||
position={position}
|
</mesh>);
|
||||||
matrix={rotationMatrix}
|
|
||||||
>
|
|
||||||
<cylinderGeometry args={[thickness, thickness, length / 2, 8]}/>
|
|
||||||
<meshStandardMaterial
|
|
||||||
color={color}
|
|
||||||
emissive={color}
|
|
||||||
emissiveIntensity={0.5}
|
|
||||||
transparent
|
|
||||||
opacity={0.6}
|
|
||||||
/>
|
|
||||||
</mesh>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Neural Network component that generates nodes and connections
|
// 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);
|
const pulseIntensity = 0.1 + (chipInput.bewegung[i % chipInput.bewegung.length] * 2);
|
||||||
|
|
||||||
nodes.push({
|
nodes.push({
|
||||||
position: [x, y, z] as [number, number, number],
|
position: [x, y, z] as [number, number, number], color, scale, pulseSpeed, pulseIntensity
|
||||||
color,
|
|
||||||
scale,
|
|
||||||
pulseSpeed,
|
|
||||||
pulseIntensity
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,74 +145,50 @@ const NeuralNetwork = ({dream}: { dream: Dream }) => {
|
|||||||
return connections;
|
return connections;
|
||||||
}, [nodes, dream]);
|
}, [nodes, dream]);
|
||||||
|
|
||||||
return (
|
return (<>
|
||||||
<>
|
|
||||||
{/* Render all nodes */}
|
{/* Render all nodes */}
|
||||||
{nodes.map((node, index) => (
|
{nodes.map((node, index) => (<NeuralNode
|
||||||
<NeuralNode
|
key={index}
|
||||||
key={`node-${index}`}
|
|
||||||
position={node.position}
|
position={node.position}
|
||||||
color={node.color}
|
color={node.color}
|
||||||
scale={node.scale}
|
scale={node.scale}
|
||||||
pulseSpeed={node.pulseSpeed}
|
pulseSpeed={node.pulseSpeed}
|
||||||
pulseIntensity={node.pulseIntensity}
|
pulseIntensity={node.pulseIntensity}
|
||||||
/>
|
/>))}
|
||||||
))}
|
|
||||||
|
|
||||||
{/* Render all connections */}
|
{/* Render all connections */}
|
||||||
{connections.map((connection, index) => (
|
{connections.map((connection, index) => (<NeuralConnection
|
||||||
<NeuralConnection
|
key={index}
|
||||||
key={`connection-${index}`}
|
|
||||||
start={connection.start}
|
start={connection.start}
|
||||||
end={connection.end}
|
end={connection.end}
|
||||||
color={connection.color}
|
color={connection.color}
|
||||||
thickness={connection.thickness}
|
thickness={connection.thickness}
|
||||||
pulseSpeed={connection.pulseSpeed}
|
pulseSpeed={connection.pulseSpeed}
|
||||||
pulseIntensity={connection.pulseIntensity}
|
pulseIntensity={connection.pulseIntensity}
|
||||||
/>
|
/>))}
|
||||||
))}
|
</>);
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Custom controls component that switches between OrbitControls and DeviceOrientationControls
|
// Camera Controls component that handles XR and non-XR states
|
||||||
const Controls = ({useDeviceOrientation}: { useDeviceOrientation: boolean }) => {
|
const CameraControls = () => {
|
||||||
const {camera} = useThree();
|
const {session} = useXR();
|
||||||
const controlsRef = useRef<DeviceOrientationControls | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
// Only enable OrbitControls when NOT in VR session
|
||||||
if (useDeviceOrientation) {
|
if (session) {
|
||||||
// Create DeviceOrientationControls
|
return null; // Let XR handle camera in VR mode
|
||||||
controlsRef.current = new DeviceOrientationControls(camera);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (controlsRef.current) {
|
|
||||||
controlsRef.current.disconnect();
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
}, [camera, useDeviceOrientation]);
|
|
||||||
|
|
||||||
useFrame(() => {
|
return (<OrbitControls
|
||||||
if (useDeviceOrientation && controlsRef.current) {
|
|
||||||
controlsRef.current.update();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// If not using device orientation, use OrbitControls
|
|
||||||
if (!useDeviceOrientation) {
|
|
||||||
return (
|
|
||||||
<OrbitControls
|
|
||||||
enableZoom={true}
|
|
||||||
enablePan={true}
|
enablePan={true}
|
||||||
|
enableZoom={true}
|
||||||
enableRotate={true}
|
enableRotate={true}
|
||||||
autoRotate={true}
|
enableDamping={true}
|
||||||
autoRotateSpeed={0.5}
|
dampingFactor={0.05}
|
||||||
/>
|
minDistance={5}
|
||||||
);
|
maxDistance={50}
|
||||||
}
|
maxPolarAngle={Math.PI}
|
||||||
|
minPolarAngle={0}
|
||||||
return null;
|
/>);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Main DreamVR component
|
// Main DreamVR component
|
||||||
@@ -270,175 +197,53 @@ interface DreamVRProps {
|
|||||||
height?: string;
|
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 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);
|
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
|
// Only render VR for dream with chip input type
|
||||||
if (dream.input.inputType !== 'chip') {
|
if (dream.input.inputType !== 'chip') {
|
||||||
return (
|
return (<div className="flex items-center justify-center h-full text-gray-500">
|
||||||
<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.
|
VR-Visualisierung ist nur für Träume mit Chip-Eingabe verfügbar.
|
||||||
</p>
|
</div>);
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (<div ref={containerRef} className="relative w-full" style={{height}}>
|
||||||
<div ref={containerRef} style={{height, width: '100%', position: 'relative'}}>
|
{/* VR Entry Button */}
|
||||||
{/* Controls */}
|
|
||||||
<div className="absolute top-2 right-2 z-10 flex space-x-2">
|
|
||||||
<button
|
<button
|
||||||
onClick={toggleFullscreen}
|
onClick={() => store.enterVR()}
|
||||||
className="p-2 bg-white/20 backdrop-blur-sm rounded-full text-white hover:bg-white/30 transition-colors"
|
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={isFullscreen ? "Exit Fullscreen" : "Enter Fullscreen"}
|
aria-label="Enter VR"
|
||||||
>
|
>
|
||||||
{isFullscreen ? (
|
🥽 VR
|
||||||
<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>
|
</button>
|
||||||
|
|
||||||
{deviceOrientationPermission === 'unknown' && (
|
<Canvas camera={{position: [0, 0, 10], fov: 60}}>
|
||||||
<button
|
<XR store={store}>
|
||||||
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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{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 */}
|
{/* Camera setup */}
|
||||||
<PerspectiveCamera makeDefault position={[0, 0, 30]}/>
|
<PerspectiveCamera makeDefault position={[0, 0, 10]}/>
|
||||||
|
|
||||||
{/* Controls - either OrbitControls or DeviceOrientationControls */}
|
{/* Camera Controls - only active outside VR */}
|
||||||
<Controls useDeviceOrientation={useDeviceOrientation}/>
|
<CameraControls/>
|
||||||
|
|
||||||
{/* Lighting */}
|
{/* Lighting */}
|
||||||
<ambientLight intensity={0.5}/>
|
<ambientLight intensity={0.2}/>
|
||||||
<pointLight position={[10, 10, 10]} intensity={1}/>
|
<pointLight position={[10, 10, 10]} intensity={0.8}/>
|
||||||
<pointLight position={[-10, -10, -10]} intensity={0.5}/>
|
<pointLight position={[-10, -10, -10]} intensity={0.4}/>
|
||||||
|
|
||||||
{/* Neural network visualization */}
|
{/* Neural network visualization */}
|
||||||
<NeuralNetwork dream={dream}/>
|
<NeuralNetwork dream={dream}/>
|
||||||
|
|
||||||
{/* Background */}
|
{/* Background */}
|
||||||
<color attach="background" args={['#000']}/>
|
<mesh scale={[100, 100, 100]}>
|
||||||
|
<sphereGeometry args={[1, 32, 32]}/>
|
||||||
|
<meshBasicMaterial color="#000012" side={THREE.BackSide}/>
|
||||||
|
</mesh>
|
||||||
|
</XR>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</div>
|
</div>);
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DreamVR;
|
export default DreamVR;
|
@@ -183,6 +183,7 @@ export default function DreamPage() {
|
|||||||
<div className="flex justify-center px-2">
|
<div className="flex justify-center px-2">
|
||||||
<video
|
<video
|
||||||
controls
|
controls
|
||||||
|
loop
|
||||||
src={`/assets/dreams/videos/${dream.ai.video}`}
|
src={`/assets/dreams/videos/${dream.ai.video}`}
|
||||||
className="max-w-full w-full rounded-lg shadow-lg object-contain mx-auto"
|
className="max-w-full w-full rounded-lg shadow-lg object-contain mx-auto"
|
||||||
style={{maxHeight: '70vh'}}
|
style={{maxHeight: '70vh'}}
|
||||||
@@ -219,6 +220,7 @@ export default function DreamPage() {
|
|||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<video
|
<video
|
||||||
controls
|
controls
|
||||||
|
loop
|
||||||
src={`/assets/dreams/videos/${dream.ai.video}`}
|
src={`/assets/dreams/videos/${dream.ai.video}`}
|
||||||
className="max-w-full w-full rounded-lg shadow-lg object-contain mx-auto"
|
className="max-w-full w-full rounded-lg shadow-lg object-contain mx-auto"
|
||||||
style={{maxHeight: '70vh'}}
|
style={{maxHeight: '70vh'}}
|
||||||
|
@@ -13,8 +13,9 @@ const ProfilePage: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page p-4 pb-20">
|
<div className="page p-4 pb-20">
|
||||||
|
{/* Profile Card */}
|
||||||
<div
|
<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={{
|
style={{
|
||||||
background: 'linear-gradient(135deg, rgba(166, 77, 255, 0.25), rgba(213, 0, 249, 0.25))',
|
background: 'linear-gradient(135deg, rgba(166, 77, 255, 0.25), rgba(213, 0, 249, 0.25))',
|
||||||
backdropFilter: 'blur(10px)',
|
backdropFilter: 'blur(10px)',
|
||||||
@@ -63,7 +64,9 @@ const ProfilePage: React.FC = () => {
|
|||||||
<p className="text-sm sm:text-base font-medium" style={{color: 'var(--text)'}}>Days Streak</p>
|
<p className="text-sm sm:text-base font-medium" style={{color: 'var(--text)'}}>Days Streak</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Account Settings (moved back into profile card) */}
|
||||||
<div className="mt-10">
|
<div className="mt-10">
|
||||||
<h3 className="dreamy-text text-lg mb-4 font-bold">Account Settings</h3>
|
<h3 className="dreamy-text text-lg mb-4 font-bold">Account Settings</h3>
|
||||||
<div
|
<div
|
||||||
@@ -101,6 +104,50 @@ const ProfilePage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
);
|
);
|
||||||
|
@@ -11,8 +11,8 @@ import * as d3 from 'd3';
|
|||||||
const dreamTypesData = [
|
const dreamTypesData = [
|
||||||
{type: "Angstträume", percentage: 34, color: "#ef4444", darkColor: "#dc2626", note: "Global durchschnittlich"},
|
{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: "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: "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"}
|
{type: "Verfolgungsträume", percentage: 8, color: "#f97316", darkColor: "#ea580c", note: "Stresskorreliert"}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -392,23 +392,6 @@ export default function Technology() {
|
|||||||
</div>
|
</div>
|
||||||
</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="flex items-center">
|
||||||
<div className="w-4 h-4 bg-purple-500 dark:bg-purple-600 mr-2"></div>
|
<div className="w-4 h-4 bg-purple-500 dark:bg-purple-600 mr-2"></div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
@@ -427,6 +410,23 @@ export default function Technology() {
|
|||||||
</div>
|
</div>
|
||||||
</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="flex items-center">
|
||||||
<div className="w-4 h-4 bg-orange-500 dark:bg-orange-600 mr-2"></div>
|
<div className="w-4 h-4 bg-orange-500 dark:bg-orange-600 mr-2"></div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
|
Reference in New Issue
Block a user