Update to v1.0.3: Guided Relaxation & UI Fixes
This commit is contained in:
BIN
MindShift-Windows/MindShift-v1.0.3.apk
Normal file
BIN
MindShift-Windows/MindShift-v1.0.3.apk
Normal file
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mindshift-cbt-therapy",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"description": "MindShift - Your personal CBT therapy companion for Windows 11",
|
||||
"main": "src/main.js",
|
||||
"homepage": "./",
|
||||
|
||||
@@ -1190,30 +1190,171 @@ function startExercise(type) {
|
||||
}
|
||||
|
||||
function quickRelax() {
|
||||
const techniques = [
|
||||
{ icon: '🌬️', title: 'Box Breathing', desc: 'Inhale 4s, Hold 4s, Exhale 4s, Hold 4s' },
|
||||
{ icon: '👁️', title: '5-4-3-2-1 Grounding', desc: 'Name 5 things you see, 4 feel, 3 hear...' },
|
||||
{ icon: '💆', title: 'Muscle Relaxation', desc: 'Tense and release each muscle group' },
|
||||
{ icon: '🌊', title: 'Visualisation', desc: 'Imagine a peaceful place in detail' }
|
||||
];
|
||||
// Launch the new Guided Grounding Experience
|
||||
startGuidedRelaxation();
|
||||
}
|
||||
|
||||
const randomTechnique = techniques[Math.floor(Math.random() * techniques.length)];
|
||||
// --- Guided Relaxation (5-4-3-2-1 Grounding) ---
|
||||
let relaxationState = {
|
||||
step: 0,
|
||||
isActive: false,
|
||||
steps: [
|
||||
{
|
||||
title: "Sight",
|
||||
instruction: "Look around you.",
|
||||
sub: "Find 5 things you can see.",
|
||||
count: 5,
|
||||
icon: "👁️",
|
||||
color: "#64B5F6"
|
||||
},
|
||||
{
|
||||
title: "Touch",
|
||||
instruction: "Feel the textures.",
|
||||
sub: "Find 4 things you can touch.",
|
||||
count: 4,
|
||||
icon: "✋",
|
||||
color: "#81C784"
|
||||
},
|
||||
{
|
||||
title: "Sound",
|
||||
instruction: "Listen carefully.",
|
||||
sub: "Identify 3 sounds you hear.",
|
||||
count: 3,
|
||||
icon: "👂",
|
||||
color: "#FFB74D"
|
||||
},
|
||||
{
|
||||
title: "Smell",
|
||||
instruction: "Breathe in deep.",
|
||||
sub: "Notice 2 things you can smell.",
|
||||
count: 2,
|
||||
icon: "👃",
|
||||
color: "#BA68C8"
|
||||
},
|
||||
{
|
||||
title: "Taste",
|
||||
instruction: "Focus on your mouth.",
|
||||
sub: "Find 1 thing you can taste.",
|
||||
count: 1,
|
||||
icon: "👅",
|
||||
color: "#E57373"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'exercise-modal';
|
||||
modal.style.display = 'block';
|
||||
modal.innerHTML = `
|
||||
<div class="card" style="text-align: center;">
|
||||
<div style="font-size: 60px; margin-bottom: 20px; animation: bounce 2s infinite;">${randomTechnique.icon}</div>
|
||||
<h3>${randomTechnique.title}</h3>
|
||||
<p style="font-size: 18px; margin-bottom: 30px;">${randomTechnique.desc}</p>
|
||||
<div class="exercise-actions" style="justify-content: center;">
|
||||
<button class="btn btn-primary" onclick="this.closest('.exercise-modal').remove(); quickRelax()">Try Another</button>
|
||||
<button class="btn btn-secondary" onclick="finishRelaxSession(this)">Done</button>
|
||||
function startGuidedRelaxation() {
|
||||
relaxationState.step = 0;
|
||||
relaxationState.isActive = true;
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'guided-relaxation-overlay';
|
||||
overlay.className = 'guided-overlay';
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
renderRelaxationStep();
|
||||
}
|
||||
|
||||
function renderRelaxationStep() {
|
||||
const overlay = document.getElementById('guided-relaxation-overlay');
|
||||
if (!overlay || !relaxationState.isActive) return;
|
||||
|
||||
const currentStep = relaxationState.steps[relaxationState.step];
|
||||
const progressDots = Array(currentStep.count).fill('<div class="progress-dot"></div>').join('');
|
||||
|
||||
overlay.innerHTML = `
|
||||
<div class="guided-controls">
|
||||
<button class="icon-btn" onclick="closeGuidedRelaxation()">
|
||||
<span class="material-icons">close</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="guided-step-icon" style="color: ${currentStep.color}">
|
||||
${currentStep.icon}
|
||||
</div>
|
||||
|
||||
<div class="guided-instruction">${currentStep.instruction}</div>
|
||||
<div class="guided-sub">${currentStep.sub}</div>
|
||||
|
||||
<div class="guided-progress-dots" id="step-dots">
|
||||
${progressDots}
|
||||
</div>
|
||||
|
||||
<button class="guided-action-btn" onclick="nextRelaxationSubStep()">
|
||||
I found one
|
||||
</button>
|
||||
`;
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Speak instruction
|
||||
speakText(`${currentStep.instruction} ${currentStep.sub}`);
|
||||
}
|
||||
|
||||
let currentDotIndex = 0;
|
||||
|
||||
function nextRelaxationSubStep() {
|
||||
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++;
|
||||
|
||||
if (currentDotIndex === dots.length) {
|
||||
setTimeout(() => {
|
||||
currentDotIndex = 0;
|
||||
relaxationState.step++;
|
||||
if (relaxationState.step < relaxationState.steps.length) {
|
||||
renderRelaxationStep();
|
||||
} else {
|
||||
finishGuidedRelaxation();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function finishGuidedRelaxation() {
|
||||
const overlay = document.getElementById('guided-relaxation-overlay');
|
||||
if (overlay) {
|
||||
overlay.innerHTML = `
|
||||
<div class="guided-step-icon">🌟</div>
|
||||
<div class="guided-instruction">You did great!</div>
|
||||
<div class="guided-sub">Feeling more grounded?</div>
|
||||
<button class="guided-action-btn" onclick="closeGuidedRelaxation()">
|
||||
Complete
|
||||
</button>
|
||||
`;
|
||||
speakText("You did great. Feeling more grounded?");
|
||||
triggerSuccessPing();
|
||||
}
|
||||
}
|
||||
|
||||
function closeGuidedRelaxation() {
|
||||
relaxationState.isActive = false;
|
||||
currentDotIndex = 0;
|
||||
const overlay = document.getElementById('guided-relaxation-overlay');
|
||||
if (overlay) overlay.remove();
|
||||
|
||||
if (window.speechSynthesis) {
|
||||
window.speechSynthesis.cancel();
|
||||
}
|
||||
|
||||
// Log session
|
||||
if (relaxationState.step >= relaxationState.steps.length) {
|
||||
exerciseAPI.logSession('grounding', 180).then(() => updateProgress());
|
||||
}
|
||||
}
|
||||
|
||||
function speakText(text) {
|
||||
if ('speechSynthesis' in window) {
|
||||
window.speechSynthesis.cancel(); // Stop previous
|
||||
const utterance = new SpeechSynthesisUtterance(text);
|
||||
utterance.rate = 0.9;
|
||||
utterance.pitch = 1.0;
|
||||
window.speechSynthesis.speak(utterance);
|
||||
}
|
||||
}
|
||||
|
||||
function finishRelaxSession(btn) {
|
||||
|
||||
@@ -255,13 +255,115 @@ body {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 12px 0;
|
||||
/* Android Safe Area Fix - Increased Padding */
|
||||
padding-bottom: calc(24px + env(safe-area-inset-bottom));
|
||||
/* Android Safe Area Fix - Significantly Increased Padding */
|
||||
padding-bottom: calc(40px + env(safe-area-inset-bottom));
|
||||
z-index: 100;
|
||||
border-top-left-radius: 24px;
|
||||
border-top-right-radius: 24px;
|
||||
}
|
||||
|
||||
/* Guided Relaxation Styles */
|
||||
.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;
|
||||
}
|
||||
|
||||
.guided-step-icon {
|
||||
font-size: 80px;
|
||||
margin-bottom: 30px;
|
||||
animation: floatIcon 3s ease-in-out infinite;
|
||||
filter: drop-shadow(0 0 20px rgba(255,255,255,0.3));
|
||||
}
|
||||
|
||||
@keyframes floatIcon {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-15px); }
|
||||
}
|
||||
|
||||
.guided-instruction {
|
||||
font-size: 28px;
|
||||
font-weight: 300;
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.guided-sub {
|
||||
font-size: 18px;
|
||||
opacity: 0.8;
|
||||
margin-bottom: 40px;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.guided-progress-dots {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.progress-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.progress-dot.active {
|
||||
background: white;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.guided-action-btn {
|
||||
background: white;
|
||||
color: #004D40;
|
||||
border: none;
|
||||
padding: 16px 40px;
|
||||
border-radius: 50px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.guided-action-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.guided-controls {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: none;
|
||||
color: white;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user