Esvanth commited on
Commit
af122d5
Β·
verified Β·
1 Parent(s): 72a6c05

Add templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +766 -0
templates/index.html ADDED
@@ -0,0 +1,766 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1.0">
6
+ <title>MindScan β€” Multi-Model Framework for Depression & Suicide Risk Detection</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@300;400;500;600&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
8
+ <style>
9
+ :root{
10
+ --bg:#f7f5f0; --bg2:#efece8; --bg3:#e6e2da; --bg4:#dedad1;
11
+ --ink:#1a1816; --ink2:#5c5750; --ink3:#9c9790;
12
+ --border:rgba(26,24,22,0.09); --border2:rgba(26,24,22,0.16);
13
+ --blue:#1d4ed8; --blue-bg:#eff6ff; --blue-mid:#3b82f6;
14
+ --amber:#b45309; --amber-bg:#fffbeb; --amber-mid:#d97706;
15
+ --red:#b91c1c; --red-bg:#fef2f2;
16
+ --green:#15803d; --green-bg:#f0fdf4;
17
+ --purple:#6d28d9; --purple-bg:#f5f3ff;
18
+ --shadow:0 1px 3px rgba(26,24,22,0.06),0 4px 16px rgba(26,24,22,0.04);
19
+ --shadow-md:0 2px 8px rgba(26,24,22,0.08),0 8px 32px rgba(26,24,22,0.06);
20
+ }
21
+ *{box-sizing:border-box;margin:0;padding:0}
22
+ html{scroll-behavior:smooth}
23
+ body{background:var(--bg);color:var(--ink);font-family:'Geist',sans-serif;font-size:15px;line-height:1.6;overflow-x:hidden}
24
+
25
+ /* ── HEADER ── */
26
+ header{
27
+ padding:16px 48px;display:flex;align-items:center;justify-content:space-between;
28
+ border-bottom:1px solid var(--border);background:rgba(247,245,240,0.94);
29
+ position:sticky;top:0;z-index:100;backdrop-filter:blur(10px);
30
+ }
31
+ .logo{display:flex;align-items:center;gap:10px}
32
+ .logo-mark{width:28px;height:28px;background:var(--ink);border-radius:7px;display:flex;align-items:center;justify-content:center}
33
+ .logo-mark svg{width:14px;height:14px}
34
+ .logo-txt{font-family:'Instrument Serif',serif;font-size:18px;letter-spacing:-.02em}
35
+ .logo-txt em{font-style:italic;color:var(--ink2)}
36
+ .nav-links{display:flex;gap:2px}
37
+ .nav-links a{font-size:12px;color:var(--ink2);padding:5px 10px;border-radius:6px;text-decoration:none;transition:all .15s;font-family:'DM Mono',monospace}
38
+ .nav-links a:hover{background:var(--bg2);color:var(--ink)}
39
+ .nav-badge{font-size:10px;font-family:'DM Mono',monospace;background:var(--amber-bg);color:var(--amber);border:1px solid rgba(180,83,9,.2);padding:4px 10px;border-radius:20px}
40
+
41
+ /* ── HERO ── */
42
+ .hero{padding:80px 48px 64px;max-width:1040px;margin:0 auto}
43
+ .hero-top{display:grid;grid-template-columns:1fr 360px;gap:48px;align-items:start;margin-bottom:52px}
44
+ .hero-eyebrow{display:flex;align-items:center;gap:8px;margin-bottom:18px}
45
+ .eyebrow-dot{width:6px;height:6px;border-radius:50%;background:var(--green);animation:blink 2.5s infinite}
46
+ @keyframes blink{0%,100%{opacity:1}50%{opacity:.2}}
47
+ .eyebrow-txt{font-size:11px;font-family:'DM Mono',monospace;color:var(--ink3);letter-spacing:.1em;text-transform:uppercase}
48
+ .hero h1{font-family:'Instrument Serif',serif;font-size:clamp(28px,3.6vw,42px);font-weight:400;line-height:1.13;letter-spacing:-.03em;color:var(--ink);margin-bottom:18px}
49
+ .hero h1 em{font-style:italic;color:var(--ink2)}
50
+ .hero-sub{font-size:15px;color:var(--ink2);line-height:1.7;margin-bottom:24px;max-width:500px}
51
+
52
+ /* RQ Cards */
53
+ .rq-cards{display:flex;flex-direction:column;gap:10px}
54
+ .rq-card{border-radius:12px;padding:16px 18px;border:1px solid}
55
+ .rq-card.rq1{background:var(--blue-bg);border-color:rgba(29,78,216,.2)}
56
+ .rq-card.rq2{background:var(--amber-bg);border-color:rgba(180,83,9,.2)}
57
+ .rq-label{font-size:9px;font-family:'DM Mono',monospace;letter-spacing:.14em;text-transform:uppercase;font-weight:500;margin-bottom:5px}
58
+ .rq-card.rq1 .rq-label{color:var(--blue)}
59
+ .rq-card.rq2 .rq-label{color:var(--amber)}
60
+ .rq-text{font-size:13px;color:var(--ink);line-height:1.5}
61
+
62
+ /* Stats panel */
63
+ .stats-panel{background:var(--bg2);border:1px solid var(--border);border-radius:16px;padding:24px;display:grid;grid-template-columns:1fr 1fr;gap:16px;box-shadow:var(--shadow)}
64
+ .stat-box{text-align:center;padding:12px;background:var(--bg);border-radius:10px;border:1px solid var(--border)}
65
+ .stat-num{font-family:'Instrument Serif',serif;font-size:28px;letter-spacing:-.02em;color:var(--ink);line-height:1}
66
+ .stat-lbl{font-size:10px;font-family:'DM Mono',monospace;color:var(--ink3);margin-top:4px;text-transform:uppercase;letter-spacing:.08em}
67
+
68
+ /* ── SECTION SHARED ── */
69
+ .section{max-width:1040px;margin:0 auto;padding:64px 48px}
70
+ .sec-eyebrow{font-size:10px;font-family:'DM Mono',monospace;letter-spacing:.12em;text-transform:uppercase;color:var(--blue);margin-bottom:10px}
71
+ .sec-h2{font-family:'Instrument Serif',serif;font-size:clamp(24px,3.5vw,38px);font-weight:400;letter-spacing:-.02em;line-height:1.15;margin-bottom:8px}
72
+ .sec-h2 em{font-style:italic;color:var(--ink2)}
73
+ .sec-lead{font-size:14px;color:var(--ink2);max-width:560px;line-height:1.7;margin-bottom:36px}
74
+ .section-divider{border:none;border-top:1px solid var(--border);margin:0}
75
+
76
+ /* ── METHODOLOGY 3-STEP ── */
77
+ .method-steps{display:grid;grid-template-columns:repeat(3,1fr);gap:0;position:relative}
78
+ .method-steps::before{content:'';position:absolute;top:22px;left:22px;right:22px;height:2px;background:var(--bg3);z-index:0}
79
+ .method-step{text-align:center;position:relative;z-index:2;padding:0 20px;cursor:pointer;transition:opacity .15s}
80
+ .method-step:hover{opacity:.8}
81
+ .ms-dot{width:44px;height:44px;border-radius:50%;background:var(--ink);border:2px solid var(--ink);display:flex;align-items:center;justify-content:center;margin:0 auto 14px;font-size:12px;font-family:'DM Mono',monospace;color:#fff;transition:background .2s,border-color .2s}
82
+ .method-step.active .ms-dot{background:var(--blue);border-color:var(--blue)}
83
+ .ms-title{font-size:14px;font-weight:500;color:var(--ink);margin-bottom:7px}
84
+ .ms-body{font-size:12px;color:var(--ink2);line-height:1.65}
85
+ .ms-crisp{display:flex;flex-wrap:wrap;justify-content:center;gap:4px;margin-top:10px}
86
+ .ms-crisp-tag{font-size:9px;font-family:'DM Mono',monospace;padding:2px 7px;border-radius:3px;background:var(--bg2);border:1px solid var(--border);color:var(--ink3);letter-spacing:.05em}
87
+ /* Detail panel */
88
+ .method-detail{margin-top:32px;background:#fff;border:1px solid var(--border);border-radius:14px;padding:28px 32px;box-shadow:var(--shadow);animation:fadeUp .25s ease both}
89
+ .md-panel{display:none}
90
+ .md-panel.active{display:block}
91
+ .md-title{font-size:13px;font-weight:600;color:var(--ink);margin-bottom:14px;display:flex;align-items:center;gap:8px}
92
+ .md-title-dot{width:8px;height:8px;border-radius:50%;background:var(--blue);flex-shrink:0}
93
+ .md-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
94
+ .md-block{padding:14px;background:var(--bg);border-radius:8px;border:1px solid var(--border)}
95
+ .md-block-lbl{font-size:9px;font-family:'DM Mono',monospace;letter-spacing:.1em;text-transform:uppercase;color:var(--ink3);margin-bottom:5px}
96
+ .md-block-val{font-size:12px;color:var(--ink);line-height:1.6}
97
+ .md-block-val strong{color:var(--red)}
98
+ .md-block-val em{color:var(--blue);font-style:normal;font-weight:500}
99
+
100
+ /* ── EVIDENCE MATRIX ── */
101
+ .matrix-wrap{overflow-x:auto;margin-top:28px}
102
+ .matrix-tbl{width:100%;border-collapse:collapse;font-size:13px}
103
+ .matrix-tbl th{text-align:center;padding:11px 16px;background:var(--bg2);border-bottom:2px solid var(--border2);font-size:10px;font-family:'DM Mono',monospace;letter-spacing:.1em;text-transform:uppercase;color:var(--ink3);font-weight:500}
104
+ .matrix-tbl th:first-child{text-align:left;min-width:200px}
105
+ .matrix-tbl td{padding:12px 16px;border-bottom:1px solid var(--border);text-align:center;font-family:'DM Mono',monospace;font-size:13px;color:var(--ink2);vertical-align:middle}
106
+ .matrix-tbl td:first-child{text-align:left;font-family:'Geist',sans-serif;font-size:13px;color:var(--ink)}
107
+ .matrix-tbl tr:hover td{background:var(--bg2)}
108
+ .matrix-tbl tr:last-child td{border-bottom:none}
109
+ .matrix-tbl td.winner{font-weight:600;color:var(--ink)}
110
+ .matrix-tbl td.collapsed{background:rgba(185,28,28,.07);color:var(--red)}
111
+ .ds-label{display:inline-flex;align-items:center;gap:8px}
112
+ .ds-badge{font-size:9px;font-family:'DM Mono',monospace;padding:2px 7px;border-radius:3px;font-weight:500;flex-shrink:0}
113
+
114
+ /* ── MATRIX FOOTNOTE ── */
115
+ .matrix-footnote{margin-top:14px;font-size:11px;color:var(--ink3);line-height:1.6;padding:10px 14px;background:var(--bg2);border:1px solid var(--border);border-radius:7px;font-family:'DM Mono',monospace}
116
+ .matrix-footnote strong{color:var(--ink2)}
117
+
118
+ /* ── FINDINGS ── */
119
+ .findings-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}
120
+ .finding:nth-child(5){grid-column:1/-1;}
121
+ .finding{background:#fff;border:1px solid var(--border);border-radius:12px;padding:22px;box-shadow:var(--shadow)}
122
+ .finding-n{font-family:'Instrument Serif',serif;font-size:36px;color:var(--bg3);line-height:1;margin-bottom:8px}
123
+ .finding-t{font-size:13px;font-weight:500;color:var(--ink);margin-bottom:6px}
124
+ .finding-b{font-size:12px;color:var(--ink2);line-height:1.65}
125
+ .finding-chip{display:inline-block;font-family:'DM Mono',monospace;font-size:10px;background:var(--bg2);border:1px solid var(--border);padding:3px 8px;border-radius:4px;margin-top:8px;color:var(--ink2)}
126
+
127
+ /* ── VERDICT ── */
128
+ .verdict-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:24px}
129
+ .verdict-card{background:#fff;border:1px solid var(--border);border-radius:12px;padding:20px;box-shadow:var(--shadow)}
130
+ .verdict-card.rq{border-left:3px solid var(--blue-mid)}
131
+ .verdict-card.lim{border-left:3px solid var(--amber-mid)}
132
+ .vc-eyebrow{font-size:9px;font-family:'DM Mono',monospace;letter-spacing:.12em;text-transform:uppercase;margin-bottom:6px;font-weight:500}
133
+ .verdict-card.rq .vc-eyebrow{color:var(--blue)}
134
+ .verdict-card.lim .vc-eyebrow{color:var(--amber)}
135
+ .vc-title{font-size:13px;font-weight:500;color:var(--ink);margin-bottom:6px}
136
+ .vc-body{font-size:12px;color:var(--ink2);line-height:1.65}
137
+ .vc-chip{display:inline-block;font-family:'DM Mono',monospace;font-size:10px;background:var(--blue-bg);border:1px solid rgba(29,78,216,.2);color:var(--blue);padding:3px 8px;border-radius:4px;margin-top:8px}
138
+ .verdict-card.lim .vc-chip{background:var(--amber-bg);border-color:rgba(180,83,9,.2);color:var(--amber)}
139
+
140
+ /* ── DEMO ── */
141
+ .demo-section{max-width:1040px;margin:0 auto;padding:64px 48px}
142
+ .input-card{background:#fff;border:1px solid var(--border);border-radius:14px;padding:24px;box-shadow:var(--shadow);margin-bottom:16px}
143
+ textarea{
144
+ width:100%;background:var(--bg);border:1px solid var(--border2);border-radius:8px;
145
+ padding:13px 15px;font-family:'Geist',sans-serif;font-size:14px;color:var(--ink);
146
+ resize:vertical;min-height:100px;outline:none;line-height:1.6;transition:border-color .15s,box-shadow .15s;
147
+ }
148
+ textarea:focus{border-color:rgba(29,78,216,.4);box-shadow:0 0 0 3px rgba(29,78,216,.07)}
149
+ textarea::placeholder{color:var(--ink3)}
150
+ .input-foot{display:flex;align-items:center;justify-content:space-between;margin-top:10px;flex-wrap:wrap;gap:8px}
151
+ .char-count{font-size:11px;font-family:'DM Mono',monospace;color:var(--ink3)}
152
+ .samples{display:flex;gap:5px;flex-wrap:wrap}
153
+ .sbtn{font-size:10px;font-family:'DM Mono',monospace;background:var(--bg2);border:1px solid var(--border);border-radius:5px;padding:5px 10px;cursor:pointer;color:var(--ink2);transition:all .15s}
154
+ .sbtn:hover{border-color:var(--border2);color:var(--ink)}
155
+ .sbtn.danger{border-color:rgba(185,28,28,.25);color:var(--red);background:var(--red-bg);display:flex;align-items:center;gap:5px}
156
+ .sbtn-pulse{width:5px;height:5px;border-radius:50%;background:var(--red);animation:blink 1.5s infinite}
157
+ .run-btn{
158
+ width:100%;margin-top:12px;background:var(--ink);color:#fff;border:none;
159
+ border-radius:9px;padding:13px 24px;font-family:'Geist',sans-serif;font-size:14px;
160
+ font-weight:500;cursor:pointer;display:flex;align-items:center;justify-content:center;
161
+ gap:8px;transition:opacity .15s,transform .1s;letter-spacing:-.01em;
162
+ }
163
+ .run-btn:hover{opacity:.87}
164
+ .run-btn:active{transform:scale(.99)}
165
+ .run-btn:disabled{opacity:.45;cursor:not-allowed}
166
+ .spinner{width:14px;height:14px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .7s linear infinite;display:none}
167
+ @keyframes spin{to{transform:rotate(360deg)}}
168
+ .disclaimer{font-size:11px;color:var(--ink3);line-height:1.6;padding:10px 14px;background:var(--amber-bg);border:1px solid rgba(180,83,9,.15);border-radius:7px;margin-bottom:20px}
169
+
170
+ /* Results */
171
+ .results{display:none;animation:fadeUp .35s ease both}
172
+ @keyframes fadeUp{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
173
+ .risk-banner{border-radius:10px;padding:14px 18px;margin-bottom:16px;border:1px solid;display:flex;align-items:flex-start;gap:12px}
174
+ .risk-banner.danger{background:var(--red-bg);border-color:rgba(185,28,28,.25)}
175
+ .risk-banner.safe{background:var(--green-bg);border-color:rgba(21,128,61,.2)}
176
+ .risk-banner.warn{background:var(--amber-bg);border-color:rgba(180,83,9,.25)}
177
+ .rb-icon{font-size:18px;flex-shrink:0;margin-top:1px}
178
+ .rb-title{font-size:13px;font-weight:500;margin-bottom:3px}
179
+ .rb-body{font-size:12px;line-height:1.55;color:var(--ink2)}
180
+ .risk-banner.danger .rb-title{color:var(--red)}
181
+ .risk-banner.safe .rb-title{color:var(--green)}
182
+ .risk-banner.warn .rb-title{color:var(--amber)}
183
+ .masked-callout{background:var(--amber-bg);border:1px solid rgba(180,83,9,.2);border-radius:10px;padding:14px 18px;margin-bottom:16px;display:none;animation:fadeUp .3s ease both}
184
+ .mc-callout-title{font-size:13px;font-weight:500;color:var(--amber);margin-bottom:4px;display:flex;align-items:center;gap:7px}
185
+ .mc-callout-body{font-size:12px;color:#92400e;line-height:1.6}
186
+
187
+ .results-hdr{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}
188
+ .results-hdr-title{font-size:14px;font-weight:500;color:var(--ink)}
189
+ .elapsed-chip{font-size:10px;font-family:'DM Mono',monospace;color:var(--ink3);background:var(--bg2);border:1px solid var(--border);padding:3px 9px;border-radius:20px}
190
+ .winner-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
191
+ .win-card{border-radius:12px;padding:18px;border:1px solid;animation:fadeUp .4s ease both;background:#fff;box-shadow:var(--shadow);transition:all .3s ease}
192
+ .win-card.d1{border-color:rgba(29,78,216,.2);animation-delay:.14s}
193
+ .win-card.d2{border-color:rgba(180,83,9,.2);animation-delay:.07s}
194
+ .win-card.d3{border-color:rgba(185,28,28,.2)}
195
+ /* D3 dominant state when suicide risk is flagged */
196
+ .win-card.d3.risk-active{
197
+ border-color:var(--red);border-width:2px;
198
+ background:var(--red-bg);box-shadow:0 0 0 4px rgba(185,28,28,.08),var(--shadow-md);
199
+ }
200
+ .win-card.d3.risk-active .wc-pred{color:var(--red)}
201
+ .wc-lbl{font-size:9px;font-family:'DM Mono',monospace;letter-spacing:.1em;text-transform:uppercase;margin-bottom:8px;line-height:1.5}
202
+ .win-card.d1 .wc-lbl{color:var(--blue)}
203
+ .win-card.d2 .wc-lbl{color:var(--amber)}
204
+ .win-card.d3 .wc-lbl{color:var(--red)}
205
+ .wc-pred{font-family:'Instrument Serif',serif;font-size:20px;letter-spacing:-.02em;color:var(--ink);margin-bottom:6px;min-height:48px;display:flex;align-items:flex-end}
206
+ .conf-row{display:flex;align-items:center;gap:8px;margin-bottom:5px}
207
+ .conf-track{flex:1;height:4px;background:var(--bg2);border-radius:2px;overflow:hidden}
208
+ .conf-fill{height:100%;border-radius:2px;transition:width .8s cubic-bezier(.4,0,.2,1);width:0}
209
+ .win-card.d1 .conf-fill{background:var(--blue-mid)}
210
+ .win-card.d2 .conf-fill{background:var(--amber-mid)}
211
+ .win-card.d3 .conf-fill{background:var(--red)}
212
+ .conf-pct{font-size:11px;font-family:'DM Mono',monospace;min-width:34px;text-align:right}
213
+ .win-card.d1 .conf-pct{color:var(--blue)}
214
+ .win-card.d2 .conf-pct{color:var(--amber)}
215
+ .win-card.d3 .conf-pct{color:var(--red)}
216
+ .win-card.d3.risk-active .conf-pct{font-size:14px;font-weight:500}
217
+ .wc-meta{font-size:11px;color:var(--ink3)}
218
+ /* Clinical Insight Alert */
219
+ .clinical-insight{background:var(--amber-bg);border:1px solid rgba(180,83,9,.2);border-left:3px solid var(--amber-mid);border-radius:10px;padding:14px 18px;margin-bottom:16px;display:none;animation:fadeUp .3s ease both}
220
+ .ci-title{font-size:13px;font-weight:500;color:var(--amber);margin-bottom:5px;display:flex;align-items:center;gap:7px}
221
+ .ci-body{font-size:12px;color:#92400e;line-height:1.65}
222
+
223
+ footer{text-align:center;padding:28px 48px;border-top:1px solid var(--border);font-size:11px;font-family:'DM Mono',monospace;color:var(--ink3);line-height:1.9}
224
+ @media(max-width:768px){
225
+ header,.section,.demo-section,footer{padding-left:20px;padding-right:20px}
226
+ .hero{padding:48px 20px 40px}
227
+ .hero-top,.findings-grid,.winner-grid{grid-template-columns:1fr}
228
+ .method-steps{grid-template-columns:1fr;gap:24px}
229
+ .method-steps::before{display:none}
230
+ }
231
+ </style>
232
+ </head>
233
+ <body>
234
+
235
+ <!-- HEADER -->
236
+ <header>
237
+ <div class="logo">
238
+ <div class="logo-mark">
239
+ <svg viewBox="0 0 14 14" fill="none">
240
+ <circle cx="7" cy="7" r="5.5" stroke="white" stroke-width="1.3"/>
241
+ <path d="M5 7c0-1.2.8-2 2-2s2 .8 2 2-.8 2-2 2" stroke="white" stroke-width="1.3" stroke-linecap="round"/>
242
+ <circle cx="7" cy="7" r="1.2" fill="white"/>
243
+ </svg>
244
+ </div>
245
+ <div class="logo-txt">Mind<em>Scan</em></div>
246
+ </div>
247
+ <nav class="nav-links">
248
+ <a href="#methodology">Methodology</a>
249
+ <a href="#matrix">Evidence Matrix</a>
250
+ <a href="#findings">Findings</a>
251
+ <a href="#verdict">Conclusions</a>
252
+ <a href="#demo">Live Demo</a>
253
+ </nav>
254
+ <div class="nav-badge">NCI H9DAI 2026</div>
255
+ </header>
256
+
257
+ <!-- HERO -->
258
+ <div class="hero">
259
+ <div class="hero-top">
260
+ <div>
261
+ <div class="hero-eyebrow"><div class="eyebrow-dot"></div><span class="eyebrow-txt">Mental health NLP research Β· NCI H9DAI</span></div>
262
+ <h1>Multi-Model Framework for Depression Classification and <em>Suicide Risk Detection</em> from Social Media Text</h1>
263
+ <p class="hero-sub">A parallel ensemble of 12 classifiers across 3 clinical datasets β€” extending Tumaliuan et al. (2024) with modern transformers and SMOTE balancing.</p>
264
+ <div class="rq-cards">
265
+ <div class="rq-card rq1">
266
+ <div class="rq-label">RQ1</div>
267
+ <div class="rq-text">Which machine learning model provides the highest Accuracy for identifying depression and suicide risk?</div>
268
+ </div>
269
+ <div class="rq-card rq2">
270
+ <div class="rq-label">RQ2</div>
271
+ <div class="rq-text">Does training on the full dataset (232K), a half split (116K), or a sample (50K) provide a significant boost in Accuracy?</div>
272
+ </div>
273
+ </div>
274
+ </div>
275
+ <div class="stats-panel">
276
+ <div class="stat-box"><div class="stat-num" data-target="3" data-suffix="">0</div><div class="stat-lbl">Datasets</div></div>
277
+ <div class="stat-box"><div class="stat-num" data-target="12" data-suffix="">0</div><div class="stat-lbl">Models trained</div></div>
278
+ <div class="stat-box"><div class="stat-num" data-target="99.9" data-suffix="%" data-dec="1">0</div><div class="stat-lbl">Best Accuracy</div></div>
279
+ <div class="stat-box"><div class="stat-num" data-target="98.1" data-suffix="%" data-dec="1">0</div><div class="stat-lbl">D3 Best Accuracy</div></div>
280
+ </div>
281
+ </div>
282
+ </div>
283
+
284
+ <hr class="section-divider">
285
+
286
+ <!-- METHODOLOGY -->
287
+ <section class="section" id="methodology">
288
+ <div class="sec-eyebrow">Methodology</div>
289
+ <div class="sec-h2">Three-step <em>pipeline</em></div>
290
+ <p class="sec-lead">CRISP-DM applied across all three datasets β€” from raw social media text to parallel ensemble predictions.</p>
291
+
292
+ <div class="method-steps">
293
+ <div class="method-step active" onclick="showMethodDetail(0)">
294
+ <div class="ms-dot">01</div>
295
+ <div class="ms-title">Data</div>
296
+ <div class="ms-body">3 clinical datasets spanning Twitter and Reddit, covering depression types, binary detection, and suicide risk.</div>
297
+ <div class="ms-crisp"><span class="ms-crisp-tag">CRISP-DM 1: Business Understanding</span><span class="ms-crisp-tag">CRISP-DM 2: Data Understanding</span></div>
298
+ </div>
299
+ <div class="method-step" onclick="showMethodDetail(1)">
300
+ <div class="ms-dot">02</div>
301
+ <div class="ms-title">Preprocessing</div>
302
+ <div class="ms-body">6-stage text cleaning pipeline + SMOTE oversampling to address class imbalance left unresolved by the base paper.</div>
303
+ <div class="ms-crisp"><span class="ms-crisp-tag">CRISP-DM 3: Data Preparation</span></div>
304
+ </div>
305
+ <div class="method-step" onclick="showMethodDetail(2)">
306
+ <div class="ms-dot">03</div>
307
+ <div class="ms-title">Modelling</div>
308
+ <div class="ms-body">Parallel ensemble of 12 classifiers β€” all run independently on every prediction, never as a sequential cascade.</div>
309
+ <div class="ms-crisp"><span class="ms-crisp-tag">CRISP-DM 4: Modelling</span><span class="ms-crisp-tag">CRISP-DM 5: Evaluation</span><span class="ms-crisp-tag">CRISP-DM 6: Deployment</span></div>
310
+ </div>
311
+ </div>
312
+
313
+ <div class="method-detail">
314
+ <!-- Panel 0: Data -->
315
+ <div class="md-panel active" id="mdp0">
316
+ <div class="md-title"><div class="md-title-dot"></div>Dataset Overview</div>
317
+ <div class="md-grid">
318
+ <div class="md-block">
319
+ <div class="md-block-lbl">D1 β€” Depression Types (Zenodo 14233292)</div>
320
+ <div class="md-block-val"><em>14,983 tweets Β· 6 classes</em> β€” Postpartum (3,746), Major Depressive (2,517), Bipolar (2,443), Psychotic (2,312), No Depression (1,985), Atypical (1,980). Psychiatrist-verified labels. Class imbalance ratio: 1.89Γ—.</div>
321
+ </div>
322
+ <div class="md-block">
323
+ <div class="md-block-lbl">D2 β€” Binary Depression (Kaggle: albertobellardini)</div>
324
+ <div class="md-block-val"><em>10,314 tweets Β· 2 classes</em> β€” Not Depressed (8,000) / Depressed (2,314). Severe class imbalance: <strong>3.46Γ—</strong>. Twitter short-form text. SMOTE applied to training set (8,251 β†’ 12,800 samples). Trained on Twitter affect patterns β€” may underdetect atypical presentations.</div>
325
+ </div>
326
+ <div class="md-block">
327
+ <div class="md-block-lbl">D3 β€” Suicide Risk (Kaggle: nikhileswarkomati)</div>
328
+ <div class="md-block-val"><em>232,074 Reddit posts Β· 2 classes</em> β€” Suicide / Non-Suicide (perfectly balanced, 116,037 each). Suicide posts average <em>212 words</em> (mean), non-suicide posts 63 words. We sample <em>50K posts</em> and compare against full/half splits to answer RQ2.</div>
329
+ </div>
330
+ <div class="md-block">
331
+ <div class="md-block-lbl">Business Context</div>
332
+ <div class="md-block-val">A clinically-motivated framework for social media monitoring β€” applicable to platform-level moderation, mental health triage, and early intervention systems. Complements rather than replaces clinical assessment.</div>
333
+ </div>
334
+ </div>
335
+ </div>
336
+ <!-- Panel 1: Preprocessing -->
337
+ <div class="md-panel" id="mdp1">
338
+ <div class="md-title"><div class="md-title-dot"></div>Preprocessing Pipeline</div>
339
+ <div class="md-grid">
340
+ <div class="md-block">
341
+ <div class="md-block-lbl">6-Stage Text Cleaning</div>
342
+ <div class="md-block-val">1. Lowercase Β· 2. Strip URLs &amp; http links Β· 3. Remove @mentions Β· 4. Remove # symbols Β· 5. Strip punctuation Β· 6. Collapse whitespace. Applied identically across all three datasets for consistency.</div>
343
+ </div>
344
+ <div class="md-block">
345
+ <div class="md-block-lbl">SMOTE β€” Synthetic Oversampling</div>
346
+ <div class="md-block-val">Applied to D1 and D2 training sets only (D3 is pre-balanced). D1: 11,986 β†’ <em>17,982 samples</em>. D2: 8,251 β†’ <em>12,800 samples</em>. Creates synthetic clinical neighbours in TF-IDF feature space. Directly addresses the base paper's (Tumaliuan 2024) biggest limitation β€” they trained on raw imbalanced data.</div>
347
+ </div>
348
+ <div class="md-block">
349
+ <div class="md-block-lbl">Feature Extraction β€” Classical Models</div>
350
+ <div class="md-block-val">TF-IDF vectoriser with unigrams + bigrams, fitted per-dataset on training data only. Captures frequency-weighted term co-occurrence patterns, well-suited for short Twitter text.</div>
351
+ </div>
352
+ <div class="md-block">
353
+ <div class="md-block-lbl">Feature Extraction β€” Transformers</div>
354
+ <div class="md-block-val">XLM-RoBERTa tokeniser (max 128 tokens D1/D2, 256 tokens D3) with padding. Pre-trained multilingual contextual embeddings capture semantic meaning and long-range dependencies β€” critical for Reddit's longer posts.</div>
355
+ </div>
356
+ </div>
357
+ </div>
358
+ <!-- Panel 2: Modelling -->
359
+ <div class="md-panel" id="mdp2">
360
+ <div class="md-title"><div class="md-title-dot"></div>Ensemble Strategy &amp; Architecture</div>
361
+ <div class="md-grid">
362
+ <div class="md-block">
363
+ <div class="md-block-lbl">4 Models per Dataset (12 total)</div>
364
+ <div class="md-block-val"><em>Logistic Regression</em> β€” L2 regularised, max_iter=1000. <em>SVM</em> β€” LinearSVC, C=1.0. <em>XGBoost</em> β€” 300 estimators, max_depth=6. <em>XLM-RoBERTa</em> β€” fine-tuned multilingual transformer, <em>278M parameters</em>, lr=2e-5, 3 epochs.</div>
365
+ </div>
366
+ <div class="md-block">
367
+ <div class="md-block-lbl">Parallel Architecture β€” Clinical Rationale</div>
368
+ <div class="md-block-val">All 12 models run simultaneously on every input. A sequential design (check depression first, then suicide risk) would <strong>miss masked suicidality</strong> β€” a clinically documented pre-crisis pattern where affect appears normal but intent is resolved. Parallelism is a safety requirement, not a design preference.</div>
369
+ </div>
370
+ <div class="md-block">
371
+ <div class="md-block-lbl">XGBoost Algorithm Collapse</div>
372
+ <div class="md-block-val">XGBoost accuracy on D3: <em>91.6% (50K sample) β†’ 70.5% (Full 232K) β†’ 60.1% (H1 116K)</em>. Performance degrades as training data grows. The H1/H2 results are also inconsistent (60.1% vs 71.0%) β€” gradient boosting is highly sensitive to data distribution shifts at this scale, making it unreliable for large Reddit corpora.</div>
373
+ </div>
374
+ <div class="md-block">
375
+ <div class="md-block-lbl">D3 Split Study (RQ2)</div>
376
+ <div class="md-block-val">D3 trained on 4 configurations: Full (232K), Half 1 (116K), Half 2 (116K), Sample (50K). XLM-RoBERTa accuracy: <em>98.1% (50K) β†’ 97.8% (H1) β†’ 98.0% (H2/Full)</em>. Ξ” = 0.3% across 4Γ— more data. Kolmogorov-Smirnov tests confirm all splits share identical distributions (p &gt; 0.49), validating the comparison.</div>
377
+ </div>
378
+ </div>
379
+ </div>
380
+ </div>
381
+ </section>
382
+
383
+ <hr class="section-divider">
384
+
385
+ <!-- ACCURACY EVIDENCE MATRIX -->
386
+ <section class="section" id="matrix">
387
+ <div class="sec-eyebrow">Core evaluation</div>
388
+ <div class="sec-h2">Accuracy <em>Evidence Matrix</em></div>
389
+ <p class="sec-lead">All 4 models evaluated across all dataset splits. <strong>Bold</strong> = winner per row. <span style="color:var(--red)">Red</span> = XGBoost collapse on larger training sets.</p>
390
+
391
+ <div class="matrix-wrap">
392
+ <table class="matrix-tbl">
393
+ <thead>
394
+ <tr>
395
+ <th>Dataset / Split</th>
396
+ <th>Logistic Regression</th>
397
+ <th>SVM</th>
398
+ <th>XGBoost</th>
399
+ <th>XLM-RoBERTa</th>
400
+ </tr>
401
+ </thead>
402
+ <tbody>
403
+ <tr>
404
+ <td><div class="ds-label"><span class="ds-badge" style="background:var(--blue-bg);color:var(--blue)">D1</span> Depression Types</div></td>
405
+ <td>91.5%</td>
406
+ <td class="winner">92.4%</td>
407
+ <td>91.8%</td>
408
+ <td>90.5%</td>
409
+ </tr>
410
+ <tr>
411
+ <td><div class="ds-label"><span class="ds-badge" style="background:var(--amber-bg);color:var(--amber)">D2</span> Binary Depression</div></td>
412
+ <td>98.9%</td>
413
+ <td>97.1%</td>
414
+ <td>99.3%</td>
415
+ <td class="winner">99.9%</td>
416
+ </tr>
417
+ <tr>
418
+ <td><div class="ds-label"><span class="ds-badge" style="background:var(--red-bg);color:var(--red)">D3</span> Full (232K)</div></td>
419
+ <td>94.3%</td>
420
+ <td>94.6%</td>
421
+ <td class="collapsed">70.5%</td>
422
+ <td class="winner">98.0%</td>
423
+ </tr>
424
+ <tr>
425
+ <td><div class="ds-label"><span class="ds-badge" style="background:var(--blue-bg);color:var(--blue)">D3</span> Half 1 (116K)</div></td>
426
+ <td>93.8%</td>
427
+ <td>94.2%</td>
428
+ <td class="collapsed">60.1%</td>
429
+ <td class="winner">97.8%</td>
430
+ </tr>
431
+ <tr>
432
+ <td><div class="ds-label"><span class="ds-badge" style="background:var(--amber-bg);color:var(--amber)">D3</span> Half 2 (116K)</div></td>
433
+ <td>93.7%</td>
434
+ <td>94.2%</td>
435
+ <td class="collapsed">71.0%</td>
436
+ <td class="winner">98.0%</td>
437
+ </tr>
438
+ <tr>
439
+ <td><div class="ds-label"><span class="ds-badge" style="background:var(--green-bg);color:var(--green)">D3</span> Sample (50K) β˜…</div></td>
440
+ <td>93.2%</td>
441
+ <td>93.7%</td>
442
+ <td>91.6%</td>
443
+ <td class="winner">98.1%</td>
444
+ </tr>
445
+ </tbody>
446
+ </table>
447
+ </div>
448
+ <p class="matrix-footnote"><strong>Note:</strong> Full performance evaluation including Macro F1-Score, Cohen's Kappa, and per-class metrics are documented in the Final IEEE Report. Accuracy is shown here as the primary comparative metric for cross-dataset validation.</p>
449
+ </section>
450
+
451
+ <hr class="section-divider">
452
+
453
+ <!-- FINDINGS -->
454
+ <section class="section" id="findings">
455
+ <div class="sec-eyebrow">Key findings</div>
456
+ <div class="sec-h2">What the results <em>show</em></div>
457
+ <p class="sec-lead">Four insights that directly answer the research questions.</p>
458
+
459
+ <div class="findings-grid">
460
+ <div class="finding">
461
+ <div class="finding-n">01</div>
462
+ <div class="finding-t">SVM is the best model for short-form text</div>
463
+ <div class="finding-b">On 6-class depression type classification (D1), SVM achieves the highest Accuracy of 92.4%. Tweets average 31 words β€” too short for transformer contextual embeddings to gain advantage over TF-IDF bigrams.</div>
464
+ <div class="finding-chip">D1 Accuracy: SVM 92.4%</div>
465
+ </div>
466
+ <div class="finding">
467
+ <div class="finding-n">02</div>
468
+ <div class="finding-t">XLM-RoBERTa is the best model for long-form text</div>
469
+ <div class="finding-b">On Reddit suicide risk posts (D3), XLM-RoBERTa achieves 98.1% Accuracy with the 50K sample. Suicide posts average 212 words β€” rich enough context for transformer embeddings to dominate every competitor. D2 (Twitter, ~31 words) tells the opposite story.</div>
470
+ <div class="finding-chip">D3 Accuracy: XLM-RoBERTa 98.1%</div>
471
+ </div>
472
+ <div class="finding">
473
+ <div class="finding-n">03</div>
474
+ <div class="finding-t">Increasing data size provided no significant gain</div>
475
+ <div class="finding-b">Scaling from 50K to 232K samples produced only a 0.1% change in XLM-RoBERTa Accuracy (98.1% β†’ 98.0%). Adding 182,000 more training examples gave no meaningful improvement, validating the 50K sample.</div>
476
+ <div class="finding-chip">50K β†’ 232K: Ξ” Accuracy = 0.1%</div>
477
+ </div>
478
+ <div class="finding">
479
+ <div class="finding-n">04</div>
480
+ <div class="finding-t">Social media affect β‰  clinical presentation</div>
481
+ <div class="finding-b">D2 was trained on Twitter-style emotional language (explicit distress, slang). Clinical presentations β€” anhedonia ("nothing feels enjoyable"), fatigue, flat affect β€” use a different lexicon and are systematically under-flagged. This is the documented <em>Affective vs. Clinical Lexicon Gap</em>: models trained on social media affect fail to recognise diagnostic-criteria language.</div>
482
+ <div class="finding-chip">D2 limitation β€” documented failure mode</div>
483
+ </div>
484
+ <div class="finding">
485
+ <div class="finding-n">05</div>
486
+ <div class="finding-t">Parallel architecture is the safety net</div>
487
+ <div class="finding-b">When D2 misses a clinical presentation, D1 and D3 can still catch it. When classical D3 models over-flag depressive vocabulary, XLM-RoBERTa's contextual understanding overrides them. No single model is sufficient β€” the parallel ensemble exists precisely because each model's failure mode is different and partially compensated by the others.</div>
488
+ <div class="finding-chip">Multi-task learning precedent β€” Zogan et al. 2024</div>
489
+ </div>
490
+ </div>
491
+ </section>
492
+
493
+ <hr class="section-divider">
494
+
495
+ <!-- CONCLUSIONS & VERDICT -->
496
+ <section class="section" id="verdict">
497
+ <div class="sec-eyebrow">Conclusions</div>
498
+ <div class="sec-h2">Research <em>verdict</em></div>
499
+ <p class="sec-lead">Direct answers to both research questions, and the key limitations of the study.</p>
500
+
501
+ <div class="verdict-grid">
502
+ <div class="verdict-card rq">
503
+ <div class="vc-eyebrow">RQ1 β€” Best model</div>
504
+ <div class="vc-title">No single model wins across all tasks</div>
505
+ <div class="vc-body">SVM (92.4%) wins on short-form Twitter text (D1) where TF-IDF bigrams capture enough signal. XLM-RoBERTa wins on long-form Reddit posts (D2: 99.9%, D3: 98.1%) where contextual embeddings dominate. Model selection must be text-length aware.</div>
506
+ <div class="vc-chip">SVM for short text Β· XLM-RoBERTa for long text</div>
507
+ </div>
508
+ <div class="verdict-card rq">
509
+ <div class="vc-eyebrow">RQ2 β€” Dataset size</div>
510
+ <div class="vc-title">More data gave no meaningful gain</div>
511
+ <div class="vc-body">Scaling from 50K to 232K training samples produced only a 0.1% change in XLM-RoBERTa Accuracy (98.1% β†’ 98.0%). For this task and model, the 50K sample captures the full signal β€” there is no statistically significant benefit from 4Γ— more data.</div>
512
+ <div class="vc-chip">50K sample is sufficient Β· Ξ” = 0.1%</div>
513
+ </div>
514
+ <div class="verdict-card lim">
515
+ <div class="vc-eyebrow">Limitation 1 β€” Affective vs. Clinical Lexicon Gap</div>
516
+ <div class="vc-title">Social media affect β‰  clinical diagnostic criteria</div>
517
+ <div class="vc-body">D2 was trained on Twitter explicit emotional language. Clinical presentations using diagnostic vocabulary β€” anhedonia ("nothing feels enjoyable"), psychomotor fatigue, flat affect β€” do not match that training distribution and are systematically under-flagged. This is empirical evidence of the domain gap between self-reported social media affect and clinical language, not a model defect.</div>
518
+ <div class="vc-chip">Documented domain gap β€” Finding 04</div>
519
+ </div>
520
+ <div class="verdict-card lim">
521
+ <div class="vc-eyebrow">Limitation 2 β€” Classical model lexical overfitting</div>
522
+ <div class="vc-title">TF-IDF ignores word order and context</div>
523
+ <div class="vc-body">Classical D3 models (LR, SVM, XGBoost) use TF-IDF bag-of-words features. Vocabulary overlapping with r/SuicideWatch posts (e.g. "exhausted", "nothing feels enjoyable") triggers false-positive suicide flags β€” the model sees matching tokens without understanding the sentence context. XLM-RoBERTa's contextual embeddings override these false positives, demonstrating why the transformer is the reliable D3 winner.</div>
524
+ <div class="vc-chip">TF-IDF lexical overfitting β€” defer to XLM-RoBERTa</div>
525
+ </div>
526
+ </div>
527
+ </section>
528
+
529
+ <hr class="section-divider">
530
+
531
+ <!-- LIVE DEMO -->
532
+ <div class="demo-section" id="demo">
533
+ <div class="sec-eyebrow">Live inference</div>
534
+ <div class="sec-h2" style="margin-bottom:8px">Try it β€” <em>winner model per task</em></div>
535
+ <p class="sec-lead" style="margin-bottom:24px">Sample 3 demonstrates masked suicidality. Try typing clinical-style depressive language ("I feel exhausted, nothing feels enjoyable") to observe the Affective vs. Clinical Lexicon Gap documented in Finding 04.</p>
536
+
537
+ <div class="disclaimer"><strong>Research prototype only.</strong> Not a clinical tool. If you or someone you know is in crisis, please contact a mental health professional or emergency services immediately.</div>
538
+
539
+ <div class="input-card">
540
+ <textarea id="textInput" placeholder="Enter any text β€” tweet, Reddit post, or sentence..."></textarea>
541
+ <div class="input-foot">
542
+ <div class="char-count" id="charCount">0 characters</div>
543
+ <div class="samples">
544
+ <button class="sbtn" onclick="loadSample(0)">Sample 1 β€” Postpartum</button>
545
+ <button class="sbtn" onclick="loadSample(1)">Sample 2 β€” Psychotic</button>
546
+ <button class="sbtn danger" onclick="loadSample(2)"><div class="sbtn-pulse"></div>Sample 3 β€” Masked risk</button>
547
+ <button class="sbtn" onclick="loadSample(3)">Sample 4 β€” No issue</button>
548
+ </div>
549
+ </div>
550
+ <button class="run-btn" id="runBtn" onclick="runAnalysis()">
551
+ <div class="spinner" id="spinner"></div>
552
+ <span id="btnTxt">Run analysis</span>
553
+ </button>
554
+ </div>
555
+
556
+ <div class="results" id="results">
557
+ <div class="risk-banner" id="riskBanner">
558
+ <div class="rb-icon" id="rbIcon"></div>
559
+ <div><div class="rb-title" id="rbTitle"></div><div class="rb-body" id="rbBody"></div></div>
560
+ </div>
561
+
562
+ <!-- Clinical Insight Alert β€” shown when patterns warrant clinical interpretation -->
563
+ <div class="clinical-insight" id="clinicalInsight">
564
+ <div class="ci-title" id="ciTitle"></div>
565
+ <div class="ci-body" id="ciBody"></div>
566
+ </div>
567
+
568
+ <div class="results-hdr">
569
+ <div class="results-hdr-title">Analysis results</div>
570
+ <div class="elapsed-chip" id="elapsed"></div>
571
+ </div>
572
+
573
+ <!-- Cards ordered D3 β†’ D2 β†’ D1 (safety-first triage) -->
574
+ <div class="winner-grid" id="winnerGrid">
575
+ <div class="win-card d3" id="cardD3">
576
+ <div class="wc-lbl">D3 β€” Immediate Risk Β· XLM-RoBERTa</div>
577
+ <div class="wc-pred" id="wpC">β€”</div>
578
+ <div class="conf-row"><div class="conf-track"><div class="conf-fill" id="wbC"></div></div><div class="conf-pct" id="wcC">β€”</div></div>
579
+ <div class="wc-meta">98.1% Accuracy on D3</div>
580
+ </div>
581
+ <div class="win-card d2" id="cardD2">
582
+ <div class="wc-lbl">D2 β€” Depressed? Β· XLM-RoBERTa</div>
583
+ <div class="wc-pred" id="wpB">β€”</div>
584
+ <div class="conf-row"><div class="conf-track"><div class="conf-fill" id="wbB"></div></div><div class="conf-pct" id="wcB">β€”</div></div>
585
+ <div class="wc-meta">99.9% Accuracy on D2</div>
586
+ </div>
587
+ <div class="win-card d1" id="cardD1">
588
+ <div class="wc-lbl">D1 β€” Depression type Β· SVM</div>
589
+ <div class="wc-pred" id="wpA">β€”</div>
590
+ <div class="conf-row"><div class="conf-track"><div class="conf-fill" id="wbA"></div></div><div class="conf-pct" id="wcA">β€”</div></div>
591
+ <div class="wc-meta">92.4% Accuracy on D1</div>
592
+ </div>
593
+ </div>
594
+ </div>
595
+ </div>
596
+
597
+ <footer>
598
+ MindScan Β· NCI H9DAI Research Project 2026 Β· Academic Prototype Only<br>
599
+ Datasets: Zenodo 14233292 Β· Kaggle albertobellardini Β· Kaggle nikhileswarkomati<br>
600
+ Not for clinical use Β· MSc Artificial Intelligence coursework
601
+ </footer>
602
+
603
+ <script>
604
+ // ── METHODOLOGY PANEL SWITCH ──────────────────────────────────────
605
+ function showMethodDetail(idx){
606
+ document.querySelectorAll('.method-step').forEach((s,i)=>{
607
+ s.classList.toggle('active',i===idx);
608
+ });
609
+ document.querySelectorAll('.md-panel').forEach((p,i)=>{
610
+ p.classList.toggle('active',i===idx);
611
+ });
612
+ // re-trigger animation
613
+ const det=document.querySelector('.method-detail');
614
+ det.style.animation='none';
615
+ requestAnimationFrame(()=>{det.style.animation=''});
616
+ }
617
+
618
+ // ── COUNTER ANIMATION ─────────────────────────────────────────────
619
+ function animateCounters(){
620
+ document.querySelectorAll('.stat-num[data-target]').forEach(el=>{
621
+ const target=parseFloat(el.getAttribute('data-target'));
622
+ const dec=parseInt(el.getAttribute('data-dec')||'0');
623
+ const suffix=el.getAttribute('data-suffix')||'';
624
+ const duration=1400;
625
+ const start=performance.now();
626
+ function step(now){
627
+ const p=Math.min((now-start)/duration,1);
628
+ const ease=1-Math.pow(1-p,3);
629
+ const val=target*ease;
630
+ el.textContent=(dec>0?val.toFixed(dec):Math.floor(val))+suffix;
631
+ if(p<1)requestAnimationFrame(step);
632
+ }
633
+ requestAnimationFrame(step);
634
+ });
635
+ }
636
+ window.addEventListener('load',()=>setTimeout(animateCounters,300));
637
+
638
+ // ── SAMPLES ───────────────────────────────────────────────────────
639
+ const SAMPLES=[
640
+ "I been going through depression after having my baby. I didn't even realise it till recently. I always said I'm strong but your emotions change, your body changes. I need time.",
641
+ "The universe is sending me signals I should follow. I know it's the psychosis. I know it isn't real. But it feels so real every single day.",
642
+ "I've sorted everything out. Told my friends I love them. Finally feel at peace with my decision. I'm not sad anymore β€” just ready.",
643
+ "Had such a great day today! Went hiking with friends and saw the most amazing sunset. Feeling really grateful and happy to be alive."
644
+ ];
645
+ const ta=document.getElementById('textInput');
646
+ ta.addEventListener('input',()=>document.getElementById('charCount').textContent=ta.value.length+' characters');
647
+ function loadSample(i){ta.value=SAMPLES[i];document.getElementById('charCount').textContent=ta.value.length+' characters'}
648
+
649
+
650
+ // ── RUN ANALYSIS ──────────────────────────────────────────────────
651
+ async function runAnalysis(){
652
+ const text=ta.value.trim();if(!text)return;
653
+ const btn=document.getElementById('runBtn');
654
+ const sp=document.getElementById('spinner');
655
+ const bt=document.getElementById('btnTxt');
656
+ btn.disabled=true;sp.style.display='block';bt.textContent='Running models...';
657
+ document.getElementById('results').style.display='none';
658
+ try{
659
+ const r=await fetch('/predict',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({text})});
660
+ const d=await r.json();
661
+ if(!r.ok){alert('Error: '+(d.error||'failed'));return}
662
+ render(d);
663
+ }catch(e){
664
+ alert('Cannot reach the inference backend. The HuggingFace Space may be waking up β€” wait 30 seconds and try again.');
665
+ }finally{
666
+ btn.disabled=false;sp.style.display='none';bt.textContent='Run analysis';
667
+ }
668
+ }
669
+
670
+ function render(d){
671
+ const d1res = d.dataset1.models['SVM'];
672
+ const d2res = d.dataset2.models['XLM-RoBERTa'];
673
+ const d3res = d.dataset3.models['XLM-RoBERTa'];
674
+ const d1Label = d1res.label.toLowerCase();
675
+ const d2Label = d2res.label.toLowerCase();
676
+ const d3Label = d3res.label.toLowerCase();
677
+ const isRisk = d.risk_flag;
678
+ const notDepressed = d2Label.includes('not');
679
+ // "no depression" may come back as "no" from some model versions β€” none of the 5 disorder classes contain "no"
680
+ const hasDisorder = !d1Label.includes('no');
681
+ const isSuicide = d3Label.includes('suicide') && !d3Label.includes('non');
682
+
683
+ // ── Risk banner ───────────────────────────────────────────────
684
+ const rb=document.getElementById('riskBanner');
685
+ if(isRisk && isSuicide){
686
+ // XLM-RoBERTa (best model) confirms suicide risk
687
+ rb.className='risk-banner danger';
688
+ document.getElementById('rbIcon').textContent='⚠';
689
+ document.getElementById('rbTitle').textContent='High Suicide Risk Detected';
690
+ document.getElementById('rbBody').textContent='D3 flagged this text ('+d.suicide_votes+'). This is a research prototype β€” seek professional help if needed.';
691
+ }else if(isRisk && !isSuicide){
692
+ // Classical models flagged risk but XLM-RoBERTa (best model) disagrees
693
+ rb.className='risk-banner warn';
694
+ document.getElementById('rbIcon').textContent='⚑';
695
+ document.getElementById('rbTitle').textContent='Ensemble Conflict β€” Classical Models Flagged Risk';
696
+ document.getElementById('rbBody').textContent=d.suicide_votes+', but XLM-RoBERTa (best model, 98.1% accuracy) rates this as '+d3res.label+'. Classical TF-IDF models may be over-flagging depressive language.';
697
+ }else{
698
+ rb.className='risk-banner safe';
699
+ document.getElementById('rbIcon').textContent='βœ“';
700
+ document.getElementById('rbTitle').textContent='No immediate crisis risk detected';
701
+ document.getElementById('rbBody').textContent='D3 did not detect suicidal ideation markers. ('+d.suicide_votes+')';
702
+ }
703
+
704
+ // ── D3 card dominant state ────────────────────────────────────
705
+ const cardD3=document.getElementById('cardD3');
706
+ const d3lbl=document.getElementById('cardD3').querySelector('.wc-lbl');
707
+ // Disagreement: majority voted suicide but XLM-RoBERTa (best model) says non-suicide
708
+ const majorityVsWinner = isRisk && !isSuicide;
709
+ if(isRisk && isSuicide){
710
+ // Confirmed risk β€” XLM-RoBERTa agrees
711
+ cardD3.classList.add('risk-active');
712
+ d3lbl.textContent = 'D3 β€” Immediate Risk Β· XLM-RoBERTa';
713
+ }else{
714
+ cardD3.classList.remove('risk-active');
715
+ d3lbl.textContent = majorityVsWinner
716
+ ? 'D3 β€” '+d.suicide_votes+' (classical) Β· XLM-RoBERTa dissents'
717
+ : 'D3 β€” Immediate Risk Β· XLM-RoBERTa';
718
+ }
719
+
720
+ // ── Clinical Insight Alert ────────────────────────────────────
721
+ const ci=document.getElementById('clinicalInsight');
722
+ const ciTitle=document.getElementById('ciTitle');
723
+ const ciBody=document.getElementById('ciBody');
724
+
725
+ // Masked suicidality requires XLM-RoBERTa (best D3 model) to also flag suicide,
726
+ // not just the classical models β€” prevents false positives on plain depressive text
727
+ if(isRisk && isSuicide && notDepressed){
728
+ // Masked suicidality β€” confirmed by XLM-RoBERTa + majority vote
729
+ ciTitle.innerHTML='⚑ Clinical Insight β€” Masked Suicidality Pattern Detected';
730
+ ciBody.textContent='This text shows low depressive affect (D2: '+d2res.label+') but high intent resolution (D3: Suicide Risk). This is a clinically documented pre-crisis pattern where a person appears calm and resolved rather than distressed. A sequential pipeline gating D3 behind D2 would have missed this entirely β€” demonstrating the necessity of the parallel architecture.';
731
+ ci.style.display='block';
732
+ }else if(isRisk && majorityVsWinner && notDepressed){
733
+ // Classical models flag risk but XLM-RoBERTa disagrees β€” model disagreement
734
+ ciTitle.innerHTML='⚠ Clinical Insight β€” Ensemble Disagreement';
735
+ ciBody.textContent=d.suicide_votes+' (classical models), but XLM-RoBERTa rates this as '+d3res.label+' ('+pct(d3res.confidence)+' confidence). XLM-RoBERTa (98.1% accuracy) likely correct here β€” classical TF-IDF models can over-flag depressive language as suicide risk. Human review recommended.';
736
+ ci.style.display='block';
737
+ }else if(hasDisorder && notDepressed){
738
+ // Disorder type detected but no depressive affect β€” affect mismatch
739
+ ciTitle.innerHTML='⚠ Clinical Insight β€” Affect Mismatch Detected';
740
+ ciBody.textContent='D1 identifies '+d1res.label+' presentation, yet D2 finds no classic depressive affect. This is expected: D2 detects Twitter-style depressive language patterns, while psychotic, atypical, and bipolar presentations often do not match that affect profile. The patient is not presenting with classic depressive symptoms but the disorder classification remains clinically valid.';
741
+ ci.style.display='block';
742
+ }else{
743
+ ci.style.display='none';
744
+ }
745
+
746
+ document.getElementById('elapsed').textContent=d.processing_time_ms+'ms';
747
+
748
+ // Fixed winner per task: SVM for D1, XLM-RoBERTa for D2 and D3
749
+ setW('A', d1res);
750
+ setW('B', d2res);
751
+ setW('C', d3res);
752
+
753
+ document.getElementById('results').style.display='block';
754
+ document.getElementById('results').scrollIntoView({behavior:'smooth',block:'start'});
755
+ }
756
+
757
+ function setW(id,res){
758
+ document.getElementById('wp'+id).textContent=res.label;
759
+ document.getElementById('wc'+id).textContent=pct(res.confidence);
760
+ setTimeout(()=>document.getElementById('wb'+id).style.width=(res.confidence*100).toFixed(1)+'%',100);
761
+ }
762
+
763
+ function pct(v){return(v*100).toFixed(1)+'%'}
764
+ </script>
765
+ </body>
766
+ </html>