let junie rework the theme one more time

This commit is contained in:
2025-07-04 15:36:04 +02:00
parent df765bed8a
commit 50b1bec73c
12 changed files with 496 additions and 72 deletions

View File

@@ -3,7 +3,9 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>REMind</title>
<meta name="theme-color" content="#9d4eff" />
<meta name="description" content="REMind - Track and analyze your dreams with our dreamy app" />
<title>REMind | Dream Tracker</title>
</head>
<body>
<div id="root"></div>

View File

@@ -1,21 +1,24 @@
#root {
max-width: 1280px;
width: 100%;
margin: 0 auto;
padding: 2rem;
padding: 0;
text-align: center;
}
.App {
min-height: 100vh;
background-color: var(--bg);
color: var(--text);
transition: background-color 0.3s, color 0.3s;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
filter: drop-shadow(0 0 2em var(--accent));
}
@keyframes logo-spin {
@@ -34,9 +37,54 @@
}
.card {
padding: 2em;
padding: 1.5em;
background-color: var(--card);
border-radius: 12px;
box-shadow: 0 4px 12px var(--shadow);
margin-bottom: 1rem;
transition: transform 0.2s, box-shadow 0.2s;
}
.read-the-docs {
color: #888;
.card:hover {
transform: translateY(-3px);
box-shadow: 0 6px 16px var(--shadow);
}
/* Responsive adjustments */
@media (max-width: 768px) {
.card {
padding: 1em;
}
}
/* Dreamy effects */
.dreamy-text {
background: var(--accent-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: bold;
}
.dreamy-border {
border: 2px solid transparent;
background:
linear-gradient(var(--bg), var(--bg)) padding-box,
var(--accent-gradient) border-box;
border-radius: 12px;
}
.dreamy-button {
background: var(--accent-gradient);
color: white;
border: none;
padding: 0.6em 1.2em;
border-radius: 8px;
font-weight: 500;
transition: transform 0.2s, box-shadow 0.2s;
box-shadow: 0 4px 12px var(--shadow);
}
.dreamy-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px var(--shadow);
}

View File

@@ -1,43 +1,175 @@
import './App.css';
import {useState} from 'react';
import Navbar from './components/Navbar';
import TopBar from './components/TopBar';
import SplashScreen from './components/SplashScreen';
import {BrowserRouter, Route, Routes} from 'react-router-dom';
import Feed from "./pages/Feed.tsx";
import DreamPage from "./pages/DreamPage.tsx";
import ProfilePage from "./pages/ProfilePage.tsx";
function Home() {
return <div className="p-4">Home Page</div>;
return (
<div className="p-4">
<h1 className="dreamy-text text-3xl md:text-4xl mb-6">Welcome to REMind</h1>
<p className="mb-8 text-lg">Your dreamy journey to better sleep and dream tracking</p>
<div className="dream-container">
<h2 className="dream-title">Track Your Dreams</h2>
<p className="mb-4">Record and analyze your dreams with our intuitive interface</p>
<button className="dreamy-button">Get Started</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mt-8">
<div className="card floating">
<h3 className="text-xl font-bold mb-2" style={{ color: 'var(--accent)' }}>Dream Journal</h3>
<p>Keep track of all your dreams in one place</p>
</div>
<div className="card" style={{ animationDelay: '2s' }}>
<h3 className="text-xl font-bold mb-2" style={{ color: 'var(--accent)' }}>Sleep Analysis</h3>
<p>Understand your sleep patterns better</p>
</div>
</div>
</div>
);
}
function Record() {
return <div className="p-4">Record Page</div>;
return (
<div className="p-4">
<h1 className="dreamy-text text-3xl md:text-4xl mb-6">Record Your Dream</h1>
<div className="dream-container">
<div className="flex flex-col items-center mb-6">
<div className="w-20 h-20 rounded-full flex items-center justify-center mb-4 floating"
style={{ background: 'var(--accent-gradient)' }}>
<svg xmlns="http://www.w3.org/2000/svg" className="h-10 w-10 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z" />
</svg>
</div>
<p className="text-lg mb-2">Tap to start recording your dream</p>
<p className="text-sm text-center" style={{ color: 'var(--text-muted)' }}>
Your voice will be transcribed automatically
</p>
</div>
<div className="dreamy-border p-4 mb-6">
<textarea
className="w-full p-3 rounded-lg bg-transparent focus:outline-none"
placeholder="Or type your dream here..."
rows={5}
style={{ color: 'var(--text)', borderColor: 'var(--accent-soft)' }}
></textarea>
</div>
<button className="dreamy-button w-full">Save Dream</button>
</div>
</div>
);
}
function Archive() {
return <div className="p-4">Archive Page</div>;
}
function Profile() {
return <div className="p-4">Profile Page</div>;
}
export default function App() {
return (
<BrowserRouter>
<div className="pb-16 pt-8 min-h-screen">
<TopBar/>
<div className="mx-auto w-full max-w-lg">
<Navbar/>
<Routes>
<Route path="/" element={<Home/>}/>
<Route path="/feed" element={<Feed/>}/>
<Route path="/record" element={<Record/>}/>
<Route path="/archive" element={<Archive/>}/>
<Route path="/profile" element={<Profile/>}/>
<Route path="/dream/:id" element={<DreamPage/>}/>
</Routes>
<div className="p-4">
<h1 className="dreamy-text text-3xl md:text-4xl mb-6">Dream Archive</h1>
<div className="mb-6 flex flex-col sm:flex-row gap-4">
<div className="flex-1">
<input
type="text"
placeholder="Search dreams..."
className="w-full p-3 rounded-lg focus:outline-none"
style={{
backgroundColor: 'var(--container)',
color: 'var(--text)',
borderColor: 'var(--accent-soft)',
boxShadow: '0 2px 8px var(--shadow)'
}}
/>
</div>
<div>
<select
className="w-full p-3 rounded-lg focus:outline-none"
style={{
backgroundColor: 'var(--container)',
color: 'var(--text)',
borderColor: 'var(--accent-soft)',
boxShadow: '0 2px 8px var(--shadow)'
}}
>
<option>All Dreams</option>
<option>Last Week</option>
<option>Last Month</option>
<option>Last Year</option>
</select>
</div>
</div>
</BrowserRouter>
<div className="space-y-4">
{[1, 2, 3].map((item) => (
<div key={item} className="card">
<div className="flex justify-between items-start mb-2">
<h3 className="text-xl font-bold" style={{ color: 'var(--accent)' }}>
Dream #{item}
</h3>
<span style={{ color: 'var(--text-muted)' }}>
{new Date().toLocaleDateString()}
</span>
</div>
<p className="mb-4">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Vivamus lacinia, nunc eu tincidunt faucibus...
</p>
<div className="flex justify-end">
<button
className="px-4 py-2 rounded-lg transition-transform hover:scale-105"
style={{
background: 'var(--accent-gradient)',
color: 'white'
}}
>
View Details
</button>
</div>
</div>
))}
</div>
</div>
);
}
export default function App() {
const [isLoading, setIsLoading] = useState(true);
const handleSplashFinished = () => {
setIsLoading(false);
};
if (isLoading) {
return <SplashScreen onFinished={handleSplashFinished}/>;
}
return (
<div className="App">
<BrowserRouter>
<div className="pb-16 pt-8 min-h-screen">
<TopBar/>
<div className="mx-auto w-full max-w-lg px-4 sm:px-6 md:max-w-2xl lg:max-w-4xl">
<Navbar/>
<div className="mt-16 mb-20">
<Routes>
<Route path="/" element={<Home/>}/>
<Route path="/feed" element={<Feed/>}/>
<Route path="/record" element={<Record/>}/>
<Route path="/archive" element={<Archive/>}/>
<Route path="/profile" element={<ProfilePage/>}/>
<Route path="/dream/:id" element={<DreamPage/>}/>
</Routes>
</div>
</div>
</div>
</BrowserRouter>
</div>
);
}

View File

@@ -1,8 +1,8 @@
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="moonGradient" x1="0%" y1="0%" x2="100%" y2="50%">
<stop offset="0%" stop-color="#5B68CE" />
<stop offset="100%" stop-color="#6A449E" />
<stop offset="0%" stop-color="darkorchid" />
<stop offset="100%" stop-color="darkslateblue" />
</linearGradient>
<mask id="moonMask">
<circle cx="100" cy="100" r="50" fill="white" />

Before

Width:  |  Height:  |  Size: 618 B

After

Width:  |  Height:  |  Size: 627 B

View File

@@ -4,8 +4,8 @@
@import url('https://fonts.googleapis.com/css2?family=Raleway:wght@400;700&amp;display=swap');
</style>
<linearGradient id="textGradient" x1="25%" y1="0%" x2="75%" y2="100%">
<stop offset="0%" stop-color="#5A8BFF" />
<stop offset="100%" stop-color="#9B5CFF" />
<stop offset="0%" stop-color="darkslateblue" />
<stop offset="100%" stop-color="indigo" />
</linearGradient>
</defs>

Before

Width:  |  Height:  |  Size: 715 B

After

Width:  |  Height:  |  Size: 720 B

View File

@@ -2,30 +2,31 @@ import {NavLink} from 'react-router-dom';
import {FaArchive, FaHome, FaList, FaMicrophone, FaUser} from "react-icons/fa";
export default function Navbar() {
return (<>
<nav
className="fixed bottom-0 left-0 right-0 flex justify-around bg-violet-900 py-4 z-10">
<NavLink to="/">
<FaHome className="w-8 h-8 text-fuchsia-400"/>
className="fixed bottom-0 left-0 right-0 flex justify-around py-4 z-10"
style={{ backgroundColor: 'var(--accent-dark)', boxShadow: '0 -2px 10px var(--shadow)' }}>
<NavLink to="/" className="transition-transform hover:scale-110">
<FaHome className="w-6 h-6 md:w-8 md:h-8" style={{ color: 'var(--accent-soft)' }}/>
</NavLink>
<NavLink to="/feed">
<FaList className="w-8 h-8 text-fuchsia-400"/>
<NavLink to="/feed" className="transition-transform hover:scale-110">
<FaList className="w-6 h-6 md:w-8 md:h-8" style={{ color: 'var(--accent-soft)' }}/>
</NavLink>
<div className="w-20"></div>
<NavLink to="/archive">
<FaArchive className="w-8 h-8 text-fuchsia-400"/>
<div className="w-16 md:w-20"></div>
<NavLink to="/archive" className="transition-transform hover:scale-110">
<FaArchive className="w-6 h-6 md:w-8 md:h-8" style={{ color: 'var(--accent-soft)' }}/>
</NavLink>
<NavLink to="/profile">
<FaUser className="w-8 h-8 text-fuchsia-400"/>
<NavLink to="/profile" className="transition-transform hover:scale-110">
<FaUser className="w-6 h-6 md:w-8 md:h-8" style={{ color: 'var(--accent-soft)' }}/>
</NavLink>
</nav>
<NavLink
to="/record"
className="microphone-button fixed bottom-6 left-1/2 transform -translate-x-1/2 p-5 rounded-full z-20"
className="microphone-button fixed bottom-6 left-1/2 transform -translate-x-1/2 p-4 md:p-5 rounded-full z-20 transition-transform hover:scale-110"
style={{ boxShadow: '0 4px 15px var(--shadow)' }}
>
<FaMicrophone className="w-10 h-10 text-white"/>
<FaMicrophone className="w-8 h-8 md:w-10 md:h-10 text-white"/>
</NavLink>
</>
);

View File

@@ -0,0 +1,31 @@
import { useEffect } from 'react';
import logo from '../assets/logo.svg';
import text from '../assets/text.svg';
interface SplashScreenProps {
onFinished: () => void;
}
export default function SplashScreen({ onFinished }: SplashScreenProps) {
useEffect(() => {
// Simulate loading time with a timeout
const timer = setTimeout(() => {
onFinished();
}, 2000); // Show splash screen for 2 seconds
return () => clearTimeout(timer);
}, [onFinished]);
return (
<div className="fixed inset-0 flex flex-col items-center justify-center bg-violet-900 z-50">
<div className="animate-pulse flex flex-col items-center">
<img src={logo} alt="REMind Logo" className="h-32 w-32 mb-4" />
<img src={text} alt="REMind Text" className="h-16" />
</div>
<div className="mt-8 text-fuchsia-400">
<div className="animate-spin h-8 w-8 border-4 border-current border-t-transparent rounded-full mx-auto"></div>
<p className="mt-4 text-center">Loading your dreams...</p>
</div>
</div>
);
}

View File

@@ -5,13 +5,41 @@ import text from '../assets/text.svg';
export default function TopBar() {
const [visible, setVisible] = useState(true);
const [lastScrollY, setLastScrollY] = useState(0);
const [darkMode, setDarkMode] = useState(false);
// Function to update theme meta tag
const updateThemeColor = (isDark: boolean) => {
const themeColor = isDark ? '#bb86fc' : '#9d4eff';
const metaThemeColor = document.querySelector('meta[name="theme-color"]');
if (metaThemeColor) {
metaThemeColor.setAttribute('content', themeColor);
}
};
// Initialize theme based on user preference or system preference
useEffect(() => {
const isDarkMode = localStorage.getItem('darkMode') === 'true' ||
window.matchMedia('(prefers-color-scheme: dark)').matches;
setDarkMode(isDarkMode);
document.documentElement.setAttribute('data-theme', isDarkMode ? 'dark' : 'light');
updateThemeColor(isDarkMode);
}, []);
// Function to toggle theme
const toggleTheme = () => {
const newDarkMode = !darkMode;
setDarkMode(newDarkMode);
document.documentElement.setAttribute('data-theme', newDarkMode ? 'dark' : 'light');
localStorage.setItem('darkMode', newDarkMode.toString());
updateThemeColor(newDarkMode);
};
useEffect(() => {
const handleScroll = () => {
const currentScrollY = window.scrollY;
// Hide the topbar when scrolling down, show when scrolling up
if (currentScrollY > lastScrollY) {
if (currentScrollY > lastScrollY && currentScrollY > 50) {
setVisible(false);
} else {
setVisible(true);
@@ -29,13 +57,39 @@ export default function TopBar() {
return (
<div
className={`fixed top-0 left-0 right-0 bg-violet-900 py-3 px-4 flex items-center transition-transform duration-300 z-20 ${
className={`fixed top-0 left-0 right-0 py-2 md:py-3 px-3 md:px-4 flex items-center transition-transform duration-300 z-20 ${
visible ? 'transform-none' : 'transform -translate-y-full'
}`}
style={{
background: 'var(--accent-gradient)',
boxShadow: '0 2px 10px var(--shadow)'
}}
>
<div className="flex items-center">
<img src={logo} alt="REMind Logo" className="h-16 w-16" />
<img src={text} alt="REMind Text" className="h-12 ml-2" />
<div className="flex items-center justify-between w-full max-w-6xl mx-auto">
<div className="flex items-center">
<img src={logo} alt="REMind Logo" className="h-12 w-12 md:h-16 md:w-16" />
<img src={text} alt="REMind Text" className="h-8 md:h-12 ml-2" />
</div>
<button
onClick={toggleTheme}
className="p-2 rounded-full focus:outline-none transition-transform hover:scale-110"
aria-label="Toggle theme"
style={{
backgroundColor: 'var(--card)',
boxShadow: '0 2px 8px var(--shadow)'
}}
>
{darkMode ? (
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 md:h-6 md:w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" style={{ color: 'var(--accent)' }}>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 md:h-6 md:w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" style={{ color: 'var(--accent-dark)' }}>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
</svg>
)}
</button>
</div>
</div>
);

View File

@@ -1,11 +1,12 @@
import User from "../types/User.ts";
export const MockUsers: User[] = [
new User(1, "Matthias", "matthias.jpg"),
new User(2, "Kim", "kim.png"),
new User(3, "Anja", "anja.png"),
new User(1, "Matthias", "matthias.jpg", "s4mapuch@uni-trier.de", 42, 7),
new User(2, "Kim", "kim.png", "kim@example.com", 28, 3),
new User(3, "Anja", "anja.png", "anja@example.com", 15, 5),
new User(4, "Neo Quantum", "neo.png", "neo@remind.dev", 64, 12),
]
export const MockUserMap: Map<number, User> = new Map(MockUsers.map(user => [user.id, user]));
export default MockUsers;
export default MockUsers;

View File

@@ -1,13 +1,16 @@
@import "tailwindcss";
:root {
--bg: #f9f6ff;
--bg: #f8f5ff;
--container: #ede6fa;
--card: #d6c6ff;
--card: #e0d4ff;
--text: #1e102e;
--text-muted: #4a375e;
--accent: #7f39fb;
--accent-gradient: linear-gradient(135deg, #7f39fb, #d500f9);
--accent: #9d4eff;
--accent-gradient: linear-gradient(135deg, #9d4eff, #d500f9);
--accent-soft: #c9a4ff;
--accent-dark: #6a0dad;
--shadow: rgba(157, 78, 255, 0.2);
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
@@ -24,13 +27,16 @@
}
[data-theme="dark"] {
--bg: #1e102e;
--container: #3a1a5d;
--card: #5c2d91;
--bg: #1a0933;
--container: #2d1155;
--card: #3d1a6e;
--text: #f5e6ff;
--text-muted: #c9a4e3;
--accent: #bb86fc;
--accent-gradient: linear-gradient(135deg, #7f39fb, #d500f9);
--accent-gradient: linear-gradient(135deg, #bb86fc, #d500f9);
--accent-soft: #9d4eff;
--accent-dark: #6a0dad;
--shadow: rgba(187, 134, 252, 0.3);
}
.border-background {
@@ -69,12 +75,13 @@ a:hover {
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
width: 100%;
background-color: var(--bg);
color: var(--text);
transition: background-color 0.3s, color 0.3s;
overflow-x: hidden;
}
h1 {
@@ -173,3 +180,75 @@ button:focus-visible {
.theme-toggle input:checked + .slider {
background-color: var(--accent);
}
/* Dreamy violet enhancements */
.dream-title {
font-size: 1.5rem;
font-weight: bold;
background: var(--accent-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 0.5rem;
}
.dream-container {
background-color: var(--container);
border-radius: 16px;
padding: 1.5rem;
margin-bottom: 1.5rem;
box-shadow: 0 4px 15px var(--shadow);
transition: transform 0.3s, box-shadow 0.3s;
}
.dream-container:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px var(--shadow);
}
/* Scrollbar styling */
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: var(--bg);
}
::-webkit-scrollbar-thumb {
background: var(--accent-soft);
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--accent);
}
/* Animations */
@keyframes dream-float {
0% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
100% {
transform: translateY(0);
}
}
.floating {
animation: dream-float 6s ease-in-out infinite;
}
/* Responsive typography */
@media (max-width: 768px) {
h1 {
font-size: 2.5em;
}
h2 {
font-size: 1.8em;
}
p {
font-size: 0.95em;
}
}

67
src/pages/ProfilePage.tsx Normal file
View File

@@ -0,0 +1,67 @@
import React from 'react';
import { MockUsers } from '../data/MockUsers';
const ProfilePage: React.FC = () => {
// Find Neo Quantum in MockUsers
const profileUser = MockUsers.find(user => user.name === "Neo Quantum");
// Default values if user not found
const defaultName = "TestUser";
const defaultEmail = "user@example.com";
const defaultDreamCount = 0;
const defaultStreakDays = 0;
return (
<div className="page p-4">
<div className="dreamPanel flex flex-col items-center p-6">
{/* Profile Picture */}
<div className="w-32 h-32 rounded-full overflow-hidden mb-4 border-4 border-accent">
<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"
/>
</div>
{/* User Information */}
<div className="text-center">
<h2 className="text-xl font-bold mb-2">{profileUser ? profileUser.name : defaultName}</h2>
<p className="text-text-muted mb-4">{profileUser ? profileUser.email : defaultEmail}</p>
<div className="grid grid-cols-2 gap-4 mt-6 text-center">
<div className="dreamPanel">
<p className="font-bold text-2xl">{profileUser ? profileUser.dreamCount : defaultDreamCount}</p>
<p className="text-sm text-text-muted">Dreams</p>
</div>
<div className="dreamPanel">
<p className="font-bold text-2xl">{profileUser ? profileUser.streakDays : defaultStreakDays}</p>
<p className="text-sm text-text-muted">Days Streak</p>
</div>
</div>
<div className="mt-8">
<h3 className="text-lg font-semibold mb-2">Account Settings</h3>
<div className="dreamPanel text-left">
<div className="flex justify-between items-center py-2">
<span>Notifications</span>
<label className="theme-toggle">
<input type="checkbox" defaultChecked />
<span className="slider"></span>
</label>
</div>
<div className="flex justify-between items-center py-2">
<span>Privacy</span>
<label className="theme-toggle">
<input type="checkbox" />
<span className="slider"></span>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default ProfilePage;

View File

@@ -2,14 +2,23 @@ export default class User {
id: number;
name: string;
profilePicture: string;
email: string;
dreamCount: number;
streakDays: number;
constructor(
id: number,
name: string,
profilePicture: string
profilePicture: string,
email: string = "",
dreamCount: number = 0,
streakDays: number = 0
){
this.id = id
this.name = name
this.profilePicture = profilePicture
this.email = email
this.dreamCount = dreamCount
this.streakDays = streakDays
}
}
}