LogitCode commited on
Commit
be6179a
Β·
verified Β·
1 Parent(s): 8423a48

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +169 -82
index.html CHANGED
@@ -104,67 +104,39 @@ tailwind.config = {
104
  --pre-bg: #ede4cc;
105
  }
106
 
107
- body { background: var(--bg); color: var(--pearl); }
108
- #sidebar { background: var(--surface); border-color: var(--border); }
109
- header { background: var(--surface); border-color: var(--border); }
110
- .bg-smoke { background: var(--surface) !important; }
111
- .bg-ash { background: var(--mist) !important; }
112
- .bg-mist { background: var(--mist) !important; }
113
- .hover\:bg-ash:hover { background: var(--border) !important; }
114
- .hover\:bg-mist:hover { background: var(--mist) !important; }
115
- .border-ash { border-color: var(--border) !important; }
116
- .border-mist { border-color: var(--mist) !important; }
117
- .text-fog { color: var(--fog) !important; }
118
- .text-ghost { color: var(--ghost) !important; }
119
- .text-pearl { color: var(--pearl) !important; }
120
- .text-cream { color: var(--cream) !important; }
121
- .text-amber { color: var(--accent) !important; }
122
- .text-ink { color: var(--user-text) !important; }
123
- .text-rose { color: var(--danger) !important; }
124
- .text-sage { color: var(--safe) !important; }
125
- .bg-amber { background: var(--user-bg) !important; }
126
- .hover\:bg-ember:hover { background: var(--accent2) !important; }
127
- .bg-amber\/10 { background: color-mix(in srgb, var(--accent) 10%, transparent) !important; }
128
- .bg-amber\/20 { background: color-mix(in srgb, var(--accent) 20%, transparent) !important; }
129
- .border-amber\/25 { border-color: color-mix(in srgb, var(--accent) 25%, transparent) !important; }
130
- .from-amber { --tw-gradient-from: var(--accent) !important; }
131
- .to-ember { --tw-gradient-to: var(--accent2) !important; }
132
- .bg-rose { background: var(--danger) !important; }
133
- .hover\:text-rose:hover { color: var(--danger) !important; }
134
- .hover\:text-sage:hover { color: var(--safe) !important; }
135
- .hover\:text-pearl:hover { color: var(--pearl) !important; }
136
- textarea, input, select { background: var(--mist); color: var(--cream); }
137
-
138
- /* Serif body text for assistant prose */
139
- .prose-msg { line-height: 1.75; color: var(--prose); font-family: 'Crimson Pro', serif; font-size: 1.05em; }
140
- .prose-msg p { margin: 0 0 0.7em; } .prose-msg p:last-child { margin-bottom: 0; }
141
- .prose-msg code { font-family: 'Courier Prime', monospace; background: var(--code-bg); padding: 0.1em 0.35em; border-radius: 3px; font-size: 0.82em; color: var(--code-col); }
142
- .prose-msg pre { background: var(--pre-bg); border: 1px solid var(--border); border-radius: 4px; padding: 12px 14px; overflow-x: auto; margin: 0.7em 0; font-family: 'Courier Prime', monospace; }
143
- .prose-msg pre code { background: transparent; padding: 0; color: var(--prose); font-size: 0.85em; }
144
- .prose-msg h1,.prose-msg h2,.prose-msg h3 { font-family: 'Crimson Pro', serif; color: var(--cream); margin: 0.9em 0 0.3em; font-weight: 600; font-style: italic; }
145
- .prose-msg h1 { font-size: 1.4em; } .prose-msg h2 { font-size: 1.2em; } .prose-msg h3 { font-size: 1.05em; }
146
- .prose-msg ul, .prose-msg ol { padding-left: 1.4em; margin: 0.4em 0 0.7em; }
147
  .prose-msg li::marker { color: var(--accent); }
148
- .prose-msg li { margin: 0.25em 0; }
149
- .prose-msg blockquote { border-left: 3px solid var(--accent); padding-left: 1em; color: var(--ghost); margin: 0.6em 0; font-style: italic; }
150
  .prose-msg a { color: var(--accent); text-decoration: underline; }
151
- .prose-msg strong { color: var(--cream); font-weight: 600; }
152
- .prose-msg em { font-style: italic; color: var(--ghost); }
153
- .prose-msg table { border-collapse: collapse; width: 100%; margin: 0.6em 0; font-size: 0.9em; }
154
- .prose-msg th { background: var(--mist); color: var(--cream); padding: 6px 10px; text-align: left; border-bottom: 2px solid var(--accent); }
155
- .prose-msg td { border-top: 1px solid var(--border); padding: 6px 10px; }
156
-
157
- /* User bubbles stay monospace */
158
- .prose-user { line-height: 1.65; color: var(--user-text); font-family: 'Courier Prime', monospace; }
159
- .prose-user p { margin: 0 0 0.5em; } .prose-user p:last-child { margin-bottom: 0; }
160
- .prose-user code { background: rgba(0,0,0,0.12); padding: 0.1em 0.3em; border-radius: 3px; font-size: 0.875em; }
161
- .prose-user strong { font-weight: 700; }
162
 
163
- /* Subtle paper texture in dark mode */
164
- body:not(.light) { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4'%3E%3Crect width='4' height='4' fill='%231c1814'/%3E%3Crect x='0' y='0' width='1' height='1' fill='%23221e1a' opacity='0.5'/%3E%3C/svg%3E"); }
165
-
166
- /* Font display overrides β€” use Crimson Pro for titles */
167
- .font-display { font-family: 'Crimson Pro', serif !important; }
168
 
169
  textarea { resize: none; overflow: hidden; }
170
  #sidebar { transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); }
@@ -173,8 +145,109 @@ tailwind.config = {
173
  .dot-2 { animation: blink 1.2s 0.2s infinite; }
174
  .dot-3 { animation: blink 1.2s 0.4s infinite; }
175
  select { -webkit-appearance: none; appearance: none; }
176
- textarea:focus, input:focus, select:focus { outline: none; box-shadow: 0 0 0 1.5px var(--accent); }
177
- #input-footer { padding-bottom: max(12px, env(safe-area-inset-bottom)); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  </style>
179
  </head>
180
  <body class="h-screen flex overflow-hidden">
@@ -258,9 +331,9 @@ tailwind.config = {
258
  <div class="relative">
259
  <select id="model-select" class="w-full bg-ash border border-mist rounded-md px-3 py-2 pr-7 text-xs font-mono text-cream focus:border-amber transition-colors cursor-pointer" onchange="saveSettings()">
260
  <optgroup label='Openrouter'>
 
261
  <option value='openrouter/hunter-alpha'>Hunter Alpha </option>
262
  <option value='openrouter/healer-alpha'>Healer Alpha </option>
263
- <option value='openrouter/free'>Free Models Router </option>
264
  </optgroup>
265
  <optgroup label='Nvidia'>
266
  <option value='nvidia/nemotron-3-super-120b-a12b:free'>NVIDIA: Nemotron 3 Super (free) </option>
@@ -577,26 +650,25 @@ let chats = {}; // { id: { id, title, messages: [], createdAt } }
577
 
578
  // ── Init ───────────────────────────────────────────────────────────────────
579
  function init() {
580
- if (localStorage.getItem('nexus_privacy_seen')) dismissPrivacyNotice();
581
 
582
- if (localStorage.getItem('nexus_theme') === 'light') toggleTheme(); // ← add this
583
 
584
  loadSettings();
585
  loadChats();
586
  updateModelBadge();
587
- newChat();
588
  }
589
 
590
  function dismissPrivacyNotice() {
591
  document.getElementById('privacy-modal').remove();
592
- localStorage.setItem('nexus_privacy_seen', '1');
593
  }
594
 
595
  function toggleTheme() {
596
  const isLight = document.body.classList.toggle('light');
597
  document.getElementById('theme-icon-dark').classList.toggle('hidden', isLight);
598
  document.getElementById('theme-icon-light').classList.toggle('hidden', !isLight);
599
- localStorage.setItem('nexus_theme', isLight ? 'light' : 'dark');
600
  document.getElementById('hljs-theme').href = isLight
601
  ? 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css'
602
  : 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css';
@@ -610,13 +682,13 @@ function saveSettings() {
610
  temperature: document.getElementById('temperature').value,
611
  maxTokens: document.getElementById('max-tokens').value,
612
  };
613
- localStorage.setItem('nexus_settings', JSON.stringify(settings));
614
  updateModelBadge();
615
  }
616
 
617
  function loadSettings() {
618
  try {
619
- const s = JSON.parse(localStorage.getItem('nexus_settings') || '{}');
620
  if (s.apiKey) document.getElementById('api-key').value = s.apiKey;
621
  if (s.model) document.getElementById('model-select').value = s.model;
622
  if (s.systemPrompt) document.getElementById('system-prompt').value = s.systemPrompt;
@@ -643,12 +715,12 @@ function toggleKeyVisibility() {
643
  function genId() { return Date.now().toString(36) + Math.random().toString(36).slice(2, 6); }
644
 
645
  function saveChats() {
646
- localStorage.setItem('nexus_chats', JSON.stringify(chats));
647
  }
648
 
649
  function loadChats() {
650
  try {
651
- chats = JSON.parse(localStorage.getItem('nexus_chats') || '{}');
652
  } catch(e) { chats = {}; }
653
  renderConvList();
654
  }
@@ -671,7 +743,9 @@ function switchChat(id) {
671
  renderMessages();
672
  renderConvList();
673
  const chat = chats[id];
674
- document.getElementById('chat-title').textContent = chat.title;
 
 
675
  closeSidebar();
676
  }
677
 
@@ -684,9 +758,11 @@ function deleteChat(id, e) {
684
  }
685
 
686
  function clearCurrentChat() {
687
- if (!currentChatId) return;
688
- chats[currentChatId].messages = [];
689
- chats[currentChatId].title = 'New conversation';
 
 
690
  document.getElementById('chat-title').textContent = 'New conversation';
691
  saveChats();
692
  renderConvList();
@@ -695,6 +771,8 @@ function clearCurrentChat() {
695
 
696
  function renderConvList() {
697
  const list = document.getElementById('conv-list');
 
 
698
  const sorted = Object.values(chats).sort((a,b) => b.createdAt - a.createdAt);
699
  if (sorted.length === 0) {
700
  list.innerHTML = '<div class="text-fog/60 text-xs font-mono px-3 py-2">No conversations yet</div>';
@@ -717,9 +795,10 @@ const msgContentMap = {};
717
 
718
  function renderMessages() {
719
  const container = document.getElementById('messages');
720
- const msgs = chats[currentChatId]?.messages || [];
 
721
 
722
- if (msgs.length === 0) {
723
  container.innerHTML = `<div id="empty-state" class="h-full flex flex-col items-center justify-center text-center py-16">
724
  <div class="font-display text-5xl italic text-cream/20 mb-4 select-none">FlexChat</div>
725
  <div class="text-fog text-sm font-mono max-w-xs">Enter your OpenRouter API key in the sidebar, choose a model, and start a conversation.</div>
@@ -762,7 +841,7 @@ function renderMessage(msg, idx) {
762
  } else {
763
  return `<div class="flex gap-3 items-start">
764
  <div class="w-7 h-7 rounded-md bg-gradient-to-br from-amber to-ember flex items-center justify-center flex-shrink-0 mt-0.5 shadow-md">
765
- <span class="text-ink text-xs font-display font-bold italic">N</span>
766
  </div>
767
  <div class="flex-1 min-w-0 group">
768
  <div class="text-xs font-mono text-ghost mb-1.5">${escHtml(msg.model || 'assistant')}</div>
@@ -810,7 +889,10 @@ async function sendMessage() {
810
  const input = document.getElementById('user-input');
811
  const text = input.value.trim();
812
  if (!text || isStreaming) return;
813
-
 
 
 
814
  const apiKey = document.getElementById('api-key').value.trim();
815
  if (!apiKey) { showError('Please enter your OpenRouter API key in the sidebar.'); return; }
816
 
@@ -855,7 +937,7 @@ async function sendMessage() {
855
  placeholder.className = 'flex gap-3 items-start';
856
  placeholder.innerHTML = `
857
  <div class="w-7 h-7 rounded-md bg-gradient-to-br from-amber to-ember flex items-center justify-center flex-shrink-0 mt-0.5 shadow-md">
858
- <span class="text-ink text-xs font-display font-bold italic">N</span>
859
  </div>
860
  <div class="flex-1 min-w-0">
861
  <div class="text-xs font-mono text-ghost mb-1.5">${escHtml(model.split('/').pop())}</div>
@@ -1041,13 +1123,18 @@ function openSidebar() {
1041
  function closeSidebar() {
1042
  const s = document.getElementById('sidebar');
1043
  const o = document.getElementById('overlay');
 
 
1044
  s.classList.add('-translate-x-full');
1045
- o.classList.add('opacity-0');
1046
- setTimeout(() => o.classList.add('hidden'), 300);
 
 
 
1047
  }
1048
 
1049
  // ── Boot ───────────────────────────────────────────────────────────────────
1050
  init();
1051
  </script>
1052
  </body>
1053
- </html>
 
104
  --pre-bg: #ede4cc;
105
  }
106
 
107
+ body:not(.light) #sidebar::before {
108
+ content: '';
109
+ position: absolute;
110
+ top: 0; left: 0; right: 0;
111
+ height: 1px;
112
+ background: linear-gradient(90deg, transparent, var(--accent), transparent);
113
+ opacity: 0.5;
114
+ }
115
+ /*#sidebar { position: relative; }*/
116
+
117
+ .font-display { font-family: 'Syne', sans-serif !important; }
118
+
119
+ .prose-msg { line-height: 1.7; color: var(--prose); }
120
+ .prose-msg p { margin: 0 0 0.6em; } .prose-msg p:last-child { margin-bottom: 0; }
121
+ .prose-msg code { font-family: 'Intel One Mono', 'Fira Code', monospace; background: var(--code-bg); padding: 0.1em 0.35em; border-radius: 4px; font-size: 0.875em; color: var(--code-col); border: 1px solid color-mix(in srgb, var(--accent) 20%, transparent); }
122
+ .prose-msg pre { background: var(--pre-bg); border: 1px solid var(--border); border-left: 2px solid var(--accent); border-radius: 6px; padding: 12px 14px; overflow-x: auto; margin: 0.6em 0; }
123
+ .prose-msg pre code { background: transparent; border: none; padding: 0; color: var(--prose); font-size: 0.82em; }
124
+ .prose-msg h1,.prose-msg h2,.prose-msg h3 { font-family: 'Syne', sans-serif; color: var(--cream); margin: 0.8em 0 0.3em; font-weight: 700; }
125
+ .prose-msg h1 { font-size: 1.25em; } .prose-msg h2 { font-size: 1.1em; } .prose-msg h3 { font-size: 1em; }
126
+ .prose-msg ul, .prose-msg ol { padding-left: 1.25em; margin: 0.4em 0 0.6em; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  .prose-msg li::marker { color: var(--accent); }
128
+ .prose-msg li { margin: 0.2em 0; }
129
+ .prose-msg blockquote { border-left: 2px solid var(--accent); padding-left: 0.75em; color: var(--ghost); margin: 0.5em 0; font-style: italic; }
130
  .prose-msg a { color: var(--accent); text-decoration: underline; }
131
+ .prose-msg strong { color: var(--cream); font-weight: 500; }
132
+ .prose-msg table { border-collapse: collapse; width: 100%; margin: 0.5em 0; font-size: 0.85em; }
133
+ .prose-msg th { background: var(--mist); color: var(--cream); padding: 5px 8px; text-align: left; border-bottom: 1px solid var(--accent); }
134
+ .prose-msg td { border-top: 1px solid var(--border); padding: 5px 8px; }
 
 
 
 
 
 
 
135
 
136
+ .prose-user { line-height: 1.65; color: var(--user-text); }
137
+ .prose-user p { margin: 0 0 0.5em; } .prose-user p:last-child { margin-bottom: 0; }
138
+ .prose-user code { background: rgba(255,255,255,0.1); padding: 0.1em 0.3em; border-radius: 3px; font-size: 0.875em; }
139
+ .prose-user strong { font-weight: 500; }
 
140
 
141
  textarea { resize: none; overflow: hidden; }
142
  #sidebar { transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); }
 
145
  .dot-2 { animation: blink 1.2s 0.2s infinite; }
146
  .dot-3 { animation: blink 1.2s 0.4s infinite; }
147
  select { -webkit-appearance: none; appearance: none; }
148
+ textarea:focus, input:focus, select:focus { outline: none; box-shadow: 0 0 0 1.5px var(--accent), 0 0 12px color-mix(in srgb, var(--accent) 20%, transparent); }
149
+
150
+ /* Tool panel styles */
151
+ #tool-panel, #file-explorer-panel { transition: all 0.3s ease; }
152
+ .tool-item { transition: all 0.2s ease; }
153
+ .tool-item:hover { transform: translateX(4px); }
154
+ .tool-item.active { background: var(--accent); color: var(--ink); }
155
+
156
+ /* File Explorer Styles */
157
+ .file-tree-item { transition: all 0.15s ease; }
158
+ .file-tree-item:hover { background: var(--mist); }
159
+ .file-tree-item.active { background: var(--accent) !important; color: var(--ink); }
160
+ .file-tree-children { border-left: 1px solid var(--border); margin-left: 11px; padding-left: 8px; }
161
+ .file-explorer-editor { animation: slideIn 0.25s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
162
+
163
+ /* CRITICAL MOBILE FIX - Main container */
164
+ @media (max-width: 768px) {
165
+ /* Override flex behavior on mobile */
166
+ main {
167
+ flex: none !important;
168
+ width: 100% !important;
169
+ max-width: 100vw !important;
170
+ margin-left: 0 !important;
171
+ position: relative !important;
172
+ }
173
+
174
+
175
+ /* Overlay */
176
+ #overlay {
177
+ position: fixed !important;
178
+ inset: 0 !important;
179
+ background: rgba(0,0,0,0.6) !important;
180
+ display: none !important;
181
+ }
182
+
183
+ #overlay:not(.hidden) {
184
+ display: block !important;
185
+ }
186
+
187
+ /* Fix message container */
188
+ #messages {
189
+ padding-left: 16px !important;
190
+ padding-right: 16px !important;
191
+ }
192
+
193
+ /* Fix input footer */
194
+ #input-footer {
195
+ padding-left: 16px !important;
196
+ padding-right: 16px !important;
197
+ padding-bottom: 5vh;
198
+
199
+ }
200
+
201
+ /* Remove any negative margins */
202
+ .flex-1 {
203
+ min-width: 0 !important;
204
+ }
205
+
206
+ /* File explorer panel on mobile */
207
+ #file-explorer-panel {
208
+ width: 100% !important;
209
+ max-width: 100vw !important;
210
+ }
211
+ }
212
+
213
+ /* Extra small screens */
214
+ @media (max-width: 480px) {
215
+ main {
216
+ width: 100vw !important;
217
+ }
218
+
219
+ #messages, #input-footer {
220
+ padding-left: 12px !important;
221
+ padding-right: 12px !important;
222
+ }
223
+ #input-footer {
224
+ padding-bottom: 5vh;
225
+
226
+ }
227
+ }
228
+
229
+ /* iOS Safari safe area */
230
+ @supports (-webkit-touch-callout: none) {
231
+ body {
232
+ padding-bottom: env(safe-area-inset-bottom);
233
+ }
234
+ #input-footer {
235
+ padding-bottom: 5vh;
236
+ }
237
+ }
238
+
239
+ /* Smooth scrolling on mobile */
240
+ #messages {
241
+ -webkit-overflow-scrolling: touch;
242
+ overscroll-behavior: contain;
243
+ }
244
+
245
+ #tool-result-banner, #error-banner {
246
+ margin-left: 12px;
247
+ margin-right: 12px;
248
+ font-size: 11px;
249
+ }
250
+
251
  </style>
252
  </head>
253
  <body class="h-screen flex overflow-hidden">
 
331
  <div class="relative">
332
  <select id="model-select" class="w-full bg-ash border border-mist rounded-md px-3 py-2 pr-7 text-xs font-mono text-cream focus:border-amber transition-colors cursor-pointer" onchange="saveSettings()">
333
  <optgroup label='Openrouter'>
334
+ <option value='openrouter/free'>Free Models Router </option>
335
  <option value='openrouter/hunter-alpha'>Hunter Alpha </option>
336
  <option value='openrouter/healer-alpha'>Healer Alpha </option>
 
337
  </optgroup>
338
  <optgroup label='Nvidia'>
339
  <option value='nvidia/nemotron-3-super-120b-a12b:free'>NVIDIA: Nemotron 3 Super (free) </option>
 
650
 
651
  // ── Init ───────────────────────────────────────────────────────────────────
652
  function init() {
653
+ if (localStorage.getItem('flexchat_privacy_seen')) dismissPrivacyNotice();
654
 
655
+ if (localStorage.getItem('flexchat_theme') === 'light') toggleTheme(); // ← add this
656
 
657
  loadSettings();
658
  loadChats();
659
  updateModelBadge();
 
660
  }
661
 
662
  function dismissPrivacyNotice() {
663
  document.getElementById('privacy-modal').remove();
664
+ localStorage.setItem('flexchat_privacy_seen', '1');
665
  }
666
 
667
  function toggleTheme() {
668
  const isLight = document.body.classList.toggle('light');
669
  document.getElementById('theme-icon-dark').classList.toggle('hidden', isLight);
670
  document.getElementById('theme-icon-light').classList.toggle('hidden', !isLight);
671
+ localStorage.setItem('flexchat_theme', isLight ? 'light' : 'dark');
672
  document.getElementById('hljs-theme').href = isLight
673
  ? 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css'
674
  : 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css';
 
682
  temperature: document.getElementById('temperature').value,
683
  maxTokens: document.getElementById('max-tokens').value,
684
  };
685
+ localStorage.setItem('flexchat_settings', JSON.stringify(settings));
686
  updateModelBadge();
687
  }
688
 
689
  function loadSettings() {
690
  try {
691
+ const s = JSON.parse(localStorage.getItem('flexchat_settings') || '{}');
692
  if (s.apiKey) document.getElementById('api-key').value = s.apiKey;
693
  if (s.model) document.getElementById('model-select').value = s.model;
694
  if (s.systemPrompt) document.getElementById('system-prompt').value = s.systemPrompt;
 
715
  function genId() { return Date.now().toString(36) + Math.random().toString(36).slice(2, 6); }
716
 
717
  function saveChats() {
718
+ localStorage.setItem('flexchat_chats', JSON.stringify(chats));
719
  }
720
 
721
  function loadChats() {
722
  try {
723
+ chats = JSON.parse(localStorage.getItem('flexchat_chats') || '{}');
724
  } catch(e) { chats = {}; }
725
  renderConvList();
726
  }
 
743
  renderMessages();
744
  renderConvList();
745
  const chat = chats[id];
746
+ if (chat) {
747
+ document.getElementById('chat-title').textContent = chat.title;
748
+ }
749
  closeSidebar();
750
  }
751
 
 
758
  }
759
 
760
  function clearCurrentChat() {
761
+ if (!currentChatId) return;
762
+ const chat = chats[currentChatId];
763
+ if (!chat) return;
764
+ chat.messages = [];
765
+ chat.title = 'New conversation';
766
  document.getElementById('chat-title').textContent = 'New conversation';
767
  saveChats();
768
  renderConvList();
 
771
 
772
  function renderConvList() {
773
  const list = document.getElementById('conv-list');
774
+ if (!list) return;
775
+
776
  const sorted = Object.values(chats).sort((a,b) => b.createdAt - a.createdAt);
777
  if (sorted.length === 0) {
778
  list.innerHTML = '<div class="text-fog/60 text-xs font-mono px-3 py-2">No conversations yet</div>';
 
795
 
796
  function renderMessages() {
797
  const container = document.getElementById('messages');
798
+ const chat = chats[currentChatId];
799
+ const msgs = chat ? chat.messages : [];
800
 
801
+ if (!chat || msgs.length === 0) {
802
  container.innerHTML = `<div id="empty-state" class="h-full flex flex-col items-center justify-center text-center py-16">
803
  <div class="font-display text-5xl italic text-cream/20 mb-4 select-none">FlexChat</div>
804
  <div class="text-fog text-sm font-mono max-w-xs">Enter your OpenRouter API key in the sidebar, choose a model, and start a conversation.</div>
 
841
  } else {
842
  return `<div class="flex gap-3 items-start">
843
  <div class="w-7 h-7 rounded-md bg-gradient-to-br from-amber to-ember flex items-center justify-center flex-shrink-0 mt-0.5 shadow-md">
844
+ <span class="text-ink text-xs font-display font-bold italic">Bot</span>
845
  </div>
846
  <div class="flex-1 min-w-0 group">
847
  <div class="text-xs font-mono text-ghost mb-1.5">${escHtml(msg.model || 'assistant')}</div>
 
889
  const input = document.getElementById('user-input');
890
  const text = input.value.trim();
891
  if (!text || isStreaming) return;
892
+
893
+ if (!currentChatId || !chats[currentChatId]) {
894
+ newChat();
895
+ }
896
  const apiKey = document.getElementById('api-key').value.trim();
897
  if (!apiKey) { showError('Please enter your OpenRouter API key in the sidebar.'); return; }
898
 
 
937
  placeholder.className = 'flex gap-3 items-start';
938
  placeholder.innerHTML = `
939
  <div class="w-7 h-7 rounded-md bg-gradient-to-br from-amber to-ember flex items-center justify-center flex-shrink-0 mt-0.5 shadow-md">
940
+ <span class="text-ink text-xs font-display font-bold italic">Bot</span>
941
  </div>
942
  <div class="flex-1 min-w-0">
943
  <div class="text-xs font-mono text-ghost mb-1.5">${escHtml(model.split('/').pop())}</div>
 
1123
  function closeSidebar() {
1124
  const s = document.getElementById('sidebar');
1125
  const o = document.getElementById('overlay');
1126
+
1127
+ s.removeAttribute('data-open');
1128
  s.classList.add('-translate-x-full');
1129
+
1130
+ o.classList.add('hidden');
1131
+ setTimeout(() => {
1132
+ o.style.display = 'none';
1133
+ }, 300);
1134
  }
1135
 
1136
  // ── Boot ───────────────────────────────────────────────────────────────────
1137
  init();
1138
  </script>
1139
  </body>
1140
+ </html>