HydroFlux 0.0.2
This commit is contained in:
6
Hydroflux/.idea/vcs.xml
generated
Normal file
6
Hydroflux/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
536
Hydroflux/REPLICATION_GUIDE.md
Normal file
536
Hydroflux/REPLICATION_GUIDE.md
Normal file
@@ -0,0 +1,536 @@
|
|||||||
|
# Hydro Flux Dashboard - Complete Replication Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
A mobile-first health tracking dashboard called "Hydro Flux" that tracks water intake, steps, sleep, Strava integration, goals, and notes. Built with React, TypeScript, Tailwind CSS, and Motion (Framer Motion).
|
||||||
|
|
||||||
|
## Design Specifications
|
||||||
|
|
||||||
|
### Color Palette
|
||||||
|
- Primary Blue: #60A5FA (blue-400)
|
||||||
|
- Secondary Blue: #3B82F6 (blue-500)
|
||||||
|
- Light Blue: #DBEAFE (blue-50)
|
||||||
|
- Orange (Strava): #EA580C (orange-600)
|
||||||
|
- Gray Background: Linear gradient from gray-100 to gray-200
|
||||||
|
- White: #FFFFFF
|
||||||
|
- Text Primary: #1F2937 (gray-800)
|
||||||
|
- Text Secondary: #6B7280 (gray-500)
|
||||||
|
|
||||||
|
### Layout
|
||||||
|
- Mobile-first design (max-width: 28rem / 448px)
|
||||||
|
- Rounded container with 3rem border radius
|
||||||
|
- White background with shadow-2xl
|
||||||
|
- Padding: 1.5rem (6 units)
|
||||||
|
- Component spacing: 1rem gap (4 units)
|
||||||
|
|
||||||
|
### Typography
|
||||||
|
- Headers: font-bold
|
||||||
|
- Body: font-medium
|
||||||
|
- Small text: text-sm, text-xs
|
||||||
|
|
||||||
|
## Dependencies Required
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"recharts": "2.15.2",
|
||||||
|
"motion": "12.23.24",
|
||||||
|
"lucide-react": "0.487.0"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Import Motion using: `import { motion } from 'motion/react'`
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
/src/app/
|
||||||
|
├── App.tsx
|
||||||
|
└── components/
|
||||||
|
├── Header.tsx
|
||||||
|
├── WaterTracker.tsx
|
||||||
|
├── StepsTracker.tsx
|
||||||
|
├── StravaIntegration.tsx
|
||||||
|
├── SleepTracker.tsx
|
||||||
|
├── Goals.tsx
|
||||||
|
└── Notes.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete Component Code
|
||||||
|
|
||||||
|
### 1. /src/app/App.tsx
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { Header } from './components/Header';
|
||||||
|
import { WaterTracker } from './components/WaterTracker';
|
||||||
|
import { StepsTracker } from './components/StepsTracker';
|
||||||
|
import { StravaIntegration } from './components/StravaIntegration';
|
||||||
|
import { SleepTracker } from './components/SleepTracker';
|
||||||
|
import { Goals } from './components/Goals';
|
||||||
|
import { Notes } from './components/Notes';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const [waterAmount, setWaterAmount] = useState(1.2);
|
||||||
|
const [currentDate, setCurrentDate] = useState('');
|
||||||
|
const [currentTime, setCurrentTime] = useState('');
|
||||||
|
const [note, setNote] = useState('Feeling hydrated today. Remember to add those gym sessions!');
|
||||||
|
const [goals, setGoals] = useState([
|
||||||
|
{ id: '1', text: 'Drink 3L of water', completed: true },
|
||||||
|
{ id: '2', text: 'Walk 10K steps', completed: false },
|
||||||
|
{ id: '3', text: 'Sleep 8 hours', completed: false },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Sleep data for the chart
|
||||||
|
const sleepData = [
|
||||||
|
{ value: 30 }, { value: 35 }, { value: 25 }, { value: 40 },
|
||||||
|
{ value: 55 }, { value: 45 }, { value: 60 }, { value: 75 },
|
||||||
|
{ value: 70 }, { value: 80 }, { value: 65 }, { value: 55 },
|
||||||
|
{ value: 50 }, { value: 45 }, { value: 40 },
|
||||||
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const updateDateTime = () => {
|
||||||
|
const now = new Date();
|
||||||
|
const dateStr = now.toLocaleDateString('en-US', {
|
||||||
|
weekday: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
});
|
||||||
|
const timeStr = now.toLocaleTimeString('en-US', {
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: true
|
||||||
|
}) + ' AEDT';
|
||||||
|
setCurrentDate(dateStr);
|
||||||
|
setCurrentTime(timeStr);
|
||||||
|
};
|
||||||
|
|
||||||
|
updateDateTime();
|
||||||
|
const interval = setInterval(updateDateTime, 1000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleAddWater = () => {
|
||||||
|
setWaterAmount((prev) => Math.min(prev + 0.25, 3.0));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleToggleGoal = (id: string) => {
|
||||||
|
setGoals((prev) =>
|
||||||
|
prev.map((goal) =>
|
||||||
|
goal.id === id ? { ...goal, completed: !goal.completed } : goal
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-gray-100 to-gray-200 flex items-center justify-center p-4">
|
||||||
|
<div className="w-full max-w-md bg-white rounded-[3rem] shadow-2xl p-6 relative overflow-hidden">
|
||||||
|
{/* Subtle background decoration */}
|
||||||
|
<div className="absolute top-0 right-0 w-64 h-64 bg-blue-200/20 rounded-full blur-3xl -z-10" />
|
||||||
|
<div className="absolute bottom-0 left-0 w-64 h-64 bg-purple-200/20 rounded-full blur-3xl -z-10" />
|
||||||
|
|
||||||
|
<Header
|
||||||
|
level={14}
|
||||||
|
streakDays={14}
|
||||||
|
currentDate={currentDate}
|
||||||
|
currentTime={currentTime}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<WaterTracker
|
||||||
|
currentAmount={waterAmount}
|
||||||
|
targetAmount={3.0}
|
||||||
|
onAddWater={handleAddWater}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4 mb-4">
|
||||||
|
<StepsTracker steps={8432} goal={10000} />
|
||||||
|
<StravaIntegration distance={5.2} lastSync="2h ago" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<SleepTracker
|
||||||
|
hours={7}
|
||||||
|
minutes={20}
|
||||||
|
sleepType="Light Sleep"
|
||||||
|
data={sleepData}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<Goals goals={goals} onToggleGoal={handleToggleGoal} />
|
||||||
|
<Notes note={note} onNoteChange={setNote} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. /src/app/components/Header.tsx
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Droplet } from 'lucide-react';
|
||||||
|
|
||||||
|
interface HeaderProps {
|
||||||
|
level: number;
|
||||||
|
streakDays: number;
|
||||||
|
currentDate: string;
|
||||||
|
currentTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Header({ level, streakDays, currentDate, currentTime }: HeaderProps) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center gap-2 mb-6">
|
||||||
|
<div className="flex items-center justify-between w-full px-4">
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<div className="w-14 h-14 rounded-full bg-gradient-to-br from-blue-400 to-blue-600 flex items-center justify-center mb-1 shadow-lg">
|
||||||
|
<div className="w-12 h-12 rounded-full bg-white/90 flex items-center justify-center text-sm font-semibold text-gray-700">
|
||||||
|
JD
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-blue-600 font-medium">Level {level}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col items-center flex-1">
|
||||||
|
<h1 className="text-2xl font-bold text-gray-800 mb-1">Hydro Flux</h1>
|
||||||
|
<div className="text-sm text-gray-600">{currentDate}</div>
|
||||||
|
<div className="text-sm text-gray-500">{currentTime}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Droplet className="w-5 h-5 fill-blue-500 text-blue-500" />
|
||||||
|
<Droplet className="w-5 h-5 fill-blue-400 text-blue-400" />
|
||||||
|
<span className="text-sm font-semibold text-gray-700 ml-1">{streakDays} Days</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. /src/app/components/WaterTracker.tsx
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Plus } from 'lucide-react';
|
||||||
|
import { motion } from 'motion/react';
|
||||||
|
|
||||||
|
interface WaterTrackerProps {
|
||||||
|
currentAmount: number;
|
||||||
|
targetAmount: number;
|
||||||
|
onAddWater: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WaterTracker({ currentAmount, targetAmount, onAddWater }: WaterTrackerProps) {
|
||||||
|
const percentage = (currentAmount / targetAmount) * 100;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative h-64 bg-gradient-to-b from-blue-50 to-blue-100 rounded-3xl overflow-hidden shadow-lg mb-6">
|
||||||
|
{/* Water wave */}
|
||||||
|
<motion.div
|
||||||
|
className="absolute bottom-0 left-0 right-0"
|
||||||
|
style={{
|
||||||
|
height: `${percentage}%`,
|
||||||
|
background: 'linear-gradient(180deg, rgba(96, 165, 250, 0.8) 0%, rgba(59, 130, 246, 0.9) 100%)',
|
||||||
|
}}
|
||||||
|
animate={{
|
||||||
|
y: [0, -10, 0],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 3,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Wave effect */}
|
||||||
|
<svg
|
||||||
|
className="absolute top-0 left-0 w-full"
|
||||||
|
viewBox="0 0 1200 120"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
style={{ height: '60px', transform: 'translateY(-50%)' }}
|
||||||
|
>
|
||||||
|
<motion.path
|
||||||
|
d="M0,50 Q300,10 600,50 T1200,50 L1200,120 L0,120 Z"
|
||||||
|
fill="rgba(96, 165, 250, 0.5)"
|
||||||
|
animate={{
|
||||||
|
d: [
|
||||||
|
"M0,50 Q300,10 600,50 T1200,50 L1200,120 L0,120 Z",
|
||||||
|
"M0,50 Q300,90 600,50 T1200,50 L1200,120 L0,120 Z",
|
||||||
|
"M0,50 Q300,10 600,50 T1200,50 L1200,120 L0,120 Z",
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 4,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Add button */}
|
||||||
|
<button
|
||||||
|
onClick={onAddWater}
|
||||||
|
className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-16 h-16 bg-blue-400/50 hover:bg-blue-400/70 backdrop-blur-sm rounded-full flex items-center justify-center shadow-xl transition-all hover:scale-110 z-10"
|
||||||
|
>
|
||||||
|
<Plus className="w-8 h-8 text-white" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Amount display */}
|
||||||
|
<div className="absolute bottom-6 left-0 right-0 text-center z-10">
|
||||||
|
<span className="text-2xl font-bold text-gray-700">
|
||||||
|
{currentAmount.toFixed(1)}L / {targetAmount.toFixed(1)}L
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. /src/app/components/StepsTracker.tsx
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
interface StepsTrackerProps {
|
||||||
|
steps: number;
|
||||||
|
goal: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StepsTracker({ steps, goal }: StepsTrackerProps) {
|
||||||
|
const percentage = Math.min((steps / goal) * 100, 100);
|
||||||
|
const circumference = 2 * Math.PI * 45;
|
||||||
|
const strokeDashoffset = circumference - (percentage / 100) * circumference;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-gradient-to-br from-blue-50 to-white rounded-2xl p-6 shadow-md">
|
||||||
|
<h3 className="text-sm font-semibold text-gray-700 mb-4">Steps Tracker</h3>
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<div className="relative w-32 h-32">
|
||||||
|
<svg className="transform -rotate-90 w-32 h-32">
|
||||||
|
<circle
|
||||||
|
cx="64"
|
||||||
|
cy="64"
|
||||||
|
r="45"
|
||||||
|
stroke="#E5E7EB"
|
||||||
|
strokeWidth="8"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="64"
|
||||||
|
cy="64"
|
||||||
|
r="45"
|
||||||
|
stroke="#60A5FA"
|
||||||
|
strokeWidth="8"
|
||||||
|
fill="none"
|
||||||
|
strokeDasharray={circumference}
|
||||||
|
strokeDashoffset={strokeDashoffset}
|
||||||
|
strokeLinecap="round"
|
||||||
|
className="transition-all duration-500"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
||||||
|
<span className="text-2xl font-bold text-gray-800">{steps.toLocaleString()}</span>
|
||||||
|
<span className="text-xs text-gray-500">steps</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. /src/app/components/StravaIntegration.tsx
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
interface StravaIntegrationProps {
|
||||||
|
distance: number;
|
||||||
|
lastSync: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StravaIntegration({ distance, lastSync }: StravaIntegrationProps) {
|
||||||
|
return (
|
||||||
|
<div className="bg-gradient-to-br from-orange-50 to-white rounded-2xl p-6 shadow-md">
|
||||||
|
<h3 className="text-sm font-semibold text-gray-700 mb-4">Strava Integration</h3>
|
||||||
|
<div className="flex flex-col items-center justify-center py-4">
|
||||||
|
<div className="w-16 h-16 bg-gradient-to-br from-orange-500 to-orange-600 rounded-2xl flex items-center justify-center mb-3 shadow-lg transform rotate-12">
|
||||||
|
<span className="text-3xl font-bold text-white transform -rotate-12">S</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-lg font-bold text-gray-800">{distance}km Run</span>
|
||||||
|
<span className="text-xs text-gray-500 mt-1">Last Sync: {lastSync}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. /src/app/components/SleepTracker.tsx
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { LineChart, Line, ResponsiveContainer, YAxis } from 'recharts';
|
||||||
|
|
||||||
|
interface SleepTrackerProps {
|
||||||
|
hours: number;
|
||||||
|
minutes: number;
|
||||||
|
sleepType: string;
|
||||||
|
data: { value: number }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SleepTracker({ hours, minutes, sleepType, data }: SleepTrackerProps) {
|
||||||
|
return (
|
||||||
|
<div className="bg-gradient-to-br from-blue-50 to-white rounded-2xl p-6 shadow-md">
|
||||||
|
<h3 className="text-sm font-semibold text-gray-700 mb-4">Sleep Tracker</h3>
|
||||||
|
<div className="h-24 mb-3">
|
||||||
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
<LineChart data={data}>
|
||||||
|
<YAxis hide domain={[0, 100]} />
|
||||||
|
<Line
|
||||||
|
type="monotone"
|
||||||
|
dataKey="value"
|
||||||
|
stroke="#60A5FA"
|
||||||
|
strokeWidth={2}
|
||||||
|
dot={false}
|
||||||
|
/>
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-xl font-bold text-gray-800">
|
||||||
|
{hours}h {minutes}m
|
||||||
|
</span>
|
||||||
|
<span className="text-sm text-blue-600 font-medium">{sleepType}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. /src/app/components/Goals.tsx
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Check } from 'lucide-react';
|
||||||
|
|
||||||
|
interface Goal {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
completed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GoalsProps {
|
||||||
|
goals: Goal[];
|
||||||
|
onToggleGoal: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Goals({ goals, onToggleGoal }: GoalsProps) {
|
||||||
|
return (
|
||||||
|
<div className="bg-gradient-to-br from-blue-50 to-white rounded-2xl p-6 shadow-md">
|
||||||
|
<h3 className="text-sm font-semibold text-gray-700 mb-4">Goals</h3>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{goals.map((goal) => (
|
||||||
|
<button
|
||||||
|
key={goal.id}
|
||||||
|
onClick={() => onToggleGoal(goal.id)}
|
||||||
|
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all ${
|
||||||
|
goal.completed
|
||||||
|
? 'bg-blue-500 text-white shadow-lg'
|
||||||
|
: 'bg-blue-100 text-gray-700 hover:bg-blue-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`w-5 h-5 rounded-full flex items-center justify-center ${
|
||||||
|
goal.completed ? 'bg-white/30' : 'bg-white'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{goal.completed && <Check className="w-4 h-4 text-blue-500" />}
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-medium">{goal.text}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. /src/app/components/Notes.tsx
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
interface NotesProps {
|
||||||
|
note: string;
|
||||||
|
onNoteChange: (note: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Notes({ note, onNoteChange }: NotesProps) {
|
||||||
|
return (
|
||||||
|
<div className="bg-gradient-to-br from-blue-50 to-white rounded-2xl p-6 shadow-md">
|
||||||
|
<h3 className="text-sm font-semibold text-gray-700 mb-4">Notes</h3>
|
||||||
|
<textarea
|
||||||
|
value={note}
|
||||||
|
onChange={(e) => onNoteChange(e.target.value)}
|
||||||
|
placeholder="Add your notes here..."
|
||||||
|
className="w-full h-24 bg-white/50 rounded-lg px-4 py-3 text-sm text-gray-700 placeholder-gray-400 border-none outline-none resize-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Features & Interactions
|
||||||
|
|
||||||
|
1. **Water Tracker**
|
||||||
|
- Animated wave visualization using SVG and Motion
|
||||||
|
- Click + button to add 0.25L (max 3.0L)
|
||||||
|
- Displays current/target amount
|
||||||
|
|
||||||
|
2. **Steps Tracker**
|
||||||
|
- Circular progress indicator using SVG circles
|
||||||
|
- Shows 8,432 / 10,000 steps
|
||||||
|
- Animated stroke-dashoffset for progress
|
||||||
|
|
||||||
|
3. **Strava Integration**
|
||||||
|
- Orange gradient background
|
||||||
|
- Rotated "S" logo
|
||||||
|
- Displays distance and last sync time
|
||||||
|
|
||||||
|
4. **Sleep Tracker**
|
||||||
|
- Recharts line chart visualization
|
||||||
|
- Shows hours and minutes
|
||||||
|
- Sleep type label (Light Sleep)
|
||||||
|
|
||||||
|
5. **Goals**
|
||||||
|
- Three clickable goal items
|
||||||
|
- Toggle completed state
|
||||||
|
- Visual feedback with color change
|
||||||
|
|
||||||
|
6. **Notes**
|
||||||
|
- Textarea for user input
|
||||||
|
- Placeholder text
|
||||||
|
- Auto-updating state
|
||||||
|
|
||||||
|
7. **Header**
|
||||||
|
- User avatar with initials "JD"
|
||||||
|
- Level indicator
|
||||||
|
- Streak counter with droplet icons
|
||||||
|
- Live date/time that updates every second
|
||||||
|
|
||||||
|
## Important Implementation Notes
|
||||||
|
|
||||||
|
- Use Tailwind CSS v4 (no config file needed)
|
||||||
|
- Motion package is imported as `import { motion } from 'motion/react'`
|
||||||
|
- All components are TypeScript with proper interfaces
|
||||||
|
- State management uses React useState hooks
|
||||||
|
- DateTime updates every 1000ms using setInterval
|
||||||
|
- Responsive design with max-width container
|
||||||
|
- All interactive elements have hover states
|
||||||
|
- Glassmorphism effects using backdrop-blur and opacity
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
- [ ] Water tracker + button increases amount
|
||||||
|
- [ ] Water amount doesn't exceed 3.0L
|
||||||
|
- [ ] Goals toggle between completed/incomplete
|
||||||
|
- [ ] Notes textarea updates on input
|
||||||
|
- [ ] Date and time update in real-time
|
||||||
|
- [ ] All animations are smooth
|
||||||
|
- [ ] Mobile responsive (320px - 448px width)
|
||||||
|
- [ ] All icons from lucide-react render correctly
|
||||||
|
- [ ] Sleep chart displays properly with recharts
|
||||||
266
Hydroflux/Single_File.html.txt
Normal file
266
Hydroflux/Single_File.html.txt
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Hydro Flux Dashboard</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<style>
|
||||||
|
@keyframes wave {
|
||||||
|
0%, 100% { transform: translateY(0); }
|
||||||
|
50% { transform: translateY(-10px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-animation {
|
||||||
|
animation: wave 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% { transform: translateY(0px); }
|
||||||
|
50% { transform: translateY(-5px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-animation {
|
||||||
|
animation: float 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.water-container {
|
||||||
|
transition: height 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom scrollbar for notes */
|
||||||
|
textarea::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea::-webkit-scrollbar-thumb {
|
||||||
|
background: #cbd5e1;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-gradient-to-br from-gray-100 to-gray-200 min-h-screen flex items-center justify-center p-4">
|
||||||
|
|
||||||
|
<div class="w-full max-w-md bg-white rounded-[3rem] shadow-2xl p-6 relative overflow-hidden">
|
||||||
|
<!-- Background decoration -->
|
||||||
|
<div class="absolute top-0 right-0 w-64 h-64 bg-blue-200 opacity-20 rounded-full blur-3xl -z-10"></div>
|
||||||
|
<div class="absolute bottom-0 left-0 w-64 h-64 bg-purple-200 opacity-20 rounded-full blur-3xl -z-10"></div>
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="flex flex-col items-center gap-2 mb-6">
|
||||||
|
<div class="flex items-center justify-between w-full px-4">
|
||||||
|
<!-- Avatar -->
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<div class="w-14 h-14 rounded-full bg-gradient-to-br from-blue-400 to-blue-600 flex items-center justify-center mb-1 shadow-lg">
|
||||||
|
<div class="w-12 h-12 rounded-full bg-white bg-opacity-90 flex items-center justify-center text-sm font-semibold text-gray-700">
|
||||||
|
JD
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs text-blue-600 font-medium">Level 14</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Title and DateTime -->
|
||||||
|
<div class="flex flex-col items-center flex-1">
|
||||||
|
<h1 class="text-2xl font-bold text-gray-800 mb-1">Hydro Flux</h1>
|
||||||
|
<div id="currentDate" class="text-sm text-gray-600"></div>
|
||||||
|
<div id="currentTime" class="text-sm text-gray-500"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Streak -->
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<svg class="w-5 h-5 fill-blue-500 text-blue-500" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z"/>
|
||||||
|
</svg>
|
||||||
|
<svg class="w-5 h-5 fill-blue-400 text-blue-400" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z"/>
|
||||||
|
</svg>
|
||||||
|
<span class="text-sm font-semibold text-gray-700 ml-1">14 Days</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Water Tracker -->
|
||||||
|
<div class="relative h-64 bg-gradient-to-b from-blue-50 to-blue-100 rounded-3xl overflow-hidden shadow-lg mb-6">
|
||||||
|
<!-- Water fill -->
|
||||||
|
<div id="waterFill" class="absolute bottom-0 left-0 right-0 water-container" style="height: 40%; background: linear-gradient(180deg, rgba(96, 165, 250, 0.8) 0%, rgba(59, 130, 246, 0.9) 100%);">
|
||||||
|
<!-- Wave SVG -->
|
||||||
|
<svg class="absolute top-0 left-0 w-full wave-animation" viewBox="0 0 1200 120" preserveAspectRatio="none" style="height: 60px; transform: translateY(-50%);">
|
||||||
|
<path d="M0,50 Q300,10 600,50 T1200,50 L1200,120 L0,120 Z" fill="rgba(96, 165, 250, 0.5)"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add button -->
|
||||||
|
<button id="addWaterBtn" class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-16 h-16 bg-blue-400 bg-opacity-50 hover:bg-opacity-70 backdrop-blur-sm rounded-full flex items-center justify-center shadow-xl transition-all hover:scale-110 z-10">
|
||||||
|
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Amount display -->
|
||||||
|
<div class="absolute bottom-6 left-0 right-0 text-center z-10">
|
||||||
|
<span id="waterAmount" class="text-2xl font-bold text-gray-700">1.2L / 3.0L</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Grid 1: Steps and Strava -->
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||||
|
<!-- Steps Tracker -->
|
||||||
|
<div class="bg-gradient-to-br from-blue-50 to-white rounded-2xl p-6 shadow-md">
|
||||||
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">Steps Tracker</h3>
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<div class="relative w-32 h-32">
|
||||||
|
<svg class="transform -rotate-90 w-32 h-32">
|
||||||
|
<circle cx="64" cy="64" r="45" stroke="#E5E7EB" stroke-width="8" fill="none"/>
|
||||||
|
<circle id="stepsCircle" cx="64" cy="64" r="45" stroke="#60A5FA" stroke-width="8" fill="none" stroke-linecap="round" style="stroke-dasharray: 282.74; stroke-dashoffset: 56.55; transition: stroke-dashoffset 0.5s;"/>
|
||||||
|
</svg>
|
||||||
|
<div class="absolute inset-0 flex flex-col items-center justify-center">
|
||||||
|
<span class="text-2xl font-bold text-gray-800">8,432</span>
|
||||||
|
<span class="text-xs text-gray-500">steps</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Strava Integration -->
|
||||||
|
<div class="bg-gradient-to-br from-orange-50 to-white rounded-2xl p-6 shadow-md">
|
||||||
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">Strava Integration</h3>
|
||||||
|
<div class="flex flex-col items-center justify-center py-4">
|
||||||
|
<div class="w-16 h-16 bg-gradient-to-br from-orange-500 to-orange-600 rounded-2xl flex items-center justify-center mb-3 shadow-lg transform rotate-12">
|
||||||
|
<span class="text-3xl font-bold text-white transform -rotate-12">S</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-lg font-bold text-gray-800">5.2km Run</span>
|
||||||
|
<span class="text-xs text-gray-500 mt-1">Last Sync: 2h ago</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sleep Tracker -->
|
||||||
|
<div class="bg-gradient-to-br from-blue-50 to-white rounded-2xl p-6 shadow-md mb-4">
|
||||||
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">Sleep Tracker</h3>
|
||||||
|
<div class="h-24 mb-3">
|
||||||
|
<svg id="sleepChart" class="w-full h-full" viewBox="0 0 300 100" preserveAspectRatio="none">
|
||||||
|
<polyline id="sleepLine" points="" fill="none" stroke="#60A5FA" stroke-width="2"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-xl font-bold text-gray-800">7h 20m</span>
|
||||||
|
<span class="text-sm text-blue-600 font-medium">Light Sleep</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Grid 2: Goals and Notes -->
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<!-- Goals -->
|
||||||
|
<div class="bg-gradient-to-br from-blue-50 to-white rounded-2xl p-6 shadow-md">
|
||||||
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">Goals</h3>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<button class="goal-btn w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all bg-blue-500 text-white shadow-lg" data-completed="true">
|
||||||
|
<div class="w-5 h-5 rounded-full flex items-center justify-center bg-white bg-opacity-30">
|
||||||
|
<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<span class="text-sm font-medium">Drink 3L of water</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="goal-btn w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all bg-blue-100 text-gray-700 hover:bg-blue-200" data-completed="false">
|
||||||
|
<div class="w-5 h-5 rounded-full flex items-center justify-center bg-white"></div>
|
||||||
|
<span class="text-sm font-medium">Walk 10K steps</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="goal-btn w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all bg-blue-100 text-gray-700 hover:bg-blue-200" data-completed="false">
|
||||||
|
<div class="w-5 h-5 rounded-full flex items-center justify-center bg-white"></div>
|
||||||
|
<span class="text-sm font-medium">Sleep 8 hours</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Notes -->
|
||||||
|
<div class="bg-gradient-to-br from-blue-50 to-white rounded-2xl p-6 shadow-md">
|
||||||
|
<h3 class="text-sm font-semibold text-gray-700 mb-4">Notes</h3>
|
||||||
|
<textarea id="notesInput" placeholder="Add your notes here..." class="w-full h-24 bg-white bg-opacity-50 rounded-lg px-4 py-3 text-sm text-gray-700 placeholder-gray-400 border-none outline-none resize-none">Feeling hydrated today. Remember to add those gym sessions!</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Water tracking
|
||||||
|
let waterAmount = 1.2;
|
||||||
|
const maxWater = 3.0;
|
||||||
|
const waterIncrement = 0.25;
|
||||||
|
|
||||||
|
function updateWaterDisplay() {
|
||||||
|
const percentage = (waterAmount / maxWater) * 100;
|
||||||
|
document.getElementById('waterFill').style.height = percentage + '%';
|
||||||
|
document.getElementById('waterAmount').textContent = waterAmount.toFixed(1) + 'L / ' + maxWater.toFixed(1) + 'L';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('addWaterBtn').addEventListener('click', function() {
|
||||||
|
if (waterAmount < maxWater) {
|
||||||
|
waterAmount = Math.min(waterAmount + waterIncrement, maxWater);
|
||||||
|
updateWaterDisplay();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// DateTime update
|
||||||
|
function updateDateTime() {
|
||||||
|
const now = new Date();
|
||||||
|
const dateOptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
|
||||||
|
const timeOptions = { hour: 'numeric', minute: '2-digit', hour12: true };
|
||||||
|
|
||||||
|
document.getElementById('currentDate').textContent = now.toLocaleDateString('en-US', dateOptions);
|
||||||
|
document.getElementById('currentTime').textContent = now.toLocaleTimeString('en-US', timeOptions) + ' AEDT';
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDateTime();
|
||||||
|
setInterval(updateDateTime, 1000);
|
||||||
|
|
||||||
|
// Steps circle calculation
|
||||||
|
const steps = 8432;
|
||||||
|
const goal = 10000;
|
||||||
|
const percentage = Math.min((steps / goal) * 100, 100);
|
||||||
|
const circumference = 2 * Math.PI * 45;
|
||||||
|
const strokeDashoffset = circumference - (percentage / 100) * circumference;
|
||||||
|
document.getElementById('stepsCircle').style.strokeDasharray = circumference;
|
||||||
|
document.getElementById('stepsCircle').style.strokeDashoffset = strokeDashoffset;
|
||||||
|
|
||||||
|
// Sleep chart
|
||||||
|
const sleepData = [30, 35, 25, 40, 55, 45, 60, 75, 70, 80, 65, 55, 50, 45, 40];
|
||||||
|
const chartWidth = 300;
|
||||||
|
const chartHeight = 100;
|
||||||
|
const points = sleepData.map((value, index) => {
|
||||||
|
const x = (index / (sleepData.length - 1)) * chartWidth;
|
||||||
|
const y = chartHeight - (value / 100) * chartHeight;
|
||||||
|
return x + ',' + y;
|
||||||
|
}).join(' ');
|
||||||
|
document.getElementById('sleepLine').setAttribute('points', points);
|
||||||
|
|
||||||
|
// Goals toggle
|
||||||
|
document.querySelectorAll('.goal-btn').forEach(button => {
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
const isCompleted = this.getAttribute('data-completed') === 'true';
|
||||||
|
const newState = !isCompleted;
|
||||||
|
|
||||||
|
this.setAttribute('data-completed', newState);
|
||||||
|
|
||||||
|
if (newState) {
|
||||||
|
this.className = 'goal-btn w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all bg-blue-500 text-white shadow-lg';
|
||||||
|
this.querySelector('div').className = 'w-5 h-5 rounded-full flex items-center justify-center bg-white bg-opacity-30';
|
||||||
|
this.querySelector('div').innerHTML = '<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>';
|
||||||
|
} else {
|
||||||
|
this.className = 'goal-btn w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all bg-blue-100 text-gray-700 hover:bg-blue-200';
|
||||||
|
this.querySelector('div').className = 'w-5 h-5 rounded-full flex items-center justify-center bg-white';
|
||||||
|
this.querySelector('div').innerHTML = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
updateWaterDisplay();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,20 +1,11 @@
|
|||||||
|
/* --- DEPRECATED GLOBAL STYLES (CLEARED FOR TAILWIND LAYOUT) --- */
|
||||||
|
/*
|
||||||
|
:root { ... }
|
||||||
|
body { ... }
|
||||||
|
#app { ... }
|
||||||
|
*/
|
||||||
|
/* Keeping Font definitions for reference if needed context */
|
||||||
:root {
|
:root {
|
||||||
/* Futuristic Palette */
|
|
||||||
--bg-dark: #050508;
|
|
||||||
--bg-panel: rgba(20, 20, 35, 0.4);
|
|
||||||
--primary-cyan: #00f3ff;
|
|
||||||
--secondary-purple: #bc13fe;
|
|
||||||
--text-main: #e0e0e0;
|
|
||||||
--text-muted: #8a8a9b;
|
|
||||||
--glass-border: rgba(255, 255, 255, 0.08);
|
|
||||||
--neon-shadow: 0 0 10px rgba(0, 243, 255, 0.5);
|
|
||||||
|
|
||||||
/* Spacing */
|
|
||||||
--spacing-sm: 8px;
|
|
||||||
--spacing-md: 16px;
|
|
||||||
--spacing-lg: 24px;
|
|
||||||
|
|
||||||
/* Font */
|
|
||||||
--font-heading: 'Orbitron', sans-serif;
|
--font-heading: 'Orbitron', sans-serif;
|
||||||
--font-body: 'Outfit', sans-serif;
|
--font-body: 'Outfit', sans-serif;
|
||||||
}
|
}
|
||||||
@@ -24,126 +15,9 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
/* Remove mobile tap highlight */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
/* Global Background handled by Tailwind Utility Classes on body */
|
||||||
background-color: var(--bg-dark);
|
|
||||||
color: var(--text-main);
|
|
||||||
font-family: var(--font-body);
|
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
background-image:
|
|
||||||
radial-gradient(circle at 10% 20%, rgba(188, 19, 254, 0.1) 0%, transparent 40%),
|
|
||||||
radial-gradient(circle at 90% 80%, rgba(0, 243, 255, 0.08) 0%, transparent 40%);
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Typography */
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3 {
|
|
||||||
font-family: var(--font-heading);
|
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glow-text {
|
|
||||||
text-shadow: 0 0 8px rgba(0, 243, 255, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlight {
|
|
||||||
color: var(--primary-cyan);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Glassmorphism Utilities */
|
|
||||||
.glass-header {
|
|
||||||
background: rgba(5, 5, 8, 0.7);
|
|
||||||
backdrop-filter: blur(12px);
|
|
||||||
-webkit-backdrop-filter: blur(12px);
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
border-bottom: 1px solid var(--glass-border);
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glass-panel {
|
|
||||||
background: var(--bg-panel);
|
|
||||||
backdrop-filter: blur(8px);
|
|
||||||
-webkit-backdrop-filter: blur(8px);
|
|
||||||
border: 1px solid var(--glass-border);
|
|
||||||
border-radius: 20px;
|
|
||||||
padding: var(--spacing-lg);
|
|
||||||
margin: var(--spacing-md);
|
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.glass-nav {
|
|
||||||
background: rgba(10, 10, 16, 0.8);
|
|
||||||
backdrop-filter: blur(16px);
|
|
||||||
-webkit-backdrop-filter: blur(16px);
|
|
||||||
border-top: 1px solid var(--glass-border);
|
|
||||||
padding: var(--spacing-md) var(--spacing-lg);
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-around;
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
padding-bottom: max(16px, env(safe-area-inset-bottom));
|
|
||||||
/* Safe area for iOS */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Content Area */
|
|
||||||
#main-content {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding-bottom: 80px;
|
|
||||||
/* Space for nav */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Interactive Elements */
|
|
||||||
.nav-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--text-muted);
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 50%;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-btn.active {
|
|
||||||
color: var(--primary-cyan);
|
|
||||||
background: rgba(0, 243, 255, 0.1);
|
|
||||||
box-shadow: 0 0 15px rgba(0, 243, 255, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-btn svg {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Animations */
|
|
||||||
@keyframes pulse-glow {
|
|
||||||
0% {
|
|
||||||
box-shadow: 0 0 5px rgba(0, 243, 255, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
box-shadow: 0 0 20px rgba(0, 243, 255, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
box-shadow: 0 0 5px rgba(0, 243, 255, 0.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Water Tracker Module Styles --- */
|
/* --- Water Tracker Module Styles --- */
|
||||||
|
|
||||||
@@ -907,49 +781,353 @@ h3 {
|
|||||||
.sch-day {
|
.sch-day {
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
.cal-day.today::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 4px;
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Modal Styles --- */
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
z-index: 100;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
width: 90%;
|
||||||
|
max-width: 400px;
|
||||||
|
background: rgba(10, 10, 20, 0.95);
|
||||||
|
/* Darker for readability */
|
||||||
|
border: 1px solid var(--primary-cyan);
|
||||||
|
box-shadow: 0 0 30px rgba(0, 243, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-bottom: 1px solid var(--glass-border);
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn-small {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-group label {
|
||||||
|
display: block;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 0.9rem;
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sch-type {
|
.glow-input {
|
||||||
font-weight: 600;
|
width: 100%;
|
||||||
}/ | |||||||