diff --git a/Hydroflux/.idea/vcs.xml b/Hydroflux/.idea/vcs.xml
new file mode 100644
index 0000000..6c0b863
--- /dev/null
+++ b/Hydroflux/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Hydroflux/REPLICATION_GUIDE.md b/Hydroflux/REPLICATION_GUIDE.md
new file mode 100644
index 0000000..7bb4b55
--- /dev/null
+++ b/Hydroflux/REPLICATION_GUIDE.md
@@ -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 (
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Hydroflux/app/src/main/assets/js/app.js b/Hydroflux/app/src/main/assets/js/app.js
index 816dccf..b2a1c6f 100644
--- a/Hydroflux/app/src/main/assets/js/app.js
+++ b/Hydroflux/app/src/main/assets/js/app.js
@@ -1,43 +1,72 @@
-console.log('HydroFlux Initialized');
+import { Dashboard } from './modules/dashboard.js';
+import { WaterView } from './modules/views/WaterView.js';
+import { FitnessView } from './modules/views/FitnessView.js';
+import { SleepView } from './modules/views/SleepView.js';
+import { GoalsView } from './modules/views/GoalsView.js';
+import { NotesView } from './modules/views/NotesView.js';
-// Simple Navigation Logic
+class App {
+ constructor() {
+ this.appContainer = document.getElementById('app');
+ this.currentView = null;
+ this.routes = {
+ 'dashboard': Dashboard,
+ 'water-detail': WaterView,
+ 'fitness-detail': FitnessView,
+ 'sleep-detail': SleepView,
+ 'goals-detail': GoalsView,
+ 'notes-detail': NotesView
+ };
-// Simple Navigation Logic - REPLACED BY NEW BLOCK BELOW
-
-// Initialize Modules
-import { WaterTracker } from './modules/water.js';
-import { StreakTracker } from './modules/streak.js';
-import { FitnessDashboard } from './modules/fitness.js';
-import { StatsDashboard } from './modules/stats.js';
-import { GoalsTracker } from './modules/goals.js';
-
-const waterTracker = new WaterTracker('water-section');
-const streakTracker = new StreakTracker('streak-section');
-const fitnessDashboard = new FitnessDashboard('fitness-section');
-const statsDashboard = new StatsDashboard('stats-section');
-const goalsTracker = new GoalsTracker('goals-section');
-
-// Navigation Logic with Auto-Refresh
-document.querySelectorAll('.nav-btn').forEach(btn => {
- btn.addEventListener('click', (e) => {
- // Toggle Active State
- document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));
- e.currentTarget.classList.add('active');
-
- const view = e.currentTarget.dataset.view;
-
- // Hide all sections
- document.querySelectorAll('section').forEach(el => el.style.display = 'none');
-
- // Show target section
- const target = document.getElementById(`${view}-section`);
- if (target) {
- target.style.display = 'block';
-
- // Auto-Refresh Stats when viewing
- if (view === 'stats') {
- statsDashboard.update();
+ // Handle browser back button
+ window.addEventListener('popstate', (e) => {
+ if (e.state && e.state.view) {
+ this.render(e.state.view, e.state.params);
+ } else {
+ this.render('dashboard');
}
+ });
+
+ this.init();
+ }
+
+ init() {
+ console.log('HydroFlux Router Initialized');
+ this.navigateTo('dashboard');
+ }
+
+ navigateTo(viewName, params = {}) {
+ history.pushState({ view: viewName, params }, '', `#${viewName}`);
+ this.render(viewName, params);
+ }
+
+ back() {
+ // history.back() can be tricky if state isn't perfect, safer to just go dashboard for now
+ this.navigateTo('dashboard');
+ }
+
+ async render(viewName, params) {
+ // Cleanup current view if needed (optional destroy method)
+ if (this.currentView && typeof this.currentView.destroy === 'function') {
+ this.currentView.destroy();
}
- });
+
+ this.appContainer.innerHTML = ''; // Clear container
+
+ const ViewClass = this.routes[viewName] || this.routes['dashboard'];
+
+ // Dynamic Import for Views (if we wanted lazy loading, but consistent imports are easier for now)
+ // For now, we instantiate directly.
+
+ this.currentView = new ViewClass('app', this, params);
+ // Note: Dashboard expects (containerId, app). We'll keep that signature.
+ }
+}
+
+// Start App
+const app = new App();
+
+// Global health bridge listener
+window.addEventListener('health-update', (e) => {
+ console.log('Health Update Received:', e.detail);
});
diff --git a/Hydroflux/app/src/main/assets/js/modules/dashboard.js b/Hydroflux/app/src/main/assets/js/modules/dashboard.js
new file mode 100644
index 0000000..8e0cb5b
--- /dev/null
+++ b/Hydroflux/app/src/main/assets/js/modules/dashboard.js
@@ -0,0 +1,319 @@
+export class Dashboard {
+ constructor(containerId, app) {
+ this.container = document.getElementById(containerId);
+ this.app = app;
+ this.render();
+ this.attachEvents();
+ this.startTimers();
+ }
+
+ render() {
+ if (!this.container) return;
+
+ // Data Retrieval (Keeping persistence)
+ const waterData = JSON.parse(localStorage.getItem('hydroflux_data') || '{"current":1.2,"goal":3.0}');
+ const stepsData = 8432;
+ const goalData = 10000;
+ const sleepHours = 7;
+ const sleepMins = 20;
+
+ const goalsData = JSON.parse(localStorage.getItem('hydroflux_goals') || JSON.stringify([
+ { 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 },
+ ]));
+
+ const currentNote = localStorage.getItem('hydroflux_note_content') || 'Feeling hydrated today. Remember to add those gym sessions!';
+
+ // HTML from Single_File.html.txt (Body Content Only)
+ // Adapted to use Template Literals for dynamic data
+ this.container.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${parseFloat(waterData.current).toFixed(1)}L / ${parseFloat(waterData.goal).toFixed(1)}L
+
+
+
+
+
+
+
+
Steps Tracker
+
+
+
+
+ ${stepsData.toLocaleString()}
+ steps
+
+
+
+
+
+
+
+
Strava Integration
+
+
+ S
+
+
5.2km Run
+
Last Sync: 2h ago
+
+
+
+
+
+
+
Sleep Tracker
+
+
+ ${sleepHours}h ${sleepMins}m
+ Light Sleep
+
+
+
+
+
+
+ `;
+
+ this.updateDynamicElements(stepsData, goalData);
+ this.renderGoals(goalsData);
+ }
+
+ updateDynamicElements(steps, goal) {
+ // Steps Circle Logic
+ const percentage = Math.min((steps / goal) * 100, 100);
+ const circumference = 2 * Math.PI * 45;
+ const strokeDashoffset = circumference - (percentage / 100) * circumference;
+ const circle = this.container.querySelector('#stepsCircle');
+ if (circle) {
+ circle.style.strokeDasharray = circumference;
+ circle.style.strokeDashoffset = strokeDashoffset;
+ }
+
+ // Sleep Chart Logic
+ 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(' ');
+ const line = this.container.querySelector('#sleepLine');
+ if (line) line.setAttribute('points', points);
+ }
+
+ renderGoals(goalsData) {
+ const list = this.container.querySelector('#goalsList');
+ if (!list) return;
+ list.innerHTML = goalsData.map(g => `
+
+ `).join('');
+
+ // Re-attach listeners for goals
+ list.querySelectorAll('.goal-btn').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const id = btn.dataset.id;
+ const goal = goalsData.find(g => g.id === id);
+ if (goal) {
+ goal.completed = !goal.completed;
+ localStorage.setItem('hydroflux_goals', JSON.stringify(goalsData));
+ this.renderGoals(goalsData); // Re-render goals only
+ }
+ });
+ });
+ }
+
+ attachEvents() {
+ const self = this; // Capture this
+
+ // Navigation Events (Widget Clicks)
+ // Water - Targeting the wrapper div mostly, ensuring specific targets don't block
+ const waterCard = this.container.querySelector('.water-card-n');
+ if (waterCard) {
+ waterCard.style.cursor = 'pointer';
+ waterCard.addEventListener('click', (e) => {
+ // IMPORTANT: Do NOT navigate if clicking the 'Add Water' button or its SVG children
+ if (e.target.closest('#addWaterBtn')) {
+ // Do nothing, the add listener below handles it
+ return;
+ }
+ this.app.navigateTo('water-detail');
+ });
+ }
+
+ // Notes
+ const notesCard = this.container.querySelector('#notesInput')?.closest('.rounded-2xl');
+ if (notesCard) {
+ // Make the whole card clickable, but maybe not the textarea itself if we want to allow quick edits?
+ // User requested "click on notes we get a fullscreen pop", so presumably we redirect immediately.
+ // Or we make the header/container clickable.
+ // Let's make the textarea readonly in dashboard view OR just redirect on focus?
+ // "click on notes we get a fullscreen pop" -> redirect on click anywhere on card
+ const ta = this.container.querySelector('#notesInput');
+ if (ta) ta.setAttribute('readonly', 'true'); // Make it read-only on dashboard to force click-through
+
+ notesCard.style.cursor = 'pointer';
+ notesCard.addEventListener('click', () => {
+ this.app.navigateTo('notes-detail');
+ });
+ }
+ const stepsCard = this.container.querySelector('#stepsCircle')?.closest('.rounded-2xl');
+ if (stepsCard) {
+ stepsCard.style.cursor = 'pointer';
+ stepsCard.addEventListener('click', () => this.app.navigateTo('fitness-detail'));
+ }
+
+ // Strava
+ const stravaCard = this.container.querySelector('.from-orange-50');
+ if (stravaCard) {
+ stravaCard.style.cursor = 'pointer';
+ stravaCard.addEventListener('click', () => this.app.navigateTo('fitness-detail'));
+ }
+
+ // Sleep
+ const sleepCard = this.container.querySelector('#sleepChart')?.closest('.rounded-2xl');
+ if (sleepCard) {
+ sleepCard.style.cursor = 'pointer';
+ sleepCard.addEventListener('click', () => this.app.navigateTo('sleep-detail'));
+ }
+
+ // Goals
+ const goalsCard = this.container.querySelector('#goalsList')?.closest('.rounded-2xl');
+ if (goalsCard) {
+ // Only navigate if not clicking a specialized button, but goals list is buttons...
+ // Maybe we add a "View All" or just make the header clickable?
+ // For now, let's make the header clickable
+ const header = goalsCard.querySelector('h3');
+ if (header) {
+ header.innerHTML += '
View All →';
+ header.addEventListener('click', () => this.app.navigateTo('goals-detail'));
+ }
+ }
+
+
+ // Water Add
+ const addBtn = this.container.querySelector('#addWaterBtn');
+ if (addBtn) {
+ addBtn.addEventListener('click', (e) => {
+ e.stopPropagation(); // Stop bubble to card click
+ const data = JSON.parse(localStorage.getItem('hydroflux_data') || '{"current":1.2,"goal":3.0}');
+ if (data.current < data.goal) {
+ data.current = Math.min(data.current + 0.25, data.goal);
+ localStorage.setItem('hydroflux_data', JSON.stringify(data));
+
+ // Update Display without full re-render
+ const percentage = (data.current / data.goal) * 100;
+ const fill = this.container.querySelector('#waterFill');
+ const amount = this.container.querySelector('#waterAmount');
+ if (fill) fill.style.height = percentage + '%';
+ if (amount) amount.textContent = data.current.toFixed(1) + 'L / ' + data.goal.toFixed(1) + 'L';
+ }
+ });
+ }
+
+ // Notes Save
+ const noteArea = this.container.querySelector('#notesInput');
+ if (noteArea) {
+ noteArea.addEventListener('input', (e) => {
+ localStorage.setItem('hydroflux_note_content', e.target.value);
+ });
+ }
+ }
+
+ startTimers() {
+ const updateTime = () => {
+ const now = new Date();
+ const dateEl = this.container.querySelector('#currentDate');
+ const timeEl = this.container.querySelector('#currentTime');
+ if (dateEl) dateEl.textContent = now.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
+ if (timeEl) timeEl.textContent = now.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }) + ' AEDT';
+ };
+ updateTime();
+ setInterval(updateTime, 1000);
+ }
+}
diff --git a/Hydroflux/app/src/main/assets/js/modules/notes.js b/Hydroflux/app/src/main/assets/js/modules/notes.js
new file mode 100644
index 0000000..b3eef3f
--- /dev/null
+++ b/Hydroflux/app/src/main/assets/js/modules/notes.js
@@ -0,0 +1,89 @@
+export class NotesTracker {
+ constructor(containerId) {
+ this.container = document.getElementById(containerId);
+ this.STORAGE_KEY = 'hydroflux_notes';
+ this.notes = [];
+ this.loadState();
+ this.render();
+ }
+
+ loadState() {
+ const saved = localStorage.getItem(this.STORAGE_KEY);
+ if (saved) {
+ this.notes = JSON.parse(saved);
+ } else {
+ this.notes = [
+ { id: 1, text: "Feeling hydrated today. Remember to add those gym sessions!", date: new Date().toISOString() }
+ ];
+ this.saveState();
+ }
+ }
+
+ saveState() {
+ localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.notes));
+ }
+
+ addNote(text) {
+ if (!text.trim()) return;
+ this.notes.unshift({
+ id: Date.now(),
+ text: text,
+ date: new Date().toISOString()
+ });
+ this.saveState();
+ this.render();
+ }
+
+ deleteNote(id) {
+ if (confirm("Delete this note?")) {
+ this.notes = this.notes.filter(n => n.id !== id);
+ this.saveState();
+ this.render();
+ }
+ }
+
+ render() {
+ if (!this.container) return;
+
+ this.container.innerHTML = `
+
+
NOTES
+
+
+
+
+
+
+
+ ${this.notes.map(note => `
+
+ `).join('')}
+
+
+ `;
+
+ this.attachEvents();
+ }
+
+ attachEvents() {
+ const btn = this.container.querySelector('#add-note-btn');
+ const input = this.container.querySelector('#new-note-text');
+
+ btn.addEventListener('click', () => {
+ this.addNote(input.value);
+ input.value = '';
+ });
+
+ this.container.querySelectorAll('.delete-note-btn').forEach(b => {
+ b.addEventListener('click', (e) => {
+ this.deleteNote(parseInt(e.currentTarget.dataset.id));
+ });
+ });
+ }
+}
diff --git a/Hydroflux/app/src/main/assets/js/modules/views/FitnessView.js b/Hydroflux/app/src/main/assets/js/modules/views/FitnessView.js
new file mode 100644
index 0000000..d8987d3
--- /dev/null
+++ b/Hydroflux/app/src/main/assets/js/modules/views/FitnessView.js
@@ -0,0 +1,84 @@
+export class FitnessView {
+ constructor(containerId, app) {
+ this.container = document.getElementById(containerId);
+ this.app = app;
+ this.render();
+ this.attachEvents();
+ }
+
+ render() {
+ // Mock Data
+ const steps = 8432;
+ const goal = 10000;
+ const pct = Math.min((steps / goal) * 100, 100);
+ const radius = 80;
+ const circumference = 2 * Math.PI * radius;
+ const offset = circumference - (pct / 100) * circumference;
+
+ this.container.innerHTML = `
+
+
+
+
+
Fitness Activity
+
+
+
+
+
+
+
+
+ ${steps.toLocaleString()}
+ steps today
+
+
+
+
+
+
+
+ Calories
+ 420
+ kcal
+
+
+ Distance
+ 5.2
+ km
+
+
+
+
+
+
+
+
S
+
+
Morning Run
+
Synced via Strava
+
+
+
+
+ `;
+ }
+
+ attachEvents() {
+ this.container.querySelector('#backBtn').addEventListener('click', () => {
+ this.app.back();
+ });
+ }
+}
diff --git a/Hydroflux/app/src/main/assets/js/modules/views/GoalsView.js b/Hydroflux/app/src/main/assets/js/modules/views/GoalsView.js
new file mode 100644
index 0000000..495ae42
--- /dev/null
+++ b/Hydroflux/app/src/main/assets/js/modules/views/GoalsView.js
@@ -0,0 +1,120 @@
+export class GoalsView {
+ constructor(containerId, app) {
+ this.container = document.getElementById(containerId);
+ this.app = app;
+ this.render();
+ this.attachEvents();
+ }
+
+ render() {
+ const goalsData = JSON.parse(localStorage.getItem('hydroflux_goals') || '[]');
+
+ this.container.innerHTML = `
+
+
+
+
+
Your Goals
+
+
+
+
+
+ ${goalsData.map(g => `
+
+
+
+
${g.text}
+
Daily Goal
+
+
+
+ `).join('')}
+
+ ${goalsData.length === 0 ? `
+
+ No goals set. Tap + to add one!
+
+ ` : ''}
+
+
+
+
+
New Goal
+
+
+
+
+
+
+
+ `;
+ }
+
+ attachEvents() {
+ this.container.querySelector('#backBtn').addEventListener('click', () => this.app.back());
+
+ // Toggle Done
+ this.container.querySelectorAll('.toggle-goal').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const id = btn.dataset.id;
+ const goals = JSON.parse(localStorage.getItem('hydroflux_goals') || '[]');
+ const goal = goals.find(g => g.id === id);
+ if (goal) {
+ goal.completed = !goal.completed;
+ localStorage.setItem('hydroflux_goals', JSON.stringify(goals));
+ this.render();
+ this.attachEvents();
+ }
+ });
+ });
+
+ // Delete
+ this.container.querySelectorAll('.delete-goal').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const id = btn.dataset.id;
+ let goals = JSON.parse(localStorage.getItem('hydroflux_goals') || '[]');
+ goals = goals.filter(g => g.id !== id);
+ localStorage.setItem('hydroflux_goals', JSON.stringify(goals));
+ this.render();
+ this.attachEvents();
+ });
+ });
+
+
+ // Add Panel Logic
+ const panel = this.container.querySelector('#newGoalPanel');
+ this.container.querySelector('#addGoalProto').addEventListener('click', () => {
+ panel.classList.remove('hidden');
+ this.container.querySelector('#newGoalInput').focus();
+ });
+
+ this.container.querySelector('#cancelAdd').addEventListener('click', () => {
+ panel.classList.add('hidden');
+ });
+
+ this.container.querySelector('#saveGoal').addEventListener('click', () => {
+ const input = this.container.querySelector('#newGoalInput');
+ const text = input.value.trim();
+ if (text) {
+ const goals = JSON.parse(localStorage.getItem('hydroflux_goals') || '[]');
+ goals.push({
+ id: Date.now().toString(),
+ text: text,
+ completed: false
+ });
+ localStorage.setItem('hydroflux_goals', JSON.stringify(goals));
+ this.render();
+ this.attachEvents();
+ }
+ });
+ }
+}
diff --git a/Hydroflux/app/src/main/assets/js/modules/views/NotesView.js b/Hydroflux/app/src/main/assets/js/modules/views/NotesView.js
new file mode 100644
index 0000000..d99d644
--- /dev/null
+++ b/Hydroflux/app/src/main/assets/js/modules/views/NotesView.js
@@ -0,0 +1,63 @@
+export class NotesView {
+ constructor(containerId, app) {
+ this.container = document.getElementById(containerId);
+ this.app = app;
+ this.render();
+ this.attachEvents();
+ }
+
+ render() {
+ const currentNote = localStorage.getItem('hydroflux_note_content') || '';
+
+ this.container.innerHTML = `
+
+
+
+
+
Notes & Thoughts
+
+
+
+
+
+
+
+
+
+ `;
+
+ // Focus textarea automatically
+ setTimeout(() => {
+ const el = this.container.querySelector('#fullNoteInput');
+ if (el) {
+ el.focus();
+ el.setSelectionRange(el.value.length, el.value.length);
+ }
+ }, 300);
+ }
+
+ attachEvents() {
+ this.container.querySelector('#backBtn').addEventListener('click', () => this.app.back());
+
+ const textarea = this.container.querySelector('#fullNoteInput');
+ const status = this.container.querySelector('#saveStatus');
+
+ textarea.addEventListener('input', (e) => {
+ status.textContent = 'Saving...';
+ localStorage.setItem('hydroflux_note_content', e.target.value);
+ setTimeout(() => {
+ status.textContent = 'Auto-saved';
+ }, 800);
+ });
+
+ this.container.querySelector('#saveNoteBtn').addEventListener('click', () => this.app.back());
+ }
+}
diff --git a/Hydroflux/app/src/main/assets/js/modules/views/SleepView.js b/Hydroflux/app/src/main/assets/js/modules/views/SleepView.js
new file mode 100644
index 0000000..e286e26
--- /dev/null
+++ b/Hydroflux/app/src/main/assets/js/modules/views/SleepView.js
@@ -0,0 +1,98 @@
+export class SleepView {
+ constructor(containerId, app) {
+ this.container = document.getElementById(containerId);
+ this.app = app;
+ this.render();
+ this.attachEvents();
+ }
+
+ render() {
+ // Mock Sleep Data
+ const sleepHours = 7;
+ const sleepMins = 20;
+
+ this.container.innerHTML = `
+
+
+
+
+
Sleep Analysis
+
+
+
+
+
+
+ ${sleepHours}
+ h
+ ${sleepMins}
+ m
+
+
Total Sleep Duration
+
+
+
+
+
Sleep Stages
+
+
+
+
+
+
+
+
+ Deep Sleep
+ 2h 10m
+
+
+
+
+
+
+
+ Light Sleep
+ 3h 25m
+
+
+
+
+
+
+
+
+
Weekly Trend
+
+ ${[6.5, 7.2, 5.8, 8.0, 7.5, 6.2, 7.3].map((val, i) => `
+
+
+
${['M', 'T', 'W', 'T', 'F', 'S', 'S'][i]}
+
+ `).join('')}
+
+
+
+ `;
+ }
+
+ attachEvents() {
+ this.container.querySelector('#backBtn').addEventListener('click', () => {
+ this.app.back();
+ });
+ }
+}
diff --git a/Hydroflux/app/src/main/assets/js/modules/views/WaterView.js b/Hydroflux/app/src/main/assets/js/modules/views/WaterView.js
new file mode 100644
index 0000000..59436e1
--- /dev/null
+++ b/Hydroflux/app/src/main/assets/js/modules/views/WaterView.js
@@ -0,0 +1,196 @@
+export class WaterView {
+ constructor(containerId, app) {
+ this.container = document.getElementById(containerId);
+ this.app = app;
+ this.history = JSON.parse(localStorage.getItem('hydroflux_water_history') || '[]');
+ this.render();
+ this.attachEvents();
+ }
+
+ render() {
+ const data = JSON.parse(localStorage.getItem('hydroflux_data') || '{"current":1.2,"goal":3.0}');
+ const drinkSize = parseInt(localStorage.getItem('hydroflux_drink_size') || '250');
+ const percentage = Math.min((data.current / data.goal) * 100, 100);
+
+ this.container.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
${data.current.toFixed(1)}L
+
of ${data.goal.toFixed(1)}L Goal
+
+
+
+
+
+
+
Today's History
+
+ ${this.history.length === 0 ?
+ '
No drinks logged yet today.
' :
+ this.history.slice().reverse().map(entry => `
+
+
+
+
+
Water
+
${new Date(entry.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
+
+
+
+${entry.amount}ml
+
+ `).join('')}
+
+
+
+
+
+
+
+
+
+
+
+
+
Select Amount
+
+
+
+
+
+
+
+
+ ${[100, 250, 330, 500, 750, 1000].map(size => `
+
+ `).join('')}
+
+
+
+
+
+
+ `;
+ }
+
+ addHistory(amount) {
+ this.history.push({
+ timestamp: Date.now(),
+ amount: amount
+ });
+ localStorage.setItem('hydroflux_water_history', JSON.stringify(this.history));
+ }
+
+ attachEvents() {
+ this.container.querySelector('#backBtn').addEventListener('click', () => {
+ this.app.back();
+ });
+
+ const updateWater = (amount) => {
+ const data = JSON.parse(localStorage.getItem('hydroflux_data') || '{"current":1.2,"goal":3.0}');
+ data.current = Math.min(data.current + (amount / 1000), data.goal); // amount in ml to L
+ localStorage.setItem('hydroflux_data', JSON.stringify(data));
+ this.addHistory(amount);
+ this.render();
+ this.attachEvents();
+ };
+
+ this.container.querySelector('#addWater').addEventListener('click', () => {
+ const drinkSize = parseInt(localStorage.getItem('hydroflux_drink_size') || '250');
+ updateWater(drinkSize);
+ });
+
+ this.container.querySelector('#removeWater').addEventListener('click', () => {
+ // Removing doesn't add to history, usually just undoes
+ const data = JSON.parse(localStorage.getItem('hydroflux_data') || '{"current":1.2,"goal":3.0}');
+ const drinkSize = parseInt(localStorage.getItem('hydroflux_drink_size') || '250');
+ data.current = Math.max(data.current - (drinkSize / 1000), 0);
+ localStorage.setItem('hydroflux_data', JSON.stringify(data));
+
+ // Optionally remove last history entry if it matches?
+ // For now just keep it simple
+ this.render();
+ this.attachEvents();
+ });
+
+ // Modal Logic
+ const modal = this.container.querySelector('#sizeModal');
+ const openModal = () => modal.classList.remove('hidden');
+ const closeModal = () => modal.classList.add('hidden');
+
+ this.container.querySelector('#drinkSizeBtn').addEventListener('click', openModal);
+ this.container.querySelector('#closeModal').addEventListener('click', closeModal);
+ this.container.querySelector('#closeModalMask').addEventListener('click', closeModal);
+
+ this.container.querySelectorAll('.size-option').forEach(btn => {
+ btn.addEventListener('click', () => {
+ localStorage.setItem('hydroflux_drink_size', btn.dataset.size);
+ closeModal();
+ this.render();
+ this.attachEvents();
+ });
+ });
+
+ // Custom Input Logic
+ this.container.querySelector('#applyCustom').addEventListener('click', () => {
+ const val = this.container.querySelector('#customAmountInput').value;
+ if (val && val > 0) {
+ localStorage.setItem('hydroflux_drink_size', val);
+ closeModal();
+ this.render();
+ this.attachEvents();
+ }
+ });
+ }
+}