Update to v1.0.5: Redesigned Guided Relaxation (Body Scan, Visualization, Grounding)

This commit is contained in:
Gemini AI
2025-12-07 00:06:37 +04:00
Unverified
parent e3d487b0b0
commit 8ffb1cb106
6 changed files with 436 additions and 73 deletions

Binary file not shown.

View File

@@ -1,6 +1,6 @@
{
"name": "mindshift-cbt-therapy",
"version": "1.0.4",
"version": "1.0.5",
"description": "MindShift - Your personal CBT therapy companion for Windows 11",
"main": "src/main.js",
"homepage": "./",

View File

@@ -1222,72 +1222,119 @@ function quickRelax() {
startGuidedRelaxation();
}
// --- Guided Relaxation (5-4-3-2-1 Grounding) ---
// --- Guided Relaxation (Mindfulness System) ---
let relaxationState = {
mode: null, // 'grounding', 'body_scan', 'visualization'
step: 0,
isActive: false,
steps: [] // Will be populated dynamically based on language
isPaused: false,
steps: [],
timer: null,
duration: 0
};
function getRelaxationSteps() {
// Data Providers
function getGroundingSteps() {
return [
{
title: t('guided_sight_title'),
instruction: t('guided_sight_instruction'),
sub: t('guided_sight_sub'),
count: 5,
icon: "👁️",
color: "#64B5F6"
},
{
title: t('guided_touch_title'),
instruction: t('guided_touch_instruction'),
sub: t('guided_touch_sub'),
count: 4,
icon: "✋",
color: "#81C784"
},
{
title: t('guided_sound_title'),
instruction: t('guided_sound_instruction'),
sub: t('guided_sound_sub'),
count: 3,
icon: "👂",
color: "#FFB74D"
},
{
title: t('guided_smell_title'),
instruction: t('guided_smell_instruction'),
sub: t('guided_smell_sub'),
count: 2,
icon: "👃",
color: "#BA68C8"
},
{
title: t('guided_taste_title'),
instruction: t('guided_taste_instruction'),
sub: t('guided_taste_sub'),
count: 1,
icon: "👅",
color: "#E57373"
}
{ title: t('guided_sight_title'), instruction: t('guided_sight_instruction'), sub: t('guided_sight_sub'), count: 5, icon: "👁️", color: "#64B5F6" },
{ title: t('guided_touch_title'), instruction: t('guided_touch_instruction'), sub: t('guided_touch_sub'), count: 4, icon: "✋", color: "#81C784" },
{ title: t('guided_sound_title'), instruction: t('guided_sound_instruction'), sub: t('guided_sound_sub'), count: 3, icon: "👂", color: "#FFB74D" },
{ title: t('guided_smell_title'), instruction: t('guided_smell_instruction'), sub: t('guided_smell_sub'), count: 2, icon: "👃", color: "#BA68C8" },
{ title: t('guided_taste_title'), instruction: t('guided_taste_instruction'), sub: t('guided_taste_sub'), count: 1, icon: "👅", color: "#E57373" }
];
}
function getBodyScanSteps() {
return [
{ instruction: t('scan_intro'), duration: 5000 },
{ instruction: t('scan_feet'), duration: 8000 },
{ instruction: t('scan_legs'), duration: 8000 },
{ instruction: t('scan_stomach'), duration: 8000 },
{ instruction: t('scan_chest'), duration: 8000 },
{ instruction: t('scan_shoulders'), duration: 8000 },
{ instruction: t('scan_face'), duration: 8000 },
{ instruction: t('scan_outro'), duration: 6000 }
];
}
function getVisualizationSteps() {
return [
{ instruction: t('vis_intro'), duration: 6000 },
{ instruction: t('vis_step1'), duration: 10000 },
{ instruction: t('vis_step2'), duration: 10000 },
{ instruction: t('vis_step3'), duration: 10000 },
{ instruction: t('vis_step4'), duration: 10000 },
{ instruction: t('vis_outro'), duration: 8000 }
];
}
// Entry Point
function startGuidedRelaxation() {
relaxationState.step = 0;
relaxationState.isActive = true;
relaxationState.steps = getRelaxationSteps();
relaxationState = { mode: null, step: 0, isActive: true, isPaused: false, steps: [], timer: null, duration: 0 };
const overlay = document.createElement('div');
overlay.id = 'guided-relaxation-overlay';
overlay.className = 'guided-overlay';
document.body.appendChild(overlay);
renderRelaxationStep();
// Add styles dynamically if not present (using the styles we wrote to guided-styles.css but injecting here for simplicity in single file context if needed, but best to rely on CSS file)
// Assuming guided-styles.css is linked or merged. For safety, we rely on the styles.css update.
renderModeSelection();
}
function renderRelaxationStep() {
function renderModeSelection() {
const overlay = document.getElementById('guided-relaxation-overlay');
if (!overlay) return;
overlay.innerHTML = `
<div class="guided-controls">
<button class="icon-btn" onclick="closeGuidedRelaxation()">
<span class="material-icons">close</span>
</button>
</div>
<h2 class="guided-title-large">${t('guided_select_mode')}</h2>
<div class="guided-mode-grid">
<div class="guided-mode-card" onclick="startSession('grounding')">
<span class="guided-mode-icon">🌿</span>
<span class="guided-mode-title">${t('mode_grounding')}</span>
</div>
<div class="guided-mode-card" onclick="startSession('body_scan')">
<span class="guided-mode-icon">🧘</span>
<span class="guided-mode-title">${t('mode_body_scan')}</span>
</div>
<div class="guided-mode-card" onclick="startSession('visualization')">
<span class="guided-mode-icon">🌄</span>
<span class="guided-mode-title">${t('mode_visualization')}</span>
</div>
</div>
`;
}
function startSession(mode) {
relaxationState.mode = mode;
relaxationState.step = 0;
relaxationState.isActive = true;
const overlay = document.getElementById('guided-relaxation-overlay');
if (overlay) overlay.className = `guided-overlay mode-${mode}`;
if (mode === 'grounding') {
relaxationState.steps = getGroundingSteps();
renderGroundingStep();
} else if (mode === 'body_scan') {
relaxationState.steps = getBodyScanSteps();
runAutoSession();
} else if (mode === 'visualization') {
relaxationState.steps = getVisualizationSteps();
runAutoSession();
}
}
// --- Grounding Logic (Interactive) ---
function renderGroundingStep() {
const overlay = document.getElementById('guided-relaxation-overlay');
if (!overlay || !relaxationState.isActive) return;
@@ -1312,24 +1359,20 @@ function renderRelaxationStep() {
${progressDots}
</div>
<button class="guided-action-btn" onclick="nextRelaxationSubStep()">
<button class="guided-action-btn" onclick="nextGroundingSubStep()">
${t('guided_found_btn')}
</button>
`;
// Speak instruction
speakText(`${currentStep.instruction} ${currentStep.sub}`);
}
let currentDotIndex = 0;
function nextRelaxationSubStep() {
function nextGroundingSubStep() {
const dots = document.querySelectorAll('.progress-dot');
if (currentDotIndex < dots.length) {
dots[currentDotIndex].classList.add('active');
// Haptic feedback
if (navigator.vibrate) navigator.vibrate(50);
// Sound feedback
soundManager.playTone(400 + (currentDotIndex * 50), 'sine', 0.1, 0.1);
currentDotIndex++;
@@ -1339,16 +1382,75 @@ function nextRelaxationSubStep() {
currentDotIndex = 0;
relaxationState.step++;
if (relaxationState.step < relaxationState.steps.length) {
renderRelaxationStep();
renderGroundingStep();
} else {
finishGuidedRelaxation();
finishSession();
}
}, 1000);
}
}
}
function finishGuidedRelaxation() {
// --- Auto Session Logic (Body Scan & Visualization) ---
function runAutoSession() {
const overlay = document.getElementById('guided-relaxation-overlay');
if (!overlay || !relaxationState.isActive) return;
const step = relaxationState.steps[relaxationState.step];
const totalSteps = relaxationState.steps.length;
const progress = ((relaxationState.step) / totalSteps) * 100;
// Visuals based on mode
let visualHTML = '';
if (relaxationState.mode === 'body_scan') {
visualHTML = `<div class="body-scan-pulse"></div><div class="guided-step-icon">🧘</div>`;
} else {
visualHTML = `<div class="guided-step-icon">🌄</div>`;
}
overlay.innerHTML = `
<div class="guided-controls">
<button class="icon-btn" onclick="closeGuidedRelaxation()">
<span class="material-icons">close</span>
</button>
</div>
${visualHTML}
<div class="guided-instruction">${step.instruction}</div>
<div class="guided-progress-bar-container">
<div class="guided-progress-bar" style="width: ${progress}%"></div>
</div>
<div class="guided-bottom-controls">
<!-- Could add Play/Pause here if needed -->
</div>
`;
// Speak and advance
speakText(step.instruction, () => {
// On end of speech, wait remainder of duration
if (!relaxationState.isActive) return;
relaxationState.timer = setTimeout(() => {
relaxationState.step++;
if (relaxationState.step < relaxationState.steps.length) {
runAutoSession();
} else {
finishSession();
}
}, 2000); // Short pause after speech
});
// Animate progress bar
setTimeout(() => {
const bar = document.querySelector('.guided-progress-bar');
if (bar) bar.style.width = `${((relaxationState.step + 1) / totalSteps) * 100}%`;
}, 100);
}
function finishSession() {
const overlay = document.getElementById('guided-relaxation-overlay');
if (overlay) {
overlay.innerHTML = `
@@ -1361,11 +1463,17 @@ function finishGuidedRelaxation() {
`;
speakText(`${t('guided_complete_title')} ${t('guided_complete_sub')}`);
triggerSuccessPing();
// Log stats
const duration = relaxationState.mode === 'grounding' ? 180 :
relaxationState.mode === 'body_scan' ? 300 : 240;
exerciseAPI.logSession(relaxationState.mode, duration).then(() => updateProgress());
}
}
function closeGuidedRelaxation() {
relaxationState.isActive = false;
if (relaxationState.timer) clearTimeout(relaxationState.timer);
currentDotIndex = 0;
const overlay = document.getElementById('guided-relaxation-overlay');
if (overlay) overlay.remove();
@@ -1373,29 +1481,27 @@ function closeGuidedRelaxation() {
if (window.speechSynthesis) {
window.speechSynthesis.cancel();
}
// Log session
if (relaxationState.step >= relaxationState.steps.length) {
exerciseAPI.logSession('grounding', 180).then(() => updateProgress());
}
}
function speakText(text) {
// Enhanced TTS Wrapper
function speakText(text, onEndCallback) {
if ('speechSynthesis' in window) {
window.speechSynthesis.cancel(); // Stop previous
window.speechSynthesis.cancel();
const utterance = new SpeechSynthesisUtterance(text);
utterance.rate = 0.9;
utterance.rate = 0.85; // Slightly slower for relaxation
utterance.pitch = 1.0;
// Map language codes to TTS locales
const langMap = {
'en': 'en-US',
'ru': 'ru-RU',
'he': 'he-IL'
};
const langMap = { 'en': 'en-US', 'ru': 'ru-RU', 'he': 'he-IL' };
utterance.lang = langMap[currentLang] || 'en-US';
if (onEndCallback) {
utterance.onend = onEndCallback;
}
window.speechSynthesis.speak(utterance);
} else {
// Fallback if no TTS
if (onEndCallback) setTimeout(onEndCallback, 3000);
}
}

View File

@@ -0,0 +1,183 @@
/* Guided Relaxation - Mindfulness Session */
.guided-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(circle at center, #2E7D32, #004D40);
z-index: 6000;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
animation: fadeIn 0.5s ease;
padding: 20px;
text-align: center;
transition: background 1s ease;
}
/* Mode specific backgrounds */
.guided-overlay.mode-body_scan {
background: radial-gradient(circle at center, #4A148C, #311B92);
}
.guided-overlay.mode-visualization {
background: radial-gradient(circle at center, #00695C, #004D40);
}
.guided-title-large {
font-size: 32px;
font-weight: 300;
margin-bottom: 40px;
letter-spacing: 1px;
animation: slideInDown 0.5s ease;
}
.guided-step-icon {
font-size: 100px;
margin-bottom: 40px;
animation: floatIcon 4s ease-in-out infinite;
filter: drop-shadow(0 0 30px rgba(255,255,255,0.4));
transition: all 0.5s ease;
}
@keyframes floatIcon {
0%, 100% { transform: translateY(0) scale(1); }
50% { transform: translateY(-20px) scale(1.05); }
}
.guided-instruction {
font-size: 28px;
font-weight: 400;
margin-bottom: 20px;
line-height: 1.4;
max-width: 800px;
animation: fadeIn 0.5s ease;
}
.guided-sub {
font-size: 20px;
opacity: 0.8;
margin-bottom: 50px;
max-width: 600px;
font-weight: 300;
animation: fadeIn 0.5s ease 0.2s both;
}
/* Progress Bar for auto-playing sessions */
.guided-progress-bar-container {
width: 80%;
max-width: 300px;
height: 6px;
background: rgba(255,255,255,0.2);
border-radius: 3px;
margin-bottom: 40px;
overflow: hidden;
}
.guided-progress-bar {
height: 100%;
background: white;
width: 0%;
transition: width linear;
}
/* Mode Selection Menu */
.guided-mode-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 20px;
width: 100%;
max-width: 800px;
}
.guided-mode-card {
background: rgba(255,255,255,0.1);
border: 1px solid rgba(255,255,255,0.3);
border-radius: 20px;
padding: 24px;
cursor: pointer;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
}
.guided-mode-card:hover {
background: rgba(255,255,255,0.2);
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
.guided-mode-icon {
font-size: 48px;
margin-bottom: 16px;
display: block;
}
.guided-mode-title {
font-size: 18px;
font-weight: 500;
}
.guided-controls {
position: absolute;
top: 20px;
right: 20px;
display: flex;
gap: 16px;
z-index: 10;
}
.guided-bottom-controls {
display: flex;
gap: 20px;
align-items: center;
}
.play-pause-btn {
width: 64px;
height: 64px;
border-radius: 50%;
background: white;
color: var(--primary-dark);
border: none;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
cursor: pointer;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
transition: transform 0.2s;
}
.play-pause-btn:hover {
transform: scale(1.1);
}
.play-pause-btn:active {
transform: scale(0.95);
}
/* Pulse Animation for Body Scan */
.body-scan-pulse {
position: absolute;
width: 300px;
height: 300px;
border-radius: 50%;
border: 2px solid rgba(255,255,255,0.3);
animation: scanPulse 4s infinite;
pointer-events: none;
}
@keyframes scanPulse {
0% { transform: scale(0.8); opacity: 0; }
50% { opacity: 1; }
100% { transform: scale(1.5); opacity: 0; }
}
/* RTL Fixes for Guided */
html[dir="rtl"] .guided-controls {
right: auto;
left: 20px;
}

View File

@@ -24,6 +24,7 @@
<!-- Styles -->
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="guided-styles.css">
</head>
<body>
<!-- Animated Background Orbs -->

View File

@@ -94,7 +94,14 @@ export const translations = {
"quick_relax_now": "Relax Now",
"close": "Close",
// Guided Relaxation (Grounding)
// Guided Relaxation
"guided_title": "Mindfulness Session",
"guided_select_mode": "Select a Session",
"mode_grounding": "Grounding (5-4-3-2-1)",
"mode_body_scan": "Body Scan",
"mode_visualization": "Visualization",
// Grounding
"guided_sight_title": "Sight",
"guided_sight_instruction": "Look around you.",
"guided_sight_sub": "Find 5 things you can see.",
@@ -114,6 +121,24 @@ export const translations = {
"guided_complete_title": "You did great!",
"guided_complete_sub": "Feeling more grounded?",
"guided_complete_btn": "Complete",
// Body Scan Script
"scan_intro": "Find a comfortable position and close your eyes.",
"scan_feet": "Focus on your feet. Feel the weight of them.",
"scan_legs": "Move your attention to your legs. Let them relax.",
"scan_stomach": "Notice your stomach. Breathe deeply into it.",
"scan_chest": "Feel your chest rise and fall with each breath.",
"scan_shoulders": "Release any tension in your shoulders.",
"scan_face": "Soften your jaw and forehead.",
"scan_outro": "Take a deep breath. When you're ready, open your eyes.",
// Visualization Script
"vis_intro": "Close your eyes and imagine a peaceful place.",
"vis_step1": "You are walking in a quiet, ancient forest.",
"vis_step2": "The air is fresh and smells of pine.",
"vis_step3": "Sunlight filters through the leaves above.",
"vis_step4": "You hear a gentle stream nearby.",
"vis_outro": "Carry this peace with you. Open your eyes.",
// Smart Breathing
"breath_balance": "Balance",
@@ -228,6 +253,12 @@ export const translations = {
"close": "Закрыть",
// Guided Relaxation
"guided_title": "Сессия осознанности",
"guided_select_mode": "Выберите сессию",
"mode_grounding": "Заземление (5-4-3-2-1)",
"mode_body_scan": "Сканирование тела",
"mode_visualization": "Визуализация",
"guided_sight_title": "Зрение",
"guided_sight_instruction": "Оглянитесь вокруг.",
"guided_sight_sub": "Найдите 5 вещей, которые вы видите.",
@@ -247,6 +278,24 @@ export const translations = {
"guided_complete_title": "Отлично!",
"guided_complete_sub": "Чувствуете себя спокойнее?",
"guided_complete_btn": "Завершить",
// Body Scan Script
"scan_intro": "Примите удобное положение и закройте глаза.",
"scan_feet": "Сосредоточьтесь на ногах. Почувствуйте их вес.",
"scan_legs": "Переведите внимание на ноги. Расслабьте их.",
"scan_stomach": "Заметьте свой живот. Дышите глубоко.",
"scan_chest": "Почувствуйте, как поднимается грудь при вдохе.",
"scan_shoulders": "Отпустите напряжение в плечах.",
"scan_face": "Расслабьте челюсть и лоб.",
"scan_outro": "Сделайте глубокий вдох. Откройте глаза.",
// Visualization Script
"vis_intro": "Закройте глаза и представьте спокойное место.",
"vis_step1": "Вы идете по тихому древнему лесу.",
"vis_step2": "Воздух свежий и пахнет хвоей.",
"vis_step3": "Солнечный свет пробивается сквозь листву.",
"vis_step4": "Вы слышите тихий ручей неподалеку.",
"vis_outro": "Сохраните это спокойствие. Откройте глаза.",
// Smart Breathing
"breath_balance": "Баланс",
@@ -361,6 +410,12 @@ export const translations = {
"close": "סגור",
// Guided Relaxation
"guided_title": "אימון קשיבות",
"guided_select_mode": "בחר אימון",
"mode_grounding": "קרקוע (5-4-3-2-1)",
"mode_body_scan": "סריקת גוף",
"mode_visualization": "דמיון מודרך",
"guided_sight_title": "ראייה",
"guided_sight_instruction": "הביטו סביבכם.",
"guided_sight_sub": "מצאו 5 דברים שאתם רואים.",
@@ -380,6 +435,24 @@ export const translations = {
"guided_complete_title": "כל הכבוד!",
"guided_complete_sub": "מרגישים מקורקעים יותר?",
"guided_complete_btn": "סיים",
// Body Scan Script
"scan_intro": "מצאו תנוחה נוחה ועצמו עיניים.",
"scan_feet": "התמקדו בכפות הרגליים. הרגישו את המשקל שלהן.",
"scan_legs": "העבירו את תשומת הלב לרגליים. הרפו אותן.",
"scan_stomach": "שימו לב לבטן. נשמו עמוק לתוכה.",
"scan_chest": "הרגישו את החזה עולה ויורד עם כל נשימה.",
"scan_shoulders": "שחררו כל מתח בכתפיים.",
"scan_face": "הרפו את הלסת והמצח.",
"scan_outro": "קחו נשימה עמוקה. כשאתם מוכנים, פקחו עיניים.",
// Visualization Script
"vis_intro": "עצמו עיניים ודמיינו מקום שליו.",
"vis_step1": "אתם הולכים ביער עתיק ושקט.",
"vis_step2": "האוויר רענן ומריח כמו אורנים.",
"vis_step3": "אור השמש מסתנן מבעד לעלים.",
"vis_step4": "אתם שומעים פלג מים עדין בקרבת מקום.",
"vis_outro": "שאו את השלווה הזו איתכם. פקחו עיניים.",
// Smart Breathing
"breath_balance": "איזון",