Update to v1.0.7: Deepen Guided Relaxation (Ambience, Stress Check, Enhanced UI)

This commit is contained in:
Gemini AI
2025-12-07 12:06:39 +04:00
Unverified
parent d55d45bae8
commit 870d1f80ec
5 changed files with 300 additions and 82 deletions

Binary file not shown.

View File

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

View File

@@ -114,8 +114,56 @@ class SoundManager {
osc.start();
osc.stop(this.ctx.currentTime + 4);
}
// Ambient Generator (Pink Noise)
startAmbience(type = 'rain') {
this.stopAmbience();
if (!this.enabled) return;
const bufferSize = 2 * this.ctx.sampleRate;
const noiseBuffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
const output = noiseBuffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
let white = Math.random() * 2 - 1;
output[i] = (lastOut + (0.02 * white)) / 1.02;
lastOut = output[i];
output[i] *= 3.5; // (roughly) compensate for gain
}
this.ambienceNode = this.ctx.createBufferSource();
this.ambienceNode.buffer = noiseBuffer;
this.ambienceNode.loop = true;
const gainNode = this.ctx.createGain();
// Lower volume for background
gainNode.gain.value = 0.05;
// Simple Lowpass filter for "rain/forest" feel
const filter = this.ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = type === 'rain' ? 800 : 400;
this.ambienceNode.connect(filter);
filter.connect(gainNode);
gainNode.connect(this.ctx.destination);
this.ambienceNode.start();
}
stopAmbience() {
if (this.ambienceNode) {
try {
this.ambienceNode.stop();
} catch(e) {}
this.ambienceNode = null;
}
}
}
let lastOut = 0;
const soundManager = new SoundManager();
// Initialize app when DOM is loaded
@@ -1224,13 +1272,15 @@ function quickRelax() {
// --- Guided Relaxation (Mindfulness System) ---
let relaxationState = {
mode: null, // 'grounding', 'body_scan', 'visualization'
mode: null,
step: 0,
isActive: false,
isPaused: false,
steps: [],
timer: null,
duration: 0
duration: 0,
preStress: 5, // 1-10
postStress: 5
};
// Data Providers
@@ -1246,40 +1296,47 @@ function getGroundingSteps() {
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 }
{ instruction: t('scan_intro'), duration: 8000 },
{ instruction: t('scan_feet'), duration: 10000 },
{ instruction: t('scan_legs'), duration: 10000 },
{ instruction: t('scan_stomach'), duration: 10000 },
{ instruction: t('scan_chest'), duration: 10000 },
{ instruction: t('scan_shoulders'), duration: 10000 },
{ instruction: t('scan_face'), duration: 10000 },
{ instruction: t('scan_outro'), duration: 8000 }
];
}
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 }
{ instruction: t('vis_intro'), duration: 8000 },
{ instruction: t('vis_step1'), duration: 12000 },
{ instruction: t('vis_step2'), duration: 12000 },
{ instruction: t('vis_step3'), duration: 12000 },
{ instruction: t('vis_step4'), duration: 12000 },
{ instruction: t('vis_outro'), duration: 10000 }
];
}
// Entry Point
function startGuidedRelaxation() {
relaxationState = { mode: null, step: 0, isActive: true, isPaused: false, steps: [], timer: null, duration: 0 };
relaxationState = {
mode: null,
step: 0,
isActive: true,
isPaused: false,
steps: [],
timer: null,
duration: 0,
preStress: 5,
postStress: 5
};
const overlay = document.createElement('div');
overlay.id = 'guided-relaxation-overlay';
overlay.className = 'guided-overlay';
document.body.appendChild(overlay);
// 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();
}
@@ -1297,15 +1354,15 @@ function renderModeSelection() {
<h2 class="guided-title-large">${t('guided_select_mode')}</h2>
<div class="guided-mode-grid">
<div class="guided-mode-card" onclick="startSession('grounding')">
<div class="guided-mode-card" onclick="startPreSession('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')">
<div class="guided-mode-card" onclick="startPreSession('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')">
<div class="guided-mode-card" onclick="startPreSession('visualization')">
<span class="guided-mode-icon">🌄</span>
<span class="guided-mode-title">${t('mode_visualization')}</span>
</div>
@@ -1313,14 +1370,39 @@ function renderModeSelection() {
`;
}
function startSession(mode) {
function startPreSession(mode) {
relaxationState.mode = mode;
relaxationState.step = 0;
relaxationState.isActive = true;
const overlay = document.getElementById('guided-relaxation-overlay');
overlay.innerHTML = `
<div class="guided-controls">
<button class="icon-btn" onclick="closeGuidedRelaxation()">
<span class="material-icons">close</span>
</button>
</div>
<div class="guided-instruction">${t('guided_pre_stress')}</div>
<div class="stress-slider-container">
<input type="range" class="stress-slider" min="1" max="10" value="5"
oninput="document.getElementById('stress-val').textContent = this.value; relaxationState.preStress = this.value">
<div class="stress-value" id="stress-val">5</div>
</div>
<button class="guided-action-btn" onclick="startSession('${mode}')">
${t('guided_start')}
</button>
`;
}
function startSession(mode) {
relaxationState.isActive = true;
const overlay = document.getElementById('guided-relaxation-overlay');
if (overlay) overlay.className = `guided-overlay mode-${mode}`;
// Start Ambience
soundManager.startAmbience(mode === 'visualization' ? 'rain' : 'forest');
if (mode === 'grounding') {
relaxationState.steps = getGroundingSteps();
renderGroundingStep();
@@ -1384,7 +1466,7 @@ function nextGroundingSubStep() {
if (relaxationState.step < relaxationState.steps.length) {
renderGroundingStep();
} else {
finishSession();
startPostSession();
}
}, 1000);
}
@@ -1422,10 +1504,6 @@ function runAutoSession() {
<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
@@ -1438,7 +1516,7 @@ function runAutoSession() {
if (relaxationState.step < relaxationState.steps.length) {
runAutoSession();
} else {
finishSession();
startPostSession();
}
}, 2000); // Short pause after speech
});
@@ -1450,18 +1528,52 @@ function runAutoSession() {
}, 100);
}
function startPostSession() {
const overlay = document.getElementById('guided-relaxation-overlay');
if (!overlay) return;
soundManager.stopAmbience(); // Quiet down
overlay.innerHTML = `
<div class="guided-controls">
<button class="icon-btn" onclick="closeGuidedRelaxation()">
<span class="material-icons">close</span>
</button>
</div>
<div class="guided-instruction">${t('guided_post_stress')}</div>
<div class="stress-slider-container">
<input type="range" class="stress-slider" min="1" max="10" value="${relaxationState.preStress}"
oninput="document.getElementById('stress-val-post').textContent = this.value; relaxationState.postStress = this.value">
<div class="stress-value" id="stress-val-post">${relaxationState.preStress}</div>
</div>
<button class="guided-action-btn" onclick="finishSession()">
${t('guided_complete_btn')}
</button>
`;
}
function finishSession() {
const overlay = document.getElementById('guided-relaxation-overlay');
if (overlay) {
const reduction = relaxationState.preStress - relaxationState.postStress;
let summaryText = reduction > 0
? `${t('guided_summary_reduced')} ${reduction} points`
: t('guided_summary_maintained');
overlay.innerHTML = `
<div class="guided-step-icon">🌟</div>
<div class="guided-instruction">${t('guided_complete_title')}</div>
<div class="guided-sub">${t('guided_complete_sub')}</div>
<button class="guided-action-btn" onclick="closeGuidedRelaxation()">
${t('guided_complete_btn')}
</button>
<div class="summary-card">
<div class="guided-step-icon">🌟</div>
<div class="guided-instruction">${t('guided_summary_title')}</div>
<div class="summary-stat">${summaryText}</div>
<button class="guided-action-btn" onclick="closeGuidedRelaxation()">
${t('close')}
</button>
</div>
`;
speakText(`${t('guided_complete_title')} ${t('guided_complete_sub')}`);
speakText(`${t('guided_complete_title')}`);
triggerSuccessPing();
// Log stats
@@ -1473,6 +1585,7 @@ function finishSession() {
function closeGuidedRelaxation() {
relaxationState.isActive = false;
soundManager.stopAmbience();
if (relaxationState.timer) clearTimeout(relaxationState.timer);
currentDotIndex = 0;
const overlay = document.getElementById('guided-relaxation-overlay');
@@ -1483,6 +1596,7 @@ function closeGuidedRelaxation() {
}
}
// Enhanced TTS Wrapper
function speakText(text, onEndCallback) {
if ('speechSynthesis' in window) {

View File

@@ -181,3 +181,77 @@ html[dir="rtl"] .guided-controls {
right: auto;
left: 20px;
}
/* Stress Slider */
.stress-slider-container {
width: 100%;
max-width: 400px;
margin: 40px 0;
}
.stress-slider {
width: 100%;
height: 10px;
background: rgba(255,255,255,0.3);
border-radius: 5px;
outline: none;
-webkit-appearance: none;
}
.stress-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 30px;
height: 30px;
background: white;
border-radius: 50%;
cursor: pointer;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
}
.stress-value {
font-size: 48px;
font-weight: bold;
margin-top: 20px;
}
/* Settings Modal */
.guided-settings-panel {
position: absolute;
bottom: 20px;
left: 20px;
background: rgba(0,0,0,0.6);
backdrop-filter: blur(10px);
padding: 20px;
border-radius: 20px;
display: none;
animation: slideInUp 0.3s ease;
}
.guided-settings-panel.active {
display: block;
}
.setting-row {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
color: white;
}
/* Summary Card */
.summary-card {
background: white;
color: var(--on-surface);
padding: 40px;
border-radius: 24px;
text-align: center;
animation: zoomIn 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.summary-stat {
font-size: 24px;
color: var(--primary);
margin: 20px 0;
font-weight: bold;
}

View File

@@ -101,6 +101,16 @@ export const translations = {
"mode_body_scan": "Body Scan",
"mode_visualization": "Visualization",
"guided_pre_stress": "How stressed are you now?",
"guided_post_stress": "How do you feel now?",
"guided_start": "Start Session",
"guided_settings": "Settings",
"guided_ambience": "Background Sounds",
"guided_voice": "Voice Guide",
"guided_summary_title": "Session Complete",
"guided_summary_reduced": "Stress Reduced by",
"guided_summary_maintained": "You maintained your calm.",
// Grounding
"guided_sight_title": "Sight",
"guided_sight_instruction": "Look around you.",
@@ -123,22 +133,22 @@ export const translations = {
"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.",
"scan_intro": "Find a comfortable position and close your eyes. Let's begin by taking a few deep breaths.",
"scan_feet": "Bring your awareness to your feet. Notice any sensations of warmth, coolness, or pressure. Let them soften.",
"scan_legs": "Move your attention up to your calves and thighs. If you notice any tension, imagine it melting away with your exhale.",
"scan_stomach": "Focus on your belly. Feel it rise gently as you inhale, and fall as you exhale. Soften your stomach muscles.",
"scan_chest": "Bring your attention to your chest and heart center. Notice the rhythm of your breath. Let your shoulders drop down away from your ears.",
"scan_shoulders": "Notice your neck and throat. Let go of any tightness here. Allow your jaw to unhinge slightly.",
"scan_face": "Soften the muscles around your eyes and forehead. Let your entire face be smooth and relaxed.",
"scan_outro": "Take a moment to feel your whole body, resting in this state of relaxation. When you are ready, gently wiggle your fingers and toes, and 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.",
"vis_intro": "Close your eyes and take a deep breath. We are going to take a journey to a peaceful place.",
"vis_step1": "Imagine you are standing at the edge of a lush, ancient forest. The trees are tall and protective. You feel safe here.",
"vis_step2": "As you walk deeper into the woods, the air becomes cool and fresh. You can smell the scent of pine and damp earth.",
"vis_step3": "Sunlight filters through the canopy above, creating dappled patterns of light on the soft mossy path beneath your feet.",
"vis_step4": "In the distance, you hear the gentle sound of a stream flowing over smooth stones. The sound is rhythmic and calming.",
"vis_outro": "Take a moment to absorb the peace of this place. Know that you can return here anytime. Slowly bring your awareness back to the room and open your eyes.",
// Smart Breathing
"breath_balance": "Balance",
@@ -259,6 +269,16 @@ export const translations = {
"mode_body_scan": "Сканирование тела",
"mode_visualization": "Визуализация",
"guided_pre_stress": "Ваш уровень стресса?",
"guided_post_stress": "Как вы себя чувствуете?",
"guided_start": "Начать сессию",
"guided_settings": "Настройки",
"guided_ambience": "Фоновые звуки",
"guided_voice": "Голос гида",
"guided_summary_title": "Сессия завершена",
"guided_summary_reduced": "Стресс снижен на",
"guided_summary_maintained": "Вы сохранили спокойствие.",
"guided_sight_title": "Зрение",
"guided_sight_instruction": "Оглянитесь вокруг.",
"guided_sight_sub": "Найдите 5 вещей, которые вы видите.",
@@ -280,22 +300,22 @@ export const translations = {
"guided_complete_btn": "Завершить",
// Body Scan Script
"scan_intro": "Примите удобное положение и закройте глаза.",
"scan_feet": "Сосредоточьтесь на ногах. Почувствуйте их вес.",
"scan_legs": "Переведите внимание на ноги. Расслабьте их.",
"scan_stomach": "Заметьте свой живот. Дышите глубоко.",
"scan_chest": очувствуйте, как поднимается грудь при вдохе.",
"scan_shoulders": "Отпустите напряжение в плечах.",
"scan_face": "Расслабьте челюсть и лоб.",
"scan_outro": "Сделайте глубокий вдох. Откройте глаза.",
"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": "Сохраните это спокойствие. Откройте глаза.",
"vis_intro": "Закройте глаза и сделайте глубокий вдох. Мы отправимся в путешествие в спокойное место.",
"vis_step1": "Представьте, что вы стоите на краю тихого древнего леса. Деревья высокие и защищают вас. Здесь безопасно.",
"vis_step2": "Вы идете вглубь леса, воздух становится прохладным и свежим. Вы чувствуете запах хвои и влажной земли.",
"vis_step3": "Солнечный свет пробивается сквозь листву, создавая узоры света на мягкой моховой тропинке под ногами.",
"vis_step4": "Вдали вы слышите тихое журчание ручья, текущего по гладким камням. Этот звук ритмичный и успокаивающий.",
"vis_outro": "Впитайте покой этого места. Знайте, что можете вернуться сюда в любой момент. Медленно откройте глаза.",
// Smart Breathing
"breath_balance": "Баланс",
@@ -416,6 +436,16 @@ export const translations = {
"mode_body_scan": "סריקת גוף",
"mode_visualization": "דמיון מודרך",
"guided_pre_stress": "כמה אתם לחוצים?",
"guided_post_stress": "איך אתם מרגישים כעת?",
"guided_start": "התחל אימון",
"guided_settings": "הגדרות",
"guided_ambience": "צלילי רקע",
"guided_voice": "קול מנחה",
"guided_summary_title": "האימון הושלם",
"guided_summary_reduced": "הלחץ ירד ב-",
"guided_summary_maintained": "שמרתם על רוגע.",
"guided_sight_title": "ראייה",
"guided_sight_instruction": "הביטו סביבכם.",
"guided_sight_sub": "מצאו 5 דברים שאתם רואים.",
@@ -437,22 +467,22 @@ export const translations = {
"guided_complete_btn": "סיים",
// Body Scan Script
"scan_intro": "מצאו תנוחה נוחה ועצמו עיניים.",
"scan_feet": "התמקדו בכפות הרגליים. הרגישו את המשקל שלהן.",
"scan_legs": "העבירו את תשומת הלב לרגליים. הרפו אותן.",
"scan_stomach": "שימו לב לבטן. נשמו עמוק לתוכה.",
"scan_chest": "הרגישו את החזה עולה ויורד עם כל נשימה.",
"scan_shoulders": חררו כל מתח בכתפיים.",
"scan_face": "הרפו את הלסת והמצח.",
"scan_outro": "קחו נשימה עמוקה. כשאתם מוכנים, פקחו עיניים.",
"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": "שאו את השלווה הזו איתכם. פקחו עיניים.",
"vis_intro": "עצמו עיניים וקחו נשימה עמוקה. אנו יוצאים למסע למקום שליו.",
"vis_step1": "דמיינו שאתם עומדים בקצה של יער עתיק ושקט. העצים גבוהים ומגנים. אתם מרגישים בטוחים כאן.",
"vis_step2": "כשאתם הולכים עמוק יותר לתוך היער, האוויר נעשה קריר ורענן. אתם יכולים להריח ריח של אורנים ואדמה לחה.",
"vis_step3": "אור השמש מסתנן מבעד לחופה מעל, יוצר תבניות של אור על שביל הטחב הרך שמתחת לרגליכם.",
"vis_step4": "במרחק, אתם שומעים צליל עדין של פלג מים הזורם על אבנים חלקות. הצליל קצבי ומרגיע.",
"vis_outro": "ספגו את השלווה של המקום הזה. דעו שאתם יכולים לחזור לכאן בכל עת. החזירו את המודעות לחדר ופקחו עיניים.",
// Smart Breathing
"breath_balance": "איזון",