diff --git a/index.html b/index.html index 5282521..1733cd8 100644 --- a/index.html +++ b/index.html @@ -80,6 +80,64 @@ html, body { gap: 4px; } +#chat-header { + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 10px 12px; + background: #10161d; + border-bottom: 1px solid #21262d; +} + +.chat-header-copy { + min-width: 0; +} + +#chat-header-title { + font-size: 0.95rem; + font-weight: 600; + color: #e6edf3; +} + +#chat-header-subtitle { + font-size: 0.75rem; + color: #8b949e; + margin-top: 2px; +} + +.safety-link-btn { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + min-height: 40px; + border-radius: 999px; + border: 1px solid #30363d; + background: transparent; + color: #8b949e; + font-size: 0.82rem; + font-weight: 600; + cursor: pointer; + transition: color 0.2s, border-color 0.2s, background 0.2s; +} + +.safety-link-btn:hover, +.safety-link-btn:focus { + color: #e6edf3; + border-color: #58a6ff; + background: rgba(88, 166, 255, 0.08); + outline: 2px solid rgba(88, 166, 255, 0.35); + outline-offset: 2px; +} + +.safety-link-btn svg { + width: 16px; + height: 16px; + flex-shrink: 0; +} + .status-dot { width: 6px; height: 6px; @@ -579,6 +637,19 @@ html, body { #chat-area { padding: 20px 24px 8px; } #input-area { padding: 10px 24px; } #banner-988 a { font-size: 0.95rem; } + #chat-header { padding: 12px 24px; } +} + +@media (max-width: 480px) { + #chat-header { + align-items: flex-start; + flex-direction: column; + } + + .safety-link-btn { + width: 100%; + justify-content: center; + } } @media (min-width: 900px) { @@ -642,13 +713,24 @@ html, body { Text HOME to 741741 - + My Safety Plan + + + Talk to Timmy + No login. No tracking. Just someone to listen. + + + + Safety plan + + + @@ -681,7 +763,7 @@ html, body { @@ -701,16 +783,16 @@ html, body { - + My Safety Plan - + - This plan is saved only on your device. No one else can see it. + This plan is saved only on your device. No one else can see it. 1. Warning signs (thoughts, moods, behaviors) @@ -738,8 +820,8 @@ html, body { @@ -812,7 +894,8 @@ Sovereignty and service always.`; var statusText = document.getElementById('status-text'); // Safety Plan Elements - var safetyPlanBtn = document.getElementById('safety-plan-btn'); + var chatSafetyPlanBtn = document.getElementById('chat-safety-plan-btn'); + var footerSafetyPlanBtn = document.getElementById('safety-plan-btn'); var crisisSafetyPlanBtn = document.getElementById('crisis-safety-plan-btn'); var safetyPlanModal = document.getElementById('safety-plan-modal'); var closeSafetyPlan = document.getElementById('close-safety-plan'); @@ -825,6 +908,7 @@ Sovereignty and service always.`; var isStreaming = false; var overlayTimer = null; var crisisPanelShown = false; + var lastSafetyPlanTrigger = null; // ===== SERVICE WORKER ===== if ('serviceWorker' in navigator) { @@ -1117,25 +1201,50 @@ Sovereignty and service always.`; } catch (e) {} } - safetyPlanBtn.addEventListener('click', function() { + function openSafetyPlanModal(trigger) { + lastSafetyPlanTrigger = trigger || document.activeElement; loadSafetyPlan(); safetyPlanModal.classList.add('active'); + safetyPlanModal.setAttribute('aria-hidden', 'false'); + document.getElementById('sp-warning-signs').focus(); + } + + function closeSafetyPlanModal() { + safetyPlanModal.classList.remove('active'); + safetyPlanModal.setAttribute('aria-hidden', 'true'); + if (lastSafetyPlanTrigger && typeof lastSafetyPlanTrigger.focus === 'function') { + lastSafetyPlanTrigger.focus(); + } + } + + chatSafetyPlanBtn.addEventListener('click', function() { + openSafetyPlanModal(chatSafetyPlanBtn); + }); + + footerSafetyPlanBtn.addEventListener('click', function() { + openSafetyPlanModal(footerSafetyPlanBtn); }); // Crisis panel safety plan button (if crisis panel is visible) if (crisisSafetyPlanBtn) { crisisSafetyPlanBtn.addEventListener('click', function() { - loadSafetyPlan(); - safetyPlanModal.classList.add('active'); + openSafetyPlanModal(crisisSafetyPlanBtn); }); } closeSafetyPlan.addEventListener('click', function() { - safetyPlanModal.classList.remove('active'); + closeSafetyPlanModal(); }); cancelSafetyPlan.addEventListener('click', function() { - safetyPlanModal.classList.remove('active'); + closeSafetyPlanModal(); + }); + + document.addEventListener('keydown', function(e) { + if (e.key === 'Escape' && safetyPlanModal.classList.contains('active')) { + e.preventDefault(); + closeSafetyPlanModal(); + } }); saveSafetyPlan.addEventListener('click', function() { @@ -1148,7 +1257,7 @@ Sovereignty and service always.`; }; try { localStorage.setItem('timmy_safety_plan', JSON.stringify(plan)); - safetyPlanModal.classList.remove('active'); + closeSafetyPlanModal(); alert('Safety plan saved locally.'); } catch (e) { alert('Error saving plan.'); @@ -1298,8 +1407,7 @@ Sovereignty and service always.`; // Check for URL params (e.g., ?safetyplan=true for PWA shortcut) var urlParams = new URLSearchParams(window.location.search); if (urlParams.get('safetyplan') === 'true') { - loadSafetyPlan(); - safetyPlanModal.classList.add('active'); + openSafetyPlanModal(chatSafetyPlanBtn); // Clean up URL window.history.replaceState({}, document.title, window.location.pathname); } diff --git a/tests/test_safety_plan_chat_header.py b/tests/test_safety_plan_chat_header.py new file mode 100644 index 0000000..1e1f2d2 --- /dev/null +++ b/tests/test_safety_plan_chat_header.py @@ -0,0 +1,42 @@ +from pathlib import Path + +INDEX_HTML = Path(__file__).resolve().parents[1] / 'index.html' +HTML = INDEX_HTML.read_text() + + +def _between(start_marker: str, end_marker: str) -> str: + start = HTML.index(start_marker) + end = HTML.index(end_marker) + return HTML[start:end] + + +def test_persistent_safety_plan_button_lives_in_chat_header(): + assert 'id="chat-header"' in HTML, 'expected a dedicated chat header area' + + header_html = _between('
Talk to Timmy
No login. No tracking. Just someone to listen.
This plan is saved only on your device. No one else can see it.