Compare commits
10 Commits
8403a17636
...
main
Author | SHA1 | Date | |
---|---|---|---|
4ccbd28c4f | |||
618e5b442e | |||
936a2fa1ee | |||
2165a63bfb | |||
2d294de69a | |||
7754370a70 | |||
2edf6b3f1f | |||
8b0996781e | |||
bb714e6fa2 | |||
75f516b408 |
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.
67
package-lock.json
generated
67
package-lock.json
generated
@@ -20,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",
|
||||
@@ -2338,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",
|
||||
@@ -2963,6 +2976,17 @@
|
||||
],
|
||||
"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",
|
||||
@@ -4025,6 +4049,12 @@
|
||||
"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",
|
||||
@@ -4785,6 +4815,12 @@
|
||||
"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",
|
||||
@@ -5757,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",
|
||||
@@ -5927,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",
|
||||
|
@@ -22,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",
|
||||
|
@@ -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;
|
@@ -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