+
⚡
Energy
-
+
😰
Anxiety
-
+
😢
Sadness
-
+
😠
Anger
@@ -906,7 +1065,7 @@ function showSection(sectionId) {
Your Progress 📈
@@ -915,8 +1074,22 @@ function showSection(sectionId) {
+
+
+
Recent History 📜
+
+
+
+
+
+
+
+
Select a category to view history
+
+
`;
updateProgress();
+ renderHistory('mood'); // Default to mood
break;
}
}
@@ -978,13 +1151,22 @@ function quickRelax() {
${randomTechnique.desc}
-
+
`;
document.body.appendChild(modal);
}
+function finishRelaxSession(btn) {
+ btn.closest('.exercise-modal').remove();
+ exerciseAPI.logSession('relaxation', 120).then(() => {
+ triggerSuccessPing();
+ showSuccessMessage('Relaxation session logged! 🧘');
+ updateProgress();
+ });
+}
+
function breathingExercise() {
startBreathing();
}
@@ -1089,9 +1271,16 @@ function stopBreathingExercise() {
clearInterval(breathingInterval);
const circle = document.getElementById('breathing-circle');
const text = document.getElementById('breathing-text');
- circle.classList.remove('inhale', 'exhale');
- text.textContent = 'Ready';
+ if (circle) circle.classList.remove('inhale', 'exhale');
+ if (text) text.textContent = 'Ready';
breathingPhase = 'inhale';
+
+ // Log session (assuming ~1 min or track actual time)
+ exerciseAPI.logSession('breathing', 60).then(() => {
+ triggerSuccessPing();
+ showSuccessMessage('Breathing session logged! 🌬️');
+ updateProgress();
+ });
}
// Export additional functions
@@ -1103,4 +1292,5 @@ window.deleteNotification = deleteNotification;
window.quickRelax = quickRelax;
window.startBreathing = breathingExercise; // Alias for startBreathing in HTML
window.showQuickActionMenu = showQuickActionMenu;
-window.navigateTo = showSection; // Alias for navigateTo in HTML
\ No newline at end of file
+window.navigateTo = showSection; // Alias for navigateTo in HTML
+window.finishRelaxSession = finishRelaxSession;
\ No newline at end of file
diff --git a/MindShift-Windows/src/styles.css b/MindShift-Windows/src/styles.css
index d6aad93..0e7b081 100644
--- a/MindShift-Windows/src/styles.css
+++ b/MindShift-Windows/src/styles.css
@@ -667,8 +667,286 @@ textarea:focus, input[type="text"]:focus {
.nav-label { font-size: 10px; }
}
+/* Exercise Modals */
+.exercise-modal {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 90%;
+ max-width: 500px;
+ max-height: 90vh;
+ overflow-y: auto;
+ z-index: 2000;
+ animation: zoomIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+}
+
+.exercise-modal .card {
+ margin: 0;
+ box-shadow: 0 20px 60px rgba(0,0,0,0.4);
+ border: 2px solid rgba(255, 255, 255, 0.8);
+}
+
+@keyframes zoomIn {
+ from { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
+ to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
+}
+
+.exercise-modal h3 {
+ color: var(--primary);
+ margin-bottom: 24px;
+ font-size: 24px;
+}
+
+.exercise-actions {
+ display: flex;
+ gap: 12px;
+ margin-top: 24px;
+ justify-content: flex-end;
+}
+
/* Desktop Sidebar */
@media (min-width: 1024px) {
- .bottom-nav { display: none; }
- /* ... (keep existing desktop styles if needed, but adapt to new look) ... */
+ .bottom-nav {
+ display: flex;
+ flex-direction: column;
+ top: 80px;
+ left: 20px;
+ right: auto;
+ bottom: 20px;
+ width: 100px;
+ height: auto;
+ border-radius: 20px;
+ justify-content: flex-start;
+ padding-top: 40px;
+ gap: 20px;
+ background: rgba(255, 255, 255, 0.85);
+ }
+
+ .main-content {
+ margin-left: 120px;
+ max-width: calc(100% - 140px);
+ }
+
+ .nav-item.active::after {
+ left: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ bottom: auto;
+ width: 4px;
+ height: 20px;
+ border-radius: 4px;
+ }
+}
+
+/* History Lists */
+.history-list {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.history-item {
+ background: rgba(255, 255, 255, 0.5);
+ border-radius: 12px;
+ padding: 16px;
+ transition: all 0.3s ease;
+}
+
+.history-item:hover {
+ background: rgba(255, 255, 255, 0.8);
+ transform: translateX(5px);
+}
+
+.history-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8px;
+}
+
+.history-type {
+ font-weight: bold;
+ font-size: 16px;
+}
+
+.history-date {
+ font-size: 12px;
+ color: var(--on-surface-variant);
+}
+
+.history-details {
+ font-size: 14px;
+ color: var(--on-surface);
+}
+
+.history-notes {
+ font-style: italic;
+ color: var(--on-surface-variant);
+ margin-top: 4px;
+}
+
+.empty-state {
+ text-align: center;
+ padding: 40px;
+ color: var(--on-surface-variant);
+ font-size: 18px;
+}
+
+.error-state {
+ text-align: center;
+ padding: 20px;
+ color: var(--error);
+ background: rgba(255, 82, 82, 0.1);
+ border-radius: 12px;
+}
+
+.btn-sm {
+ padding: 8px 16px;
+ font-size: 14px;
+}
+
+/* Loading Spinner */
+.spinner {
+ width: 40px;
+ height: 40px;
+ border: 4px solid rgba(0,0,0,0.1);
+ border-left-color: var(--primary);
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin: 20px auto;
+}
+@keyframes spin { to { transform: rotate(360deg); } }
+
+/* Auth Modal Styles */
+.modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.4);
+ backdrop-filter: blur(8px);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 3000;
+ animation: fadeIn 0.3s ease;
+}
+
+.modal-card {
+ background: rgba(255, 255, 255, 0.9);
+ backdrop-filter: blur(20px);
+ padding: 40px;
+ border-radius: 30px;
+ width: 90%;
+ max-width: 400px;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
+ border: 1px solid rgba(255, 255, 255, 0.8);
+ text-align: center;
+ animation: slideInUp 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+ position: relative;
+ overflow: hidden;
+}
+
+/* Animated gradient border top */
+.modal-card::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 6px;
+ background: linear-gradient(90deg, var(--primary), var(--secondary), var(--tertiary));
+ animation: gradientFlow 3s linear infinite;
+ background-size: 200% 100%;
+}
+
+@keyframes gradientFlow {
+ 0% { background-position: 100% 0; }
+ 100% { background-position: -100% 0; }
+}
+
+.auth-title {
+ font-size: 32px;
+ font-weight: 800;
+ margin-bottom: 30px;
+ background: linear-gradient(45deg, var(--primary), var(--secondary));
+ -webkit-background-clip: text;
+ background-clip: text;
+ color: transparent;
+ animation: pulseText 3s infinite ease-in-out;
+}
+
+@keyframes pulseText {
+ 0%, 100% { transform: scale(1); filter: brightness(100%); }
+ 50% { transform: scale(1.05); filter: brightness(110%); }
+}
+
+.form-group {
+ margin-bottom: 20px;
+ text-align: left;
+}
+
+.form-group label {
+ display: block;
+ margin-bottom: 8px;
+ color: var(--on-surface-variant);
+ font-weight: 500;
+ font-size: 14px;
+ margin-left: 4px;
+}
+
+.form-input {
+ width: 100%;
+ padding: 16px;
+ border-radius: 16px;
+ border: 2px solid rgba(0,0,0,0.05);
+ background: rgba(255, 255, 255, 0.5);
+ font-size: 16px;
+ transition: all 0.3s ease;
+}
+
+.form-input:focus {
+ background: white;
+ border-color: var(--primary);
+ box-shadow: 0 0 0 4px rgba(255, 107, 107, 0.1);
+ transform: translateY(-2px);
+}
+
+.switch-form {
+ margin-top: 24px;
+ font-size: 14px;
+ color: var(--on-surface-variant);
+}
+
+.switch-form a {
+ color: var(--primary);
+ text-decoration: none;
+ font-weight: 700;
+ margin-left: 4px;
+ position: relative;
+}
+
+.switch-form a::after {
+ content: '';
+ position: absolute;
+ bottom: -2px;
+ left: 0;
+ width: 100%;
+ height: 2px;
+ background: var(--primary);
+ transform: scaleX(0);
+ transition: transform 0.3s ease;
+ transform-origin: right;
+}
+
+.switch-form a:hover::after {
+ transform: scaleX(1);
+ transform-origin: left;
+}
+
+@keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
}
diff --git a/backend/server.js b/backend/server.js
index 90fa12f..f9cfd48 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -98,6 +98,17 @@ db.serialize(() => {
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users (id)
)`);
+
+ // Exercise Logs table
+ db.run(`CREATE TABLE IF NOT EXISTS exercise_logs (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ user_id INTEGER NOT NULL,
+ exercise_type TEXT NOT NULL,
+ duration INTEGER NOT NULL,
+ completed BOOLEAN DEFAULT 1,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (user_id) REFERENCES users (id)
+ )`);
});
// JWT authentication middleware
@@ -376,6 +387,27 @@ app.post('/api/gratitude', authenticateToken, (req, res) => {
}
});
+// Log Exercise Session
+app.post('/api/exercises', authenticateToken, (req, res) => {
+ try {
+ const { exerciseType, duration } = req.body;
+
+ if (!exerciseType || !duration) {
+ return res.status(400).json({ error: 'Type and duration required' });
+ }
+
+ db.run('INSERT INTO exercise_logs (user_id, exercise_type, duration) VALUES (?, ?, ?)',
+ [req.user.id, exerciseType, duration], function(err) {
+ if (err) {
+ return res.status(500).json({ error: 'Database error' });
+ }
+ res.status(201).json({ success: true, id: this.lastID });
+ });
+ } catch (error) {
+ res.status(500).json({ error: 'Server error' });
+ }
+});
+
// Get gratitude entries
app.get('/api/gratitude', authenticateToken, (req, res) => {
const limit = parseInt(req.query.limit) || 30;
@@ -427,8 +459,9 @@ app.get('/api/dashboard/stats', authenticateToken, (req, res) => {
db.get(`SELECT
(SELECT COUNT(*) FROM mood_entries WHERE user_id = ?) as totalMoods,
(SELECT COUNT(*) FROM thoughts WHERE user_id = ?) as totalThoughts,
- (SELECT COUNT(*) FROM gratitude_entries WHERE user_id = ?) as totalGratitude`,
- [req.user.id, req.user.id, req.user.id], (err, totals) => {
+ (SELECT COUNT(*) FROM gratitude_entries WHERE user_id = ?) as totalGratitude,
+ (SELECT COUNT(*) FROM exercise_logs WHERE user_id = ?) as totalSessions`,
+ [req.user.id, req.user.id, req.user.id, req.user.id], (err, totals) => {
if (err) {
return res.status(500).json({ error: 'Database error' });
}