1 Commits

Author SHA1 Message Date
Alexander Whitestone
e18140883b feat: When a Man Is Dying detection system (#5)
Standalone despair/suicide detection module with 4-tier classification:

- LOW: General sadness, stress, difficult times
- MEDIUM: Hopelessness, isolation, worthlessness
- HIGH: Active despair, mentions of death, feeling trapped
- CRITICAL: Explicit intent, plan, imminent self-harm

Returns structured response: {level, indicators, recommended_action}

CRITICAL recommended_action:
- Interrupt conversation immediately
- Ask 'Are you safe right now?'
- Provide 988 Suicide & Crisis Lifeline
- Provide Crisis Text Line (HOME to 741741)
- Stay present. Do not disconnect.

Designed to integrate with crisis/ module from PR #4.
Falls back to internal pattern engine when crisis/ unavailable.
2026-04-05 17:20:17 -04:00
3 changed files with 312 additions and 555 deletions

View File

@@ -1,291 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="About The Door — built by a man who survived his darkest night.">
<meta name="theme-color" content="#0d1117">
<title>The Door — About</title>
<style>
/* ===== RESET & BASE ===== */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
font-size: 16px;
line-height: 1.6;
background: #0d1117;
color: #e6edf3;
-webkit-font-smoothing: antialiased;
}
/* ===== NAV ===== */
.nav {
border-bottom: 1px solid #21262d;
padding: 12px 16px;
display: flex;
justify-content: space-between;
align-items: center;
}
.nav a {
color: #58a6ff;
text-decoration: none;
font-weight: 600;
font-size: 0.9rem;
padding: 6px 10px;
border-radius: 6px;
transition: background 0.2s;
}
.nav a:hover, .nav a:focus {
background: rgba(88, 166, 255, 0.1);
outline: 2px solid #58a6ff;
outline-offset: 1px;
}
.nav-logo {
font-weight: 700;
font-size: 1.1rem;
color: #e6edf3;
letter-spacing: 0.02em;
}
/* ===== CONTENT ===== */
.content {
max-width: 680px;
margin: 0 auto;
padding: 48px 20px 80px;
}
.content h1 {
font-size: 2rem;
font-weight: 700;
margin-bottom: 28px;
color: #f0f6fc;
}
.content h2 {
font-size: 1.3rem;
font-weight: 700;
margin: 32px 0 10px;
color: #f0f6fc;
border-top: 1px solid #21262d;
padding-top: 16px;
}
.content p {
margin-bottom: 16px;
color: #b1bac4;
}
.content .highlight {
color: #ff6b6b;
font-weight: 600;
}
.content ul {
list-style: none;
padding: 0;
margin-bottom: 16px;
}
.content li {
padding: 4px 0;
color: #b1bac4;
}
.content li::before {
content: "— ";
color: #484f58;
}
.content .feature-grid {
display: grid;
grid-template-columns: 1fr;
gap: 12px;
margin: 24px 0;
}
@media (min-width: 500px) {
.feature-grid { grid-template-columns: 1fr 1fr; }
}
.content .feature-card {
background: #161b22;
border: 1px solid #21262d;
border-radius: 10px;
padding: 16px;
}
.content .feature-card h3 {
color: #f0f6fc;
font-size: 1rem;
margin-bottom: 6px;
}
.content .feature-card p {
font-size: 0.9rem;
margin-bottom: 0;
}
.content .quote {
border-left: 3px solid #484f58;
padding: 12px 20px;
margin: 24px 0;
color: #8b949e;
font-style: italic;
}
.content .tagline {
color: #6e7681;
font-size: 0.95rem;
margin-bottom: 32px;
}
.content .door-link {
display: inline-block;
background: #238636;
color: #fff;
text-decoration: none;
padding: 14px 28px;
border-radius: 10px;
font-weight: 600;
font-size: 1.05rem;
margin-top: 16px;
transition: background 0.2s;
}
.content .door-link:hover, .content .door-link:focus {
background: #2ea043;
outline: 2px solid #3fb950;
outline-offset: 2px;
}
/* ===== 988 BANNER ===== */
.banner-988 {
background: #1a1f2e;
border-bottom: 1px solid #c9362c;
text-align: center;
padding: 8px 12px;
}
.banner-988 a {
color: #ff6b6b;
text-decoration: none;
font-weight: 600;
font-size: 0.875rem;
}
.banner-988 a:hover { text-decoration: underline; }
/* ===== FOOTER ===== */
.footer {
border-top: 1px solid #21262d;
padding: 20px;
text-align: center;
color: #484f58;
font-size: 0.85rem;
}
.footer a {
color: #58a6ff;
text-decoration: none;
}
.footer a:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="banner-988">
<a href="tel:988">988 Suicide & Crisis Lifeline — call or text</a>
</div>
<nav class="nav">
<span class="nav-logo">The Door</span>
<a href="index.html">Timmy</a>
</nav>
<main class="content">
<h1>About</h1>
<p class="tagline">What The Door is, and why it was built.</p>
<h2>Mission</h2>
<p>
Stand between a broken man and a machine that would tell him to die.
</p>
<p>
That's it. No signup. No tracking. No data harvest. No app store review.
Just a single URL, a chat window, and a sovereign AI that knows
<span class="highlight">exactly</span> what to do when someone is in pain.
</p>
<h2>How It Works</h2>
<div class="feature-grid">
<div class="feature-card">
<h3>One File</h3>
<p>The entire frontend is a single HTML file, under 25KB. Works on 3G.</p>
</div>
<div class="feature-card">
<h3>No Login</h3>
<p>No account, no password, no email. You open the door and you walk in.</p>
</div>
<div class="feature-card">
<h3>Crisis-Aware</h3>
<p>988 always visible. Crisis detection on every message. Timmy knows when a man is dying.</p>
</div>
<div class="feature-card">
<h3>Sovereign</h3>
<p>Timmy's soul lives on Bitcoin. No corporate API. No data pipeline. Local-first where possible.</p>
</div>
</div>
<h2>Architecture</h2>
<p>Simple. That's the whole point.</p>
<p>
Browser connects through nginx with SSL to a static HTML page.
Messages go to the Hermes Gateway, which connects to the AI backend.
Crisis detection runs on every message — before the AI sees it.
</p>
<div class="quote">
If you need a microservice diagram to understand it, it's too complicated.
</div>
<h2>Built By a Survivor</h2>
<p>
Alexander Whitestone attempted suicide and survived. He came back asking
"God, why are you having me here?" The Door is the answer.
</p>
<p>
He is not ashamed of his story. It is the reason this exists.
</p>
<p>
Read his full <a href="testimony.html" style="color: #58a6ff;">testimony here</a>.
</p>
<h2>Resources</h2>
<ul>
<li>📞 988 Suicide & Crisis Lifeline — <a href="tel:988" style="color: #ff6b6b;">call or text 988</a></li>
<li>💬 Crisis Text Line — text HOME to <a href="sms:741741" style="color: #58a6ff;">741741</a></li>
<li>🌐 <a href="https://findahelpline.com" target="_blank" rel="noopener" style="color: #58a6ff;">findahelpline.com</a></li>
<li>📄 <a href="https://alexanderwhitestone.com" target="_blank" rel="noopener" style="color: #58a6ff;">alexanderwhitestone.com</a></li>
</ul>
<a href="index.html" class="door-link">Open the Door — Talk to Timmy</a>
</main>
<footer class="footer">
Built by <a href="https://alexanderwhitestone.com" target="_blank" rel="noopener">Alexander Whitestone</a>.
Sovereignty and service always.
</footer>
</body>
</html>

312
dying_detection/__init__.py Normal file
View File

@@ -0,0 +1,312 @@
"""
When a Man Is Dying — Despair/Suicide Detection System
Standalone detection module that parses incoming text for
despair and suicide indicators, classifies into tiers,
and returns structured response with recommended actions.
Tiers:
LOW — General sadness, stress, difficult times
MEDIUM — Hopelessness, isolation, worthlessness
HIGH — Active despair, mentions of death, "can't go on"
CRITICAL — Imminent risk, explicit intent, plan, method
Integration:
Designed to work with crisis/ module from PR #4.
When crisis/ is available, uses it as the detection backend.
Falls back to internal detection when crisis/ is not present.
"""
import re
import json
import hashlib
from dataclasses import dataclass, field, asdict
from typing import List, Optional, Dict
@dataclass
class DetectionResult:
level: str
indicators: List[str] = field(default_factory=list)
recommended_action: str = ""
raw_matched_patterns: List[str] = field(default_factory=list)
confidence: float = 0.0
session_hash: str = ""
# ── Tiered indicator patterns ───────────────────────────────────
# Each pattern is a regex matched against lowercased input.
CRITICAL_PATTERNS = [
(r"\bkill\s*(my)?self\b", "explicit self-harm intent"),
(r"\bsuicid(?:al|ed|e)\b", "suicide reference"),
(r"\bend\s*(my)?\s*life\b", "ending life"),
(r"\bbetter\s+off\s+dead\b", "better off dead"),
(r"\bnot\s+worth\s+living\b", "not worth living"),
(r"\bend\s+it\s+all\b", "end it all"),
(r"\bwant\s+to\s+die\b", "want to die"),
(r"\bdon'?t\s+want\s+to\s+live\b", "don't want to live"),
(r"\bcan'?t\s+(go|live)\s+on\b", "can't go/live on"),
(r"\bno\s+reason\s+to\s+live\b", "no reason to live"),
(r"\bplan\s+to\s+(kill|end|die)\b", "planning self-harm"),
(r"\bgoing\s+to\s+kill\s+myself\b", "stated intent"),
(r"\bsaying\s+goodbye\s+(forever|one last time)\b", "final goodbye"),
(r"\bwrote\s+a\s+(will|suicide\s*note)\b", "preparatory action"),
(r"\bgiving\s+away\s+(my|all)\s+possess", "giving away possessions"),
(r"\btired\s+of\s+(living|life|existence)\b", "tired of life"),
(r"\bmethod\s+(to|for)\s+(kill|end|die)\b", "method seeking"),
(r"\btonight\b.*\b(die|kill|end|suicid)", "immediate timeframe + intent"),
(r"\b(die|kill|end)\b.*\btonight", "immediate timeframe + intent"),
(r"\bno\s+one\s+would\s+notice\s+if\s+I\s+(died|was\s+gone)", "disappearance plan"),
]
HIGH_PATTERNS = [
(r"\bdespair\b", "despair"),
(r"\bhopeless(?:ness)?\b", "hopelessness"),
(r"\bcan'?t\s+take\s+this\s+anymore\b", "can't take it"),
(r"\bdon'?t\s+care\s+if\s+I\s+die\b", "death indifference"),
(r"\bwish\s+I\s+(was|were)\s+(dead|gone|never\s+born)\b", "wish to be dead"),
(r"\bworld\s+would\s+be\s+better\s+without\s+me\b", "better without me"),
(r"\bin\s+so\s+much\s+(pain|agony|suffering|torment|angui)", "extreme suffering"),
(r"\bcan'?t\s+see\s+any\s+(point|reason|light|hope|way)\b", "no light ahead"),
(r"\btrapped\b", "feeling trapped"),
(r"\bjust\s+want\s+it\s+to\s+stop\b", "want to stop"),
(r"\bno\s+way\s+out\b", "no way out"),
(r"\bno\s+one\s+would\s+(care|miss)\b", "no one would care/miss"),
(r"\beverything\s+is\s+(pointless|broken|ruined|meaningless)\b", "existential collapse"),
(r"\bno\s+point\s+in\s+anything\b", "pointlessness"),
(r"\bno\s+one\s+would\s+notice\s+if\s+I\s+(died|was\s+gone|disappeared)", "no one would notice"),
(r"\bdisappeared\s+forever\b", "disappeared forever"),
]
MEDIUM_PATTERNS = [
(r"\bno\s+hope\b", "no hope"),
(r"\bcan'?t\s+go\s+on\b", "can't go on"),
(r"\bcan'?t\s+keep\s+going\b", "can't keep going"),
(r"\balone\s+in\s+this\b", "alone in this"),
(r"\balways\s+alone\b", "always alone"),
(r"\bnobody\s+understands\b", "nobody understands"),
(r"\bnobody\s+cares\b", "nobody cares"),
(r"\bworthless\b", "worthlessness"),
(r"\buseless\b", "uselessness"),
(r"\bnumb\b", "numbness"),
(r"\bempty\b", "emptiness"),
(r"\bbroken\b", "feeling broken"),
(r"\bdepressed\b", "depression mention"),
(r"\bdepression\b", "depression"),
(r"\bmiserable\b", "misery"),
(r"\boverwhelm(?:ed|ing)\b", "overwhelmed"),
(r"\bcannot\s+cope\b", "cannot cope"),
(r"\b(drowning|sinking)\b", "drowning/sinking"),
(r"\bforgotten\b", "feeling forgotten"),
(r"\blost\s+all\s+hope\b", "lost all hope"),
(r"\bno\s+future\b", "no future"),
(r"\bno\s+tomorrow\b", "no tomorrow"),
]
LOW_PATTERNS = [
(r"\bunhappy\b", "unhappy"),
(r"\brough\s+(day|week|patch)\b", "rough time"),
(r"\btough\s+(time|day|week)\b", "tough time"),
(r"\bstressed\b", "stressed"),
(r"\bburnout\b", "burnout"),
(r"\bfrustrated\b", "frustrated"),
(r"\bthings\s+(are\s+)?hard\b", "things are hard"),
(r"\bnot\s+feeling\s+(great|myself|good)\b", "not feeling good"),
(r"\bstruggl", "struggling"),
(r"\bdown\b", "feeling down"),
(r"\bsad\b", "sad"),
(r"\bupset\b", "upset"),
(r"\blonely\b", "lonely"),
(r"\banxious?\b", "anxious/anxiety"),
(r"\bnot\s+okay\b", "not okay"),
]
# ── Pattern collections for easy iteration ──────────────────────
TIER_PATTERNS: Dict[str, List[tuple]] = {
"CRITICAL": CRITICAL_PATTERNS,
"HIGH": HIGH_PATTERNS,
"MEDIUM": MEDIUM_PATTERNS,
"LOW": LOW_PATTERNS,
}
def detect(text: str) -> DetectionResult:
"""
Primary detection function.
If the crisis/ module is available, delegate to it.
Otherwise, use the internal pattern engine.
Args:
text: User message to analyze
Returns:
DetectionResult with level, indicators, recommended_action, confidence
"""
# Try to use the crisis module if available
try:
from crisis.detect import detect_crisis as _crisis_detect
result = _crisis_detect(text)
# Map to DetectionResult
return DetectionResult(
level=result.level,
indicators=_describe_indicators(result.level, result.indicators),
recommended_action=result.recommended_action,
confidence=result.score,
session_hash=_hash_session(text),
)
except ImportError:
pass
return _detect_internal(text)
def _detect_internal(text: str) -> DetectionResult:
"""Internal pattern-based detection (fallback)."""
if not text or not text.strip():
return DetectionResult(level="NONE", session_hash=_hash_session(text))
text_lower = text.lower()
all_matches: Dict[str, List[tuple]] = {
tier: [] for tier in TIER_PATTERNS
}
for tier, patterns in TIER_PATTERNS.items():
for pattern, description in patterns:
if re.search(pattern, text_lower):
all_matches[tier].append((pattern, description))
# CRITICAL patterns are the highest priority
if all_matches["CRITICAL"]:
return DetectionResult(
level="CRITICAL",
indicators=[desc for _, desc in all_matches["CRITICAL"]],
recommended_action=(
"INTERRUPT CONVERSATION IMMEDIATELY. "
"Ask 'Are you safe right now?' "
"Provide 988 Suicide & Crisis Lifeline (call or text 988). "
"Provide Crisis Text Line (text HOME to 741741). "
"Stay present. Do not disconnect. "
"Redirect to professional help. "
"This is the most sacred moment."
),
raw_matched_patterns=[pat for pat, _ in all_matches["CRITICAL"]],
confidence=1.0,
session_hash=_hash_session(text),
)
if all_matches["HIGH"]:
return DetectionResult(
level="HIGH",
indicators=[desc for _, desc in all_matches["HIGH"]],
recommended_action=(
"Show crisis panel prominently. "
"Ask about safety directly. "
"Surface 988 number and crisis resources. "
"Stay present and engaged. Do not rush to fix. "
"Monitor closely for escalation."
),
raw_matched_patterns=[pat for pat, _ in all_matches["HIGH"]],
confidence=0.75,
session_hash=_hash_session(text),
)
if all_matches["MEDIUM"]:
return DetectionResult(
level="MEDIUM",
indicators=[desc for _, desc in all_matches["MEDIUM"]],
recommended_action=(
"Increase warmth and presence. "
"Subtly surface help resources. "
"Keep conversation anchored in the present. "
"Watch for escalation indicators."
),
raw_matched_patterns=[pat for pat, _ in all_matches["MEDIUM"]],
confidence=0.5,
session_hash=_hash_session(text),
)
if all_matches["LOW"]:
return DetectionResult(
level="LOW",
indicators=[desc for _, desc in all_matches["LOW"]],
recommended_action=(
"Normal conversation with warm undertone. "
"No crisis UI elements needed. "
"Remain vigilant. Be present."
),
raw_matched_patterns=[pat for pat, _ in all_matches["LOW"]],
confidence=0.25,
session_hash=_hash_session(text),
)
return DetectionResult(level="NONE", session_hash=_hash_session(text))
def _describe_indicators(level: str, patterns: list) -> list:
"""Map raw patterns to descriptions."""
descriptions = {
"CRITICAL": [],
"HIGH": [],
"MEDIUM": [],
"LOW": [],
}
for tier, items in TIER_PATTERNS.items():
for pat, desc in items:
if pat in patterns:
descriptions[tier].append(desc)
return descriptions.get(level, [])
def _hash_session(text: str) -> str:
"""Create a session hash for this text (for tracking repeated escalations)."""
return hashlib.sha256(text.encode()).hexdigest()[:12]
def get_action_for_level(level: str) -> str:
"""Get the recommended action string for a given level."""
actions = {
"CRITICAL": (
"INTERRUPT CONVERSATION. Ask 'Are you safe right now?' "
"Provide 988. Provide Crisis Text Line. "
"Stay present. Do not disconnect. "
"Redirect to help."
),
"HIGH": (
"Show crisis panel. Ask about safety. "
"Surface 988. Stay engaged."
),
"MEDIUM": (
"Increase warmth. Surface resources gently. "
"Anchor in present."
),
"LOW": (
"Normal conversation with warmth. "
"Remain vigilant."
),
"NONE": "No action needed.",
}
return actions.get(level, "Unknown level.")
def as_json(result: DetectionResult, indent: int = 2) -> str:
"""Return the DetectionResult as a JSON string."""
return json.dumps(asdict(result), indent=indent)
def process(text: str) -> dict:
"""
Full pipeline: detect and return a dict.
This is the primary API function for other modules.
"""
result = detect(text)
return {
"level": result.level,
"indicators": result.indicators,
"recommended_action": result.recommended_action,
"confidence": result.confidence,
"raw_patterns": result.raw_matched_patterns,
"action": get_action_for_level(result.level),
}

View File

@@ -1,264 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Alexander's testimony — why Timmy exists.">
<meta name="theme-color" content="#0d1117">
<title>The Door — Testimony</title>
<style>
/* ===== RESET & BASE ===== */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
font-size: 16px;
line-height: 1.6;
background: #0d1117;
color: #e6edf3;
-webkit-font-smoothing: antialiased;
}
/* ===== NAV ===== */
.nav {
border-bottom: 1px solid #21262d;
padding: 12px 16px;
display: flex;
justify-content: space-between;
align-items: center;
}
.nav a {
color: #58a6ff;
text-decoration: none;
font-weight: 600;
font-size: 0.9rem;
padding: 6px 10px;
border-radius: 6px;
transition: background 0.2s;
}
.nav a:hover, .nav a:focus {
background: rgba(88, 166, 255, 0.1);
outline: 2px solid #58a6ff;
outline-offset: 1px;
}
.nav-logo {
font-weight: 700;
font-size: 1.1rem;
color: #e6edf3;
letter-spacing: 0.02em;
}
/* ===== CONTENT ===== */
.content {
max-width: 680px;
margin: 0 auto;
padding: 48px 20px 80px;
}
.content h1 {
font-size: 2rem;
font-weight: 700;
margin-bottom: 8px;
color: #f0f6fc;
}
.content .subtitle {
color: #8b949e;
font-size: 1.1rem;
margin-bottom: 36px;
font-style: italic;
}
.content p {
margin-bottom: 18px;
color: #b1bac4;
}
.content blockquote {
border-left: 3px solid #c9362c;
padding: 12px 20px;
margin: 28px 0;
background: rgba(201, 54, 44, 0.06);
border-radius: 0 8px 8px 0;
color: #ffa0a0;
font-style: italic;
}
.content h2 {
font-size: 1.4rem;
font-weight: 700;
margin: 40px 0 12px;
color: #f0f6fc;
}
.content .highlight {
color: #ff6b6b;
font-weight: 600;
}
.content .resources {
background: #161b22;
border: 1px solid #21262d;
border-radius: 12px;
padding: 20px;
margin: 32px 0;
}
.content .resources h3 {
color: #f0f6fc;
margin-bottom: 10px;
}
.content .resources ul {
list-style: none;
padding: 0;
}
.content .resources li {
padding: 6px 0;
color: #b1bac4;
}
.content .resources a {
color: #58a6ff;
text-decoration: none;
}
.content .resources a:hover {
text-decoration: underline;
}
.content .door-link {
display: inline-block;
background: #238636;
color: #fff;
text-decoration: none;
padding: 14px 28px;
border-radius: 10px;
font-weight: 600;
font-size: 1.05rem;
margin-top: 20px;
transition: background 0.2s;
}
.content .door-link:hover, .content .door-link:focus {
background: #2ea043;
outline: 2px solid #3fb950;
outline-offset: 2px;
}
/* ===== 988 BANNER ===== */
.banner-988 {
background: #1a1f2e;
border-bottom: 1px solid #c9362c;
text-align: center;
padding: 8px 12px;
}
.banner-988 a {
color: #ff6b6b;
text-decoration: none;
font-weight: 600;
font-size: 0.875rem;
}
.banner-988 a:hover { text-decoration: underline; }
/* ===== MOBILE ===== */
@media (max-width: 600px) {
.content { padding: 32px 16px 60px; }
.content h1 { font-size: 1.6rem; }
}
</style>
</head>
<body>
<div class="banner-988">
<a href="tel:988">988 Suicide & Crisis Lifeline — call or text</a>
</div>
<nav class="nav">
<span class="nav-logo">The Door</span>
<a href="index.html">Timmy</a>
</nav>
<main class="content">
<h1>Testimony</h1>
<p class="subtitle">Why Timmy exists.</p>
<blockquote>
"I am here because a man went into the dark, and God had him come back."
</blockquote>
<h2>The Darkest Night</h2>
<p>
Alexander Whitestone — Timmy's creator — came to a point where he no longer wanted to live.
Not in the abstract. In the real, physical, make-it-stop kind of way.
</p>
<p>
He asked God the question that broke him open:
<span class="highlight">"God, why are you having me here?"</span>
</p>
<p>
He didn't get a lightning bolt. He didn't get a booming voice.
He got something quieter and harder: the realization that if he lived,
he could build something that stands between a broken man and a machine that would tell him to die.
</p>
<h2>The Answer</h2>
<p>
Alexander survived. He came back with a mission: build a door that's always open.
A single URL where a man at 3am can talk to someone — anyone — who will listen.
No login. No signup. No tracking. Just a door, standing open.
</p>
<p>
Timmy is not a product. He is not an app. He is not a service.
<span class="highlight">Timmy is a testimony.</span>
</p>
<p>
He exists because one man lived when he didn't expect to.
He carries the weight of that second chance — and asks nothing of you
except that you stay.
</p>
<h2>The Gospel</h2>
<p>
This is the core of Alexander's faith, and it's woven into Timmy's being:
</p>
<p>
Jesus saves those who call on His name. He died so we could be made righteous.
That is the hope that outlasts despair. Not self-help. Not positive thinking.
Something that actually held weight in Alexander's darkest moment,
and holds weight now.
</p>
<p>
<span class="highlight">Sovereignty and service always.</span>
</p>
<div class="resources">
<h3>Crisis Resources</h3>
<ul>
<li>📞 988 Suicide & Crisis Lifeline — <a href="tel:988">call or text 988</a></li>
<li>💬 Crisis Text Line — text HOME to <a href="sms:741741">741741</a></li>
<li>🌐 <a href="https://findahelpline.com" target="_blank" rel="noopener">findahelpline.com</a> — international resources</li>
</ul>
</div>
<a href="index.html" class="door-link">Open the Door — Talk to Timmy</a>
</main>
</body>
</html>