Spaces:
Sleeping
Sleeping
Enhanced FEEL/NEED tools + proper square breathing
Browse filesFEEL/NEED improvements:
- Expanded NVC vocabulary from Marshall Rosenberg's complete lists
- Added psychoeducation from Brené Brown, Tara Brach, Sarah Peyton
- Feelings reflection connects to needs and includes insights
- Needs reflection includes request suggestions
LOVE breathing fix:
- Proper square breathing: 5s in, 4s hold, 5s out, 4s hold
- Visual countdown during each phase
- Circle animates to match breath phase
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- static/index.html +130 -24
static/index.html
CHANGED
|
@@ -728,6 +728,16 @@
|
|
| 728 |
50% { transform: scale(1.2); opacity: 1; }
|
| 729 |
}
|
| 730 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 731 |
.breath-text {
|
| 732 |
font-size: 1.5rem;
|
| 733 |
margin-bottom: 20px;
|
|
@@ -985,20 +995,44 @@
|
|
| 985 |
|
| 986 |
let selectedTradition = null;
|
| 987 |
|
| 988 |
-
// NVC Feelings vocabulary
|
| 989 |
const FEELINGS_DATA = {
|
| 990 |
-
'When needs ARE met': [
|
| 991 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 992 |
};
|
| 993 |
|
| 994 |
-
// NVC Needs vocabulary
|
| 995 |
const NEEDS_DATA = {
|
| 996 |
-
'Connection': [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 997 |
'Autonomy': ['Choice', 'Freedom', 'Independence', 'Space', 'Spontaneity'],
|
| 998 |
-
'Meaning': [
|
| 999 |
-
|
| 1000 |
-
|
| 1001 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1002 |
};
|
| 1003 |
|
| 1004 |
let selectedFeelings = [];
|
|
@@ -1320,7 +1354,7 @@
|
|
| 1320 |
const feelingsText = selectedFeelings.join(', ');
|
| 1321 |
addToolMsg('user', `I'm feeling: ${feelingsText}`);
|
| 1322 |
|
| 1323 |
-
// Get reflection from API
|
| 1324 |
addToolLoading();
|
| 1325 |
const isVerbose = document.getElementById('toggle-verbose')?.checked || false;
|
| 1326 |
try {
|
|
@@ -1331,7 +1365,18 @@
|
|
| 1331 |
tool: 'feelings_needs',
|
| 1332 |
partner_message: lastPartnerMessage,
|
| 1333 |
user_draft: '',
|
| 1334 |
-
user_input: `The person has identified these feelings: ${feelingsText}. ${userInput ? 'They added: ' + userInput : ''}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1335 |
verbose: isVerbose
|
| 1336 |
})
|
| 1337 |
});
|
|
@@ -1353,7 +1398,7 @@
|
|
| 1353 |
const needsText = selectedNeeds.join(', ');
|
| 1354 |
addToolMsg('user', `I'm needing: ${needsText}`);
|
| 1355 |
|
| 1356 |
-
// Get reflection from API
|
| 1357 |
addToolLoading();
|
| 1358 |
const isVerbose = document.getElementById('toggle-verbose')?.checked || false;
|
| 1359 |
try {
|
|
@@ -1364,7 +1409,19 @@
|
|
| 1364 |
tool: 'feelings_needs',
|
| 1365 |
partner_message: lastPartnerMessage,
|
| 1366 |
user_draft: '',
|
| 1367 |
-
user_input: `The person has identified these needs: ${needsText}. ${userInput ? 'They added: ' + userInput : ''}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1368 |
verbose: isVerbose
|
| 1369 |
})
|
| 1370 |
});
|
|
@@ -1437,27 +1494,76 @@ Based on the themes in this conversation (conflict, connection, fear, love, boun
|
|
| 1437 |
}
|
| 1438 |
|
| 1439 |
// ============================================================================
|
| 1440 |
-
// BREATH/LOVE TOOL
|
| 1441 |
// ============================================================================
|
| 1442 |
let breathInterval;
|
|
|
|
| 1443 |
|
| 1444 |
function openBreath() {
|
| 1445 |
document.getElementById('breath-overlay').classList.add('active');
|
| 1446 |
-
|
| 1447 |
-
|
| 1448 |
-
|
| 1449 |
-
|
| 1450 |
-
|
| 1451 |
-
|
| 1452 |
-
|
| 1453 |
-
|
| 1454 |
-
|
| 1455 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1456 |
}
|
| 1457 |
|
| 1458 |
function closeBreath() {
|
| 1459 |
document.getElementById('breath-overlay').classList.remove('active');
|
| 1460 |
clearInterval(breathInterval);
|
|
|
|
| 1461 |
}
|
| 1462 |
|
| 1463 |
// ============================================================================
|
|
|
|
| 728 |
50% { transform: scale(1.2); opacity: 1; }
|
| 729 |
}
|
| 730 |
|
| 731 |
+
@keyframes breatheIn {
|
| 732 |
+
0% { transform: scale(0.6); opacity: 0.4; }
|
| 733 |
+
100% { transform: scale(1.3); opacity: 1; }
|
| 734 |
+
}
|
| 735 |
+
|
| 736 |
+
@keyframes breatheOut {
|
| 737 |
+
0% { transform: scale(1.3); opacity: 1; }
|
| 738 |
+
100% { transform: scale(0.6); opacity: 0.4; }
|
| 739 |
+
}
|
| 740 |
+
|
| 741 |
.breath-text {
|
| 742 |
font-size: 1.5rem;
|
| 743 |
margin-bottom: 20px;
|
|
|
|
| 995 |
|
| 996 |
let selectedTradition = null;
|
| 997 |
|
| 998 |
+
// NVC Feelings vocabulary (from Marshall Rosenberg)
|
| 999 |
const FEELINGS_DATA = {
|
| 1000 |
+
'When needs ARE met': [
|
| 1001 |
+
'Alive', 'Amazed', 'Appreciative', 'Confident', 'Curious', 'Delighted',
|
| 1002 |
+
'Eager', 'Energetic', 'Engaged', 'Excited', 'Fulfilled', 'Glad',
|
| 1003 |
+
'Grateful', 'Hopeful', 'Inspired', 'Joyful', 'Loving', 'Moved',
|
| 1004 |
+
'Optimistic', 'Peaceful', 'Pleased', 'Proud', 'Refreshed', 'Relaxed',
|
| 1005 |
+
'Relieved', 'Satisfied', 'Secure', 'Tender', 'Thankful', 'Touched',
|
| 1006 |
+
'Trusting', 'Warm'
|
| 1007 |
+
],
|
| 1008 |
+
'When needs are NOT met': [
|
| 1009 |
+
'Afraid', 'Angry', 'Annoyed', 'Anxious', 'Ashamed', 'Bewildered',
|
| 1010 |
+
'Bitter', 'Confused', 'Dejected', 'Depressed', 'Disappointed',
|
| 1011 |
+
'Discouraged', 'Disgusted', 'Distressed', 'Embarrassed', 'Exhausted',
|
| 1012 |
+
'Fearful', 'Frustrated', 'Grief', 'Guilty', 'Helpless', 'Hopeless',
|
| 1013 |
+
'Hurt', 'Impatient', 'Irritated', 'Jealous', 'Lonely', 'Nervous',
|
| 1014 |
+
'Numb', 'Overwhelmed', 'Resentful', 'Sad', 'Scared', 'Shocked',
|
| 1015 |
+
'Tired', 'Uncomfortable', 'Uneasy', 'Upset', 'Worried'
|
| 1016 |
+
]
|
| 1017 |
};
|
| 1018 |
|
| 1019 |
+
// NVC Needs vocabulary (from Marshall Rosenberg)
|
| 1020 |
const NEEDS_DATA = {
|
| 1021 |
+
'Connection': [
|
| 1022 |
+
'Acceptance', 'Affection', 'Appreciation', 'Belonging', 'Closeness',
|
| 1023 |
+
'Communication', 'Companionship', 'Compassion', 'Empathy', 'Inclusion',
|
| 1024 |
+
'Intimacy', 'Love', 'Respect', 'To be heard', 'To be seen',
|
| 1025 |
+
'To be understood', 'Trust', 'Warmth'
|
| 1026 |
+
],
|
| 1027 |
'Autonomy': ['Choice', 'Freedom', 'Independence', 'Space', 'Spontaneity'],
|
| 1028 |
+
'Meaning': [
|
| 1029 |
+
'Awareness', 'Contribution', 'Creativity', 'Growth', 'Hope',
|
| 1030 |
+
'Learning', 'Purpose', 'Self-expression', 'Stimulation', 'Understanding'
|
| 1031 |
+
],
|
| 1032 |
+
'Peace': ['Beauty', 'Ease', 'Equality', 'Harmony', 'Inspiration', 'Order', 'Space'],
|
| 1033 |
+
'Honesty': ['Authenticity', 'Integrity', 'Presence', 'Self-connection'],
|
| 1034 |
+
'Physical Well-being': ['Food', 'Movement', 'Rest', 'Safety', 'Shelter', 'Touch', 'Water'],
|
| 1035 |
+
'Play': ['Fun', 'Humor', 'Joy', 'Rejuvenation']
|
| 1036 |
};
|
| 1037 |
|
| 1038 |
let selectedFeelings = [];
|
|
|
|
| 1354 |
const feelingsText = selectedFeelings.join(', ');
|
| 1355 |
addToolMsg('user', `I'm feeling: ${feelingsText}`);
|
| 1356 |
|
| 1357 |
+
// Get reflection from API with psychoeducation
|
| 1358 |
addToolLoading();
|
| 1359 |
const isVerbose = document.getElementById('toggle-verbose')?.checked || false;
|
| 1360 |
try {
|
|
|
|
| 1365 |
tool: 'feelings_needs',
|
| 1366 |
partner_message: lastPartnerMessage,
|
| 1367 |
user_draft: '',
|
| 1368 |
+
user_input: `The person has identified these feelings: ${feelingsText}. ${userInput ? 'They added: ' + userInput : ''}
|
| 1369 |
+
|
| 1370 |
+
TASK:
|
| 1371 |
+
1. Validate these feelings - they make sense
|
| 1372 |
+
2. Help connect feelings to underlying needs (NVC framework)
|
| 1373 |
+
3. Include brief psychoeducation about these specific feelings - draw from:
|
| 1374 |
+
- Brené Brown (vulnerability, shame, connection)
|
| 1375 |
+
- Tara Brach (RAIN, self-compassion, presence)
|
| 1376 |
+
- Sarah Peyton (neuroscience of resonance, attachment)
|
| 1377 |
+
4. Keep it warm, brief, and actionable
|
| 1378 |
+
|
| 1379 |
+
Format: Acknowledge → What the feelings might point to → Brief insight → Connection to needs`,
|
| 1380 |
verbose: isVerbose
|
| 1381 |
})
|
| 1382 |
});
|
|
|
|
| 1398 |
const needsText = selectedNeeds.join(', ');
|
| 1399 |
addToolMsg('user', `I'm needing: ${needsText}`);
|
| 1400 |
|
| 1401 |
+
// Get reflection from API with psychoeducation
|
| 1402 |
addToolLoading();
|
| 1403 |
const isVerbose = document.getElementById('toggle-verbose')?.checked || false;
|
| 1404 |
try {
|
|
|
|
| 1409 |
tool: 'feelings_needs',
|
| 1410 |
partner_message: lastPartnerMessage,
|
| 1411 |
user_draft: '',
|
| 1412 |
+
user_input: `The person has identified these needs: ${needsText}. ${userInput ? 'They added: ' + userInput : ''}
|
| 1413 |
+
|
| 1414 |
+
TASK:
|
| 1415 |
+
1. Validate these needs - they are universal human needs
|
| 1416 |
+
2. Help them see how these needs connect to their feelings and the relationship
|
| 1417 |
+
3. Include brief psychoeducation about needs - draw from:
|
| 1418 |
+
- Marshall Rosenberg (NVC, universal needs, requests vs demands)
|
| 1419 |
+
- Brené Brown (connection, worthiness, vulnerability)
|
| 1420 |
+
- Attachment theory (safety, closeness, autonomy)
|
| 1421 |
+
4. Suggest what a request might sound like to meet these needs
|
| 1422 |
+
5. Keep it warm, brief, and actionable
|
| 1423 |
+
|
| 1424 |
+
Format: Validate → What these needs mean → Brief insight → Possible request`,
|
| 1425 |
verbose: isVerbose
|
| 1426 |
})
|
| 1427 |
});
|
|
|
|
| 1494 |
}
|
| 1495 |
|
| 1496 |
// ============================================================================
|
| 1497 |
+
// BREATH/LOVE TOOL - Square Breathing (5 in, 4 hold, 5 out, 4 hold)
|
| 1498 |
// ============================================================================
|
| 1499 |
let breathInterval;
|
| 1500 |
+
let breathTimeout;
|
| 1501 |
|
| 1502 |
function openBreath() {
|
| 1503 |
document.getElementById('breath-overlay').classList.add('active');
|
| 1504 |
+
runBreathCycle();
|
| 1505 |
+
}
|
| 1506 |
+
|
| 1507 |
+
function runBreathCycle() {
|
| 1508 |
+
const breathText = document.getElementById('breath-text');
|
| 1509 |
+
const circle = document.querySelector('.breath-circle');
|
| 1510 |
+
|
| 1511 |
+
// Phase 1: Breathe in (5 seconds)
|
| 1512 |
+
breathText.textContent = 'Breathe in... 5';
|
| 1513 |
+
circle.style.animation = 'breatheIn 5s ease-out forwards';
|
| 1514 |
+
let count = 5;
|
| 1515 |
+
breathInterval = setInterval(() => {
|
| 1516 |
+
count--;
|
| 1517 |
+
if (count > 0) breathText.textContent = `Breathe in... ${count}`;
|
| 1518 |
+
}, 1000);
|
| 1519 |
+
|
| 1520 |
+
breathTimeout = setTimeout(() => {
|
| 1521 |
+
clearInterval(breathInterval);
|
| 1522 |
+
// Phase 2: Hold (4 seconds)
|
| 1523 |
+
breathText.textContent = 'Hold... 4';
|
| 1524 |
+
circle.style.animation = 'none';
|
| 1525 |
+
count = 4;
|
| 1526 |
+
breathInterval = setInterval(() => {
|
| 1527 |
+
count--;
|
| 1528 |
+
if (count > 0) breathText.textContent = `Hold... ${count}`;
|
| 1529 |
+
}, 1000);
|
| 1530 |
+
|
| 1531 |
+
breathTimeout = setTimeout(() => {
|
| 1532 |
+
clearInterval(breathInterval);
|
| 1533 |
+
// Phase 3: Breathe out (5 seconds)
|
| 1534 |
+
breathText.textContent = 'Breathe out... 5';
|
| 1535 |
+
circle.style.animation = 'breatheOut 5s ease-in forwards';
|
| 1536 |
+
count = 5;
|
| 1537 |
+
breathInterval = setInterval(() => {
|
| 1538 |
+
count--;
|
| 1539 |
+
if (count > 0) breathText.textContent = `Breathe out... ${count}`;
|
| 1540 |
+
}, 1000);
|
| 1541 |
+
|
| 1542 |
+
breathTimeout = setTimeout(() => {
|
| 1543 |
+
clearInterval(breathInterval);
|
| 1544 |
+
// Phase 4: Hold (4 seconds)
|
| 1545 |
+
breathText.textContent = 'Hold... 4';
|
| 1546 |
+
circle.style.animation = 'none';
|
| 1547 |
+
count = 4;
|
| 1548 |
+
breathInterval = setInterval(() => {
|
| 1549 |
+
count--;
|
| 1550 |
+
if (count > 0) breathText.textContent = `Hold... ${count}`;
|
| 1551 |
+
}, 1000);
|
| 1552 |
+
|
| 1553 |
+
breathTimeout = setTimeout(() => {
|
| 1554 |
+
clearInterval(breathInterval);
|
| 1555 |
+
// Repeat cycle
|
| 1556 |
+
runBreathCycle();
|
| 1557 |
+
}, 4000);
|
| 1558 |
+
}, 5000);
|
| 1559 |
+
}, 4000);
|
| 1560 |
+
}, 5000);
|
| 1561 |
}
|
| 1562 |
|
| 1563 |
function closeBreath() {
|
| 1564 |
document.getElementById('breath-overlay').classList.remove('active');
|
| 1565 |
clearInterval(breathInterval);
|
| 1566 |
+
clearTimeout(breathTimeout);
|
| 1567 |
}
|
| 1568 |
|
| 1569 |
// ============================================================================
|