CaffeinatedCoding commited on
Commit
0ce5624
·
verified ·
1 Parent(s): edb155f

Upload folder using huggingface_hub

Browse files
frontend/app.js CHANGED
@@ -1,38 +1,35 @@
1
- // ── Config ──────────────────────────────────────────────────────
2
- const API_BASE = ""; // "" = same origin (HF Spaces). "http://localhost:8000" for local dev.
3
 
4
- // ── State ───────────────────────────────────────────────────────
5
- let sessions = []; // [{id, title, messages:[]}]
6
  let activeSessionId = null;
7
  let isLoading = false;
8
  let sidebarCollapsed = localStorage.getItem("sidebarCollapsed") === "true";
9
 
10
- // ── Init ────────────────────────────────────────────────────────
11
- const textarea = document.getElementById("query-input");
12
- const sendBtn = document.getElementById("send-btn");
13
- const msgsList = document.getElementById("messages-list");
14
 
15
- // Apply sidebar collapsed state on load
16
  if (sidebarCollapsed) {
17
  document.getElementById("sidebar").classList.add("collapsed");
18
  }
19
 
20
  textarea.addEventListener("input", () => {
21
  textarea.style.height = "auto";
22
- textarea.style.height = Math.min(textarea.scrollHeight, 140) + "px";
23
  });
24
 
25
  textarea.addEventListener("keydown", e => {
26
- if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); submitQuery(); }
 
 
 
27
  });
28
 
29
- // ── Screen switching ─────────────────────────────────────────────
30
  function showScreen(name) {
31
  document.querySelectorAll(".screen").forEach(s => s.classList.remove("active"));
32
  document.getElementById("screen-" + name).classList.add("active");
33
  }
34
 
35
- // ── Sidebar toggle ───────────────────────────────────────────────
36
  function toggleSidebar() {
37
  const sidebar = document.getElementById("sidebar");
38
  sidebarCollapsed = !sidebarCollapsed;
@@ -40,12 +37,9 @@ function toggleSidebar() {
40
  localStorage.setItem("sidebarCollapsed", sidebarCollapsed);
41
  }
42
 
43
- // ── Session management ───────────────────────────────────────────
44
  function createSession(firstQuery) {
45
  const id = Date.now();
46
- const title = firstQuery.length > 45
47
- ? firstQuery.substring(0, 45) + "…"
48
- : firstQuery;
49
  const session = { id, title, messages: [] };
50
  sessions.unshift(session);
51
  activeSessionId = id;
@@ -68,7 +62,7 @@ function switchSession(id) {
68
 
69
  function newChat() {
70
  activeSessionId = null;
71
- msgsList.innerHTML = "";
72
  showScreen("welcome");
73
  document.getElementById("topbar-title").textContent = "New Research Session";
74
  renderSessionsList();
@@ -78,15 +72,12 @@ function newChat() {
78
  function renderSessionsList() {
79
  const list = document.getElementById("sessions-list");
80
  if (sessions.length === 0) {
81
- list.innerHTML = '<div class="sessions-empty">No sessions yet</div>';
82
  return;
83
  }
84
  list.innerHTML = sessions.map(s => `
85
- <div class="session-item ${s.id === activeSessionId ? "active" : ""}"
86
- onclick="switchSession(${s.id})">
87
- <div class="session-dot"></div>
88
- <span class="session-label">${escHtml(s.title)}</span>
89
- <span class="session-count">${s.messages.filter(m => m.role === "ai").length}</span>
90
  </div>
91
  `).join("");
92
  }
@@ -94,7 +85,7 @@ function renderSessionsList() {
94
  function renderMessages() {
95
  const session = getActiveSession();
96
  if (!session) return;
97
- msgsList.innerHTML = "";
98
  session.messages.forEach(msg => {
99
  if (msg.role === "user") appendUserBubble(msg.text, false);
100
  else if (msg.role === "ai") appendAIBubble(msg.data, false);
@@ -103,12 +94,11 @@ function renderMessages() {
103
  scrollBottom();
104
  }
105
 
106
- // ── Submit ───────────────────────────────────────────────────────
107
  async function submitQuery() {
108
  const query = textarea.value.trim();
109
  if (!query || isLoading) return;
110
- if (query.length < 10) { showToast("Query too short — minimum 10 characters."); return; }
111
- if (query.length > 1000) { showToast("Query too long — maximum 1000 characters."); return; }
112
 
113
  if (!activeSessionId) {
114
  createSession(query);
@@ -117,10 +107,8 @@ async function submitQuery() {
117
  }
118
 
119
  getActiveSession().messages.push({ role: "user", text: query });
120
-
121
  textarea.value = "";
122
  textarea.style.height = "auto";
123
-
124
  appendUserBubble(query);
125
  const loaderId = appendLoader();
126
  setLoading(true);
@@ -139,17 +127,16 @@ async function submitQuery() {
139
  removeLoader(loaderId);
140
 
141
  if (!res.ok) {
142
- const msg = data.detail || "Something went wrong. Please try again.";
143
  getActiveSession().messages.push({ role: "error", text: msg });
144
  appendErrorBubble(msg);
145
  } else {
146
  getActiveSession().messages.push({ role: "ai", data });
147
  appendAIBubble(data);
148
  }
149
-
150
  } catch (err) {
151
  removeLoader(loaderId);
152
- const msg = "Could not reach the server. The Space may be waking up — try again in 30 seconds.";
153
  getActiveSession().messages.push({ role: "error", text: msg });
154
  appendErrorBubble(msg);
155
  }
@@ -164,56 +151,44 @@ function usesuggestion(el) {
164
  submitQuery();
165
  }
166
 
167
- // ── Bubble renderers ─────────────────────────────────────────────
168
  function appendUserBubble(text, scroll = true) {
169
  const div = document.createElement("div");
170
- div.className = "msg msg-user";
171
- div.innerHTML = `<div class="bubble-user">${escHtml(text)}</div>`;
172
- msgsList.appendChild(div);
173
  if (scroll) scrollBottom();
174
  }
175
 
176
  function appendAIBubble(data, scroll = true) {
177
  const verified = data.verification_status === true || data.verification_status === "verified";
178
  const badgeClass = verified ? "verified" : "unverified";
179
- const badgeText = verified ? "✓ Verified" : "⚠ Unverified";
180
-
181
- const truncNote = data.truncated
182
- ? `<div class="truncated-note">3 of 5 retrieved documents used — context limit reached.</div>`
183
- : "";
184
 
185
  const sourceCount = (data.sources || []).length;
186
- const sourcesBtn = sourceCount > 0
187
- ? `<button class="sources-btn" onclick='openSources(${escAttr(JSON.stringify(data.sources))})'>
188
- 📄 ${sourceCount} Source${sourceCount > 1 ? "s" : ""}
189
- </button>`
190
- : "";
191
 
192
- const latency = data.latency_ms
193
- ? `<span class="latency-label">${Math.round(data.latency_ms)}ms</span>`
194
- : "";
195
 
196
  const div = document.createElement("div");
197
- div.className = "msg msg-ai";
198
  div.innerHTML = `
199
- <div class="bubble-ai">
200
- <div class="bubble-answer">${formatAnswer(data.answer)}</div>
201
- ${truncNote}
202
  <div class="bubble-meta">
203
- <span class="verify-badge ${badgeClass}">${badgeText}</span>
204
  ${sourcesBtn}
205
  ${latency}
206
  </div>
207
  </div>`;
208
- msgsList.appendChild(div);
209
  if (scroll) scrollBottom();
210
  }
211
 
212
  function appendErrorBubble(text, scroll = true) {
213
  const div = document.createElement("div");
214
- div.className = "msg msg-ai";
215
- div.innerHTML = `<div class="bubble-error">⚠ ${escHtml(text)}</div>`;
216
- msgsList.appendChild(div);
217
  if (scroll) scrollBottom();
218
  }
219
 
@@ -221,13 +196,13 @@ function appendLoader() {
221
  const id = "loader-" + Date.now();
222
  const div = document.createElement("div");
223
  div.id = id;
224
- div.className = "msg msg-ai";
225
  div.innerHTML = `
226
- <div class="bubble-ai bubble-loading">
227
  <div class="dots"><span></span><span></span><span></span></div>
228
- Searching judgments…
229
  </div>`;
230
- msgsList.appendChild(div);
231
  scrollBottom();
232
  return id;
233
  }
@@ -237,256 +212,51 @@ function removeLoader(id) {
237
  if (el) el.remove();
238
  }
239
 
240
- // ── Sources panel ────────────────────────────────────────────────
241
- function openSources(sources) {
242
- const panel = document.getElementById("sources-panel");
243
- const overlay = document.getElementById("sources-overlay");
244
- const body = document.getElementById("sources-panel-body");
245
-
246
- body.innerHTML = sources.map((s, i) => {
247
- const meta = s.meta || {};
248
- const id = meta.judgment_id || "Unknown";
249
- const year = meta.year ? ` · ${meta.year}` : "";
250
- const excerpt = (s.text || "").trim().substring(0, 400);
251
- return `
252
- <div class="source-card">
253
- <div class="source-num">${i + 1}</div>
254
- <div class="source-id">${escHtml(id)}</div>
255
- <div class="source-year">Supreme Court of India${year}</div>
256
- <div class="source-excerpt">${escHtml(excerpt)}${s.text && s.text.length > 400 ? "…" : ""}</div>
257
- </div>`;
258
- }).join("");
259
-
260
- panel.classList.add("open");
261
- overlay.classList.add("open");
262
- requestAnimationFrame(() => { panel.style.transform = "translateX(0)"; });
263
- }
264
-
265
- function closeSourcesPanel() {
266
- const panel = document.getElementById("sources-panel");
267
- const overlay = document.getElementById("sources-overlay");
268
- panel.classList.remove("open");
269
- overlay.classList.remove("open");
270
- }
271
-
272
- // ── Helpers ──────────────────────────────────────────────────────
273
- function setLoading(state) {
274
- isLoading = state;
275
- sendBtn.disabled = state;
276
  const pill = document.getElementById("status-pill");
277
- const text = document.getElementById("status-text");
278
- if (state) {
279
  pill.classList.add("loading");
280
- text.textContent = "Searching";
281
  } else {
282
  pill.classList.remove("loading");
283
- text.textContent = "Ready";
284
  }
285
  }
286
 
287
  function scrollBottom() {
288
- const c = document.querySelector(".messages-container");
289
- if (c) c.scrollTop = c.scrollHeight;
290
- }
291
-
292
- function escHtml(str) {
293
- return String(str || "")
294
- .replace(/&/g, "&amp;")
295
- .replace(/</g, "&lt;")
296
- .replace(/>/g, "&gt;")
297
- .replace(/"/g, "&quot;");
298
  }
299
 
300
- function escAttr(str) {
301
- return String(str || "").replace(/'/g, "&#39;").replace(/"/g, "&quot;");
302
- }
303
-
304
- // ── Answer formatter ─────────────────────────────────────────────
305
  function formatAnswer(text) {
306
  if (!text) return "";
307
- text = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
308
-
309
- const lines = text.split('\n');
310
- let html = '';
311
- let inTable = false;
312
- let tableHtml = '';
313
- let inList = false;
314
- let listType = '';
315
-
316
- for (let i = 0; i < lines.length; i++) {
317
- const line = lines[i];
318
-
319
- // Table row
320
- if (line.trim().startsWith('|')) {
321
- if (line.match(/^\|[\s\-|]+\|$/)) continue; // skip separator rows
322
- if (!inTable) { tableHtml = '<table class="answer-table">'; inTable = true; }
323
- const cells = line.split('|').filter((c, idx, a) => idx > 0 && idx < a.length - 1);
324
- tableHtml += '<tr>' + cells.map(c => `<td>${inline(c.trim())}</td>`).join('') + '</tr>';
325
- continue;
326
- } else if (inTable) {
327
- html += tableHtml + '</table>';
328
- tableHtml = ''; inTable = false;
329
- }
330
-
331
- // Numbered list
332
- if (line.match(/^\d+\.\s+/)) {
333
- if (!inList || listType !== 'ol') {
334
- if (inList) html += `</${listType}>`;
335
- html += '<ol>'; inList = true; listType = 'ol';
336
- }
337
- html += `<li>${inline(line.replace(/^\d+\.\s+/, ''))}</li>`;
338
- continue;
339
- }
340
-
341
- // Bullet list
342
- if (line.match(/^[\*\-]\s+/)) {
343
- if (!inList || listType !== 'ul') {
344
- if (inList) html += `</${listType}>`;
345
- html += '<ul>'; inList = true; listType = 'ul';
346
- }
347
- html += `<li>${inline(line.replace(/^[\*\-]\s+/, ''))}</li>`;
348
- continue;
349
- }
350
-
351
- // Close list on blank line
352
- if (inList && line.trim() === '') {
353
- html += `</${listType}>`;
354
- inList = false; listType = '';
355
- }
356
-
357
- // Headers
358
- if (line.startsWith('### ')) { html += `<h3>${inline(line.slice(4))}</h3>`; continue; }
359
- if (line.startsWith('## ')) { html += `<h2>${inline(line.slice(3))}</h2>`; continue; }
360
- if (line.startsWith('# ')) { html += `<h1>${inline(line.slice(2))}</h1>`; continue; }
361
-
362
- // Blank line
363
- if (line.trim() === '') { html += '<br>'; continue; }
364
-
365
- // Normal paragraph line
366
- html += `<p>${inline(line)}</p>`;
367
- }
368
-
369
- // Close any unclosed tags
370
- if (inTable) html += tableHtml + '</table>';
371
- if (inList) html += `</${listType}>`;
372
-
373
- return html;
374
- }
375
-
376
- function inline(text) {
377
- return String(text || '')
378
- .replace(/&/g, '&amp;')
379
- .replace(/</g, '&lt;')
380
- .replace(/>/g, '&gt;')
381
- .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
382
- .replace(/\*(.+?)\*/g, '<em>$1</em>')
383
- .replace(/`(.+?)`/g, '<code>$1</code>');
384
  }
385
 
386
- function showToast(msg) {
387
- alert(msg);
 
388
  }
389
 
390
- // ── Analytics ────────────────────────────────────────────────────────
391
- async function showAnalytics() {
392
  showScreen("analytics");
393
  document.getElementById("topbar-title").textContent = "System Analytics";
394
- await loadAnalytics();
395
  }
396
 
397
- async function loadAnalytics() {
398
- try {
399
- const res = await fetch(`${API_BASE}/analytics`);
400
- const data = await res.json();
401
-
402
- if (data.total_queries === 0) {
403
- document.getElementById("stat-total").textContent = "0";
404
- document.getElementById("stat-verified").textContent = "";
405
- document.getElementById("stat-latency").textContent = "—";
406
- document.getElementById("stat-ood").textContent = "—";
407
- document.getElementById("stat-sources").textContent = "";
408
- document.getElementById("chart-stages").innerHTML = "<p class='no-data'>No queries yet. Start asking questions.</p>";
409
- document.getElementById("chart-entities").innerHTML = "<p class='no-data'>No entity data yet.</p>";
410
- document.getElementById("chart-latency").innerHTML = "<p class='no-data'>No latency data yet.</p>";
411
- return;
412
- }
413
-
414
- // Stat cards
415
- document.getElementById("stat-total").textContent = data.total_queries;
416
- document.getElementById("stat-verified").textContent = data.verified_ratio + "%";
417
- document.getElementById("stat-latency").textContent = data.avg_latency_ms + "ms";
418
- document.getElementById("stat-ood").textContent = data.out_of_domain_rate + "%";
419
- document.getElementById("stat-sources").textContent = data.avg_sources;
420
-
421
- // Stage distribution bar chart
422
- renderBarChart("chart-stages", data.stage_distribution);
423
-
424
- // Entity frequency bar chart
425
- renderBarChart("chart-entities", data.entity_type_frequency);
426
-
427
- // Latency sparkline
428
- renderSparkline("chart-latency", data.recent_latencies);
429
-
430
- } catch (err) {
431
- document.getElementById("chart-stages").innerHTML = "<p class='no-data'>Could not load analytics.</p>";
432
- }
433
  }
434
-
435
- function renderBarChart(containerId, data) {
436
- const container = document.getElementById(containerId);
437
- if (!data || Object.keys(data).length === 0) {
438
- container.innerHTML = "<p class='no-data'>No data yet.</p>";
439
- return;
440
- }
441
-
442
- const max = Math.max(...Object.values(data));
443
- const html = Object.entries(data)
444
- .sort((a, b) => b[1] - a[1])
445
- .map(([label, value]) => `
446
- <div class="bar-row">
447
- <span class="bar-label">${escHtml(label)}</span>
448
- <div class="bar-track">
449
- <div class="bar-fill" style="width: ${Math.round(value / max * 100)}%"></div>
450
- </div>
451
- <span class="bar-value">${value}</span>
452
- </div>
453
- `).join("");
454
-
455
- container.innerHTML = `<div class="bar-chart">${html}</div>`;
456
- }
457
-
458
- function renderSparkline(containerId, latencies) {
459
- const container = document.getElementById(containerId);
460
- if (!latencies || latencies.length === 0) {
461
- container.innerHTML = "<p class='no-data'>No data yet.</p>";
462
- return;
463
- }
464
-
465
- const max = Math.max(...latencies);
466
- const min = Math.min(...latencies);
467
- const range = max - min || 1;
468
- const height = 60;
469
- const width = 300;
470
- const step = width / (latencies.length - 1 || 1);
471
-
472
- const points = latencies.map((v, i) => {
473
- const x = i * step;
474
- const y = height - ((v - min) / range) * height;
475
- return `${x},${y}`;
476
- }).join(" ");
477
-
478
- container.innerHTML = `
479
- <svg viewBox="0 0 ${width} ${height}" class="sparkline">
480
- <polyline points="${points}" fill="none" stroke="var(--accent)" stroke-width="2"/>
481
- </svg>
482
- <div class="sparkline-range">
483
- <span>${Math.round(min)}ms min</span>
484
- <span>${Math.round(max)}ms max</span>
485
- </div>
486
- `;
487
- }
488
-
489
- function escHtml(text) {
490
- const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' };
491
- return String(text).replace(/[&<>"']/g, m => map[m]);
492
- }
 
1
+ const API_BASE = "";
 
2
 
3
+ let sessions = [];
 
4
  let activeSessionId = null;
5
  let isLoading = false;
6
  let sidebarCollapsed = localStorage.getItem("sidebarCollapsed") === "true";
7
 
8
+ const textarea = document.getElementById("query-input");
9
+ const sendBtn = document.getElementById("send-btn");
10
+ const messagesList = document.getElementById("messages-list");
 
11
 
 
12
  if (sidebarCollapsed) {
13
  document.getElementById("sidebar").classList.add("collapsed");
14
  }
15
 
16
  textarea.addEventListener("input", () => {
17
  textarea.style.height = "auto";
18
+ textarea.style.height = Math.min(textarea.scrollHeight, 120) + "px";
19
  });
20
 
21
  textarea.addEventListener("keydown", e => {
22
+ if (e.key === "Enter" && !e.shiftKey) {
23
+ e.preventDefault();
24
+ submitQuery();
25
+ }
26
  });
27
 
 
28
  function showScreen(name) {
29
  document.querySelectorAll(".screen").forEach(s => s.classList.remove("active"));
30
  document.getElementById("screen-" + name).classList.add("active");
31
  }
32
 
 
33
  function toggleSidebar() {
34
  const sidebar = document.getElementById("sidebar");
35
  sidebarCollapsed = !sidebarCollapsed;
 
37
  localStorage.setItem("sidebarCollapsed", sidebarCollapsed);
38
  }
39
 
 
40
  function createSession(firstQuery) {
41
  const id = Date.now();
42
+ const title = firstQuery.length > 40 ? firstQuery.substring(0, 40) + "…" : firstQuery;
 
 
43
  const session = { id, title, messages: [] };
44
  sessions.unshift(session);
45
  activeSessionId = id;
 
62
 
63
  function newChat() {
64
  activeSessionId = null;
65
+ messagesList.innerHTML = "";
66
  showScreen("welcome");
67
  document.getElementById("topbar-title").textContent = "New Research Session";
68
  renderSessionsList();
 
72
  function renderSessionsList() {
73
  const list = document.getElementById("sessions-list");
74
  if (sessions.length === 0) {
75
+ list.innerHTML = '<div class="sessions-empty">No sessions</div>';
76
  return;
77
  }
78
  list.innerHTML = sessions.map(s => `
79
+ <div class="session-item ${s.id === activeSessionId ? "active" : ""}" onclick="switchSession(${s.id})">
80
+ ${escHtml(s.title)}
 
 
 
81
  </div>
82
  `).join("");
83
  }
 
85
  function renderMessages() {
86
  const session = getActiveSession();
87
  if (!session) return;
88
+ messagesList.innerHTML = "";
89
  session.messages.forEach(msg => {
90
  if (msg.role === "user") appendUserBubble(msg.text, false);
91
  else if (msg.role === "ai") appendAIBubble(msg.data, false);
 
94
  scrollBottom();
95
  }
96
 
 
97
  async function submitQuery() {
98
  const query = textarea.value.trim();
99
  if (!query || isLoading) return;
100
+ if (query.length < 10) { alert("Query too short"); return; }
101
+ if (query.length > 1000) { alert("Query too long"); return; }
102
 
103
  if (!activeSessionId) {
104
  createSession(query);
 
107
  }
108
 
109
  getActiveSession().messages.push({ role: "user", text: query });
 
110
  textarea.value = "";
111
  textarea.style.height = "auto";
 
112
  appendUserBubble(query);
113
  const loaderId = appendLoader();
114
  setLoading(true);
 
127
  removeLoader(loaderId);
128
 
129
  if (!res.ok) {
130
+ const msg = data.detail || "Error: please try again";
131
  getActiveSession().messages.push({ role: "error", text: msg });
132
  appendErrorBubble(msg);
133
  } else {
134
  getActiveSession().messages.push({ role: "ai", data });
135
  appendAIBubble(data);
136
  }
 
137
  } catch (err) {
138
  removeLoader(loaderId);
139
+ const msg = "Could not reach server";
140
  getActiveSession().messages.push({ role: "error", text: msg });
141
  appendErrorBubble(msg);
142
  }
 
151
  submitQuery();
152
  }
153
 
 
154
  function appendUserBubble(text, scroll = true) {
155
  const div = document.createElement("div");
156
+ div.className = "message user";
157
+ div.innerHTML = `<div class="bubble user">${escHtml(text)}</div>`;
158
+ messagesList.appendChild(div);
159
  if (scroll) scrollBottom();
160
  }
161
 
162
  function appendAIBubble(data, scroll = true) {
163
  const verified = data.verification_status === true || data.verification_status === "verified";
164
  const badgeClass = verified ? "verified" : "unverified";
165
+ const badgeText = verified ? "✓ Verified" : "⚠ Unverified";
 
 
 
 
166
 
167
  const sourceCount = (data.sources || []).length;
168
+ const sourcesBtn = sourceCount > 0 ? `<button class="sources-btn">📄 ${sourceCount} Source${sourceCount > 1 ? "s" : ""}</button>` : "";
 
 
 
 
169
 
170
+ const latency = data.latency_ms ? `<span style="margin-left: auto; font-size: 10px; color: var(--text-3);">${Math.round(data.latency_ms)}ms</span>` : "";
 
 
171
 
172
  const div = document.createElement("div");
173
+ div.className = "message ai";
174
  div.innerHTML = `
175
+ <div class="bubble ai">
176
+ <div>${formatAnswer(data.answer)}</div>
 
177
  <div class="bubble-meta">
178
+ <span class="badge ${badgeClass}">${badgeText}</span>
179
  ${sourcesBtn}
180
  ${latency}
181
  </div>
182
  </div>`;
183
+ messagesList.appendChild(div);
184
  if (scroll) scrollBottom();
185
  }
186
 
187
  function appendErrorBubble(text, scroll = true) {
188
  const div = document.createElement("div");
189
+ div.className = "message ai";
190
+ div.innerHTML = `<div class="bubble ai" style="border-left-color: var(--red);">⚠ ${escHtml(text)}</div>`;
191
+ messagesList.appendChild(div);
192
  if (scroll) scrollBottom();
193
  }
194
 
 
196
  const id = "loader-" + Date.now();
197
  const div = document.createElement("div");
198
  div.id = id;
199
+ div.className = "message ai";
200
  div.innerHTML = `
201
+ <div class="bubble ai loading">
202
  <div class="dots"><span></span><span></span><span></span></div>
203
+ Searching...
204
  </div>`;
205
+ messagesList.appendChild(div);
206
  scrollBottom();
207
  return id;
208
  }
 
212
  if (el) el.remove();
213
  }
214
 
215
+ function setLoading(loading) {
216
+ isLoading = loading;
217
+ sendBtn.disabled = loading;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  const pill = document.getElementById("status-pill");
219
+ if (loading) {
 
220
  pill.classList.add("loading");
221
+ document.getElementById("status-text").textContent = "Searching...";
222
  } else {
223
  pill.classList.remove("loading");
224
+ document.getElementById("status-text").textContent = "Ready";
225
  }
226
  }
227
 
228
  function scrollBottom() {
229
+ setTimeout(() => {
230
+ const container = document.querySelector(".chat-area");
231
+ if (container) container.scrollTop = container.scrollHeight;
232
+ }, 0);
 
 
 
 
 
 
233
  }
234
 
 
 
 
 
 
235
  function formatAnswer(text) {
236
  if (!text) return "";
237
+ return text.replace(/\n/g, "<br>");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  }
239
 
240
+ function escHtml(text) {
241
+ const map = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#039;" };
242
+ return text.replace(/[&<>"']/g, m => map[m]);
243
  }
244
 
245
+ function showAnalytics() {
 
246
  showScreen("analytics");
247
  document.getElementById("topbar-title").textContent = "System Analytics";
248
+ loadAnalytics();
249
  }
250
 
251
+ function loadAnalytics() {
252
+ fetch(`${API_BASE}/analytics`)
253
+ .then(r => r.json())
254
+ .then(data => {
255
+ document.getElementById("stat-total").textContent = data.total_queries || "—";
256
+ document.getElementById("stat-verified").textContent = (data.verified_rate || 0).toFixed(1) + "%";
257
+ document.getElementById("stat-latency").textContent = Math.round(data.avg_latency_ms || 0) + "ms";
258
+ document.getElementById("stat-ood").textContent = (data.ood_rate || 0).toFixed(1) + "%";
259
+ document.getElementById("stat-sources").textContent = (data.avg_sources || 0).toFixed(1);
260
+ })
261
+ .catch(err => console.error("Analytics load failed:", err));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/app_old.js ADDED
@@ -0,0 +1,492 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ── Config ──────────────────────────────────────────────────────
2
+ const API_BASE = ""; // "" = same origin (HF Spaces). "http://localhost:8000" for local dev.
3
+
4
+ // ── State ───────────────────────────────────────────────────────
5
+ let sessions = []; // [{id, title, messages:[]}]
6
+ let activeSessionId = null;
7
+ let isLoading = false;
8
+ let sidebarCollapsed = localStorage.getItem("sidebarCollapsed") === "true";
9
+
10
+ // ── Init ────────────────────────────────────────────────────────
11
+ const textarea = document.getElementById("query-input");
12
+ const sendBtn = document.getElementById("send-btn");
13
+ const msgsList = document.getElementById("messages-list");
14
+
15
+ // Apply sidebar collapsed state on load
16
+ if (sidebarCollapsed) {
17
+ document.getElementById("sidebar").classList.add("collapsed");
18
+ }
19
+
20
+ textarea.addEventListener("input", () => {
21
+ textarea.style.height = "auto";
22
+ textarea.style.height = Math.min(textarea.scrollHeight, 140) + "px";
23
+ });
24
+
25
+ textarea.addEventListener("keydown", e => {
26
+ if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); submitQuery(); }
27
+ });
28
+
29
+ // ── Screen switching ─────────────────────────────────────────────
30
+ function showScreen(name) {
31
+ document.querySelectorAll(".screen").forEach(s => s.classList.remove("active"));
32
+ document.getElementById("screen-" + name).classList.add("active");
33
+ }
34
+
35
+ // ── Sidebar toggle ───────────────────────────────────────────────
36
+ function toggleSidebar() {
37
+ const sidebar = document.getElementById("sidebar");
38
+ sidebarCollapsed = !sidebarCollapsed;
39
+ sidebar.classList.toggle("collapsed");
40
+ localStorage.setItem("sidebarCollapsed", sidebarCollapsed);
41
+ }
42
+
43
+ // ── Session management ───────────────────────────────────────────
44
+ function createSession(firstQuery) {
45
+ const id = Date.now();
46
+ const title = firstQuery.length > 45
47
+ ? firstQuery.substring(0, 45) + "…"
48
+ : firstQuery;
49
+ const session = { id, title, messages: [] };
50
+ sessions.unshift(session);
51
+ activeSessionId = id;
52
+ renderSessionsList();
53
+ return session;
54
+ }
55
+
56
+ function getActiveSession() {
57
+ return sessions.find(s => s.id === activeSessionId);
58
+ }
59
+
60
+ function switchSession(id) {
61
+ activeSessionId = id;
62
+ renderSessionsList();
63
+ renderMessages();
64
+ showScreen("chat");
65
+ const session = getActiveSession();
66
+ document.getElementById("topbar-title").textContent = session.title;
67
+ }
68
+
69
+ function newChat() {
70
+ activeSessionId = null;
71
+ msgsList.innerHTML = "";
72
+ showScreen("welcome");
73
+ document.getElementById("topbar-title").textContent = "New Research Session";
74
+ renderSessionsList();
75
+ textarea.focus();
76
+ }
77
+
78
+ function renderSessionsList() {
79
+ const list = document.getElementById("sessions-list");
80
+ if (sessions.length === 0) {
81
+ list.innerHTML = '<div class="sessions-empty">No sessions yet</div>';
82
+ return;
83
+ }
84
+ list.innerHTML = sessions.map(s => `
85
+ <div class="session-item ${s.id === activeSessionId ? "active" : ""}"
86
+ onclick="switchSession(${s.id})">
87
+ <div class="session-dot"></div>
88
+ <span class="session-label">${escHtml(s.title)}</span>
89
+ <span class="session-count">${s.messages.filter(m => m.role === "ai").length}</span>
90
+ </div>
91
+ `).join("");
92
+ }
93
+
94
+ function renderMessages() {
95
+ const session = getActiveSession();
96
+ if (!session) return;
97
+ msgsList.innerHTML = "";
98
+ session.messages.forEach(msg => {
99
+ if (msg.role === "user") appendUserBubble(msg.text, false);
100
+ else if (msg.role === "ai") appendAIBubble(msg.data, false);
101
+ else if (msg.role === "error") appendErrorBubble(msg.text, false);
102
+ });
103
+ scrollBottom();
104
+ }
105
+
106
+ // ── Submit ───────────────────────────────────────────────────────
107
+ async function submitQuery() {
108
+ const query = textarea.value.trim();
109
+ if (!query || isLoading) return;
110
+ if (query.length < 10) { showToast("Query too short — minimum 10 characters."); return; }
111
+ if (query.length > 1000) { showToast("Query too long — maximum 1000 characters."); return; }
112
+
113
+ if (!activeSessionId) {
114
+ createSession(query);
115
+ showScreen("chat");
116
+ document.getElementById("topbar-title").textContent = getActiveSession().title;
117
+ }
118
+
119
+ getActiveSession().messages.push({ role: "user", text: query });
120
+
121
+ textarea.value = "";
122
+ textarea.style.height = "auto";
123
+
124
+ appendUserBubble(query);
125
+ const loaderId = appendLoader();
126
+ setLoading(true);
127
+
128
+ try {
129
+ const res = await fetch(`${API_BASE}/query`, {
130
+ method: "POST",
131
+ headers: { "Content-Type": "application/json" },
132
+ body: JSON.stringify({
133
+ query,
134
+ session_id: activeSessionId ? String(activeSessionId) : "default"
135
+ })
136
+ });
137
+
138
+ const data = await res.json();
139
+ removeLoader(loaderId);
140
+
141
+ if (!res.ok) {
142
+ const msg = data.detail || "Something went wrong. Please try again.";
143
+ getActiveSession().messages.push({ role: "error", text: msg });
144
+ appendErrorBubble(msg);
145
+ } else {
146
+ getActiveSession().messages.push({ role: "ai", data });
147
+ appendAIBubble(data);
148
+ }
149
+
150
+ } catch (err) {
151
+ removeLoader(loaderId);
152
+ const msg = "Could not reach the server. The Space may be waking up — try again in 30 seconds.";
153
+ getActiveSession().messages.push({ role: "error", text: msg });
154
+ appendErrorBubble(msg);
155
+ }
156
+
157
+ setLoading(false);
158
+ scrollBottom();
159
+ }
160
+
161
+ function usesuggestion(el) {
162
+ textarea.value = el.textContent;
163
+ textarea.dispatchEvent(new Event("input"));
164
+ submitQuery();
165
+ }
166
+
167
+ // ── Bubble renderers ─────────────────────────────────────────────
168
+ function appendUserBubble(text, scroll = true) {
169
+ const div = document.createElement("div");
170
+ div.className = "msg msg-user";
171
+ div.innerHTML = `<div class="bubble-user">${escHtml(text)}</div>`;
172
+ msgsList.appendChild(div);
173
+ if (scroll) scrollBottom();
174
+ }
175
+
176
+ function appendAIBubble(data, scroll = true) {
177
+ const verified = data.verification_status === true || data.verification_status === "verified";
178
+ const badgeClass = verified ? "verified" : "unverified";
179
+ const badgeText = verified ? "✓ Verified" : "⚠ Unverified";
180
+
181
+ const truncNote = data.truncated
182
+ ? `<div class="truncated-note">3 of 5 retrieved documents used — context limit reached.</div>`
183
+ : "";
184
+
185
+ const sourceCount = (data.sources || []).length;
186
+ const sourcesBtn = sourceCount > 0
187
+ ? `<button class="sources-btn" onclick='openSources(${escAttr(JSON.stringify(data.sources))})'>
188
+ 📄 ${sourceCount} Source${sourceCount > 1 ? "s" : ""}
189
+ </button>`
190
+ : "";
191
+
192
+ const latency = data.latency_ms
193
+ ? `<span class="latency-label">${Math.round(data.latency_ms)}ms</span>`
194
+ : "";
195
+
196
+ const div = document.createElement("div");
197
+ div.className = "msg msg-ai";
198
+ div.innerHTML = `
199
+ <div class="bubble-ai">
200
+ <div class="bubble-answer">${formatAnswer(data.answer)}</div>
201
+ ${truncNote}
202
+ <div class="bubble-meta">
203
+ <span class="verify-badge ${badgeClass}">${badgeText}</span>
204
+ ${sourcesBtn}
205
+ ${latency}
206
+ </div>
207
+ </div>`;
208
+ msgsList.appendChild(div);
209
+ if (scroll) scrollBottom();
210
+ }
211
+
212
+ function appendErrorBubble(text, scroll = true) {
213
+ const div = document.createElement("div");
214
+ div.className = "msg msg-ai";
215
+ div.innerHTML = `<div class="bubble-error">⚠ ${escHtml(text)}</div>`;
216
+ msgsList.appendChild(div);
217
+ if (scroll) scrollBottom();
218
+ }
219
+
220
+ function appendLoader() {
221
+ const id = "loader-" + Date.now();
222
+ const div = document.createElement("div");
223
+ div.id = id;
224
+ div.className = "msg msg-ai";
225
+ div.innerHTML = `
226
+ <div class="bubble-ai bubble-loading">
227
+ <div class="dots"><span></span><span></span><span></span></div>
228
+ Searching judgments…
229
+ </div>`;
230
+ msgsList.appendChild(div);
231
+ scrollBottom();
232
+ return id;
233
+ }
234
+
235
+ function removeLoader(id) {
236
+ const el = document.getElementById(id);
237
+ if (el) el.remove();
238
+ }
239
+
240
+ // ── Sources panel ────────────────────────────────────────────────
241
+ function openSources(sources) {
242
+ const panel = document.getElementById("sources-panel");
243
+ const overlay = document.getElementById("sources-overlay");
244
+ const body = document.getElementById("sources-panel-body");
245
+
246
+ body.innerHTML = sources.map((s, i) => {
247
+ const meta = s.meta || {};
248
+ const id = meta.judgment_id || "Unknown";
249
+ const year = meta.year ? ` · ${meta.year}` : "";
250
+ const excerpt = (s.text || "").trim().substring(0, 400);
251
+ return `
252
+ <div class="source-card">
253
+ <div class="source-num">${i + 1}</div>
254
+ <div class="source-id">${escHtml(id)}</div>
255
+ <div class="source-year">Supreme Court of India${year}</div>
256
+ <div class="source-excerpt">${escHtml(excerpt)}${s.text && s.text.length > 400 ? "…" : ""}</div>
257
+ </div>`;
258
+ }).join("");
259
+
260
+ panel.classList.add("open");
261
+ overlay.classList.add("open");
262
+ requestAnimationFrame(() => { panel.style.transform = "translateX(0)"; });
263
+ }
264
+
265
+ function closeSourcesPanel() {
266
+ const panel = document.getElementById("sources-panel");
267
+ const overlay = document.getElementById("sources-overlay");
268
+ panel.classList.remove("open");
269
+ overlay.classList.remove("open");
270
+ }
271
+
272
+ // ── Helpers ──────────────────────────────────────────────────────
273
+ function setLoading(state) {
274
+ isLoading = state;
275
+ sendBtn.disabled = state;
276
+ const pill = document.getElementById("status-pill");
277
+ const text = document.getElementById("status-text");
278
+ if (state) {
279
+ pill.classList.add("loading");
280
+ text.textContent = "Searching…";
281
+ } else {
282
+ pill.classList.remove("loading");
283
+ text.textContent = "Ready";
284
+ }
285
+ }
286
+
287
+ function scrollBottom() {
288
+ const c = document.querySelector(".messages-container");
289
+ if (c) c.scrollTop = c.scrollHeight;
290
+ }
291
+
292
+ function escHtml(str) {
293
+ return String(str || "")
294
+ .replace(/&/g, "&amp;")
295
+ .replace(/</g, "&lt;")
296
+ .replace(/>/g, "&gt;")
297
+ .replace(/"/g, "&quot;");
298
+ }
299
+
300
+ function escAttr(str) {
301
+ return String(str || "").replace(/'/g, "&#39;").replace(/"/g, "&quot;");
302
+ }
303
+
304
+ // ── Answer formatter ─────────────────────────────────────────────
305
+ function formatAnswer(text) {
306
+ if (!text) return "";
307
+ text = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
308
+
309
+ const lines = text.split('\n');
310
+ let html = '';
311
+ let inTable = false;
312
+ let tableHtml = '';
313
+ let inList = false;
314
+ let listType = '';
315
+
316
+ for (let i = 0; i < lines.length; i++) {
317
+ const line = lines[i];
318
+
319
+ // Table row
320
+ if (line.trim().startsWith('|')) {
321
+ if (line.match(/^\|[\s\-|]+\|$/)) continue; // skip separator rows
322
+ if (!inTable) { tableHtml = '<table class="answer-table">'; inTable = true; }
323
+ const cells = line.split('|').filter((c, idx, a) => idx > 0 && idx < a.length - 1);
324
+ tableHtml += '<tr>' + cells.map(c => `<td>${inline(c.trim())}</td>`).join('') + '</tr>';
325
+ continue;
326
+ } else if (inTable) {
327
+ html += tableHtml + '</table>';
328
+ tableHtml = ''; inTable = false;
329
+ }
330
+
331
+ // Numbered list
332
+ if (line.match(/^\d+\.\s+/)) {
333
+ if (!inList || listType !== 'ol') {
334
+ if (inList) html += `</${listType}>`;
335
+ html += '<ol>'; inList = true; listType = 'ol';
336
+ }
337
+ html += `<li>${inline(line.replace(/^\d+\.\s+/, ''))}</li>`;
338
+ continue;
339
+ }
340
+
341
+ // Bullet list
342
+ if (line.match(/^[\*\-]\s+/)) {
343
+ if (!inList || listType !== 'ul') {
344
+ if (inList) html += `</${listType}>`;
345
+ html += '<ul>'; inList = true; listType = 'ul';
346
+ }
347
+ html += `<li>${inline(line.replace(/^[\*\-]\s+/, ''))}</li>`;
348
+ continue;
349
+ }
350
+
351
+ // Close list on blank line
352
+ if (inList && line.trim() === '') {
353
+ html += `</${listType}>`;
354
+ inList = false; listType = '';
355
+ }
356
+
357
+ // Headers
358
+ if (line.startsWith('### ')) { html += `<h3>${inline(line.slice(4))}</h3>`; continue; }
359
+ if (line.startsWith('## ')) { html += `<h2>${inline(line.slice(3))}</h2>`; continue; }
360
+ if (line.startsWith('# ')) { html += `<h1>${inline(line.slice(2))}</h1>`; continue; }
361
+
362
+ // Blank line
363
+ if (line.trim() === '') { html += '<br>'; continue; }
364
+
365
+ // Normal paragraph line
366
+ html += `<p>${inline(line)}</p>`;
367
+ }
368
+
369
+ // Close any unclosed tags
370
+ if (inTable) html += tableHtml + '</table>';
371
+ if (inList) html += `</${listType}>`;
372
+
373
+ return html;
374
+ }
375
+
376
+ function inline(text) {
377
+ return String(text || '')
378
+ .replace(/&/g, '&amp;')
379
+ .replace(/</g, '&lt;')
380
+ .replace(/>/g, '&gt;')
381
+ .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
382
+ .replace(/\*(.+?)\*/g, '<em>$1</em>')
383
+ .replace(/`(.+?)`/g, '<code>$1</code>');
384
+ }
385
+
386
+ function showToast(msg) {
387
+ alert(msg);
388
+ }
389
+
390
+ // ── Analytics ────────────────────────────────────────────────────────
391
+ async function showAnalytics() {
392
+ showScreen("analytics");
393
+ document.getElementById("topbar-title").textContent = "System Analytics";
394
+ await loadAnalytics();
395
+ }
396
+
397
+ async function loadAnalytics() {
398
+ try {
399
+ const res = await fetch(`${API_BASE}/analytics`);
400
+ const data = await res.json();
401
+
402
+ if (data.total_queries === 0) {
403
+ document.getElementById("stat-total").textContent = "0";
404
+ document.getElementById("stat-verified").textContent = "—";
405
+ document.getElementById("stat-latency").textContent = "—";
406
+ document.getElementById("stat-ood").textContent = "—";
407
+ document.getElementById("stat-sources").textContent = "—";
408
+ document.getElementById("chart-stages").innerHTML = "<p class='no-data'>No queries yet. Start asking questions.</p>";
409
+ document.getElementById("chart-entities").innerHTML = "<p class='no-data'>No entity data yet.</p>";
410
+ document.getElementById("chart-latency").innerHTML = "<p class='no-data'>No latency data yet.</p>";
411
+ return;
412
+ }
413
+
414
+ // Stat cards
415
+ document.getElementById("stat-total").textContent = data.total_queries;
416
+ document.getElementById("stat-verified").textContent = data.verified_ratio + "%";
417
+ document.getElementById("stat-latency").textContent = data.avg_latency_ms + "ms";
418
+ document.getElementById("stat-ood").textContent = data.out_of_domain_rate + "%";
419
+ document.getElementById("stat-sources").textContent = data.avg_sources;
420
+
421
+ // Stage distribution bar chart
422
+ renderBarChart("chart-stages", data.stage_distribution);
423
+
424
+ // Entity frequency bar chart
425
+ renderBarChart("chart-entities", data.entity_type_frequency);
426
+
427
+ // Latency sparkline
428
+ renderSparkline("chart-latency", data.recent_latencies);
429
+
430
+ } catch (err) {
431
+ document.getElementById("chart-stages").innerHTML = "<p class='no-data'>Could not load analytics.</p>";
432
+ }
433
+ }
434
+
435
+ function renderBarChart(containerId, data) {
436
+ const container = document.getElementById(containerId);
437
+ if (!data || Object.keys(data).length === 0) {
438
+ container.innerHTML = "<p class='no-data'>No data yet.</p>";
439
+ return;
440
+ }
441
+
442
+ const max = Math.max(...Object.values(data));
443
+ const html = Object.entries(data)
444
+ .sort((a, b) => b[1] - a[1])
445
+ .map(([label, value]) => `
446
+ <div class="bar-row">
447
+ <span class="bar-label">${escHtml(label)}</span>
448
+ <div class="bar-track">
449
+ <div class="bar-fill" style="width: ${Math.round(value / max * 100)}%"></div>
450
+ </div>
451
+ <span class="bar-value">${value}</span>
452
+ </div>
453
+ `).join("");
454
+
455
+ container.innerHTML = `<div class="bar-chart">${html}</div>`;
456
+ }
457
+
458
+ function renderSparkline(containerId, latencies) {
459
+ const container = document.getElementById(containerId);
460
+ if (!latencies || latencies.length === 0) {
461
+ container.innerHTML = "<p class='no-data'>No data yet.</p>";
462
+ return;
463
+ }
464
+
465
+ const max = Math.max(...latencies);
466
+ const min = Math.min(...latencies);
467
+ const range = max - min || 1;
468
+ const height = 60;
469
+ const width = 300;
470
+ const step = width / (latencies.length - 1 || 1);
471
+
472
+ const points = latencies.map((v, i) => {
473
+ const x = i * step;
474
+ const y = height - ((v - min) / range) * height;
475
+ return `${x},${y}`;
476
+ }).join(" ");
477
+
478
+ container.innerHTML = `
479
+ <svg viewBox="0 0 ${width} ${height}" class="sparkline">
480
+ <polyline points="${points}" fill="none" stroke="var(--accent)" stroke-width="2"/>
481
+ </svg>
482
+ <div class="sparkline-range">
483
+ <span>${Math.round(min)}ms min</span>
484
+ <span>${Math.round(max)}ms max</span>
485
+ </div>
486
+ `;
487
+ }
488
+
489
+ function escHtml(text) {
490
+ const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' };
491
+ return String(text).replace(/[&<>"']/g, m => map[m]);
492
+ }
frontend/index.html CHANGED
@@ -1,118 +1,84 @@
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>NyayaSetu — Indian Legal Research</title>
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
  <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;0,700;1,400&family=DM+Sans:wght@300;400;500;600&display=swap" rel="stylesheet">
9
- <link rel="stylesheet" href="/static/style.css">
10
  </head>
11
  <body>
12
-
13
- <div class="app-layout">
14
-
15
- <!-- ── SIDEBAR ── -->
16
  <aside class="sidebar" id="sidebar">
17
- <div class="sidebar-brand">
18
- <div class="brand-mark">⚖</div>
19
- <div class="brand-text">
20
- <span class="brand-name">NyayaSetu</span>
21
- <span class="brand-sub">Legal Research</span>
 
 
22
  </div>
23
- <button class="sidebar-toggle" onclick="toggleSidebar()" title="Toggle sidebar">
24
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
25
- <line x1="3" y1="6" x2="21" y2="6"></line>
26
- <line x1="3" y1="12" x2="21" y2="12"></line>
27
- <line x1="3" y1="18" x2="21" y2="18"></line>
28
- </svg>
29
- </button>
30
  </div>
31
 
32
- <button class="new-chat-btn" onclick="newChat()">
33
- <span class="new-chat-icon"></span>
34
- New Research Session
35
- </button>
36
-
37
- <button class="analytics-btn" onclick="showAnalytics()">
38
- <span class="analytics-icon">📊</span>
39
- System Analytics
40
- </button>
41
 
42
- <button class="analytics-btn" onclick="window.location.href='/court/ui'">
43
- <span class="analytics-icon">⚖️</span>
44
- Moot Court
45
- </button>
46
-
47
- <div class="sidebar-section-label">SESSIONS</div>
48
- <div id="sessions-list" class="sessions-list">
49
- <div class="sessions-empty">No sessions yet</div>
50
  </div>
51
 
52
  <div class="sidebar-footer">
53
- <div class="footer-disclaimer">
54
- <span class="disclaimer-icon"></span>
55
- <span>Not legal advice. Consult a qualified advocate.</span>
56
- </div>
57
- <div class="footer-meta">
58
- Supreme Court of India · 1950–2024<br>
59
- 443,598 judgment chunks indexed
60
- </div>
61
  </div>
62
  </aside>
63
 
64
- <!-- ── MAIN ── -->
65
- <div class="main-wrapper">
66
-
67
- <!-- Top bar -->
68
  <header class="topbar">
69
- <div class="topbar-left">
70
- <span class="topbar-title" id="topbar-title">New Research Session</span>
71
- </div>
72
- <div class="topbar-right">
73
- <div class="status-pill" id="status-pill">
74
- <span class="status-dot"></span>
75
- <span id="status-text">Ready</span>
76
- </div>
77
  </div>
78
  </header>
79
 
80
- <!-- ── WELCOME SCREEN ── -->
81
- <div id="screen-welcome" class="screen screen-welcome active">
82
- <div class="welcome-inner">
83
- <div class="welcome-emblem">⚖</div>
84
- <h1 class="welcome-heading">Ask Indian Law</h1>
85
- <p class="welcome-body">
86
- Search across 26,688 Supreme Court judgments.<br>
87
- Every answer is cited. Every source is traceable.
88
- </p>
89
- <div class="suggestion-grid">
90
- <button class="suggestion-pill" onclick="usesuggestion(this)">What are fundamental rights under the Constitution?</button>
91
- <button class="suggestion-pill" onclick="usesuggestion(this)">What is the right to privacy under Article 21?</button>
92
- <button class="suggestion-pill" onclick="usesuggestion(this)">How does the Supreme Court define bail in non-bailable offences?</button>
93
- <button class="suggestion-pill" onclick="usesuggestion(this)">What is the basic structure doctrine?</button>
94
- <button class="suggestion-pill" onclick="usesuggestion(this)">What does Article 15 prohibit?</button>
95
- <button class="suggestion-pill" onclick="usesuggestion(this)">How is freedom of speech limited under Article 19?</button>
96
  </div>
97
  </div>
98
- </div>
99
 
100
- <!-- ── CHAT SCREEN ── -->
101
- <div id="screen-chat" class="screen screen-chat">
102
- <div id="messages-container" class="messages-container">
103
- <div id="messages-list" class="messages-list"></div>
104
  </div>
105
- </div>
106
-
107
- <!-- ── ANALYTICS SCREEN ── -->
108
- <div id="screen-analytics" class="screen screen-analytics">
109
- <div class="analytics-inner">
110
- <div class="analytics-header">
111
- <h2>System Analytics</h2>
112
- <p>Live metrics from inference logs</p>
113
- </div>
114
-
115
- <div class="analytics-grid">
116
  <div class="stat-card">
117
  <div class="stat-value" id="stat-total">—</div>
118
  <div class="stat-label">Total Queries</div>
@@ -127,69 +93,27 @@
127
  </div>
128
  <div class="stat-card">
129
  <div class="stat-value" id="stat-ood">—</div>
130
- <div class="stat-label">Out-of-Domain Rate</div>
131
  </div>
132
  <div class="stat-card">
133
  <div class="stat-value" id="stat-sources">—</div>
134
- <div class="stat-label">Avg Sources / Query</div>
135
  </div>
136
  </div>
137
-
138
- <div class="analytics-charts">
139
- <div class="chart-card">
140
- <h3>Stage Distribution</h3>
141
- <div id="chart-stages" class="chart-container"></div>
142
- </div>
143
- <div class="chart-card">
144
- <h3>Entity Types Extracted</h3>
145
- <div id="chart-entities" class="chart-container"></div>
146
- </div>
147
- <div class="chart-card">
148
- <h3>Recent Query Latencies (ms)</h3>
149
- <div id="chart-latency" class="chart-container"></div>
150
- </div>
151
- </div>
152
-
153
- <div class="analytics-footer">
154
- <button class="refresh-btn" onclick="loadAnalytics()">↻ Refresh</button>
155
- <span class="analytics-note">Data from current session logs. Resets on container restart.</span>
156
- </div>
157
- </div>
158
- </div>
159
-
160
- <!-- ── SOURCES PANEL ── -->
161
- <div id="sources-panel" class="sources-panel">
162
- <div class="sources-panel-header">
163
- <span class="sources-panel-title">Sources</span>
164
- <button class="sources-panel-close" onclick="closeSourcesPanel()">✕</button>
165
  </div>
166
- <div id="sources-panel-body" class="sources-panel-body"></div>
167
- </div>
168
- <div id="sources-overlay" class="sources-overlay" onclick="closeSourcesPanel()"></div>
169
 
170
- <!-- ── INPUT ── -->
171
- <div class="input-zone">
172
- <div class="input-box">
173
- <textarea
174
- id="query-input"
175
- class="query-textarea"
176
- placeholder="Ask a question about Indian law…"
177
- rows="1"
178
- maxlength="1000"
179
- ></textarea>
180
- <button id="send-btn" class="send-btn" onclick="submitQuery()">
181
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
182
- <line x1="22" y1="2" x2="11" y2="13"></line>
183
- <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
184
- </svg>
185
- </button>
186
  </div>
187
- <p class="input-disclaimer">NyayaSetu is not a substitute for professional legal advice.</p>
188
  </div>
189
-
190
- </div>
191
  </div>
192
 
193
- <script src="/static/app.js?v=2"></script>
194
  </body>
195
- </html>
 
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>NyayaSetu — Indian Legal Research</title>
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
  <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;0,700;1,400&family=DM+Sans:wght@300;400;500;600&display=swap" rel="stylesheet">
9
+ <link rel="stylesheet" href="/static/style_new.css">
10
  </head>
11
  <body>
12
+ <div class="app-container">
13
+
14
+ <!-- SIDEBAR -->
 
15
  <aside class="sidebar" id="sidebar">
16
+ <div class="sidebar-header">
17
+ <div class="brand">
18
+ <span class="brand-icon">⚖</span>
19
+ <div class="brand-text">
20
+ <div class="brand-name">NyayaSetu</div>
21
+ <div class="brand-sub">Legal Research</div>
22
+ </div>
23
  </div>
24
+ <button class="sidebar-toggle" onclick="toggleSidebar()">☰</button>
 
 
 
 
 
 
25
  </div>
26
 
27
+ <button class="btn-primary" onclick="newChat()">+ New Session</button>
28
+ <button class="btn-secondary" onclick="showAnalytics()">📊 Analytics</button>
29
+ <button class="btn-secondary" onclick="window.location.href='/court/ui'">⚖ Moot Court</button>
 
 
 
 
 
 
30
 
31
+ <div class="sessions-header">SESSIONS</div>
32
+ <div class="sessions-list" id="sessions-list">
33
+ <div class="sessions-empty">No sessions</div>
 
 
 
 
 
34
  </div>
35
 
36
  <div class="sidebar-footer">
37
+ <p class="disclaimer">⚠ Not legal advice</p>
38
+ <p class="meta">Supreme Court of India · 1950–2024</p>
 
 
 
 
 
 
39
  </div>
40
  </aside>
41
 
42
+ <!-- MAIN CONTENT -->
43
+ <main class="main-content">
 
 
44
  <header class="topbar">
45
+ <h1 class="topbar-title" id="topbar-title">New Research Session</h1>
46
+ <div class="status-pill" id="status-pill">
47
+ <span class="status-dot"></span>
48
+ <span id="status-text">Ready</span>
 
 
 
 
49
  </div>
50
  </header>
51
 
52
+ <!-- WELCOME SCREEN -->
53
+ <section id="screen-welcome" class="screen active">
54
+ <div class="welcome-container">
55
+ <div class="welcome-icon">⚖</div>
56
+ <h2>Ask Indian Law</h2>
57
+ <p>Search across 26,688 Supreme Court judgments.<br>Every answer is cited. Every source is traceable.</p>
58
+ <div class="suggestions">
59
+ <button class="suggestion" onclick="usesuggestion(this)">What are fundamental rights?</button>
60
+ <button class="suggestion" onclick="usesuggestion(this)">What is the right to privacy under Article 21?</button>
61
+ <button class="suggestion" onclick="usesuggestion(this)">How does the Supreme Court define bail?</button>
62
+ <button class="suggestion" onclick="usesuggestion(this)">What is the basic structure doctrine?</button>
63
+ <button class="suggestion" onclick="usesuggestion(this)">What does Article 15 prohibit?</button>
64
+ <button class="suggestion" onclick="usesuggestion(this)">How is freedom of speech limited?</button>
 
 
 
65
  </div>
66
  </div>
67
+ </section>
68
 
69
+ <!-- CHAT SCREEN -->
70
+ <section id="screen-chat" class="screen">
71
+ <div class="chat-area">
72
+ <div class="messages" id="messages-list"></div>
73
  </div>
74
+ </section>
75
+
76
+ <!-- ANALYTICS SCREEN -->
77
+ <section id="screen-analytics" class="screen">
78
+ <div class="analytics-container">
79
+ <h2>System Analytics</h2>
80
+ <p>Live metrics from inference logs</p>
81
+ <div class="stats-grid">
 
 
 
82
  <div class="stat-card">
83
  <div class="stat-value" id="stat-total">—</div>
84
  <div class="stat-label">Total Queries</div>
 
93
  </div>
94
  <div class="stat-card">
95
  <div class="stat-value" id="stat-ood">—</div>
96
+ <div class="stat-label">Out-of-Domain</div>
97
  </div>
98
  <div class="stat-card">
99
  <div class="stat-value" id="stat-sources">—</div>
100
+ <div class="stat-label">Avg Sources</div>
101
  </div>
102
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  </div>
104
+ </section>
 
 
105
 
106
+ <!-- INPUT AREA -->
107
+ <div class="input-area">
108
+ <div class="input-wrapper">
109
+ <textarea id="query-input" class="query-input" placeholder="Ask about Indian law..." maxlength="1000"></textarea>
110
+ <button id="send-btn" class="send-btn" onclick="submitQuery()">→</button>
 
 
 
 
 
 
 
 
 
 
 
111
  </div>
112
+ <p class="disclaimer-line">Not a substitute for professional legal advice</p>
113
  </div>
114
+ </main>
 
115
  </div>
116
 
117
+ <script src="/static/app_new.js"></script>
118
  </body>
119
+ </html>
frontend/index_old.html ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>NyayaSetu — Indian Legal Research</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;0,700;1,400&family=DM+Sans:wght@300;400;500;600&display=swap" rel="stylesheet">
9
+ <link rel="stylesheet" href="/static/style.css">
10
+ </head>
11
+ <body>
12
+
13
+ <div class="app-layout">
14
+
15
+ <!-- ── SIDEBAR ── -->
16
+ <aside class="sidebar" id="sidebar">
17
+ <div class="sidebar-brand">
18
+ <div class="brand-mark">⚖</div>
19
+ <div class="brand-text">
20
+ <span class="brand-name">NyayaSetu</span>
21
+ <span class="brand-sub">Legal Research</span>
22
+ </div>
23
+ <button class="sidebar-toggle" onclick="toggleSidebar()" title="Toggle sidebar">
24
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
25
+ <line x1="3" y1="6" x2="21" y2="6"></line>
26
+ <line x1="3" y1="12" x2="21" y2="12"></line>
27
+ <line x1="3" y1="18" x2="21" y2="18"></line>
28
+ </svg>
29
+ </button>
30
+ </div>
31
+
32
+ <button class="new-chat-btn" onclick="newChat()">
33
+ <span class="new-chat-icon">+</span>
34
+ New Research Session
35
+ </button>
36
+
37
+ <button class="analytics-btn" onclick="showAnalytics()">
38
+ <span class="analytics-icon">📊</span>
39
+ System Analytics
40
+ </button>
41
+
42
+ <button class="analytics-btn" onclick="window.location.href='/court/ui'">
43
+ <span class="analytics-icon">⚖️</span>
44
+ Moot Court
45
+ </button>
46
+
47
+ <div class="sidebar-section-label">SESSIONS</div>
48
+ <div id="sessions-list" class="sessions-list">
49
+ <div class="sessions-empty">No sessions yet</div>
50
+ </div>
51
+
52
+ <div class="sidebar-footer">
53
+ <div class="footer-disclaimer">
54
+ <span class="disclaimer-icon">⚠</span>
55
+ <span>Not legal advice. Consult a qualified advocate.</span>
56
+ </div>
57
+ <div class="footer-meta">
58
+ Supreme Court of India · 1950–2024<br>
59
+ 443,598 judgment chunks indexed
60
+ </div>
61
+ </div>
62
+ </aside>
63
+
64
+ <!-- ── MAIN ── -->
65
+ <div class="main-wrapper">
66
+
67
+ <!-- Top bar -->
68
+ <header class="topbar">
69
+ <div class="topbar-left">
70
+ <span class="topbar-title" id="topbar-title">New Research Session</span>
71
+ </div>
72
+ <div class="topbar-right">
73
+ <div class="status-pill" id="status-pill">
74
+ <span class="status-dot"></span>
75
+ <span id="status-text">Ready</span>
76
+ </div>
77
+ </div>
78
+ </header>
79
+
80
+ <!-- ── WELCOME SCREEN ── -->
81
+ <div id="screen-welcome" class="screen screen-welcome active">
82
+ <div class="welcome-inner">
83
+ <div class="welcome-emblem">⚖</div>
84
+ <h1 class="welcome-heading">Ask Indian Law</h1>
85
+ <p class="welcome-body">
86
+ Search across 26,688 Supreme Court judgments.<br>
87
+ Every answer is cited. Every source is traceable.
88
+ </p>
89
+ <div class="suggestion-grid">
90
+ <button class="suggestion-pill" onclick="usesuggestion(this)">What are fundamental rights under the Constitution?</button>
91
+ <button class="suggestion-pill" onclick="usesuggestion(this)">What is the right to privacy under Article 21?</button>
92
+ <button class="suggestion-pill" onclick="usesuggestion(this)">How does the Supreme Court define bail in non-bailable offences?</button>
93
+ <button class="suggestion-pill" onclick="usesuggestion(this)">What is the basic structure doctrine?</button>
94
+ <button class="suggestion-pill" onclick="usesuggestion(this)">What does Article 15 prohibit?</button>
95
+ <button class="suggestion-pill" onclick="usesuggestion(this)">How is freedom of speech limited under Article 19?</button>
96
+ </div>
97
+ </div>
98
+ </div>
99
+
100
+ <!-- ── CHAT SCREEN ── -->
101
+ <div id="screen-chat" class="screen screen-chat">
102
+ <div id="messages-container" class="messages-container">
103
+ <div id="messages-list" class="messages-list"></div>
104
+ </div>
105
+ </div>
106
+
107
+ <!-- ── ANALYTICS SCREEN ── -->
108
+ <div id="screen-analytics" class="screen screen-analytics">
109
+ <div class="analytics-inner">
110
+ <div class="analytics-header">
111
+ <h2>System Analytics</h2>
112
+ <p>Live metrics from inference logs</p>
113
+ </div>
114
+
115
+ <div class="analytics-grid">
116
+ <div class="stat-card">
117
+ <div class="stat-value" id="stat-total">—</div>
118
+ <div class="stat-label">Total Queries</div>
119
+ </div>
120
+ <div class="stat-card">
121
+ <div class="stat-value" id="stat-verified">—</div>
122
+ <div class="stat-label">Verified Rate</div>
123
+ </div>
124
+ <div class="stat-card">
125
+ <div class="stat-value" id="stat-latency">—</div>
126
+ <div class="stat-label">Avg Latency</div>
127
+ </div>
128
+ <div class="stat-card">
129
+ <div class="stat-value" id="stat-ood">—</div>
130
+ <div class="stat-label">Out-of-Domain Rate</div>
131
+ </div>
132
+ <div class="stat-card">
133
+ <div class="stat-value" id="stat-sources">—</div>
134
+ <div class="stat-label">Avg Sources / Query</div>
135
+ </div>
136
+ </div>
137
+
138
+ <div class="analytics-charts">
139
+ <div class="chart-card">
140
+ <h3>Stage Distribution</h3>
141
+ <div id="chart-stages" class="chart-container"></div>
142
+ </div>
143
+ <div class="chart-card">
144
+ <h3>Entity Types Extracted</h3>
145
+ <div id="chart-entities" class="chart-container"></div>
146
+ </div>
147
+ <div class="chart-card">
148
+ <h3>Recent Query Latencies (ms)</h3>
149
+ <div id="chart-latency" class="chart-container"></div>
150
+ </div>
151
+ </div>
152
+
153
+ <div class="analytics-footer">
154
+ <button class="refresh-btn" onclick="loadAnalytics()">↻ Refresh</button>
155
+ <span class="analytics-note">Data from current session logs. Resets on container restart.</span>
156
+ </div>
157
+ </div>
158
+ </div>
159
+
160
+ <!-- ── SOURCES PANEL ── -->
161
+ <div id="sources-panel" class="sources-panel">
162
+ <div class="sources-panel-header">
163
+ <span class="sources-panel-title">Sources</span>
164
+ <button class="sources-panel-close" onclick="closeSourcesPanel()">✕</button>
165
+ </div>
166
+ <div id="sources-panel-body" class="sources-panel-body"></div>
167
+ </div>
168
+ <div id="sources-overlay" class="sources-overlay" onclick="closeSourcesPanel()"></div>
169
+
170
+ <!-- ── INPUT ── -->
171
+ <div class="input-zone">
172
+ <div class="input-box">
173
+ <textarea
174
+ id="query-input"
175
+ class="query-textarea"
176
+ placeholder="Ask a question about Indian law…"
177
+ rows="1"
178
+ maxlength="1000"
179
+ ></textarea>
180
+ <button id="send-btn" class="send-btn" onclick="submitQuery()">
181
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
182
+ <line x1="22" y1="2" x2="11" y2="13"></line>
183
+ <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
184
+ </svg>
185
+ </button>
186
+ </div>
187
+ <p class="input-disclaimer">NyayaSetu is not a substitute for professional legal advice.</p>
188
+ </div>
189
+
190
+ </div>
191
+ </div>
192
+
193
+ <script src="/static/app.js?v=2"></script>
194
+ </body>
195
+ </html>
frontend/style.css CHANGED
@@ -1,29 +1,24 @@
1
- /* ── Reset & Variables ── */
2
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
3
 
4
  :root {
5
- --navy: #0f1724;
6
- --navy-2: #161f30;
7
- --navy-3: #1c2840;
8
- --navy-4: #243050;
9
- --gold: #c8a84b;
10
- --gold-dim: #9a7d34;
11
- --gold-glow: rgba(200,168,75,0.15);
12
- --text-1: #e8e2d4;
13
- --text-2: #9ba8bc;
14
- --text-3: #576070;
15
- --border: rgba(255,255,255,0.07);
16
  --border-gold: rgba(200,168,75,0.3);
17
- --red-soft: #e05252;
18
- --green-soft: #4caf82;
19
- --sidebar-w: 272px;
20
- --topbar-h: 56px;
21
- --input-h: 96px;
22
- --radius: 12px;
23
- --transition: 0.2s ease;
24
  }
25
 
26
- html { height: 100%; }
27
 
28
  body {
29
  font-family: 'DM Sans', sans-serif;
@@ -31,27 +26,25 @@ body {
31
  color: var(--text-1);
32
  font-size: 14px;
33
  line-height: 1.6;
34
- min-height: 100vh;
35
- overflow-x: hidden;
36
- overflow-y: auto;
37
  }
38
 
39
- /* ── Layout ── */
40
- .app-layout {
41
  display: flex;
42
- min-height: 100vh;
43
- overflow: visible;
44
  }
45
 
46
- /* ── Sidebar ── */
47
  .sidebar {
48
- width: var(--sidebar-w);
49
  background: var(--navy-2);
50
  border-right: 1px solid var(--border);
51
  display: flex;
52
  flex-direction: column;
53
- flex-shrink: 0;
54
- overflow: hidden;
 
55
  transition: width 0.3s ease;
56
  }
57
 
@@ -60,116 +53,70 @@ body {
60
  }
61
 
62
  .sidebar.collapsed .brand-text,
63
- .sidebar.collapsed .new-chat-btn span:not(.new-chat-icon),
64
- .sidebar.collapsed .sidebar-section-label,
65
- .sidebar.collapsed .session-label,
66
- .sidebar.collapsed .session-count,
67
- .sidebar.collapsed .footer-disclaimer,
68
- .sidebar.collapsed .footer-meta {
69
- display: none;
70
- }
71
-
72
- .sidebar.collapsed .new-chat-btn {
73
- width: calc(100% - 16px);
74
- margin: 12px 8px;
75
- padding: 8px;
76
- justify-content: center;
77
- }
78
-
79
- .sidebar.collapsed .analytics-btn {
80
- width: calc(100% - 16px);
81
- margin: 0 8px 8px;
82
- padding: 10px 8px;
83
- justify-content: center;
84
- }
85
-
86
- .sidebar.collapsed .new-chat-icon,
87
- .sidebar.collapsed .analytics-icon {
88
- margin: 0;
89
- }
90
-
91
- .sidebar.collapsed .sessions-list {
92
- padding: 0 8px;
93
- }
94
-
95
- .sidebar.collapsed .session-item {
96
- padding: 9px 10px;
97
- justify-content: center;
98
- }
99
-
100
  .sidebar.collapsed .sidebar-footer {
101
- padding: 8px 8px;
102
  }
103
 
104
- .sidebar-brand {
105
  display: flex;
106
  align-items: center;
107
- gap: 12px;
108
- padding: 16px 16px 12px;
 
109
  border-bottom: 1px solid var(--border);
110
- position: relative;
111
  }
112
 
113
- .sidebar-toggle {
114
- position: absolute;
115
- right: 8px;
116
- top: 50%;
117
- transform: translateY(-50%);
118
- background: none;
119
- border: none;
120
- color: var(--text-3);
121
- cursor: pointer;
122
- padding: 6px;
123
- border-radius: 6px;
124
  display: flex;
125
  align-items: center;
126
- justify-content: center;
127
- transition: color var(--transition), background var(--transition);
128
- }
129
-
130
- .sidebar-toggle:hover {
131
- color: var(--text-1);
132
- background: var(--navy-3);
133
  }
134
 
135
- .brand-mark {
136
- width: 38px;
137
- height: 38px;
138
- background: var(--gold-glow);
139
- border: 1px solid var(--border-gold);
140
- border-radius: 10px;
141
- display: flex;
142
- align-items: center;
143
- justify-content: center;
144
- font-size: 18px;
145
- color: var(--gold);
146
- flex-shrink: 0;
147
  }
148
 
149
  .brand-name {
150
  font-family: 'Cormorant Garamond', serif;
151
- font-size: 20px;
152
  font-weight: 700;
153
  color: var(--gold);
154
- display: block;
155
  line-height: 1.1;
156
  }
157
 
158
  .brand-sub {
159
  font-size: 10px;
160
  color: var(--text-3);
161
- display: block;
162
- letter-spacing: 0.8px;
163
  text-transform: uppercase;
 
164
  }
165
 
166
- .new-chat-btn {
167
- margin: 12px 12px 8px;
168
- padding: 8px 12px;
169
- background: var(--gold-glow);
170
- border: 1px solid var(--border-gold);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  border-radius: 8px;
172
- color: var(--gold);
173
  font-family: 'DM Sans', sans-serif;
174
  font-size: 12px;
175
  font-weight: 500;
@@ -177,129 +124,111 @@ body {
177
  display: flex;
178
  align-items: center;
179
  gap: 6px;
180
- transition: background var(--transition), border-color var(--transition);
181
- width: calc(100% - 24px);
 
 
 
 
 
182
  }
183
 
184
- .new-chat-btn:hover {
185
- background: rgba(200,168,75,0.22);
186
  border-color: var(--gold);
187
  }
188
 
189
- .new-chat-icon { font-size: 16px; font-weight: 300; }
 
 
 
 
 
 
 
 
 
 
190
 
191
- .sidebar-section-label {
192
- padding: 8px 16px 4px;
193
  font-size: 9px;
194
  font-weight: 600;
195
  color: var(--text-3);
196
  letter-spacing: 1px;
 
 
 
197
  }
198
 
199
  .sessions-list {
200
  flex: 1;
201
  overflow-y: auto;
202
- padding: 0 8px;
 
 
203
  }
204
 
205
- .sessions-list::-webkit-scrollbar { width: 3px; }
206
- .sessions-list::-webkit-scrollbar-thumb { background: var(--navy-4); border-radius: 2px; }
207
-
208
  .sessions-empty {
209
- padding: 12px 10px;
210
  font-size: 12px;
211
  color: var(--text-3);
212
  font-style: italic;
 
213
  }
214
 
215
  .session-item {
216
- display: flex;
217
- align-items: center;
218
- gap: 10px;
219
- padding: 9px 10px;
220
- border-radius: 8px;
 
221
  cursor: pointer;
222
- transition: background var(--transition);
223
- margin-bottom: 2px;
224
- position: relative;
225
  }
226
 
227
- .session-item:hover { background: var(--navy-3); }
 
 
228
 
229
  .session-item.active {
230
  background: var(--navy-4);
231
- border: 1px solid var(--border-gold);
232
- }
233
-
234
- .session-dot {
235
- width: 6px;
236
- height: 6px;
237
- border-radius: 50%;
238
- background: var(--gold-dim);
239
- flex-shrink: 0;
240
- }
241
-
242
- .session-item.active .session-dot { background: var(--gold); }
243
-
244
- .session-label {
245
- font-size: 12px;
246
- color: var(--text-2);
247
- white-space: nowrap;
248
- overflow: hidden;
249
- text-overflow: ellipsis;
250
- flex: 1;
251
- }
252
-
253
- .session-item.active .session-label { color: var(--text-1); }
254
-
255
- .session-count {
256
- font-size: 10px;
257
- color: var(--text-3);
258
- flex-shrink: 0;
259
  }
260
 
261
  .sidebar-footer {
262
- padding: 10px 14px;
263
  border-top: 1px solid var(--border);
264
- display: flex;
265
- flex-direction: column;
266
- gap: 6px;
267
- }
268
-
269
- .footer-disclaimer {
270
- display: flex;
271
- gap: 8px;
272
- align-items: flex-start;
273
  font-size: 10px;
274
  color: var(--text-3);
275
- line-height: 1.3;
276
  }
277
 
278
- .disclaimer-icon { color: var(--gold-dim); flex-shrink: 0; }
 
 
279
 
280
- .footer-meta {
281
  font-size: 9px;
282
- color: var(--text-3);
283
- line-height: 1.4;
284
  }
285
 
286
- /* ── Main wrapper ── */
287
- .main-wrapper {
288
  flex: 1;
289
  display: flex;
290
  flex-direction: column;
291
  overflow: hidden;
292
- position: relative;
293
  }
294
 
295
- /* ── Topbar ── */
296
  .topbar {
297
- height: var(--topbar-h);
298
  border-bottom: 1px solid var(--border);
299
  display: flex;
300
  align-items: center;
301
  justify-content: space-between;
302
- padding: 0 20px;
303
  flex-shrink: 0;
304
  background: var(--navy);
305
  }
@@ -309,10 +238,6 @@ body {
309
  font-size: 17px;
310
  font-weight: 600;
311
  color: var(--text-1);
312
- white-space: nowrap;
313
- overflow: hidden;
314
- text-overflow: ellipsis;
315
- max-width: 500px;
316
  }
317
 
318
  .status-pill {
@@ -331,8 +256,7 @@ body {
331
  width: 6px;
332
  height: 6px;
333
  border-radius: 50%;
334
- background: var(--green-soft);
335
- transition: background var(--transition);
336
  }
337
 
338
  .status-pill.loading .status-dot {
@@ -340,408 +264,297 @@ body {
340
  animation: pulse 1s infinite;
341
  }
342
 
343
- .status-pill.error .status-dot { background: var(--red-soft); }
344
-
345
  @keyframes pulse {
346
  0%, 100% { opacity: 1; }
347
  50% { opacity: 0.3; }
348
  }
349
 
350
- /* ── Screens ── */
351
  .screen {
352
  display: none;
353
  flex: 1;
354
  overflow: hidden;
355
- position: relative;
356
  }
357
 
358
- .screen.active { display: flex; }
 
 
359
 
360
- /* ── Welcome screen ── */
361
- .screen-welcome {
 
 
 
362
  align-items: center;
363
  justify-content: center;
364
- padding: 20px 24px;
365
- flex-direction: column;
366
- }
367
-
368
- .welcome-inner {
369
- max-width: 720px;
370
- width: 100%;
371
  text-align: center;
372
- animation: fadeUp 0.5s ease;
373
  }
374
 
375
- @keyframes fadeUp {
376
- from { opacity: 0; transform: translateY(16px); }
377
- to { opacity: 1; transform: translateY(0); }
378
- }
379
-
380
- .welcome-emblem {
381
- font-size: 40px;
382
- margin-bottom: 12px;
383
  opacity: 0.7;
384
  }
385
 
386
- .welcome-heading {
387
  font-family: 'Cormorant Garamond', serif;
388
- font-size: 42px;
389
  font-weight: 700;
 
390
  color: var(--text-1);
391
- margin-bottom: 10px;
392
- line-height: 1.1;
393
  }
394
 
395
- .welcome-body {
396
- font-size: 14px;
397
  color: var(--text-2);
398
- line-height: 1.6;
399
- margin-bottom: 24px;
400
  }
401
 
402
- .suggestion-grid {
403
  display: grid;
404
- grid-template-columns: 1fr 1fr 1fr;
405
- gap: 8px;
 
406
  }
407
 
408
- .suggestion-pill {
 
409
  background: var(--navy-2);
410
  border: 1px solid var(--border);
411
- border-radius: 10px;
412
- padding: 10px 12px;
413
  font-family: 'DM Sans', sans-serif;
414
  font-size: 12px;
415
- color: var(--text-2);
416
  cursor: pointer;
417
  text-align: left;
418
  line-height: 1.4;
419
- transition: all var(--transition);
420
  }
421
 
422
- .suggestion-pill:hover {
423
  background: var(--navy-3);
424
  border-color: var(--border-gold);
425
  color: var(--text-1);
426
- transform: translateY(-1px);
427
  }
428
 
429
- /* ── Chat screen ── */
430
- .screen-chat { flex-direction: column; }
431
-
432
- .messages-container {
433
  flex: 1;
434
  overflow-y: auto;
435
- overflow-x: hidden;
436
- padding: 16px 0 12px;
437
- width: 100%;
438
- max-height: calc(100vh - var(--topbar-h) - var(--input-h) - 40px);
439
  display: flex;
440
  flex-direction: column;
441
- align-items: center;
442
  }
443
 
444
- .messages-container::-webkit-scrollbar { width: 4px; }
445
- .messages-container::-webkit-scrollbar-thumb { background: var(--navy-4); border-radius: 2px; }
446
-
447
- .messages-list {
448
  width: 100%;
449
  max-width: 900px;
 
 
450
  display: flex;
451
  flex-direction: column;
452
  gap: 16px;
453
- padding: 0 24px;
454
  }
455
 
456
- /* ── Message bubbles ── */
457
- .msg {
458
  display: flex;
459
  flex-direction: column;
460
- gap: 6px;
461
- animation: fadeUp 0.3s ease;
462
- width: 100%;
463
- margin-bottom: 4px;
464
  }
465
 
466
- .msg-user { align-items: flex-end; margin: 0 0 8px 0; }
467
- .msg-ai { align-items: flex-start; margin: 0 0 8px 0; }
 
 
468
 
469
- .bubble-user {
470
- background: var(--navy-4);
471
- border: 1px solid var(--border);
472
- border-radius: 16px 16px 4px 16px;
473
- padding: 10px 14px;
 
 
 
 
 
 
474
  max-width: 70%;
475
  word-wrap: break-word;
476
  overflow-wrap: break-word;
477
- word-break: break-word;
478
- color: var(--text-1);
479
  font-size: 13px;
480
  line-height: 1.5;
481
  }
482
 
483
- .bubble-ai {
 
 
 
 
 
 
484
  background: var(--navy-2);
485
  border: 1px solid var(--border);
486
  border-left: 3px solid var(--gold);
487
- border-radius: 4px 16px 16px 16px;
488
- padding: 14px 18px;
489
- max-width: 85%;
490
- word-wrap: break-word;
491
- overflow-wrap: break-word;
492
- word-break: break-word;
493
  color: var(--text-1);
494
- font-size: 13px;
495
- line-height: 1.6;
496
- position: relative;
 
 
497
  }
498
 
499
- .bubble-ai p { margin-bottom: 10px; }
500
- .bubble-ai p:last-child { margin-bottom: 0; }
 
501
 
502
- /* AI bubble meta row */
503
  .bubble-meta {
504
  display: flex;
505
  align-items: center;
506
- gap: 10px;
507
- flex-wrap: wrap;
508
- margin-top: 10px;
509
  padding-top: 8px;
510
  border-top: 1px solid var(--border);
 
 
511
  }
512
 
513
- .verify-badge {
514
  display: inline-flex;
515
  align-items: center;
516
  gap: 4px;
517
- font-size: 10px;
518
- font-weight: 600;
519
  padding: 2px 8px;
520
- border-radius: 20px;
 
521
  }
522
 
523
- .verify-badge.verified {
524
- background: rgba(76,175,130,0.12);
525
- color: var(--green-soft);
526
- border: 1px solid rgba(76,175,130,0.3);
527
  }
528
 
529
- .verify-badge.unverified {
530
- background: rgba(224,82,82,0.1);
531
  color: #e8a060;
532
- border: 1px solid rgba(224,82,82,0.3);
533
  }
534
 
535
  .sources-btn {
536
- display: inline-flex;
537
- align-items: center;
538
- gap: 5px;
539
- font-size: 10px;
540
- font-weight: 500;
541
- color: var(--gold);
542
  background: var(--gold-glow);
543
  border: 1px solid var(--border-gold);
544
- border-radius: 20px;
545
- padding: 2px 10px;
 
546
  cursor: pointer;
547
- transition: background var(--transition);
 
 
548
  }
549
 
550
- .sources-btn:hover { background: rgba(200,168,75,0.22); }
551
-
552
- .latency-label {
553
- font-size: 10px;
554
- color: var(--text-3);
555
- margin-left: auto;
556
  }
557
 
558
- .truncated-note {
559
- font-size: 10px;
560
  color: var(--text-3);
561
- margin-top: 6px;
562
- font-style: italic;
563
  }
564
 
565
- /* Loading bubble */
566
- .bubble-loading {
567
  display: flex;
568
- align-items: center;
569
- gap: 10px;
570
- color: var(--text-3);
571
- font-size: 12px;
572
- padding: 10px 14px;
573
  }
574
 
575
- .dots { display: flex; gap: 4px; }
576
  .dots span {
577
  width: 5px;
578
  height: 5px;
579
  border-radius: 50%;
580
  background: var(--gold);
581
- animation: dotBounce 1.2s infinite;
582
  }
 
583
  .dots span:nth-child(2) { animation-delay: 0.2s; }
584
  .dots span:nth-child(3) { animation-delay: 0.4s; }
585
 
586
- @keyframes dotBounce {
587
  0%, 60%, 100% { transform: translateY(0); opacity: 0.3; }
588
  30% { transform: translateY(-6px); opacity: 1; }
589
  }
590
 
591
- /* Error bubble */
592
- .bubble-error {
593
- background: rgba(224,82,82,0.08);
594
- border: 1px solid rgba(224,82,82,0.25);
595
- border-left: 3px solid var(--red-soft);
596
- border-radius: 4px 16px 16px 16px;
597
- padding: 14px 18px;
598
- color: #e8a0a0;
599
- font-size: 13px;
600
- max-width: 80%;
601
  }
602
 
603
- /* ── Sources side panel ── */
604
- .sources-overlay {
605
- display: none;
606
- position: fixed;
607
- inset: 0;
608
- background: rgba(0,0,0,0.4);
609
- z-index: 100;
610
  }
611
 
612
- .sources-panel {
613
- display: none;
614
- position: fixed;
615
- top: 0;
616
- right: 0;
617
- width: 380px;
618
- height: 100vh;
619
- background: var(--navy-2);
620
- border-left: 1px solid var(--border);
621
- z-index: 101;
622
- flex-direction: column;
623
- transform: translateX(100%);
624
- transition: transform 0.3s ease;
625
  }
626
 
627
- .sources-panel.open,
628
- .sources-overlay.open {
629
- display: flex;
 
 
630
  }
631
 
632
- .sources-panel.open { transform: translateX(0); }
633
-
634
- .sources-panel-header {
635
- display: flex;
636
- align-items: center;
637
- justify-content: space-between;
638
- padding: 18px 20px;
639
- border-bottom: 1px solid var(--border);
640
- flex-shrink: 0;
641
- }
642
-
643
- .sources-panel-title {
644
- font-family: 'Cormorant Garamond', serif;
645
- font-size: 18px;
646
- font-weight: 600;
647
- color: var(--text-1);
648
- }
649
-
650
- .sources-panel-close {
651
- background: none;
652
- border: none;
653
- color: var(--text-3);
654
- font-size: 16px;
655
- cursor: pointer;
656
- padding: 4px 8px;
657
- border-radius: 6px;
658
- transition: color var(--transition);
659
- }
660
-
661
- .sources-panel-close:hover { color: var(--text-1); }
662
-
663
- .sources-panel-body {
664
- flex: 1;
665
- overflow-y: auto;
666
- padding: 16px;
667
- display: flex;
668
- flex-direction: column;
669
- gap: 12px;
670
- }
671
-
672
- .sources-panel-body::-webkit-scrollbar { width: 3px; }
673
- .sources-panel-body::-webkit-scrollbar-thumb { background: var(--navy-4); }
674
-
675
- .source-card {
676
- background: var(--navy-3);
677
  border: 1px solid var(--border);
678
- border-radius: 10px;
679
- padding: 14px;
680
- transition: border-color var(--transition);
681
  }
682
 
683
- .source-card:hover { border-color: var(--border-gold); }
684
-
685
- .source-id {
686
- font-family: 'Cormorant Garamond', serif;
687
- font-size: 14px;
688
  font-weight: 600;
689
  color: var(--gold);
690
- margin-bottom: 4px;
691
- }
692
-
693
- .source-year {
694
- font-size: 11px;
695
- color: var(--text-3);
696
- margin-bottom: 10px;
697
- }
698
-
699
- .source-excerpt {
700
- font-size: 12px;
701
- color: var(--text-2);
702
- line-height: 1.6;
703
- border-left: 2px solid var(--border-gold);
704
- padding-left: 10px;
705
  }
706
 
707
- .source-num {
708
- display: inline-flex;
709
- align-items: center;
710
- justify-content: center;
711
- width: 20px;
712
- height: 20px;
713
- background: var(--navy-4);
714
- border-radius: 50%;
715
  font-size: 10px;
716
  color: var(--text-3);
717
- margin-bottom: 6px;
 
718
  }
719
 
720
- /* ── Input zone ── */
721
- .input-zone {
722
- padding: 8px 20px 10px;
723
  background: linear-gradient(transparent, var(--navy) 30%);
 
724
  flex-shrink: 0;
725
- height: var(--input-h);
726
- display: flex;
727
- flex-direction: column;
728
- justify-content: flex-end;
729
  }
730
 
731
- .input-box {
732
  display: flex;
733
- align-items: flex-end;
734
  gap: 8px;
735
  background: var(--navy-2);
736
  border: 1px solid var(--border);
737
- border-radius: 12px;
738
- padding: 8px 10px 8px 14px;
739
- transition: border-color var(--transition);
 
 
740
  }
741
 
742
- .input-box:focus-within { border-color: var(--border-gold); }
 
 
743
 
744
- .query-textarea {
745
  flex: 1;
746
  background: none;
747
  border: none;
@@ -750,419 +563,69 @@ body {
750
  font-size: 14px;
751
  color: var(--text-1);
752
  resize: none;
753
- max-height: 140px;
754
- line-height: 1.6;
755
- padding: 2px 0;
756
  }
757
 
758
- .query-textarea::placeholder { color: var(--text-3); }
 
 
759
 
760
  .send-btn {
761
  width: 36px;
762
  height: 36px;
763
  background: var(--gold);
764
  border: none;
765
- border-radius: 8px;
766
  color: var(--navy);
 
767
  cursor: pointer;
768
  display: flex;
769
  align-items: center;
770
  justify-content: center;
771
  flex-shrink: 0;
772
- transition: background var(--transition), transform 0.1s;
773
- }
774
-
775
- .send-btn:hover { background: #dbb95c; transform: scale(1.04); }
776
- .send-btn:disabled { background: var(--navy-4); color: var(--text-3); cursor: not-allowed; transform: none; }
777
-
778
- .input-disclaimer {
779
- text-align: center;
780
- font-size: 10px;
781
- color: var(--text-3);
782
- margin-top: 4px;
783
- line-height: 1.3;
784
- }
785
-
786
- /* ── Answer formatting ── */
787
- .bubble-ai ol, .bubble-ai ul {
788
- margin: 8px 0 8px 18px;
789
- display: flex;
790
- flex-direction: column;
791
- gap: 4px;
792
- }
793
-
794
- .bubble-ai ol { list-style: decimal; }
795
- .bubble-ai ul { list-style: disc; }
796
-
797
- .bubble-ai li {
798
- color: var(--text-1);
799
- line-height: 1.5;
800
- padding-left: 2px;
801
  }
802
 
803
- .bubble-ai h1, .bubble-ai h2, .bubble-ai h3 {
804
- font-family: 'Cormorant Garamond', serif;
805
- color: var(--gold);
806
- margin: 12px 0 6px;
807
  }
808
 
809
- .bubble-ai h1 { font-size: 18px; }
810
- .bubble-ai h2 { font-size: 15px; }
811
- .bubble-ai h3 { font-size: 13px; }
812
-
813
- .bubble-ai strong { color: var(--text-1); font-weight: 600; }
814
- .bubble-ai em { color: var(--text-2); font-style: italic; }
815
-
816
- .bubble-ai code {
817
  background: var(--navy-4);
818
- padding: 2px 6px;
819
- border-radius: 4px;
820
- font-family: monospace;
821
- font-size: 12px;
822
- color: var(--gold);
823
- }
824
-
825
- .answer-table {
826
- width: 100%;
827
- border-collapse: collapse;
828
- margin: 10px 0;
829
- font-size: 12px;
830
- }
831
-
832
- .answer-table td {
833
- padding: 6px 10px;
834
- border: 1px solid var(--border);
835
- color: var(--text-2);
836
- line-height: 1.4;
837
- }
838
-
839
- .answer-table tr:first-child td {
840
- background: var(--navy-3);
841
- color: var(--text-1);
842
- font-weight: 600;
843
- }
844
-
845
- .answer-table tr:hover td { background: var(--navy-3); }
846
-
847
- .bubble-ai p {
848
- margin-bottom: 10px;
849
- }
850
-
851
- .bubble-ai p:last-child { margin-bottom: 0; }
852
-
853
- /* ── Analytics ────────────────────────────────────────────── */
854
- .analytics-btn {
855
- display: flex;
856
- align-items: center;
857
- gap: 8px;
858
- width: 100%;
859
- padding: 10px 14px;
860
- margin-top: 8px;
861
- background: transparent;
862
- border: 1px solid var(--border);
863
- border-radius: 8px;
864
- color: var(--text-2);
865
- font-size: 13px;
866
- cursor: pointer;
867
- transition: background var(--transition), border-color var(--transition), color var(--transition);
868
- }
869
- .analytics-btn:hover {
870
- background: var(--navy-3);
871
- border-color: var(--border-gold);
872
- color: var(--text-1);
873
- transform: none;
874
- }
875
- .analytics-inner {
876
- max-width: 900px;
877
- margin: 0 auto;
878
- }
879
- .analytics-header h2 {
880
- font-family: 'Cormorant Garamond', serif;
881
- font-size: 24px;
882
- margin: 0 0 4px;
883
- }
884
- .analytics-header p {
885
- color: var(--text-2);
886
- font-size: 13px;
887
- margin: 0 0 16px;
888
  }
889
 
890
- .analytics-grid {
891
- display: grid;
892
- grid-template-columns: repeat(5, 1fr);
893
- gap: 12px;
894
- margin-bottom: 20px;
895
- }
896
- .stat-card {
897
- background: var(--navy-2);
898
- border: 1px solid var(--border);
899
- border-radius: 10px;
900
- padding: 14px 12px;
901
  text-align: center;
902
- }
903
- .stat-value {
904
- font-size: 22px;
905
- font-weight: 600;
906
- }
907
- .stat-label {
908
- font-size: 11px;
909
  color: var(--text-3);
910
  margin-top: 6px;
911
  }
912
 
913
- .analytics-charts {
914
- display: grid;
915
- grid-template-columns: repeat(3, 1fr);
916
- gap: 12px;
917
- margin-bottom: 16px;
918
- }
919
-
920
- .chart-card {
921
- background: var(--navy-2);
922
- border: 1px solid var(--border);
923
- border-radius: 10px;
924
- padding: 14px;
925
- }
926
-
927
- .chart-card h3 {
928
- font-family: 'Cormorant Garamond', serif;
929
- font-size: 14px;
930
- font-weight: 600;
931
- color: var(--text-1);
932
- margin-bottom: 10px;
933
- }
934
-
935
- .chart-container {
936
- min-height: 140px;
937
- display: flex;
938
- align-items: center;
939
- justify-content: center;
940
- }
941
-
942
- /* ── Responsive Improvements ── */
943
- html {
944
- scroll-behavior: smooth;
945
- }
946
-
947
  @media (max-width: 768px) {
948
- :root {
949
- --sidebar-w: 200px;
950
- --topbar-h: 48px;
951
- --input-h: 80px;
952
- }
953
-
954
- .app-layout {
955
  flex-direction: column;
956
  }
957
 
958
  .sidebar {
959
- width: 100% !important;
960
- height: auto;
961
- max-height: 35vh;
962
- overflow-y: auto;
963
  border-right: none;
964
  border-bottom: 1px solid var(--border);
965
  }
966
 
967
- .main-wrapper {
968
- flex: 1;
969
- overflow-y: hidden;
970
- }
971
-
972
- .topbar-title {
973
- font-size: 13px;
974
- max-width: 60%;
975
- }
976
-
977
- .screen {
978
- overflow-y: auto !important;
979
- overflow-x: hidden !important;
980
- }
981
-
982
- .screen-welcome {
983
- padding: 18px 16px 12px !important;
984
- }
985
-
986
- .welcome-heading {
987
- font-size: 32px !important;
988
- }
989
-
990
- .welcome-body {
991
- font-size: 13px !important;
992
- margin-bottom: 16px !important;
993
- }
994
-
995
- .suggestion-grid {
996
- grid-template-columns: 1fr !important;
997
- gap: 6px !important;
998
- }
999
-
1000
- .suggestion-pill {
1001
- font-size: 11px !important;
1002
- padding: 8px 10px !important;
1003
- }
1004
-
1005
- .messages-list {
1006
- gap: 12px !important;
1007
- }
1008
-
1009
- .bubble-ai,
1010
- .bubble-user {
1011
- max-width: 95% !important;
1012
- font-size: 12px !important;
1013
- padding: 10px 12px !important;
1014
- }
1015
-
1016
- .analytics-grid {
1017
- grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)) !important;
1018
- gap: 8px !important;
1019
- }
1020
-
1021
- .analytics-charts {
1022
- grid-template-columns: 1fr !important;
1023
- }
1024
- }
1025
-
1026
- @media (max-width: 480px) {
1027
- .sessions-list {
1028
- max-height: 200px;
1029
- }
1030
-
1031
- .topbar {
1032
- padding: 0 12px;
1033
- }
1034
-
1035
- .topbar-title {
1036
- font-size: 12px;
1037
- }
1038
-
1039
- .status-pill {
1040
- font-size: 11px;
1041
- padding: 4px 8px;
1042
- }
1043
-
1044
- .screen-welcome {
1045
- padding: 16px 12px 40px !important;
1046
  }
1047
 
1048
- .welcome-inner h1 {
1049
- font-size: 20px !important;
1050
- line-height: 1.3;
1051
  }
1052
 
1053
- .chat-container {
1054
- max-height: 300px;
1055
  }
1056
  }
1057
- color: var(--text-1);
1058
- font-family: 'Cormorant Garamond', serif;
1059
- }
1060
- .stat-label {
1061
- font-size: 11px;
1062
- color: var(--text-3);
1063
- margin-top: 4px;
1064
- text-transform: uppercase;
1065
- letter-spacing: 0.05em;
1066
- }
1067
-
1068
- .analytics-charts {
1069
- display: flex;
1070
- flex-direction: column;
1071
- gap: 24px;
1072
- }
1073
- .chart-card {
1074
- background: var(--navy-2);
1075
- border: 1px solid var(--border);
1076
- border-radius: 12px;
1077
- padding: 20px;
1078
- }
1079
- .chart-card h3 {
1080
- font-size: 14px;
1081
- font-weight: 500;
1082
- margin: 0 0 16px;
1083
- color: var(--text-2);
1084
- text-transform: uppercase;
1085
- letter-spacing: 0.05em;
1086
- }
1087
- .chart-container {
1088
- min-height: 60px;
1089
- }
1090
- .no-data {
1091
- color: var(--text-3);
1092
- font-size: 13px;
1093
- text-align: center;
1094
- padding: 16px 0;
1095
- }
1096
-
1097
- .bar-chart {
1098
- display: flex;
1099
- flex-direction: column;
1100
- gap: 8px;
1101
- }
1102
- .bar-row {
1103
- display: flex;
1104
- align-items: center;
1105
- gap: 10px;
1106
- font-size: 12px;
1107
- }
1108
- .bar-label {
1109
- width: 100px;
1110
- color: var(--text-3);
1111
- text-align: right;
1112
- flex-shrink: 0;
1113
- }
1114
- .bar-track {
1115
- flex: 1;
1116
- height: 8px;
1117
- background: var(--navy-3);
1118
- border-radius: 4px;
1119
- overflow: hidden;
1120
- }
1121
- .bar-fill {
1122
- height: 100%;
1123
- background: var(--gold);
1124
- border-radius: 4px;
1125
- transition: width 0.4s ease;
1126
- }
1127
- .bar-value {
1128
- width: 30px;
1129
- color: var(--text-1);
1130
- font-weight: 500;
1131
- text-align: right;
1132
- }
1133
-
1134
- .sparkline {
1135
- width: 100%;
1136
- height: 60px;
1137
- }
1138
- .sparkline-range {
1139
- display: flex;
1140
- justify-content: space-between;
1141
- font-size: 11px;
1142
- color: var(--text-3);
1143
- margin-top: 4px;
1144
- }
1145
-
1146
- .analytics-footer {
1147
- display: flex;
1148
- align-items: center;
1149
- gap: 16px;
1150
- margin-top: 24px;
1151
- }
1152
- .refresh-btn {
1153
- padding: 8px 16px;
1154
- background: var(--navy-3);
1155
- border: 1px solid var(--border);
1156
- border-radius: 8px;
1157
- color: var(--text-1);
1158
- font-size: 13px;
1159
- cursor: pointer;
1160
- transition: background var(--transition);
1161
- }
1162
- .refresh-btn:hover {
1163
- background: var(--navy-4);
1164
- }
1165
- .analytics-note {
1166
- font-size: 12px;
1167
- color: var(--text-3);
1168
- }
 
1
+ /* ── RESET & CORE ── */
2
+ * { margin: 0; padding: 0; box-sizing: border-box; }
3
 
4
  :root {
5
+ --navy: #0f1724;
6
+ --navy-2: #161f30;
7
+ --navy-3: #1c2840;
8
+ --navy-4: #243050;
9
+ --gold: #c8a84b;
10
+ --gold-dim: #9a7d34;
11
+ --gold-glow: rgba(200,168,75,0.15);
12
+ --text-1: #e8e2d4;
13
+ --text-2: #9ba8bc;
14
+ --text-3: #576070;
15
+ --border: rgba(255,255,255,0.07);
16
  --border-gold: rgba(200,168,75,0.3);
17
+ --green: #4caf82;
18
+ --red: #e05252;
 
 
 
 
 
19
  }
20
 
21
+ html, body { height: 100%; }
22
 
23
  body {
24
  font-family: 'DM Sans', sans-serif;
 
26
  color: var(--text-1);
27
  font-size: 14px;
28
  line-height: 1.6;
 
 
 
29
  }
30
 
31
+ /* ── LAYOUT ── */
32
+ .app-container {
33
  display: flex;
34
+ height: 100vh;
35
+ overflow: hidden;
36
  }
37
 
38
+ /* ── SIDEBAR ── */
39
  .sidebar {
40
+ width: 272px;
41
  background: var(--navy-2);
42
  border-right: 1px solid var(--border);
43
  display: flex;
44
  flex-direction: column;
45
+ padding: 16px;
46
+ gap: 12px;
47
+ overflow-y: auto;
48
  transition: width 0.3s ease;
49
  }
50
 
 
53
  }
54
 
55
  .sidebar.collapsed .brand-text,
56
+ .sidebar.collapsed .btn-primary span,
57
+ .sidebar.collapsed .btn-secondary span,
58
+ .sidebar.collapsed .sessions-header,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  .sidebar.collapsed .sidebar-footer {
60
+ display: none;
61
  }
62
 
63
+ .sidebar-header {
64
  display: flex;
65
  align-items: center;
66
+ justify-content: space-between;
67
+ gap: 8px;
68
+ padding-bottom: 12px;
69
  border-bottom: 1px solid var(--border);
 
70
  }
71
 
72
+ .brand {
 
 
 
 
 
 
 
 
 
 
73
  display: flex;
74
  align-items: center;
75
+ gap: 10px;
76
+ flex: 1;
 
 
 
 
 
77
  }
78
 
79
+ .brand-icon {
80
+ font-size: 24px;
 
 
 
 
 
 
 
 
 
 
81
  }
82
 
83
  .brand-name {
84
  font-family: 'Cormorant Garamond', serif;
85
+ font-size: 18px;
86
  font-weight: 700;
87
  color: var(--gold);
 
88
  line-height: 1.1;
89
  }
90
 
91
  .brand-sub {
92
  font-size: 10px;
93
  color: var(--text-3);
 
 
94
  text-transform: uppercase;
95
+ letter-spacing: 0.5px;
96
  }
97
 
98
+ .sidebar-toggle {
99
+ background: none;
100
+ border: none;
101
+ color: var(--text-2);
102
+ font-size: 20px;
103
+ cursor: pointer;
104
+ padding: 4px;
105
+ border-radius: 4px;
106
+ transition: all 0.2s ease;
107
+ }
108
+
109
+ .sidebar-toggle:hover {
110
+ background: var(--navy-3);
111
+ color: var(--text-1);
112
+ }
113
+
114
+ /* ── BUTTONS ── */
115
+ .btn-primary, .btn-secondary {
116
+ width: 100%;
117
+ padding: 10px 12px;
118
  border-radius: 8px;
119
+ border: 1px solid var(--border);
120
  font-family: 'DM Sans', sans-serif;
121
  font-size: 12px;
122
  font-weight: 500;
 
124
  display: flex;
125
  align-items: center;
126
  gap: 6px;
127
+ transition: all 0.2s ease;
128
+ }
129
+
130
+ .btn-primary {
131
+ background: var(--gold-glow);
132
+ border-color: var(--border-gold);
133
+ color: var(--gold);
134
  }
135
 
136
+ .btn-primary:hover {
137
+ background: rgba(200,168,75,0.25);
138
  border-color: var(--gold);
139
  }
140
 
141
+ .btn-secondary {
142
+ background: transparent;
143
+ color: var(--text-2);
144
+ }
145
+
146
+ .btn-secondary:hover {
147
+ background: var(--navy-3);
148
+ border-color: var(--border-gold);
149
+ color: var(--text-1);
150
+ transform: none;
151
+ }
152
 
153
+ /* ── SESSIONS ── */
154
+ .sessions-header {
155
  font-size: 9px;
156
  font-weight: 600;
157
  color: var(--text-3);
158
  letter-spacing: 1px;
159
+ text-transform: uppercase;
160
+ padding: 8px 0 4px;
161
+ margin-top: 8px;
162
  }
163
 
164
  .sessions-list {
165
  flex: 1;
166
  overflow-y: auto;
167
+ display: flex;
168
+ flex-direction: column;
169
+ gap: 4px;
170
  }
171
 
 
 
 
172
  .sessions-empty {
 
173
  font-size: 12px;
174
  color: var(--text-3);
175
  font-style: italic;
176
+ padding: 12px 8px;
177
  }
178
 
179
  .session-item {
180
+ padding: 8px 10px;
181
+ border-radius: 6px;
182
+ background: transparent;
183
+ border: 1px solid transparent;
184
+ font-size: 12px;
185
+ color: var(--text-2);
186
  cursor: pointer;
187
+ transition: all 0.2s ease;
 
 
188
  }
189
 
190
+ .session-item:hover {
191
+ background: var(--navy-3);
192
+ }
193
 
194
  .session-item.active {
195
  background: var(--navy-4);
196
+ border-color: var(--border-gold);
197
+ color: var(--text-1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  }
199
 
200
  .sidebar-footer {
201
+ padding-top: 12px;
202
  border-top: 1px solid var(--border);
 
 
 
 
 
 
 
 
 
203
  font-size: 10px;
204
  color: var(--text-3);
205
+ line-height: 1.4;
206
  }
207
 
208
+ .disclaimer {
209
+ margin-bottom: 6px;
210
+ }
211
 
212
+ .meta {
213
  font-size: 9px;
 
 
214
  }
215
 
216
+ /* ── MAIN CONTENT ── */
217
+ .main-content {
218
  flex: 1;
219
  display: flex;
220
  flex-direction: column;
221
  overflow: hidden;
 
222
  }
223
 
224
+ /* ── TOPBAR ── */
225
  .topbar {
226
+ height: 56px;
227
  border-bottom: 1px solid var(--border);
228
  display: flex;
229
  align-items: center;
230
  justify-content: space-between;
231
+ padding: 0 24px;
232
  flex-shrink: 0;
233
  background: var(--navy);
234
  }
 
238
  font-size: 17px;
239
  font-weight: 600;
240
  color: var(--text-1);
 
 
 
 
241
  }
242
 
243
  .status-pill {
 
256
  width: 6px;
257
  height: 6px;
258
  border-radius: 50%;
259
+ background: var(--green);
 
260
  }
261
 
262
  .status-pill.loading .status-dot {
 
264
  animation: pulse 1s infinite;
265
  }
266
 
 
 
267
  @keyframes pulse {
268
  0%, 100% { opacity: 1; }
269
  50% { opacity: 0.3; }
270
  }
271
 
272
+ /* ── SCREENS ── */
273
  .screen {
274
  display: none;
275
  flex: 1;
276
  overflow: hidden;
 
277
  }
278
 
279
+ .screen.active {
280
+ display: flex;
281
+ }
282
 
283
+ /* ── WELCOME ── */
284
+ .welcome-container {
285
+ flex: 1;
286
+ display: flex;
287
+ flex-direction: column;
288
  align-items: center;
289
  justify-content: center;
290
+ padding: 40px 24px;
 
 
 
 
 
 
291
  text-align: center;
 
292
  }
293
 
294
+ .welcome-icon {
295
+ font-size: 48px;
296
+ margin-bottom: 16px;
 
 
 
 
 
297
  opacity: 0.7;
298
  }
299
 
300
+ .welcome-container h2 {
301
  font-family: 'Cormorant Garamond', serif;
302
+ font-size: 40px;
303
  font-weight: 700;
304
+ margin-bottom: 12px;
305
  color: var(--text-1);
 
 
306
  }
307
 
308
+ .welcome-container > p {
 
309
  color: var(--text-2);
310
+ margin-bottom: 32px;
311
+ max-width: 600px;
312
  }
313
 
314
+ .suggestions {
315
  display: grid;
316
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
317
+ gap: 12px;
318
+ max-width: 800px;
319
  }
320
 
321
+ .suggestion {
322
+ padding: 12px 14px;
323
  background: var(--navy-2);
324
  border: 1px solid var(--border);
325
+ border-radius: 8px;
326
+ color: var(--text-2);
327
  font-family: 'DM Sans', sans-serif;
328
  font-size: 12px;
 
329
  cursor: pointer;
330
  text-align: left;
331
  line-height: 1.4;
332
+ transition: all 0.2s ease;
333
  }
334
 
335
+ .suggestion:hover {
336
  background: var(--navy-3);
337
  border-color: var(--border-gold);
338
  color: var(--text-1);
 
339
  }
340
 
341
+ /* ── CHAT ── */
342
+ .chat-area {
 
 
343
  flex: 1;
344
  overflow-y: auto;
 
 
 
 
345
  display: flex;
346
  flex-direction: column;
347
+ padding: 20px 0;
348
  }
349
 
350
+ .messages {
 
 
 
351
  width: 100%;
352
  max-width: 900px;
353
+ margin: 0 auto;
354
+ padding: 0 24px;
355
  display: flex;
356
  flex-direction: column;
357
  gap: 16px;
 
358
  }
359
 
360
+ .message {
 
361
  display: flex;
362
  flex-direction: column;
363
+ gap: 4px;
364
+ animation: slideIn 0.3s ease;
 
 
365
  }
366
 
367
+ @keyframes slideIn {
368
+ from { opacity: 0; transform: translateY(10px); }
369
+ to { opacity: 1; transform: translateY(0); }
370
+ }
371
 
372
+ .message.user {
373
+ align-items: flex-end;
374
+ }
375
+
376
+ .message.ai {
377
+ align-items: flex-start;
378
+ }
379
+
380
+ .bubble {
381
+ padding: 12px 16px;
382
+ border-radius: 12px;
383
  max-width: 70%;
384
  word-wrap: break-word;
385
  overflow-wrap: break-word;
 
 
386
  font-size: 13px;
387
  line-height: 1.5;
388
  }
389
 
390
+ .bubble.user {
391
+ background: var(--navy-4);
392
+ border: 1px solid var(--border);
393
+ color: var(--text-1);
394
+ }
395
+
396
+ .bubble.ai {
397
  background: var(--navy-2);
398
  border: 1px solid var(--border);
399
  border-left: 3px solid var(--gold);
 
 
 
 
 
 
400
  color: var(--text-1);
401
+ max-width: 85%;
402
+ }
403
+
404
+ .bubble p {
405
+ margin-bottom: 8px;
406
  }
407
 
408
+ .bubble p:last-child {
409
+ margin-bottom: 0;
410
+ }
411
 
 
412
  .bubble-meta {
413
  display: flex;
414
  align-items: center;
415
+ gap: 8px;
416
+ margin-top: 8px;
 
417
  padding-top: 8px;
418
  border-top: 1px solid var(--border);
419
+ font-size: 10px;
420
+ flex-wrap: wrap;
421
  }
422
 
423
+ .badge {
424
  display: inline-flex;
425
  align-items: center;
426
  gap: 4px;
 
 
427
  padding: 2px 8px;
428
+ border-radius: 12px;
429
+ font-weight: 600;
430
  }
431
 
432
+ .badge.verified {
433
+ background: rgba(76,175,130,0.15);
434
+ color: var(--green);
 
435
  }
436
 
437
+ .badge.unverified {
438
+ background: rgba(224,82,82,0.15);
439
  color: #e8a060;
 
440
  }
441
 
442
  .sources-btn {
 
 
 
 
 
 
443
  background: var(--gold-glow);
444
  border: 1px solid var(--border-gold);
445
+ color: var(--gold);
446
+ padding: 2px 8px;
447
+ border-radius: 12px;
448
  cursor: pointer;
449
+ font-size: 10px;
450
+ font-weight: 500;
451
+ transition: all 0.2s ease;
452
  }
453
 
454
+ .sources-btn:hover {
455
+ background: rgba(200,168,75,0.25);
 
 
 
 
456
  }
457
 
458
+ .bubble.loading {
 
459
  color: var(--text-3);
 
 
460
  }
461
 
462
+ .dots {
 
463
  display: flex;
464
+ gap: 4px;
 
 
 
 
465
  }
466
 
 
467
  .dots span {
468
  width: 5px;
469
  height: 5px;
470
  border-radius: 50%;
471
  background: var(--gold);
472
+ animation: bounce 1.2s infinite;
473
  }
474
+
475
  .dots span:nth-child(2) { animation-delay: 0.2s; }
476
  .dots span:nth-child(3) { animation-delay: 0.4s; }
477
 
478
+ @keyframes bounce {
479
  0%, 60%, 100% { transform: translateY(0); opacity: 0.3; }
480
  30% { transform: translateY(-6px); opacity: 1; }
481
  }
482
 
483
+ /* ── ANALYTICS ── */
484
+ .analytics-container {
485
+ flex: 1;
486
+ overflow-y: auto;
487
+ padding: 32px 24px;
488
+ max-width: 1200px;
489
+ margin: 0 auto;
 
 
 
490
  }
491
 
492
+ .analytics-container h2 {
493
+ font-family: 'Cormorant Garamond', serif;
494
+ font-size: 28px;
495
+ font-weight: 700;
496
+ margin-bottom: 8px;
 
 
497
  }
498
 
499
+ .analytics-container > p {
500
+ color: var(--text-2);
501
+ margin-bottom: 24px;
 
 
 
 
 
 
 
 
 
 
502
  }
503
 
504
+ .stats-grid {
505
+ display: grid;
506
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
507
+ gap: 12px;
508
+ margin-bottom: 24px;
509
  }
510
 
511
+ .stat-card {
512
+ background: var(--navy-2);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
513
  border: 1px solid var(--border);
514
+ border-radius: 8px;
515
+ padding: 16px 12px;
516
+ text-align: center;
517
  }
518
 
519
+ .stat-value {
520
+ font-size: 20px;
 
 
 
521
  font-weight: 600;
522
  color: var(--gold);
523
+ margin-bottom: 6px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
524
  }
525
 
526
+ .stat-label {
 
 
 
 
 
 
 
527
  font-size: 10px;
528
  color: var(--text-3);
529
+ text-transform: uppercase;
530
+ letter-spacing: 0.5px;
531
  }
532
 
533
+ /* ── INPUT ── */
534
+ .input-area {
535
+ padding: 12px 20px 16px;
536
  background: linear-gradient(transparent, var(--navy) 30%);
537
+ border-top: 1px solid var(--border);
538
  flex-shrink: 0;
 
 
 
 
539
  }
540
 
541
+ .input-wrapper {
542
  display: flex;
 
543
  gap: 8px;
544
  background: var(--navy-2);
545
  border: 1px solid var(--border);
546
+ border-radius: 10px;
547
+ padding: 8px 12px;
548
+ transition: border-color 0.2s ease;
549
+ max-width: 900px;
550
+ margin: 0 auto;
551
  }
552
 
553
+ .input-wrapper:focus-within {
554
+ border-color: var(--border-gold);
555
+ }
556
 
557
+ .query-input {
558
  flex: 1;
559
  background: none;
560
  border: none;
 
563
  font-size: 14px;
564
  color: var(--text-1);
565
  resize: none;
566
+ max-height: 120px;
567
+ line-height: 1.5;
 
568
  }
569
 
570
+ .query-input::placeholder {
571
+ color: var(--text-3);
572
+ }
573
 
574
  .send-btn {
575
  width: 36px;
576
  height: 36px;
577
  background: var(--gold);
578
  border: none;
579
+ border-radius: 6px;
580
  color: var(--navy);
581
+ font-size: 20px;
582
  cursor: pointer;
583
  display: flex;
584
  align-items: center;
585
  justify-content: center;
586
  flex-shrink: 0;
587
+ transition: background 0.2s ease;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
588
  }
589
 
590
+ .send-btn:hover {
591
+ background: #dbb95c;
 
 
592
  }
593
 
594
+ .send-btn:disabled {
 
 
 
 
 
 
 
595
  background: var(--navy-4);
596
+ color: var(--text-3);
597
+ cursor: not-allowed;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
598
  }
599
 
600
+ .disclaimer-line {
 
 
 
 
 
 
 
 
 
 
601
  text-align: center;
602
+ font-size: 10px;
 
 
 
 
 
 
603
  color: var(--text-3);
604
  margin-top: 6px;
605
  }
606
 
607
+ /* ── RESPONSIVE ── */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
608
  @media (max-width: 768px) {
609
+ .app-container {
 
 
 
 
 
 
610
  flex-direction: column;
611
  }
612
 
613
  .sidebar {
614
+ width: 100%;
615
+ max-height: 40vh;
 
 
616
  border-right: none;
617
  border-bottom: 1px solid var(--border);
618
  }
619
 
620
+ .messages {
621
+ padding: 0 16px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
622
  }
623
 
624
+ .bubble {
625
+ max-width: 90%;
 
626
  }
627
 
628
+ .suggestions {
629
+ grid-template-columns: 1fr;
630
  }
631
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/style_old.css ADDED
@@ -0,0 +1,1168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ── Reset & Variables ── */
2
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
3
+
4
+ :root {
5
+ --navy: #0f1724;
6
+ --navy-2: #161f30;
7
+ --navy-3: #1c2840;
8
+ --navy-4: #243050;
9
+ --gold: #c8a84b;
10
+ --gold-dim: #9a7d34;
11
+ --gold-glow: rgba(200,168,75,0.15);
12
+ --text-1: #e8e2d4;
13
+ --text-2: #9ba8bc;
14
+ --text-3: #576070;
15
+ --border: rgba(255,255,255,0.07);
16
+ --border-gold: rgba(200,168,75,0.3);
17
+ --red-soft: #e05252;
18
+ --green-soft: #4caf82;
19
+ --sidebar-w: 272px;
20
+ --topbar-h: 56px;
21
+ --input-h: 96px;
22
+ --radius: 12px;
23
+ --transition: 0.2s ease;
24
+ }
25
+
26
+ html { height: 100%; }
27
+
28
+ body {
29
+ font-family: 'DM Sans', sans-serif;
30
+ background: var(--navy);
31
+ color: var(--text-1);
32
+ font-size: 14px;
33
+ line-height: 1.6;
34
+ min-height: 100vh;
35
+ overflow-x: hidden;
36
+ overflow-y: auto;
37
+ }
38
+
39
+ /* ── Layout ── */
40
+ .app-layout {
41
+ display: flex;
42
+ min-height: 100vh;
43
+ overflow: visible;
44
+ }
45
+
46
+ /* ── Sidebar ── */
47
+ .sidebar {
48
+ width: var(--sidebar-w);
49
+ background: var(--navy-2);
50
+ border-right: 1px solid var(--border);
51
+ display: flex;
52
+ flex-direction: column;
53
+ flex-shrink: 0;
54
+ overflow: hidden;
55
+ transition: width 0.3s ease;
56
+ }
57
+
58
+ .sidebar.collapsed {
59
+ width: 60px;
60
+ }
61
+
62
+ .sidebar.collapsed .brand-text,
63
+ .sidebar.collapsed .new-chat-btn span:not(.new-chat-icon),
64
+ .sidebar.collapsed .sidebar-section-label,
65
+ .sidebar.collapsed .session-label,
66
+ .sidebar.collapsed .session-count,
67
+ .sidebar.collapsed .footer-disclaimer,
68
+ .sidebar.collapsed .footer-meta {
69
+ display: none;
70
+ }
71
+
72
+ .sidebar.collapsed .new-chat-btn {
73
+ width: calc(100% - 16px);
74
+ margin: 12px 8px;
75
+ padding: 8px;
76
+ justify-content: center;
77
+ }
78
+
79
+ .sidebar.collapsed .analytics-btn {
80
+ width: calc(100% - 16px);
81
+ margin: 0 8px 8px;
82
+ padding: 10px 8px;
83
+ justify-content: center;
84
+ }
85
+
86
+ .sidebar.collapsed .new-chat-icon,
87
+ .sidebar.collapsed .analytics-icon {
88
+ margin: 0;
89
+ }
90
+
91
+ .sidebar.collapsed .sessions-list {
92
+ padding: 0 8px;
93
+ }
94
+
95
+ .sidebar.collapsed .session-item {
96
+ padding: 9px 10px;
97
+ justify-content: center;
98
+ }
99
+
100
+ .sidebar.collapsed .sidebar-footer {
101
+ padding: 8px 8px;
102
+ }
103
+
104
+ .sidebar-brand {
105
+ display: flex;
106
+ align-items: center;
107
+ gap: 12px;
108
+ padding: 16px 16px 12px;
109
+ border-bottom: 1px solid var(--border);
110
+ position: relative;
111
+ }
112
+
113
+ .sidebar-toggle {
114
+ position: absolute;
115
+ right: 8px;
116
+ top: 50%;
117
+ transform: translateY(-50%);
118
+ background: none;
119
+ border: none;
120
+ color: var(--text-3);
121
+ cursor: pointer;
122
+ padding: 6px;
123
+ border-radius: 6px;
124
+ display: flex;
125
+ align-items: center;
126
+ justify-content: center;
127
+ transition: color var(--transition), background var(--transition);
128
+ }
129
+
130
+ .sidebar-toggle:hover {
131
+ color: var(--text-1);
132
+ background: var(--navy-3);
133
+ }
134
+
135
+ .brand-mark {
136
+ width: 38px;
137
+ height: 38px;
138
+ background: var(--gold-glow);
139
+ border: 1px solid var(--border-gold);
140
+ border-radius: 10px;
141
+ display: flex;
142
+ align-items: center;
143
+ justify-content: center;
144
+ font-size: 18px;
145
+ color: var(--gold);
146
+ flex-shrink: 0;
147
+ }
148
+
149
+ .brand-name {
150
+ font-family: 'Cormorant Garamond', serif;
151
+ font-size: 20px;
152
+ font-weight: 700;
153
+ color: var(--gold);
154
+ display: block;
155
+ line-height: 1.1;
156
+ }
157
+
158
+ .brand-sub {
159
+ font-size: 10px;
160
+ color: var(--text-3);
161
+ display: block;
162
+ letter-spacing: 0.8px;
163
+ text-transform: uppercase;
164
+ }
165
+
166
+ .new-chat-btn {
167
+ margin: 12px 12px 8px;
168
+ padding: 8px 12px;
169
+ background: var(--gold-glow);
170
+ border: 1px solid var(--border-gold);
171
+ border-radius: 8px;
172
+ color: var(--gold);
173
+ font-family: 'DM Sans', sans-serif;
174
+ font-size: 12px;
175
+ font-weight: 500;
176
+ cursor: pointer;
177
+ display: flex;
178
+ align-items: center;
179
+ gap: 6px;
180
+ transition: background var(--transition), border-color var(--transition);
181
+ width: calc(100% - 24px);
182
+ }
183
+
184
+ .new-chat-btn:hover {
185
+ background: rgba(200,168,75,0.22);
186
+ border-color: var(--gold);
187
+ }
188
+
189
+ .new-chat-icon { font-size: 16px; font-weight: 300; }
190
+
191
+ .sidebar-section-label {
192
+ padding: 8px 16px 4px;
193
+ font-size: 9px;
194
+ font-weight: 600;
195
+ color: var(--text-3);
196
+ letter-spacing: 1px;
197
+ }
198
+
199
+ .sessions-list {
200
+ flex: 1;
201
+ overflow-y: auto;
202
+ padding: 0 8px;
203
+ }
204
+
205
+ .sessions-list::-webkit-scrollbar { width: 3px; }
206
+ .sessions-list::-webkit-scrollbar-thumb { background: var(--navy-4); border-radius: 2px; }
207
+
208
+ .sessions-empty {
209
+ padding: 12px 10px;
210
+ font-size: 12px;
211
+ color: var(--text-3);
212
+ font-style: italic;
213
+ }
214
+
215
+ .session-item {
216
+ display: flex;
217
+ align-items: center;
218
+ gap: 10px;
219
+ padding: 9px 10px;
220
+ border-radius: 8px;
221
+ cursor: pointer;
222
+ transition: background var(--transition);
223
+ margin-bottom: 2px;
224
+ position: relative;
225
+ }
226
+
227
+ .session-item:hover { background: var(--navy-3); }
228
+
229
+ .session-item.active {
230
+ background: var(--navy-4);
231
+ border: 1px solid var(--border-gold);
232
+ }
233
+
234
+ .session-dot {
235
+ width: 6px;
236
+ height: 6px;
237
+ border-radius: 50%;
238
+ background: var(--gold-dim);
239
+ flex-shrink: 0;
240
+ }
241
+
242
+ .session-item.active .session-dot { background: var(--gold); }
243
+
244
+ .session-label {
245
+ font-size: 12px;
246
+ color: var(--text-2);
247
+ white-space: nowrap;
248
+ overflow: hidden;
249
+ text-overflow: ellipsis;
250
+ flex: 1;
251
+ }
252
+
253
+ .session-item.active .session-label { color: var(--text-1); }
254
+
255
+ .session-count {
256
+ font-size: 10px;
257
+ color: var(--text-3);
258
+ flex-shrink: 0;
259
+ }
260
+
261
+ .sidebar-footer {
262
+ padding: 10px 14px;
263
+ border-top: 1px solid var(--border);
264
+ display: flex;
265
+ flex-direction: column;
266
+ gap: 6px;
267
+ }
268
+
269
+ .footer-disclaimer {
270
+ display: flex;
271
+ gap: 8px;
272
+ align-items: flex-start;
273
+ font-size: 10px;
274
+ color: var(--text-3);
275
+ line-height: 1.3;
276
+ }
277
+
278
+ .disclaimer-icon { color: var(--gold-dim); flex-shrink: 0; }
279
+
280
+ .footer-meta {
281
+ font-size: 9px;
282
+ color: var(--text-3);
283
+ line-height: 1.4;
284
+ }
285
+
286
+ /* ── Main wrapper ── */
287
+ .main-wrapper {
288
+ flex: 1;
289
+ display: flex;
290
+ flex-direction: column;
291
+ overflow: hidden;
292
+ position: relative;
293
+ }
294
+
295
+ /* ── Topbar ── */
296
+ .topbar {
297
+ height: var(--topbar-h);
298
+ border-bottom: 1px solid var(--border);
299
+ display: flex;
300
+ align-items: center;
301
+ justify-content: space-between;
302
+ padding: 0 20px;
303
+ flex-shrink: 0;
304
+ background: var(--navy);
305
+ }
306
+
307
+ .topbar-title {
308
+ font-family: 'Cormorant Garamond', serif;
309
+ font-size: 17px;
310
+ font-weight: 600;
311
+ color: var(--text-1);
312
+ white-space: nowrap;
313
+ overflow: hidden;
314
+ text-overflow: ellipsis;
315
+ max-width: 500px;
316
+ }
317
+
318
+ .status-pill {
319
+ display: flex;
320
+ align-items: center;
321
+ gap: 6px;
322
+ background: var(--navy-2);
323
+ border: 1px solid var(--border);
324
+ border-radius: 20px;
325
+ padding: 5px 12px;
326
+ font-size: 12px;
327
+ color: var(--text-2);
328
+ }
329
+
330
+ .status-dot {
331
+ width: 6px;
332
+ height: 6px;
333
+ border-radius: 50%;
334
+ background: var(--green-soft);
335
+ transition: background var(--transition);
336
+ }
337
+
338
+ .status-pill.loading .status-dot {
339
+ background: var(--gold);
340
+ animation: pulse 1s infinite;
341
+ }
342
+
343
+ .status-pill.error .status-dot { background: var(--red-soft); }
344
+
345
+ @keyframes pulse {
346
+ 0%, 100% { opacity: 1; }
347
+ 50% { opacity: 0.3; }
348
+ }
349
+
350
+ /* ── Screens ── */
351
+ .screen {
352
+ display: none;
353
+ flex: 1;
354
+ overflow: hidden;
355
+ position: relative;
356
+ }
357
+
358
+ .screen.active { display: flex; }
359
+
360
+ /* ── Welcome screen ── */
361
+ .screen-welcome {
362
+ align-items: center;
363
+ justify-content: center;
364
+ padding: 20px 24px;
365
+ flex-direction: column;
366
+ }
367
+
368
+ .welcome-inner {
369
+ max-width: 720px;
370
+ width: 100%;
371
+ text-align: center;
372
+ animation: fadeUp 0.5s ease;
373
+ }
374
+
375
+ @keyframes fadeUp {
376
+ from { opacity: 0; transform: translateY(16px); }
377
+ to { opacity: 1; transform: translateY(0); }
378
+ }
379
+
380
+ .welcome-emblem {
381
+ font-size: 40px;
382
+ margin-bottom: 12px;
383
+ opacity: 0.7;
384
+ }
385
+
386
+ .welcome-heading {
387
+ font-family: 'Cormorant Garamond', serif;
388
+ font-size: 42px;
389
+ font-weight: 700;
390
+ color: var(--text-1);
391
+ margin-bottom: 10px;
392
+ line-height: 1.1;
393
+ }
394
+
395
+ .welcome-body {
396
+ font-size: 14px;
397
+ color: var(--text-2);
398
+ line-height: 1.6;
399
+ margin-bottom: 24px;
400
+ }
401
+
402
+ .suggestion-grid {
403
+ display: grid;
404
+ grid-template-columns: 1fr 1fr 1fr;
405
+ gap: 8px;
406
+ }
407
+
408
+ .suggestion-pill {
409
+ background: var(--navy-2);
410
+ border: 1px solid var(--border);
411
+ border-radius: 10px;
412
+ padding: 10px 12px;
413
+ font-family: 'DM Sans', sans-serif;
414
+ font-size: 12px;
415
+ color: var(--text-2);
416
+ cursor: pointer;
417
+ text-align: left;
418
+ line-height: 1.4;
419
+ transition: all var(--transition);
420
+ }
421
+
422
+ .suggestion-pill:hover {
423
+ background: var(--navy-3);
424
+ border-color: var(--border-gold);
425
+ color: var(--text-1);
426
+ transform: translateY(-1px);
427
+ }
428
+
429
+ /* ── Chat screen ── */
430
+ .screen-chat { flex-direction: column; }
431
+
432
+ .messages-container {
433
+ flex: 1;
434
+ overflow-y: auto;
435
+ overflow-x: hidden;
436
+ padding: 16px 0 12px;
437
+ width: 100%;
438
+ max-height: calc(100vh - var(--topbar-h) - var(--input-h) - 40px);
439
+ display: flex;
440
+ flex-direction: column;
441
+ align-items: center;
442
+ }
443
+
444
+ .messages-container::-webkit-scrollbar { width: 4px; }
445
+ .messages-container::-webkit-scrollbar-thumb { background: var(--navy-4); border-radius: 2px; }
446
+
447
+ .messages-list {
448
+ width: 100%;
449
+ max-width: 900px;
450
+ display: flex;
451
+ flex-direction: column;
452
+ gap: 16px;
453
+ padding: 0 24px;
454
+ }
455
+
456
+ /* ── Message bubbles ── */
457
+ .msg {
458
+ display: flex;
459
+ flex-direction: column;
460
+ gap: 6px;
461
+ animation: fadeUp 0.3s ease;
462
+ width: 100%;
463
+ margin-bottom: 4px;
464
+ }
465
+
466
+ .msg-user { align-items: flex-end; margin: 0 0 8px 0; }
467
+ .msg-ai { align-items: flex-start; margin: 0 0 8px 0; }
468
+
469
+ .bubble-user {
470
+ background: var(--navy-4);
471
+ border: 1px solid var(--border);
472
+ border-radius: 16px 16px 4px 16px;
473
+ padding: 10px 14px;
474
+ max-width: 70%;
475
+ word-wrap: break-word;
476
+ overflow-wrap: break-word;
477
+ word-break: break-word;
478
+ color: var(--text-1);
479
+ font-size: 13px;
480
+ line-height: 1.5;
481
+ }
482
+
483
+ .bubble-ai {
484
+ background: var(--navy-2);
485
+ border: 1px solid var(--border);
486
+ border-left: 3px solid var(--gold);
487
+ border-radius: 4px 16px 16px 16px;
488
+ padding: 14px 18px;
489
+ max-width: 85%;
490
+ word-wrap: break-word;
491
+ overflow-wrap: break-word;
492
+ word-break: break-word;
493
+ color: var(--text-1);
494
+ font-size: 13px;
495
+ line-height: 1.6;
496
+ position: relative;
497
+ }
498
+
499
+ .bubble-ai p { margin-bottom: 10px; }
500
+ .bubble-ai p:last-child { margin-bottom: 0; }
501
+
502
+ /* AI bubble meta row */
503
+ .bubble-meta {
504
+ display: flex;
505
+ align-items: center;
506
+ gap: 10px;
507
+ flex-wrap: wrap;
508
+ margin-top: 10px;
509
+ padding-top: 8px;
510
+ border-top: 1px solid var(--border);
511
+ }
512
+
513
+ .verify-badge {
514
+ display: inline-flex;
515
+ align-items: center;
516
+ gap: 4px;
517
+ font-size: 10px;
518
+ font-weight: 600;
519
+ padding: 2px 8px;
520
+ border-radius: 20px;
521
+ }
522
+
523
+ .verify-badge.verified {
524
+ background: rgba(76,175,130,0.12);
525
+ color: var(--green-soft);
526
+ border: 1px solid rgba(76,175,130,0.3);
527
+ }
528
+
529
+ .verify-badge.unverified {
530
+ background: rgba(224,82,82,0.1);
531
+ color: #e8a060;
532
+ border: 1px solid rgba(224,82,82,0.3);
533
+ }
534
+
535
+ .sources-btn {
536
+ display: inline-flex;
537
+ align-items: center;
538
+ gap: 5px;
539
+ font-size: 10px;
540
+ font-weight: 500;
541
+ color: var(--gold);
542
+ background: var(--gold-glow);
543
+ border: 1px solid var(--border-gold);
544
+ border-radius: 20px;
545
+ padding: 2px 10px;
546
+ cursor: pointer;
547
+ transition: background var(--transition);
548
+ }
549
+
550
+ .sources-btn:hover { background: rgba(200,168,75,0.22); }
551
+
552
+ .latency-label {
553
+ font-size: 10px;
554
+ color: var(--text-3);
555
+ margin-left: auto;
556
+ }
557
+
558
+ .truncated-note {
559
+ font-size: 10px;
560
+ color: var(--text-3);
561
+ margin-top: 6px;
562
+ font-style: italic;
563
+ }
564
+
565
+ /* Loading bubble */
566
+ .bubble-loading {
567
+ display: flex;
568
+ align-items: center;
569
+ gap: 10px;
570
+ color: var(--text-3);
571
+ font-size: 12px;
572
+ padding: 10px 14px;
573
+ }
574
+
575
+ .dots { display: flex; gap: 4px; }
576
+ .dots span {
577
+ width: 5px;
578
+ height: 5px;
579
+ border-radius: 50%;
580
+ background: var(--gold);
581
+ animation: dotBounce 1.2s infinite;
582
+ }
583
+ .dots span:nth-child(2) { animation-delay: 0.2s; }
584
+ .dots span:nth-child(3) { animation-delay: 0.4s; }
585
+
586
+ @keyframes dotBounce {
587
+ 0%, 60%, 100% { transform: translateY(0); opacity: 0.3; }
588
+ 30% { transform: translateY(-6px); opacity: 1; }
589
+ }
590
+
591
+ /* Error bubble */
592
+ .bubble-error {
593
+ background: rgba(224,82,82,0.08);
594
+ border: 1px solid rgba(224,82,82,0.25);
595
+ border-left: 3px solid var(--red-soft);
596
+ border-radius: 4px 16px 16px 16px;
597
+ padding: 14px 18px;
598
+ color: #e8a0a0;
599
+ font-size: 13px;
600
+ max-width: 80%;
601
+ }
602
+
603
+ /* ── Sources side panel ── */
604
+ .sources-overlay {
605
+ display: none;
606
+ position: fixed;
607
+ inset: 0;
608
+ background: rgba(0,0,0,0.4);
609
+ z-index: 100;
610
+ }
611
+
612
+ .sources-panel {
613
+ display: none;
614
+ position: fixed;
615
+ top: 0;
616
+ right: 0;
617
+ width: 380px;
618
+ height: 100vh;
619
+ background: var(--navy-2);
620
+ border-left: 1px solid var(--border);
621
+ z-index: 101;
622
+ flex-direction: column;
623
+ transform: translateX(100%);
624
+ transition: transform 0.3s ease;
625
+ }
626
+
627
+ .sources-panel.open,
628
+ .sources-overlay.open {
629
+ display: flex;
630
+ }
631
+
632
+ .sources-panel.open { transform: translateX(0); }
633
+
634
+ .sources-panel-header {
635
+ display: flex;
636
+ align-items: center;
637
+ justify-content: space-between;
638
+ padding: 18px 20px;
639
+ border-bottom: 1px solid var(--border);
640
+ flex-shrink: 0;
641
+ }
642
+
643
+ .sources-panel-title {
644
+ font-family: 'Cormorant Garamond', serif;
645
+ font-size: 18px;
646
+ font-weight: 600;
647
+ color: var(--text-1);
648
+ }
649
+
650
+ .sources-panel-close {
651
+ background: none;
652
+ border: none;
653
+ color: var(--text-3);
654
+ font-size: 16px;
655
+ cursor: pointer;
656
+ padding: 4px 8px;
657
+ border-radius: 6px;
658
+ transition: color var(--transition);
659
+ }
660
+
661
+ .sources-panel-close:hover { color: var(--text-1); }
662
+
663
+ .sources-panel-body {
664
+ flex: 1;
665
+ overflow-y: auto;
666
+ padding: 16px;
667
+ display: flex;
668
+ flex-direction: column;
669
+ gap: 12px;
670
+ }
671
+
672
+ .sources-panel-body::-webkit-scrollbar { width: 3px; }
673
+ .sources-panel-body::-webkit-scrollbar-thumb { background: var(--navy-4); }
674
+
675
+ .source-card {
676
+ background: var(--navy-3);
677
+ border: 1px solid var(--border);
678
+ border-radius: 10px;
679
+ padding: 14px;
680
+ transition: border-color var(--transition);
681
+ }
682
+
683
+ .source-card:hover { border-color: var(--border-gold); }
684
+
685
+ .source-id {
686
+ font-family: 'Cormorant Garamond', serif;
687
+ font-size: 14px;
688
+ font-weight: 600;
689
+ color: var(--gold);
690
+ margin-bottom: 4px;
691
+ }
692
+
693
+ .source-year {
694
+ font-size: 11px;
695
+ color: var(--text-3);
696
+ margin-bottom: 10px;
697
+ }
698
+
699
+ .source-excerpt {
700
+ font-size: 12px;
701
+ color: var(--text-2);
702
+ line-height: 1.6;
703
+ border-left: 2px solid var(--border-gold);
704
+ padding-left: 10px;
705
+ }
706
+
707
+ .source-num {
708
+ display: inline-flex;
709
+ align-items: center;
710
+ justify-content: center;
711
+ width: 20px;
712
+ height: 20px;
713
+ background: var(--navy-4);
714
+ border-radius: 50%;
715
+ font-size: 10px;
716
+ color: var(--text-3);
717
+ margin-bottom: 6px;
718
+ }
719
+
720
+ /* ── Input zone ── */
721
+ .input-zone {
722
+ padding: 8px 20px 10px;
723
+ background: linear-gradient(transparent, var(--navy) 30%);
724
+ flex-shrink: 0;
725
+ height: var(--input-h);
726
+ display: flex;
727
+ flex-direction: column;
728
+ justify-content: flex-end;
729
+ }
730
+
731
+ .input-box {
732
+ display: flex;
733
+ align-items: flex-end;
734
+ gap: 8px;
735
+ background: var(--navy-2);
736
+ border: 1px solid var(--border);
737
+ border-radius: 12px;
738
+ padding: 8px 10px 8px 14px;
739
+ transition: border-color var(--transition);
740
+ }
741
+
742
+ .input-box:focus-within { border-color: var(--border-gold); }
743
+
744
+ .query-textarea {
745
+ flex: 1;
746
+ background: none;
747
+ border: none;
748
+ outline: none;
749
+ font-family: 'DM Sans', sans-serif;
750
+ font-size: 14px;
751
+ color: var(--text-1);
752
+ resize: none;
753
+ max-height: 140px;
754
+ line-height: 1.6;
755
+ padding: 2px 0;
756
+ }
757
+
758
+ .query-textarea::placeholder { color: var(--text-3); }
759
+
760
+ .send-btn {
761
+ width: 36px;
762
+ height: 36px;
763
+ background: var(--gold);
764
+ border: none;
765
+ border-radius: 8px;
766
+ color: var(--navy);
767
+ cursor: pointer;
768
+ display: flex;
769
+ align-items: center;
770
+ justify-content: center;
771
+ flex-shrink: 0;
772
+ transition: background var(--transition), transform 0.1s;
773
+ }
774
+
775
+ .send-btn:hover { background: #dbb95c; transform: scale(1.04); }
776
+ .send-btn:disabled { background: var(--navy-4); color: var(--text-3); cursor: not-allowed; transform: none; }
777
+
778
+ .input-disclaimer {
779
+ text-align: center;
780
+ font-size: 10px;
781
+ color: var(--text-3);
782
+ margin-top: 4px;
783
+ line-height: 1.3;
784
+ }
785
+
786
+ /* ── Answer formatting ── */
787
+ .bubble-ai ol, .bubble-ai ul {
788
+ margin: 8px 0 8px 18px;
789
+ display: flex;
790
+ flex-direction: column;
791
+ gap: 4px;
792
+ }
793
+
794
+ .bubble-ai ol { list-style: decimal; }
795
+ .bubble-ai ul { list-style: disc; }
796
+
797
+ .bubble-ai li {
798
+ color: var(--text-1);
799
+ line-height: 1.5;
800
+ padding-left: 2px;
801
+ }
802
+
803
+ .bubble-ai h1, .bubble-ai h2, .bubble-ai h3 {
804
+ font-family: 'Cormorant Garamond', serif;
805
+ color: var(--gold);
806
+ margin: 12px 0 6px;
807
+ }
808
+
809
+ .bubble-ai h1 { font-size: 18px; }
810
+ .bubble-ai h2 { font-size: 15px; }
811
+ .bubble-ai h3 { font-size: 13px; }
812
+
813
+ .bubble-ai strong { color: var(--text-1); font-weight: 600; }
814
+ .bubble-ai em { color: var(--text-2); font-style: italic; }
815
+
816
+ .bubble-ai code {
817
+ background: var(--navy-4);
818
+ padding: 2px 6px;
819
+ border-radius: 4px;
820
+ font-family: monospace;
821
+ font-size: 12px;
822
+ color: var(--gold);
823
+ }
824
+
825
+ .answer-table {
826
+ width: 100%;
827
+ border-collapse: collapse;
828
+ margin: 10px 0;
829
+ font-size: 12px;
830
+ }
831
+
832
+ .answer-table td {
833
+ padding: 6px 10px;
834
+ border: 1px solid var(--border);
835
+ color: var(--text-2);
836
+ line-height: 1.4;
837
+ }
838
+
839
+ .answer-table tr:first-child td {
840
+ background: var(--navy-3);
841
+ color: var(--text-1);
842
+ font-weight: 600;
843
+ }
844
+
845
+ .answer-table tr:hover td { background: var(--navy-3); }
846
+
847
+ .bubble-ai p {
848
+ margin-bottom: 10px;
849
+ }
850
+
851
+ .bubble-ai p:last-child { margin-bottom: 0; }
852
+
853
+ /* ── Analytics ────────────────────────────────────────────── */
854
+ .analytics-btn {
855
+ display: flex;
856
+ align-items: center;
857
+ gap: 8px;
858
+ width: 100%;
859
+ padding: 10px 14px;
860
+ margin-top: 8px;
861
+ background: transparent;
862
+ border: 1px solid var(--border);
863
+ border-radius: 8px;
864
+ color: var(--text-2);
865
+ font-size: 13px;
866
+ cursor: pointer;
867
+ transition: background var(--transition), border-color var(--transition), color var(--transition);
868
+ }
869
+ .analytics-btn:hover {
870
+ background: var(--navy-3);
871
+ border-color: var(--border-gold);
872
+ color: var(--text-1);
873
+ transform: none;
874
+ }
875
+ .analytics-inner {
876
+ max-width: 900px;
877
+ margin: 0 auto;
878
+ }
879
+ .analytics-header h2 {
880
+ font-family: 'Cormorant Garamond', serif;
881
+ font-size: 24px;
882
+ margin: 0 0 4px;
883
+ }
884
+ .analytics-header p {
885
+ color: var(--text-2);
886
+ font-size: 13px;
887
+ margin: 0 0 16px;
888
+ }
889
+
890
+ .analytics-grid {
891
+ display: grid;
892
+ grid-template-columns: repeat(5, 1fr);
893
+ gap: 12px;
894
+ margin-bottom: 20px;
895
+ }
896
+ .stat-card {
897
+ background: var(--navy-2);
898
+ border: 1px solid var(--border);
899
+ border-radius: 10px;
900
+ padding: 14px 12px;
901
+ text-align: center;
902
+ }
903
+ .stat-value {
904
+ font-size: 22px;
905
+ font-weight: 600;
906
+ }
907
+ .stat-label {
908
+ font-size: 11px;
909
+ color: var(--text-3);
910
+ margin-top: 6px;
911
+ }
912
+
913
+ .analytics-charts {
914
+ display: grid;
915
+ grid-template-columns: repeat(3, 1fr);
916
+ gap: 12px;
917
+ margin-bottom: 16px;
918
+ }
919
+
920
+ .chart-card {
921
+ background: var(--navy-2);
922
+ border: 1px solid var(--border);
923
+ border-radius: 10px;
924
+ padding: 14px;
925
+ }
926
+
927
+ .chart-card h3 {
928
+ font-family: 'Cormorant Garamond', serif;
929
+ font-size: 14px;
930
+ font-weight: 600;
931
+ color: var(--text-1);
932
+ margin-bottom: 10px;
933
+ }
934
+
935
+ .chart-container {
936
+ min-height: 140px;
937
+ display: flex;
938
+ align-items: center;
939
+ justify-content: center;
940
+ }
941
+
942
+ /* ── Responsive Improvements ── */
943
+ html {
944
+ scroll-behavior: smooth;
945
+ }
946
+
947
+ @media (max-width: 768px) {
948
+ :root {
949
+ --sidebar-w: 200px;
950
+ --topbar-h: 48px;
951
+ --input-h: 80px;
952
+ }
953
+
954
+ .app-layout {
955
+ flex-direction: column;
956
+ }
957
+
958
+ .sidebar {
959
+ width: 100% !important;
960
+ height: auto;
961
+ max-height: 35vh;
962
+ overflow-y: auto;
963
+ border-right: none;
964
+ border-bottom: 1px solid var(--border);
965
+ }
966
+
967
+ .main-wrapper {
968
+ flex: 1;
969
+ overflow-y: hidden;
970
+ }
971
+
972
+ .topbar-title {
973
+ font-size: 13px;
974
+ max-width: 60%;
975
+ }
976
+
977
+ .screen {
978
+ overflow-y: auto !important;
979
+ overflow-x: hidden !important;
980
+ }
981
+
982
+ .screen-welcome {
983
+ padding: 18px 16px 12px !important;
984
+ }
985
+
986
+ .welcome-heading {
987
+ font-size: 32px !important;
988
+ }
989
+
990
+ .welcome-body {
991
+ font-size: 13px !important;
992
+ margin-bottom: 16px !important;
993
+ }
994
+
995
+ .suggestion-grid {
996
+ grid-template-columns: 1fr !important;
997
+ gap: 6px !important;
998
+ }
999
+
1000
+ .suggestion-pill {
1001
+ font-size: 11px !important;
1002
+ padding: 8px 10px !important;
1003
+ }
1004
+
1005
+ .messages-list {
1006
+ gap: 12px !important;
1007
+ }
1008
+
1009
+ .bubble-ai,
1010
+ .bubble-user {
1011
+ max-width: 95% !important;
1012
+ font-size: 12px !important;
1013
+ padding: 10px 12px !important;
1014
+ }
1015
+
1016
+ .analytics-grid {
1017
+ grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)) !important;
1018
+ gap: 8px !important;
1019
+ }
1020
+
1021
+ .analytics-charts {
1022
+ grid-template-columns: 1fr !important;
1023
+ }
1024
+ }
1025
+
1026
+ @media (max-width: 480px) {
1027
+ .sessions-list {
1028
+ max-height: 200px;
1029
+ }
1030
+
1031
+ .topbar {
1032
+ padding: 0 12px;
1033
+ }
1034
+
1035
+ .topbar-title {
1036
+ font-size: 12px;
1037
+ }
1038
+
1039
+ .status-pill {
1040
+ font-size: 11px;
1041
+ padding: 4px 8px;
1042
+ }
1043
+
1044
+ .screen-welcome {
1045
+ padding: 16px 12px 40px !important;
1046
+ }
1047
+
1048
+ .welcome-inner h1 {
1049
+ font-size: 20px !important;
1050
+ line-height: 1.3;
1051
+ }
1052
+
1053
+ .chat-container {
1054
+ max-height: 300px;
1055
+ }
1056
+ }
1057
+ color: var(--text-1);
1058
+ font-family: 'Cormorant Garamond', serif;
1059
+ }
1060
+ .stat-label {
1061
+ font-size: 11px;
1062
+ color: var(--text-3);
1063
+ margin-top: 4px;
1064
+ text-transform: uppercase;
1065
+ letter-spacing: 0.05em;
1066
+ }
1067
+
1068
+ .analytics-charts {
1069
+ display: flex;
1070
+ flex-direction: column;
1071
+ gap: 24px;
1072
+ }
1073
+ .chart-card {
1074
+ background: var(--navy-2);
1075
+ border: 1px solid var(--border);
1076
+ border-radius: 12px;
1077
+ padding: 20px;
1078
+ }
1079
+ .chart-card h3 {
1080
+ font-size: 14px;
1081
+ font-weight: 500;
1082
+ margin: 0 0 16px;
1083
+ color: var(--text-2);
1084
+ text-transform: uppercase;
1085
+ letter-spacing: 0.05em;
1086
+ }
1087
+ .chart-container {
1088
+ min-height: 60px;
1089
+ }
1090
+ .no-data {
1091
+ color: var(--text-3);
1092
+ font-size: 13px;
1093
+ text-align: center;
1094
+ padding: 16px 0;
1095
+ }
1096
+
1097
+ .bar-chart {
1098
+ display: flex;
1099
+ flex-direction: column;
1100
+ gap: 8px;
1101
+ }
1102
+ .bar-row {
1103
+ display: flex;
1104
+ align-items: center;
1105
+ gap: 10px;
1106
+ font-size: 12px;
1107
+ }
1108
+ .bar-label {
1109
+ width: 100px;
1110
+ color: var(--text-3);
1111
+ text-align: right;
1112
+ flex-shrink: 0;
1113
+ }
1114
+ .bar-track {
1115
+ flex: 1;
1116
+ height: 8px;
1117
+ background: var(--navy-3);
1118
+ border-radius: 4px;
1119
+ overflow: hidden;
1120
+ }
1121
+ .bar-fill {
1122
+ height: 100%;
1123
+ background: var(--gold);
1124
+ border-radius: 4px;
1125
+ transition: width 0.4s ease;
1126
+ }
1127
+ .bar-value {
1128
+ width: 30px;
1129
+ color: var(--text-1);
1130
+ font-weight: 500;
1131
+ text-align: right;
1132
+ }
1133
+
1134
+ .sparkline {
1135
+ width: 100%;
1136
+ height: 60px;
1137
+ }
1138
+ .sparkline-range {
1139
+ display: flex;
1140
+ justify-content: space-between;
1141
+ font-size: 11px;
1142
+ color: var(--text-3);
1143
+ margin-top: 4px;
1144
+ }
1145
+
1146
+ .analytics-footer {
1147
+ display: flex;
1148
+ align-items: center;
1149
+ gap: 16px;
1150
+ margin-top: 24px;
1151
+ }
1152
+ .refresh-btn {
1153
+ padding: 8px 16px;
1154
+ background: var(--navy-3);
1155
+ border: 1px solid var(--border);
1156
+ border-radius: 8px;
1157
+ color: var(--text-1);
1158
+ font-size: 13px;
1159
+ cursor: pointer;
1160
+ transition: background var(--transition);
1161
+ }
1162
+ .refresh-btn:hover {
1163
+ background: var(--navy-4);
1164
+ }
1165
+ .analytics-note {
1166
+ font-size: 12px;
1167
+ color: var(--text-3);
1168
+ }