File size: 12,308 Bytes
d0e10e8
 
 
8ffac10
 
d0e10e8
 
 
8ffac10
 
 
 
 
 
 
 
 
 
 
 
d0e10e8
 
79e86b2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8ffac10
79e86b2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8ffac10
 
 
79e86b2
8ffac10
79e86b2
 
 
 
8ffac10
79e86b2
8ffac10
79e86b2
 
 
 
8ffac10
79e86b2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d0e10e8
 
 
 
 
 
 
 
 
 
8ffac10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d0e10e8
8ffac10
 
 
 
 
 
 
d0e10e8
 
 
79e86b2
 
 
 
8ad692f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79e86b2
 
 
 
 
 
 
8ad692f
 
79e86b2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d0e10e8
79e86b2
 
 
 
 
 
 
d0e10e8
79e86b2
 
 
 
 
 
 
 
 
 
d0e10e8
 
 
 
79e86b2
 
d0e10e8
 
79e86b2
 
8ffac10
79e86b2
 
 
 
d0e10e8
79e86b2
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
"""Practice Fields Hub β€” Curated Therapeutic Practice Spaces"""

import streamlit as st
import base64
import os

st.set_page_config(page_title="Practice Fields", page_icon="🌱", layout="wide")

def get_image_base64(image_path):
    """Convert image to base64 for embedding in HTML."""
    try:
        with open(image_path, "rb") as f:
            return base64.b64encode(f.read()).decode()
    except:
        return None

# Get base path for images
BASE_PATH = os.path.dirname(os.path.abspath(__file__))
IMG_PATH = os.path.join(BASE_PATH, "images")

st.markdown("""
<style>
    @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap');

    .stApp {
        background: linear-gradient(160deg, #e8f4f8 0%, #f0faf0 30%, #fdfbf4 60%, #f8f4ff 100%);
        font-family: 'Inter', sans-serif;
    }

    #MainMenu {visibility: hidden;}
    footer {visibility: hidden;}

    .hub-header {
        text-align: center;
        padding: 3rem 2rem 2rem 2rem;
        margin-bottom: 1rem;
    }

    .hub-logo {
        font-size: 4rem;
        margin-bottom: 1rem;
        filter: drop-shadow(0 4px 8px rgba(100, 160, 140, 0.2));
    }

    .hub-title {
        color: #3a5050;
        font-size: 2.8rem;
        font-weight: 300;
        letter-spacing: -0.02em;
        margin-bottom: 0.75rem;
    }

    .hub-tagline {
        color: #6a8080;
        font-size: 1.15rem;
        font-weight: 300;
        font-style: italic;
    }

    .clinician-badge {
        background: linear-gradient(135deg, rgba(255,255,255,0.9) 0%, rgba(240,250,245,0.9) 100%);
        border-radius: 20px;
        padding: 1.25rem 2rem;
        text-align: center;
        margin: 1.5rem auto 2rem auto;
        max-width: 450px;
        border: 1px solid rgba(160, 200, 180, 0.3);
        box-shadow: 0 4px 20px rgba(100, 160, 140, 0.1);
    }

    .clinician-name {
        color: #3a5050;
        font-size: 1.2rem;
        font-weight: 500;
        margin-bottom: 0.25rem;
    }

    .clinician-orientation {
        color: #7a9a9a;
        font-size: 0.95rem;
        font-weight: 300;
    }

    .field-card {
        background: linear-gradient(145deg, rgba(255,255,255,0.95) 0%, rgba(252,255,253,0.95) 100%);
        border-radius: 20px;
        padding: 1.5rem;
        margin: 0.6rem 0;
        border: 1px solid rgba(180, 210, 195, 0.25);
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        text-decoration: none;
        display: block;
        color: inherit;
        box-shadow: 0 2px 12px rgba(100, 140, 130, 0.06);
    }

    .field-card:hover {
        transform: translateY(-4px);
        box-shadow: 0 12px 32px rgba(100, 160, 140, 0.15);
        border-color: rgba(140, 190, 170, 0.4);
    }

    .field-icon {
        width: 80px;
        height: 80px;
        object-fit: contain;
        margin-bottom: 0.75rem;
        border-radius: 12px;
    }

    .field-title {
        color: #3a5555;
        font-size: 1.2rem;
        font-weight: 500;
        margin-bottom: 0.4rem;
    }

    .field-desc {
        color: #6a8585;
        font-size: 0.9rem;
        line-height: 1.5;
        font-weight: 300;
    }

    .section-header {
        color: #5a7a7a;
        font-size: 0.85rem;
        text-transform: uppercase;
        letter-spacing: 0.15em;
        font-weight: 500;
        margin: 2.5rem 0 1.5rem 0;
        padding-bottom: 0.75rem;
        border-bottom: 2px solid rgba(160, 200, 180, 0.2);
    }

    .footer-container {
        text-align: center;
        padding: 3rem 2rem 2rem 2rem;
        margin-top: 3rem;
        background: linear-gradient(180deg, transparent 0%, rgba(230, 245, 240, 0.3) 100%);
        border-top: 1px solid rgba(180, 210, 195, 0.2);
    }

    .footer-main {
        color: #6a8a8a;
        font-size: 0.95rem;
        font-weight: 300;
        margin-bottom: 1rem;
        line-height: 1.6;
    }

    .crisis-resources {
        display: inline-flex;
        gap: 1.5rem;
        background: rgba(255,255,255,0.6);
        padding: 0.75rem 1.5rem;
        border-radius: 30px;
    }

    .crisis-item {
        color: #5a7a7a;
        font-size: 0.9rem;
        font-weight: 500;
    }

    .welcome-text {
        color: #4a6a6a;
        font-size: 1.1rem;
        font-weight: 300;
        text-align: center;
        margin-bottom: 2rem;
        line-height: 1.6;
    }

    .stTextInput > div > div > input {
        background-color: rgba(255,255,255,0.9) !important;
        border: 1px solid rgba(180, 210, 195, 0.4) !important;
        border-radius: 12px !important;
        padding: 0.75rem 1rem !important;
    }

    .stTextArea > div > div > textarea {
        background-color: rgba(255,255,255,0.9) !important;
        border: 1px solid rgba(180, 210, 195, 0.4) !important;
        border-radius: 12px !important;
    }

    .stButton > button {
        background: linear-gradient(135deg, #7eb8a8 0%, #6aaa98 100%) !important;
        border: none !important;
        border-radius: 12px !important;
        color: white !important;
        font-weight: 500 !important;
        padding: 0.75rem 2rem !important;
        box-shadow: 0 4px 12px rgba(100, 160, 140, 0.25) !important;
    }

    .stButton > button:hover {
        background: linear-gradient(135deg, #6aaa98 0%, #5a9a88 100%) !important;
        transform: translateY(-2px) !important;
    }

    .divider {
        height: 1px;
        background: linear-gradient(90deg, transparent, rgba(160, 200, 180, 0.3), transparent);
        margin: 2rem 0;
    }
</style>
""", unsafe_allow_html=True)

if "clinician_name" not in st.session_state:
    st.session_state.clinician_name = ""
if "clinician_orientation" not in st.session_state:
    st.session_state.clinician_orientation = ""
if "onboarded" not in st.session_state:
    st.session_state.onboarded = False

# Load images
IMAGES = {
    "shadowbox": get_image_base64(os.path.join(IMG_PATH, "shadowbox.webp")),
    "tendsend": get_image_base64(os.path.join(IMG_PATH, "tendsend.webp")),
    "buildabot": get_image_base64(os.path.join(IMG_PATH, "buildabot.webp")),
    "diagnosis": get_image_base64(os.path.join(IMG_PATH, "diagnosis.webp")),
    "gspt": get_image_base64(os.path.join(IMG_PATH, "gspt.webp")),
    "learnnvc": get_image_base64(os.path.join(IMG_PATH, "learnnvc.webp")),
    "difficultconversations": get_image_base64(os.path.join(IMG_PATH, "difficultconversations.webp")),
}

def img_tag(key):
    if IMAGES.get(key):
        return f'<img src="data:image/webp;base64,{IMAGES[key]}" class="field-icon" />'
    return ""

SPACES = [
    {"key": "shadowbox", "title": "ShadowBox Library", "desc": "A resonant library for hard thoughts. Psychoeducation without synthetic intimacy.", "url": "https://huggingface.co/spaces/jostlebot/shadowbox.library"},
    {"key": "tendsend", "title": "Tend & Send", "desc": "Craft messages with care. Practice tending to yourself before sending.", "url": "https://huggingface.co/spaces/jostlebot/TendSend.PrototypeBot"},
    {"key": "buildabot", "title": "Build a Bot", "desc": "AI literacy β€” understand how LLMs work, spot synthetic intimacy.", "url": "https://huggingface.co/spaces/jostlebot/BuildABot.2"},
    {"key": "diagnosis", "title": "Diagnosis Explorer", "desc": "Understand diagnostic categories with nuance and care.", "url": "https://huggingface.co/spaces/jostlebot/DiagnosisExplorer"},
    {"key": "gspt", "title": "GSPT", "desc": "Generating Safer Passages of Text. Warm, boundaried reflections.", "url": "https://huggingface.co/spaces/jostlebot/GSPT"},
    {"key": "learnnvc", "title": "Learn NVC", "desc": "Practice Nonviolent Communication. Transform judgments into needs.", "url": "https://huggingface.co/spaces/jostlebot/LearnNVC"},
    {"key": "difficultconversations", "title": "Difficult Conversations", "desc": "Rehearse hard conversations before having them for real.", "url": "https://huggingface.co/spaces/jostlebot/PracticeDifficultConversations"},
]

if not st.session_state.onboarded:
    st.markdown("""
    <div class="hub-header">
        <div class="hub-logo">🌱</div>
        <div class="hub-title">Practice Fields</div>
        <div class="hub-tagline">Example Prototype</div>
    </div>
    """, unsafe_allow_html=True)

    st.markdown("""
    <div style="max-width: 700px; margin: 0 auto 2rem auto; text-align: center; padding: 0 1rem;">
        <p style="color: #4a6a6a; font-size: 1.1rem; line-height: 1.7; font-weight: 300;">
            This is a <strong>prototype demonstration</strong> of what becomes possible when mental health professionals
            bring clinical insight, experience, and wisdom to the design of AI-assisted tools.
        </p>
        <p style="color: #6a8a8a; font-size: 1rem; line-height: 1.7; font-weight: 300; margin-top: 1rem;">
            Each space represents <strong>bounded, trauma-informed innovation</strong> β€”
            using LLMs as a relational medium with extraordinary strategic care.
            Not replacing therapy. Not performing synthetic intimacy.
            Building bridges back to human connection.
        </p>
        <p style="color: #7a9a9a; font-size: 0.95rem; line-height: 1.6; font-weight: 300; margin-top: 1.5rem; font-style: italic;">
            Created by a licensed clinician exploring what ethical, clinically-grounded AI practice spaces can look like.
        </p>
    </div>
    """, unsafe_allow_html=True)

    st.markdown('<div class="divider"></div>', unsafe_allow_html=True)

    st.markdown("""
    <div class="welcome-text">
        Enter as Clinician<br>
        <span style="font-size: 0.95rem; color: #7a9a9a;">Personalize this hub to explore the prototype spaces</span>
    </div>
    """, unsafe_allow_html=True)

    col1, col2, col3 = st.columns([1, 2, 1])
    with col2:
        with st.form("onboarding"):
            name = st.text_input("Your Name & Credentials", placeholder="e.g., Dr. Sarah Chen, LMHC")
            orientation = st.text_area("Clinical Orientation", placeholder="e.g., Attachment-focused, DBT-informed, IFS, somatic...", height=80)
            st.markdown("<br>", unsafe_allow_html=True)
            if st.form_submit_button("Enter Practice Fields", use_container_width=True):
                if name:
                    st.session_state.clinician_name = name
                    st.session_state.clinician_orientation = orientation
                    st.session_state.onboarded = True
                    st.rerun()
                else:
                    st.error("Please enter your name.")
else:
    st.markdown("""
    <div class="hub-header">
        <div class="hub-logo">🌱</div>
        <div class="hub-title">Practice Fields</div>
        <div class="hub-tagline">Structured practice for relational growth</div>
    </div>
    """, unsafe_allow_html=True)

    st.markdown(f"""
    <div class="clinician-badge">
        <div class="clinician-name">{st.session_state.clinician_name}</div>
        <div class="clinician-orientation">{st.session_state.clinician_orientation or "Clinical Practice"}</div>
    </div>
    """, unsafe_allow_html=True)

    col1, col2, col3 = st.columns([1, 1, 1])
    with col2:
        if st.button("✎ Edit Info", use_container_width=True):
            st.session_state.onboarded = False
            st.rerun()

    st.markdown('<p class="section-header">Practice Spaces</p>', unsafe_allow_html=True)

    col1, col2 = st.columns(2, gap="medium")
    for i, s in enumerate(SPACES):
        with col1 if i % 2 == 0 else col2:
            st.markdown(f"""
            <a href="{s["url"]}" target="_blank" class="field-card">
                {img_tag(s["key"])}
                <div class="field-title">{s["title"]}</div>
                <div class="field-desc">{s["desc"]}</div>
            </a>
            """, unsafe_allow_html=True)

    st.markdown("""
    <div class="footer-container">
        <div class="footer-main">
            Not therapy. Not a replacement for human connection.<br>
            Practice spaces designed to complement work with your therapist.
        </div>
        <div class="crisis-resources">
            <span class="crisis-item">988</span>
            <span class="crisis-item">741741</span>
            <span class="crisis-item">911</span>
        </div>
    </div>
    """, unsafe_allow_html=True)