Compare commits

...

6 Commits

Author SHA1 Message Date
4ccbd28c4f expanded and updated README.md to include detailed project overview, features breakdown, technical stack, usage guide, project structure, and educational context for improved clarity and documentation quality.
Signed-off-by: Matthias Puchstein <matthias@puchstein.bayern>
2025-07-17 04:18:19 +02:00
618e5b442e refactored DreamCharts to consolidate chart rendering logic into reusable renderChart function; improved chart resize handling for better responsiveness and maintainability
Signed-off-by: Matthias Puchstein <matthias@puchstein.bayern>
2025-07-17 03:56:10 +02:00
936a2fa1ee adjusted chip icon position in DreamCardCompact to align with design improvements
Signed-off-by: Matthias Puchstein <matthias@puchstein.bayern>
2025-07-17 03:50:53 +02:00
2165a63bfb added @types/node and dependencies to devDependencies for extended Node.js type support; adjusted chip icon positioning in DreamCardCompact for improved UI alignment
Signed-off-by: Matthias Puchstein <matthias@puchstein.bayern>
2025-07-17 03:50:18 +02:00
2d294de69a refactored DreamVR to simplify component structure, integrate OrbitControls for non-VR mode, enhance lighting, and optimize render logic for improved performance and maintainability
Signed-off-by: Matthias Puchstein <matthias@puchstein.bayern>
2025-07-17 03:36:20 +02:00
7754370a70 swapped colors and labels for "Sexuelle Träume" and "Flugträume" for consistent data representation and accuracy in Technology visualization
Signed-off-by: Matthias Puchstein <matthias@puchstein.bayern>
2025-07-17 03:34:06 +02:00
7 changed files with 587 additions and 414 deletions

311
README.md
View File

@@ -1,90 +1,269 @@
# REMind (Mockup Demo)
# REMind - Dream Analysis & VR Experience Platform
🚧 **This is a MOCKUP / DEMO APPLICATION for a university project no real backend, AI features, or data storage.** 🚧
![Project Status](https://img.shields.io/badge/Status-University%20Project%20Demo-orange)
![License](https://img.shields.io/badge/License-AGPL--3.0-blue)
![TypeScript](https://img.shields.io/badge/TypeScript-95.2%25-blue)
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, its designed for demos and concept validation only.
> **🚧 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.**
## Features (all mockup/demo only)
## Project Overview
- **(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.
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.
## Tech Stack
**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.
- **Framework:** React + TypeScript
- **Bundler:** Vite
- **Styling:** Tailwind CSS
- **Routing:** React Router Dom
- **Icons:** React Icons
- **3D \& VR:** @react-three/fiber
- **Linting:** ESLint
## ✨ Key Features
## Getting Started
### 🌙 Multi-Modal Dream Recording
> **Note:** This repository contains only a front-end prototype with hard-coded mock data. There is no production backend or AI service.
- **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
1. Clone the repo
### 🤖 AI-Powered Analysis (Simulated)
```bash
git clone https://gitea.puchstein.bayern/mpuchstein/REMind.git
cd REMind
```
- **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
2. Install dependencies
### 🎮 VR Dreamscapes
```bash
npm install
```
- **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
3. Run development server
### 📊 Advanced Analytics
```bash
npm run dev
```
- **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
Open `http://localhost:5173` in your browser. 4. Build for production (static demo)
### 🗂️ Dream Archive & Management
```bash
npm run build
npm run preview
```
- **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
## Project Structure
### 🌐 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
```
.
├── 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.

18
package-lock.json generated
View File

@@ -26,6 +26,7 @@
"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",
@@ -2340,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",
@@ -5782,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",

View File

@@ -28,6 +28,7 @@
"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",

View File

@@ -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>

View File

@@ -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;
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;
// Clear previous chart
d3.select(chartRef.current).selectAll('*').remove();
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 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;
// X scale
const x = d3.scaleLinear()
.domain([0, chipInput.eeg.alpha.length - 1])
.range([0, width]);
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})`);
// Y scale
const y = d3.scaleLinear()
.domain([0, d3.max(eegData.flatMap(d => d.values)) || 50])
.range([height, 0]);
// X scale
const x = d3.scaleLinear()
.domain([0, chipInput.eeg.alpha.length - 1])
.range([0, width]);
// Line generator
const line = d3.line<number>()
.x((_d, i) => x(i))
.y(d => y(d))
.curve(d3.curveMonotoneX);
// Y scale
const y = d3.scaleLinear()
.domain([0, d3.max(eegData.flatMap(d => d.values)) || 50])
.range([height, 0]);
// Color scale
const color = d3.scaleOrdinal<string>()
.domain(eegData.map(d => d.name))
.range(['#8884d8', '#82ca9d', '#ffc658', '#ff8042']);
// Line generator
const line = d3.line<number>()
.x((_d, i) => x(i))
.y(d => y(d))
.curve(d3.curveMonotoneX);
// Add X axis
svg.append('g')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x))
.selectAll('text')
.style('fill', 'var(--text)');
// Color scale
const color = d3.scaleOrdinal<string>()
.domain(eegData.map(d => d.name))
.range(['#8884d8', '#82ca9d', '#ffc658', '#ff8042']);
// Add Y axis
svg.append('g')
.call(d3.axisLeft(y))
.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 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 Y axis
svg.append('g')
.call(d3.axisLeft(y))
.selectAll('text')
.style('fill', 'var(--text)');
// Add legend at the top right
const legendItemHeight = 20; // Height allocated for each legend item
// 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);
});
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})`);
// Add legend at the top right
const legendItemHeight = 20; // Height allocated for each legend item
legend.append('rect')
.attr('x', -15)
.attr('width', 15)
.attr('height', 15)
.attr('fill', d => color(d.name));
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('text')
.attr('x', -20)
.attr('y', 7.5)
.attr('dy', '0.32em')
.style('fill', 'var(--text)')
.text(d => 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);
};
// 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;
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;
// Clear previous chart
d3.select(chartRef.current).selectAll('*').remove();
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 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;
// X scale
const x = d3.scaleLinear()
.domain([0, chipInput.puls.length - 1])
.range([0, width]);
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})`);
// 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]);
// X scale
const x = d3.scaleLinear()
.domain([0, chipInput.puls.length - 1])
.range([0, width]);
// Line generator
const line = d3.line<number>()
.x((_d, i) => x(i))
.y(d => y(d))
.curve(d3.curveMonotoneX);
// 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]);
// Color scale
const color = d3.scaleOrdinal<string>()
.domain(vitalsData.map(d => d.name))
.range(['#ff6b6b', '#48dbfb']);
// Line generator
const line = d3.line<number>()
.x((_d, i) => x(i))
.y(d => y(d))
.curve(d3.curveMonotoneX);
// Add X axis
svg.append('g')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x))
.selectAll('text')
.style('fill', 'var(--text)');
// Color scale
const color = d3.scaleOrdinal<string>()
.domain(vitalsData.map(d => d.name))
.range(['#ff6b6b', '#48dbfb']);
// Add Y axis
svg.append('g')
.call(d3.axisLeft(y))
.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 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 Y axis
svg.append('g')
.call(d3.axisLeft(y))
.selectAll('text')
.style('fill', 'var(--text)');
// Add legend at the top right
const legendItemHeight = 20; // Height allocated for each legend item
// 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);
});
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})`);
// Add legend at the top right
const legendItemHeight = 20; // Height allocated for each legend item
legend.append('rect')
.attr('x', -15)
.attr('width', 15)
.attr('height', 15)
.attr('fill', d => color(d.name));
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('text')
.attr('x', -20)
.attr('y', 7.5)
.attr('dy', '0.32em')
.style('fill', 'var(--text)')
.text(d => 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);
};
// 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;
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;
// Clear previous chart
d3.select(chartRef.current).selectAll('*').remove();
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 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;
// X scale
const x = d3.scaleLinear()
.domain([0, chipInput.bewegung.length - 1])
.range([0, width]);
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})`);
// Y scale
const y = d3.scaleLinear()
.domain([0, d3.max(bewegungData[0].values) || 100])
.range([height, 0]);
// X scale
const x = d3.scaleLinear()
.domain([0, chipInput.bewegung.length - 1])
.range([0, width]);
// Line generator
const line = d3.line<number>()
.x((_d, i) => x(i))
.y(d => y(d))
.curve(d3.curveMonotoneX);
// Y scale
const y = d3.scaleLinear()
.domain([0, d3.max(bewegungData[0].values) || 100])
.range([height, 0]);
// Color scale
const color = '#1dd1a1'; // Use the same color as before for consistency
// Line generator
const line = d3.line<number>()
.x((_d, i) => x(i))
.y(d => y(d))
.curve(d3.curveMonotoneX);
// Add X axis
svg.append('g')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x))
.selectAll('text')
.style('fill', 'var(--text)');
// Color scale
const color = '#1dd1a1'; // Use the same color as before for consistency
// Add Y axis
svg.append('g')
.call(d3.axisLeft(y))
.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 line
svg.append('path')
.datum(bewegungData[0].values)
.attr('fill', 'none')
.attr('stroke', color)
.attr('stroke-width', 1.5)
.attr('d', line);
// Add Y axis
svg.append('g')
.call(d3.axisLeft(y))
.selectAll('text')
.style('fill', 'var(--text)');
// Add legend at the top right
const legendItemHeight = 20; // Height allocated for each legend item
// Add line
svg.append('path')
.datum(bewegungData[0].values)
.attr('fill', 'none')
.attr('stroke', color)
.attr('stroke-width', 1.5)
.attr('d', line);
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})`);
// Add legend at the top right
const legendItemHeight = 20; // Height allocated for each legend item
legend.append('rect')
.attr('x', -15)
.attr('width', 15)
.attr('height', 15)
.attr('fill', () => color);
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('text')
.attr('x', -20)
.attr('y', 7.5)
.attr('dy', '0.32em')
.style('fill', 'var(--text)')
.text(d => d.name);
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);
};
// 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);

View File

@@ -1,17 +1,13 @@
import React, {useMemo, useRef} from 'react';
import {Canvas, useFrame} from '@react-three/fiber';
import {createXRStore, XR} from '@react-three/xr';
import {PerspectiveCamera} from '@react-three/drei';
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';
// 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;
@@ -24,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
@@ -51,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
@@ -147,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
});
}
@@ -185,34 +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}
/>
))}
</>
);
/>))}
</>);
};
// Camera Controls component that handles XR and non-XR states
const CameraControls = () => {
const {session} = useXR();
// Only enable OrbitControls when NOT in VR session
if (session) {
return null; // Let XR handle camera in VR mode
}
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
@@ -229,61 +205,45 @@ const DreamVR: React.FC<DreamVRProps> = ({dream, height = '500px'}) => {
// 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'}}>
return (<div ref={containerRef} className="relative w-full" style={{height}}>
{/* VR Entry Button */}
<div className="absolute top-2 right-2 z-10 flex space-x-2">
<button
onClick={() => store.enterVR()}
className="p-2 bg-white/20 backdrop-blur-sm rounded-full text-white hover:bg-white/30 transition-colors"
aria-label="Enter 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"
>
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
<line x1="8" y1="21" x2="16" y2="21"/>
<line x1="12" y1="17" x2="12" y2="21"/>
</svg>
</button>
</div>
<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>
<Canvas shadows>
<Canvas camera={{position: [0, 0, 10], fov: 60}}>
<XR store={store}>
{/* Camera setup */}
<PerspectiveCamera makeDefault position={[0, 0, 30]}/>
<PerspectiveCamera makeDefault position={[0, 0, 10]}/>
{/* Camera Controls - only active outside VR */}
<CameraControls/>
{/* Lighting */}
<ambientLight intensity={0.5}/>
<directionalLight position={[10, 10, 10]} intensity={1}/>
<directionalLight position={[-10, -10, -10]} intensity={0.5}/>
<ambientLight intensity={0.2}/>
<pointLight position={[10, 10, 10]} intensity={0.8}/>
<pointLight position={[-10, -10, -10]} intensity={0.4}/>
{/* Neural network visualization */}
<NeuralNetwork dream={dream}/>
{/* 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>
</div>
);
</div>);
};
export default DreamVR;

View File

@@ -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">