gnumanth commited on
Commit
4c03e6e
Β·
1 Parent(s): 3c37047

style: full comic book aesthetic with newsprint dot grid, chunky ink borders, speech bubbles, and zero purple

Browse files
Files changed (1) hide show
  1. index.html +213 -217
index.html CHANGED
@@ -6,55 +6,48 @@
6
  <title>PhoneLLM + Pipecat XKCD Voice Agent</title>
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
- <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@500;600&display=swap" rel="stylesheet">
10
  <style>
11
  :root {
12
- /* Soft Pastel Color Palette */
13
- --bg-gradient-1: #fbf9fe;
14
- --bg-gradient-2: #f4f0fb;
15
- --bg-gradient-3: #edf2fb;
16
- --card-bg: rgba(255, 255, 255, 0.85);
17
- --card-raised: #ffffff;
18
- --border-pastel: #e2daf0;
19
- --border-soft: #ede6f6;
20
 
21
- --pastel-lavender: #8b5cf6;
22
- --pastel-lavender-soft: #ede9fe;
23
- --pastel-lavender-border: #ddd6fe;
24
 
25
- --pastel-mint: #059669;
26
- --pastel-mint-soft: #d1fae5;
27
- --pastel-mint-border: #a7f3d0;
28
 
29
- --pastel-peach: #ea580c;
30
- --pastel-peach-soft: #ffedd5;
31
- --pastel-peach-border: #fed7aa;
32
 
33
- --pastel-rose: #e11d48;
34
- --pastel-rose-soft: #ffe4e6;
35
- --pastel-rose-border: #fecdd3;
36
-
37
- --pastel-sky: #0284c7;
38
- --pastel-sky-soft: #e0f2fe;
39
- --pastel-sky-border: #bae6fd;
40
 
41
- --text-main: #2e2640;
42
- --text-muted: #6b6280;
43
- --shadow-pastel: 0 12px 34px -8px rgba(139, 92, 246, 0.12), 0 4px 16px -2px rgba(0, 0, 0, 0.04);
44
- --shadow-card: 0 20px 40px -12px rgba(112, 90, 160, 0.14), 0 2px 10px rgba(0, 0, 0, 0.03);
45
  }
46
 
47
  * { box-sizing: border-box; margin: 0; padding: 0; }
48
 
49
  body {
50
- background: linear-gradient(135deg, var(--bg-gradient-1) 0%, var(--bg-gradient-2) 50%, var(--bg-gradient-3) 100%);
51
- color: var(--text-main);
52
- font-family: 'Plus Jakarta Sans', -apple-system, sans-serif;
 
 
53
  min-height: 100vh;
54
  display: flex;
55
  flex-direction: column;
56
  align-items: center;
57
- padding: 28px 16px 48px;
58
  }
59
 
60
  header {
@@ -65,61 +58,52 @@
65
 
66
  .badge-row {
67
  display: flex;
68
- gap: 10px;
69
  justify-content: center;
70
- margin-bottom: 14px;
71
  flex-wrap: wrap;
72
  }
73
 
74
  .badge {
75
- background: var(--pastel-lavender-soft);
76
- border: 1px solid var(--pastel-lavender-border);
77
- color: #6d28d9;
78
- padding: 5px 14px;
79
- border-radius: 9999px;
80
- font-size: 0.8rem;
81
  font-family: 'JetBrains Mono', monospace;
82
- font-weight: 600;
83
  text-decoration: none;
84
- transition: all 0.2s ease;
85
- box-shadow: 0 2px 6px rgba(109, 40, 217, 0.06);
 
86
  }
87
  .badge:hover {
88
- transform: translateY(-1px);
89
- background: #e4dcfa;
90
- }
91
-
92
- .badge.mint {
93
- background: var(--pastel-mint-soft);
94
- border-color: var(--pastel-mint-border);
95
- color: #047857;
96
  }
97
- .badge.mint:hover { background: #c2f5dc; }
98
 
99
- .badge.peach {
100
- background: var(--pastel-peach-soft);
101
- border-color: var(--pastel-peach-border);
102
- color: #c2410c;
103
- }
104
- .badge.peach:hover { background: #fde0be; }
105
 
106
  h1 {
107
- font-size: clamp(2rem, 4.5vw, 2.75rem);
108
- font-weight: 800;
109
- letter-spacing: -0.03em;
110
- background: linear-gradient(135deg, #4c1d95 0%, #7c3aed 50%, #db2777 100%);
111
- -webkit-background-clip: text;
112
- -webkit-text-fill-color: transparent;
113
- margin-bottom: 8px;
114
  }
115
 
116
  p.sub {
117
- color: var(--text-muted);
118
- font-size: 1rem;
119
- font-weight: 500;
120
  }
121
 
122
- /* Layout Grid */
123
  .main-grid {
124
  display: grid;
125
  grid-template-columns: 1fr;
@@ -131,108 +115,111 @@
131
  .main-grid { grid-template-columns: 1.15fr 1fr; }
132
  }
133
 
 
134
  .panel {
135
- background: var(--card-bg);
136
- backdrop-filter: blur(16px);
137
- border: 1px solid var(--border-pastel);
138
- border-radius: 24px;
139
  padding: 24px;
140
  display: flex;
141
  flex-direction: column;
142
- box-shadow: var(--shadow-card);
143
  position: relative;
144
  }
145
 
146
- /* Controls Bar */
147
- .controls-bar {
148
  display: flex;
149
  justify-content: space-between;
150
  align-items: center;
151
- background: #ffffff;
152
- border: 1px solid var(--border-soft);
153
- padding: 8px 14px;
154
- border-radius: 14px;
155
  margin-bottom: 16px;
156
- font-size: 0.82rem;
157
- color: var(--text-muted);
 
158
  flex-wrap: wrap;
159
  gap: 8px;
160
  }
161
- .controls-bar select {
162
- background: var(--pastel-lavender-soft);
163
- border: 1px solid var(--pastel-lavender-border);
164
- color: #5b21b6;
165
- font-size: 0.8rem;
166
- font-weight: 600;
 
 
167
  padding: 4px 8px;
168
  border-radius: 8px;
169
  outline: none;
170
  cursor: pointer;
 
171
  }
172
 
173
- /* Voice Orb */
174
  .orb-container {
175
  display: flex;
176
  flex-direction: column;
177
  align-items: center;
178
  justify-content: center;
179
- padding: 14px 0 10px;
180
- gap: 14px;
181
  }
182
 
183
  .voice-orb {
184
- width: 110px;
185
- height: 110px;
186
  border-radius: 50%;
187
- background: radial-gradient(circle at 35% 35%, #c4b5fd 0%, #a78bfa 40%, #7c3aed 100%);
188
- box-shadow: 0 10px 30px rgba(124, 58, 237, 0.35), 0 0 0 6px rgba(167, 139, 250, 0.25);
 
189
  display: flex;
190
  align-items: center;
191
  justify-content: center;
192
  cursor: pointer;
193
- transition: all 0.35s cubic-bezier(0.16, 1, 0.3, 1);
194
  position: relative;
195
  }
196
  .voice-orb:hover {
197
- transform: scale(1.06);
198
- box-shadow: 0 14px 38px rgba(124, 58, 237, 0.45), 0 0 0 8px rgba(167, 139, 250, 0.35);
199
  }
200
  .voice-orb.listening {
201
- transform: scale(1.14);
202
- background: radial-gradient(circle at 35% 35%, #6ee7b7 0%, #34d399 40%, #059669 100%);
203
- box-shadow: 0 14px 45px rgba(16, 185, 129, 0.5), 0 0 0 10px rgba(52, 211, 153, 0.35);
204
- animation: pastelPulse 1.5s infinite alternate ease-in-out;
205
  }
206
  .voice-orb.speaking {
207
- background: radial-gradient(circle at 35% 35%, #fbcfe8 0%, #f472b6 40%, #db2777 100%);
208
- box-shadow: 0 14px 45px rgba(219, 39, 119, 0.45), 0 0 0 10px rgba(244, 114, 182, 0.35);
209
- animation: speakingPulse 1.2s infinite alternate ease-in-out;
 
210
  }
211
 
212
- @keyframes pastelPulse {
213
- 0% { transform: scale(1.08); box-shadow: 0 10px 30px rgba(16, 185, 129, 0.4), 0 0 0 6px rgba(52, 211, 153, 0.3); }
214
- 100% { transform: scale(1.18); box-shadow: 0 18px 50px rgba(16, 185, 129, 0.6), 0 0 0 12px rgba(52, 211, 153, 0.4); }
215
  }
216
- @keyframes speakingPulse {
217
- 0% { transform: scale(1.05); box-shadow: 0 10px 30px rgba(219, 39, 119, 0.35), 0 0 0 6px rgba(244, 114, 182, 0.25); }
218
- 100% { transform: scale(1.12); box-shadow: 0 16px 45px rgba(219, 39, 119, 0.55), 0 0 0 12px rgba(244, 114, 182, 0.4); }
219
  }
220
 
221
- .mic-icon { width: 44px; height: 44px; fill: white; pointer-events: none; }
222
 
223
  .status-badge {
224
- font-size: 0.84rem;
225
- color: #5b21b6;
226
- font-family: 'JetBrains Mono', monospace;
227
- font-weight: 600;
228
- background: #ffffff;
229
- border: 1px solid var(--border-soft);
230
- padding: 5px 14px;
231
  border-radius: 9999px;
232
- box-shadow: 0 2px 8px rgba(0,0,0,0.03);
233
  }
234
 
235
- /* Pastel Action Chips */
236
  .chips-row {
237
  display: flex;
238
  gap: 8px;
@@ -241,24 +228,24 @@
241
  }
242
  .chip {
243
  background: #ffffff;
244
- border: 1px solid var(--border-pastel);
245
- color: var(--text-main);
246
- font-size: 0.8rem;
247
- font-weight: 600;
248
- padding: 6px 13px;
249
- border-radius: 9999px;
 
250
  cursor: pointer;
251
- transition: all 0.2s ease;
252
- box-shadow: 0 2px 5px rgba(0,0,0,0.02);
253
  }
254
  .chip:hover {
255
- background: var(--pastel-lavender-soft);
256
- border-color: var(--pastel-lavender-border);
257
- color: #6d28d9;
258
- transform: translateY(-1px);
259
  }
260
 
261
- /* Chat Messages Box */
262
  .chat-box {
263
  flex: 1;
264
  min-height: 240px;
@@ -266,51 +253,52 @@
266
  overflow-y: auto;
267
  display: flex;
268
  flex-direction: column;
269
- gap: 12px;
270
- padding-right: 6px;
271
  margin: 8px 0 16px;
272
  }
273
- .chat-box::-webkit-scrollbar { width: 5px; }
274
- .chat-box::-webkit-scrollbar-thumb { background: #ddd6fe; border-radius: 4px; }
275
 
276
  .msg {
277
- padding: 12px 16px;
278
- border-radius: 16px;
279
- font-size: 0.92rem;
280
- line-height: 1.5;
281
  max-width: 88%;
282
- box-shadow: 0 2px 8px rgba(0,0,0,0.03);
 
 
 
283
  }
284
  .msg.user {
285
  align-self: flex-end;
286
- background: linear-gradient(135deg, #7c3aed 0%, #6d28d9 100%);
287
- color: #ffffff;
288
- border-bottom-right-radius: 4px;
289
  }
290
  .msg.agent {
291
  align-self: flex-start;
292
  background: #ffffff;
293
- border: 1px solid var(--border-soft);
294
- color: var(--text-main);
295
- border-bottom-left-radius: 4px;
296
  }
297
 
298
  .tool-call-pill {
299
  display: inline-flex;
300
  align-items: center;
301
  gap: 6px;
302
- font-size: 0.74rem;
303
  font-family: 'JetBrains Mono', monospace;
304
- font-weight: 600;
305
- color: #b45309;
306
- background: #fef3c7;
307
- border: 1px solid #fde68a;
308
- padding: 3px 10px;
309
  border-radius: 6px;
310
- margin-bottom: 8px;
 
311
  }
312
 
313
- /* Input area */
314
  .input-row {
315
  display: flex;
316
  gap: 10px;
@@ -319,38 +307,40 @@
319
  input[type="text"] {
320
  flex: 1;
321
  background: #ffffff;
322
- border: 1px solid var(--border-pastel);
323
- color: var(--text-main);
324
  padding: 12px 16px;
325
- border-radius: 14px;
326
  outline: none;
327
- font-family: inherit;
328
- font-size: 0.92rem;
329
- box-shadow: inset 0 2px 4px rgba(0,0,0,0.02);
330
- transition: border-color 0.2s ease;
331
  }
332
  input[type="text"]:focus {
333
- border-color: var(--pastel-lavender);
334
- box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.15);
335
  }
336
  button.btn-send {
337
- background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
338
- color: white;
339
- border: none;
340
  padding: 0 22px;
341
- border-radius: 14px;
 
342
  font-weight: 700;
343
- font-size: 0.92rem;
344
  cursor: pointer;
345
- transition: all 0.2s ease;
346
- box-shadow: 0 4px 12px rgba(124, 58, 237, 0.25);
347
  }
348
  button.btn-send:hover {
349
- transform: translateY(-1px);
350
- box-shadow: 0 6px 18px rgba(124, 58, 237, 0.35);
 
 
351
  }
352
 
353
- /* Right Comic Display Card */
354
  .comic-card {
355
  display: flex;
356
  flex-direction: column;
@@ -360,43 +350,50 @@
360
  justify-content: center;
361
  }
362
  .comic-placeholder {
363
- color: var(--text-muted);
364
- font-size: 0.95rem;
 
365
  padding: 48px 20px;
366
  }
367
  .comic-title {
368
- font-size: 1.35rem;
369
- font-weight: 800;
370
- color: #3b0764;
371
- margin-bottom: 10px;
 
 
 
 
 
 
372
  }
373
  .comic-img-wrap {
374
  background: #ffffff;
375
- padding: 16px;
376
- border-radius: 16px;
 
377
  margin: 12px 0;
378
  max-width: 100%;
379
- border: 1px solid var(--border-soft);
380
- box-shadow: 0 10px 30px rgba(124, 58, 237, 0.08);
381
  }
382
  .comic-img {
383
  max-width: 100%;
384
  max-height: 290px;
385
  object-fit: contain;
386
  display: block;
387
- border-radius: 6px;
388
  }
389
  .comic-alt {
390
- font-size: 0.86rem;
391
- color: #4c1d95;
392
- background: var(--pastel-lavender-soft);
393
- border-left: 4px solid var(--pastel-lavender);
394
  padding: 12px 16px;
395
- border-radius: 0 12px 12px 0;
396
  text-align: left;
397
- margin-top: 10px;
398
- line-height: 1.5;
399
- font-weight: 500;
 
400
  }
401
  </style>
402
  </head>
@@ -404,20 +401,20 @@
404
  <header>
405
  <div class="badge-row">
406
  <a class="badge" href="https://huggingface.co/pipecat-ai/phonellm-alpha-1" target="_blank">πŸŽ™οΈ PhoneLLM Alpha 1</a>
407
- <span class="badge mint">⚑ Pipecat Voice Engine</span>
408
- <a class="badge peach" href="https://xkcd.hemanth.deno.net/" target="_blank">🌐 xkcd.hemanth.deno.net</a>
409
  </div>
410
- <h1>PhoneLLM XKCD Voice Agent</h1>
411
- <p class="sub">Soft pastel voice interface with high-fidelity on-device neural speech & live XKCD topic search.</p>
412
  </header>
413
 
414
  <main class="main-grid">
415
- <!-- Left Voice & Conversation Panel -->
416
  <div class="panel">
417
- <div class="controls-bar">
418
- <span>πŸ”Š <strong>On-Device Voice:</strong></span>
419
  <select id="voice-select">
420
- <option value="default">Loading high-quality neural voices...</option>
421
  </select>
422
  </div>
423
 
@@ -425,7 +422,7 @@
425
  <div id="voice-orb" class="voice-orb" title="Click to Speak / Stop">
426
  <svg class="mic-icon" viewBox="0 0 24 24"><path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z"/><path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"/></svg>
427
  </div>
428
- <span id="status-badge" class="status-badge">Tap mic orb or select a prompt below</span>
429
  </div>
430
 
431
  <div class="chips-row">
@@ -440,16 +437,15 @@
440
 
441
  <div class="input-row">
442
  <input id="user-input" type="text" placeholder="Speak or type (e.g. 'Show me a comic on Python')..." />
443
- <button id="send-btn" class="btn-send">Send</button>
444
  </div>
445
  </div>
446
 
447
- <!-- Right Live XKCD Display Panel -->
448
  <div class="panel">
449
  <div id="comic-card" class="comic-card">
450
  <div class="comic-placeholder">
451
- <svg width="54" height="54" fill="none" stroke="#a78bfa" stroke-width="1.5" viewBox="0 0 24 24" style="margin-bottom:12px;"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
452
- <p>Ask the agent for an XKCD comic to render it live here.</p>
453
  </div>
454
  </div>
455
  </div>
@@ -556,11 +552,11 @@
556
 
557
  utterance.onend = () => {
558
  orb.classList.remove('speaking');
559
- statusBadge.textContent = "Ready";
560
  };
561
  utterance.onerror = () => {
562
  orb.classList.remove('speaking');
563
- statusBadge.textContent = "Ready";
564
  };
565
 
566
  window.speechSynthesis.speak(utterance);
@@ -654,7 +650,7 @@
654
  if (toolName) {
655
  const toolPill = document.createElement('div');
656
  toolPill.className = 'tool-call-pill';
657
- toolPill.innerHTML = `<span>⚑ Tool Call:</span> <span>${toolName}</span>`;
658
  msg.appendChild(toolPill);
659
  }
660
 
@@ -669,11 +665,11 @@
669
  function renderComic(comic) {
670
  if (!comic || comic.error) return;
671
  comicCard.innerHTML = `
672
- <h2 class="comic-title">#${comic.num}: ${comic.title} (${comic.year || ''})</h2>
673
  <div class="comic-img-wrap">
674
  <img class="comic-img" src="${comic.img}" alt="${comic.alt || ''}" />
675
  </div>
676
- <div class="comic-alt"><strong>Alt Text Punchline:</strong> ${comic.alt || 'No alt text'}</div>
677
  `;
678
  }
679
 
@@ -700,7 +696,7 @@
700
  isListening = true;
701
  orb.classList.remove('speaking');
702
  orb.classList.add('listening');
703
- statusBadge.textContent = "πŸŽ™οΈ Listening to your voice...";
704
  };
705
 
706
  recognition.onresult = (event) => {
@@ -711,7 +707,7 @@
711
  recognition.onend = () => {
712
  isListening = false;
713
  orb.classList.remove('listening');
714
- statusBadge.textContent = "⚑ Processing audio...";
715
  };
716
 
717
  recognition.onerror = () => {
@@ -737,7 +733,7 @@
737
 
738
  // Initial greeting
739
  setTimeout(() => {
740
- addMessage("Hello! I am your Pipecat voice assistant powered by PhoneLLM Alpha 1. Try asking: 'Show me a comic on Python' or 'What is Little Bobby Tables?'", "agent");
741
  }, 400);
742
  </script>
743
  </body>
 
6
  <title>PhoneLLM + Pipecat XKCD Voice Agent</title>
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Comic+Neue:ital,wght@0,400;0,700;1,700&family=Space+Grotesk:wght@600;700&family=JetBrains+Mono:wght@600;700&display=swap" rel="stylesheet">
10
  <style>
11
  :root {
12
+ /* Comic / Newsprint Pastel Palette (Zero Purple) */
13
+ --bg-paper: #fbf7ee;
14
+ --bg-dots: rgba(0, 0, 0, 0.04);
15
+ --ink: #1e1e24;
 
 
 
 
16
 
17
+ --comic-yellow: #fef08a;
18
+ --comic-yellow-border: #eab308;
 
19
 
20
+ --comic-cyan: #bae6fd;
21
+ --comic-cyan-border: #0284c7;
 
22
 
23
+ --comic-mint: #bbf7d0;
24
+ --comic-mint-border: #16a34a;
 
25
 
26
+ --comic-coral: #fed7aa;
27
+ --comic-coral-border: #ea580c;
28
+
29
+ --comic-pink: #fecdd3;
30
+ --comic-pink-border: #e11d48;
 
 
31
 
32
+ --card-paper: #ffffff;
33
+ --shadow-hard: 5px 5px 0px var(--ink);
34
+ --shadow-hard-sm: 3px 3px 0px var(--ink);
35
+ --shadow-hard-btn: 4px 4px 0px var(--ink);
36
  }
37
 
38
  * { box-sizing: border-box; margin: 0; padding: 0; }
39
 
40
  body {
41
+ background-color: var(--bg-paper);
42
+ background-image: radial-gradient(var(--bg-dots) 1.5px, transparent 1.5px);
43
+ background-size: 16px 16px;
44
+ color: var(--ink);
45
+ font-family: 'Comic Neue', cursive, sans-serif;
46
  min-height: 100vh;
47
  display: flex;
48
  flex-direction: column;
49
  align-items: center;
50
+ padding: 24px 16px 48px;
51
  }
52
 
53
  header {
 
58
 
59
  .badge-row {
60
  display: flex;
61
+ gap: 12px;
62
  justify-content: center;
63
+ margin-bottom: 12px;
64
  flex-wrap: wrap;
65
  }
66
 
67
  .badge {
68
+ background: var(--comic-yellow);
69
+ border: 2px solid var(--ink);
70
+ color: var(--ink);
71
+ padding: 4px 14px;
72
+ border-radius: 8px;
73
+ font-size: 0.85rem;
74
  font-family: 'JetBrains Mono', monospace;
75
+ font-weight: 700;
76
  text-decoration: none;
77
+ box-shadow: var(--shadow-hard-sm);
78
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
79
+ display: inline-block;
80
  }
81
  .badge:hover {
82
+ transform: translate(-1px, -1px);
83
+ box-shadow: 4px 4px 0px var(--ink);
 
 
 
 
 
 
84
  }
 
85
 
86
+ .badge.cyan { background: var(--comic-cyan); }
87
+ .badge.coral { background: var(--comic-coral); }
88
+ .badge.mint { background: var(--comic-mint); }
 
 
 
89
 
90
  h1 {
91
+ font-size: clamp(2.2rem, 5vw, 3.2rem);
92
+ font-weight: 700;
93
+ letter-spacing: -0.02em;
94
+ color: var(--ink);
95
+ text-transform: uppercase;
96
+ text-shadow: 2px 2px 0px var(--comic-yellow), 4px 4px 0px var(--ink);
97
+ margin-bottom: 6px;
98
  }
99
 
100
  p.sub {
101
+ font-size: 1.15rem;
102
+ font-weight: 700;
103
+ color: #4b5563;
104
  }
105
 
106
+ /* Comic Layout Grid */
107
  .main-grid {
108
  display: grid;
109
  grid-template-columns: 1fr;
 
115
  .main-grid { grid-template-columns: 1.15fr 1fr; }
116
  }
117
 
118
+ /* Comic Book Panels */
119
  .panel {
120
+ background: var(--card-paper);
121
+ border: 3px solid var(--ink);
122
+ border-radius: 16px;
 
123
  padding: 24px;
124
  display: flex;
125
  flex-direction: column;
126
+ box-shadow: var(--shadow-hard);
127
  position: relative;
128
  }
129
 
130
+ .panel-header-strip {
 
131
  display: flex;
132
  justify-content: space-between;
133
  align-items: center;
134
+ border-bottom: 2px dashed var(--ink);
135
+ padding-bottom: 12px;
 
 
136
  margin-bottom: 16px;
137
+ font-family: 'JetBrains Mono', monospace;
138
+ font-size: 0.85rem;
139
+ font-weight: 700;
140
  flex-wrap: wrap;
141
  gap: 8px;
142
  }
143
+
144
+ .panel-header-strip select {
145
+ background: var(--comic-cyan);
146
+ border: 2px solid var(--ink);
147
+ color: var(--ink);
148
+ font-family: 'Comic Neue', cursive;
149
+ font-size: 0.95rem;
150
+ font-weight: 700;
151
  padding: 4px 8px;
152
  border-radius: 8px;
153
  outline: none;
154
  cursor: pointer;
155
+ box-shadow: 2px 2px 0px var(--ink);
156
  }
157
 
158
+ /* Comic Mic Orb */
159
  .orb-container {
160
  display: flex;
161
  flex-direction: column;
162
  align-items: center;
163
  justify-content: center;
164
+ padding: 12px 0;
165
+ gap: 12px;
166
  }
167
 
168
  .voice-orb {
169
+ width: 100px;
170
+ height: 100px;
171
  border-radius: 50%;
172
+ background: var(--comic-yellow);
173
+ border: 3px solid var(--ink);
174
+ box-shadow: var(--shadow-hard);
175
  display: flex;
176
  align-items: center;
177
  justify-content: center;
178
  cursor: pointer;
179
+ transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
180
  position: relative;
181
  }
182
  .voice-orb:hover {
183
+ transform: translate(-2px, -2px) scale(1.04);
184
+ box-shadow: 7px 7px 0px var(--ink);
185
  }
186
  .voice-orb.listening {
187
+ background: var(--comic-mint);
188
+ transform: scale(1.1);
189
+ box-shadow: 6px 6px 0px var(--ink);
190
+ animation: comicPulse 1s infinite alternate ease-in-out;
191
  }
192
  .voice-orb.speaking {
193
+ background: var(--comic-coral);
194
+ transform: scale(1.08);
195
+ box-shadow: 6px 6px 0px var(--ink);
196
+ animation: comicTalk 0.8s infinite alternate ease-in-out;
197
  }
198
 
199
+ @keyframes comicPulse {
200
+ 0% { transform: scale(1.05) rotate(-2deg); }
201
+ 100% { transform: scale(1.14) rotate(2deg); }
202
  }
203
+ @keyframes comicTalk {
204
+ 0% { transform: scale(1.04) translateY(-2px); }
205
+ 100% { transform: scale(1.1) translateY(2px); }
206
  }
207
 
208
+ .mic-icon { width: 44px; height: 44px; fill: var(--ink); pointer-events: none; }
209
 
210
  .status-badge {
211
+ font-size: 0.95rem;
212
+ color: var(--ink);
213
+ font-family: 'Comic Neue', cursive;
214
+ font-weight: 700;
215
+ background: var(--bg-paper);
216
+ border: 2px solid var(--ink);
217
+ padding: 4px 14px;
218
  border-radius: 9999px;
219
+ box-shadow: 2px 2px 0px var(--ink);
220
  }
221
 
222
+ /* Comic Prompt Chips */
223
  .chips-row {
224
  display: flex;
225
  gap: 8px;
 
228
  }
229
  .chip {
230
  background: #ffffff;
231
+ border: 2px solid var(--ink);
232
+ color: var(--ink);
233
+ font-size: 0.9rem;
234
+ font-family: 'Comic Neue', cursive;
235
+ font-weight: 700;
236
+ padding: 6px 12px;
237
+ border-radius: 10px;
238
  cursor: pointer;
239
+ box-shadow: var(--shadow-hard-sm);
240
+ transition: all 0.15s ease;
241
  }
242
  .chip:hover {
243
+ background: var(--comic-yellow);
244
+ transform: translate(-1px, -1px);
245
+ box-shadow: 4px 4px 0px var(--ink);
 
246
  }
247
 
248
+ /* Speech Bubble Chat */
249
  .chat-box {
250
  flex: 1;
251
  min-height: 240px;
 
253
  overflow-y: auto;
254
  display: flex;
255
  flex-direction: column;
256
+ gap: 14px;
257
+ padding: 10px 8px 10px 2px;
258
  margin: 8px 0 16px;
259
  }
260
+ .chat-box::-webkit-scrollbar { width: 6px; }
261
+ .chat-box::-webkit-scrollbar-thumb { background: var(--ink); border-radius: 4px; }
262
 
263
  .msg {
264
+ padding: 12px 18px;
265
+ border: 2px solid var(--ink);
266
+ font-size: 1.05rem;
267
+ line-height: 1.4;
268
  max-width: 88%;
269
+ position: relative;
270
+ border-radius: 14px;
271
+ box-shadow: var(--shadow-hard-sm);
272
+ font-weight: 700;
273
  }
274
  .msg.user {
275
  align-self: flex-end;
276
+ background: var(--comic-yellow);
277
+ border-bottom-right-radius: 2px;
 
278
  }
279
  .msg.agent {
280
  align-self: flex-start;
281
  background: #ffffff;
282
+ border-bottom-left-radius: 2px;
 
 
283
  }
284
 
285
  .tool-call-pill {
286
  display: inline-flex;
287
  align-items: center;
288
  gap: 6px;
289
+ font-size: 0.8rem;
290
  font-family: 'JetBrains Mono', monospace;
291
+ font-weight: 700;
292
+ color: #065f46;
293
+ background: var(--comic-mint);
294
+ border: 1.5px solid var(--ink);
295
+ padding: 2px 8px;
296
  border-radius: 6px;
297
+ margin-bottom: 6px;
298
+ box-shadow: 2px 2px 0px var(--ink);
299
  }
300
 
301
+ /* Input Strip */
302
  .input-row {
303
  display: flex;
304
  gap: 10px;
 
307
  input[type="text"] {
308
  flex: 1;
309
  background: #ffffff;
310
+ border: 2px solid var(--ink);
311
+ color: var(--ink);
312
  padding: 12px 16px;
313
+ border-radius: 12px;
314
  outline: none;
315
+ font-family: 'Comic Neue', cursive;
316
+ font-size: 1.05rem;
317
+ font-weight: 700;
318
+ box-shadow: var(--shadow-hard-sm);
319
  }
320
  input[type="text"]:focus {
321
+ background: #fefce8;
 
322
  }
323
  button.btn-send {
324
+ background: var(--comic-coral);
325
+ border: 2px solid var(--ink);
326
+ color: var(--ink);
327
  padding: 0 22px;
328
+ border-radius: 12px;
329
+ font-family: 'Comic Neue', cursive;
330
  font-weight: 700;
331
+ font-size: 1.1rem;
332
  cursor: pointer;
333
+ box-shadow: var(--shadow-hard-btn);
334
+ transition: all 0.15s ease;
335
  }
336
  button.btn-send:hover {
337
+ transform: translate(-1px, -1px);
338
+ box-shadow: 5px 5px 0px var(--ink);
339
+ background: #f97316;
340
+ color: white;
341
  }
342
 
343
+ /* Right Comic Display Panel */
344
  .comic-card {
345
  display: flex;
346
  flex-direction: column;
 
350
  justify-content: center;
351
  }
352
  .comic-placeholder {
353
+ color: #6b7280;
354
+ font-size: 1.15rem;
355
+ font-weight: 700;
356
  padding: 48px 20px;
357
  }
358
  .comic-title {
359
+ font-size: 1.45rem;
360
+ font-weight: 700;
361
+ color: var(--ink);
362
+ margin-bottom: 12px;
363
+ background: var(--comic-yellow);
364
+ border: 2px solid var(--ink);
365
+ padding: 6px 16px;
366
+ border-radius: 8px;
367
+ box-shadow: var(--shadow-hard-sm);
368
+ display: inline-block;
369
  }
370
  .comic-img-wrap {
371
  background: #ffffff;
372
+ padding: 14px;
373
+ border: 3px solid var(--ink);
374
+ border-radius: 12px;
375
  margin: 12px 0;
376
  max-width: 100%;
377
+ box-shadow: var(--shadow-hard);
 
378
  }
379
  .comic-img {
380
  max-width: 100%;
381
  max-height: 290px;
382
  object-fit: contain;
383
  display: block;
 
384
  }
385
  .comic-alt {
386
+ font-size: 0.95rem;
387
+ color: var(--ink);
388
+ background: #fef3c7;
389
+ border: 2px solid var(--ink);
390
  padding: 12px 16px;
391
+ border-radius: 10px;
392
  text-align: left;
393
+ margin-top: 12px;
394
+ line-height: 1.45;
395
+ font-weight: 700;
396
+ box-shadow: var(--shadow-hard-sm);
397
  }
398
  </style>
399
  </head>
 
401
  <header>
402
  <div class="badge-row">
403
  <a class="badge" href="https://huggingface.co/pipecat-ai/phonellm-alpha-1" target="_blank">πŸŽ™οΈ PhoneLLM Alpha 1</a>
404
+ <span class="badge mint">⚑ Pipecat Engine</span>
405
+ <a class="badge cyan" href="https://xkcd.hemanth.deno.net/" target="_blank">🌐 xkcd.hemanth.deno.net</a>
406
  </div>
407
+ <h1>XKCD Comic Voice Agent</h1>
408
+ <p class="sub">Talk or type naturally. PhoneLLM finds the strip and reads the punchline!</p>
409
  </header>
410
 
411
  <main class="main-grid">
412
+ <!-- Left Voice & Speech Bubble Panel -->
413
  <div class="panel">
414
+ <div class="panel-header-strip">
415
+ <span>πŸ”Š <strong>ON-DEVICE VOICE:</strong></span>
416
  <select id="voice-select">
417
+ <option value="default">Loading neural voices...</option>
418
  </select>
419
  </div>
420
 
 
422
  <div id="voice-orb" class="voice-orb" title="Click to Speak / Stop">
423
  <svg class="mic-icon" viewBox="0 0 24 24"><path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z"/><path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"/></svg>
424
  </div>
425
+ <span id="status-badge" class="status-badge">Tap mic orb or click a prompt!</span>
426
  </div>
427
 
428
  <div class="chips-row">
 
437
 
438
  <div class="input-row">
439
  <input id="user-input" type="text" placeholder="Speak or type (e.g. 'Show me a comic on Python')..." />
440
+ <button id="send-btn" class="btn-send">SEND πŸ’₯</button>
441
  </div>
442
  </div>
443
 
444
+ <!-- Right Live Comic Frame -->
445
  <div class="panel">
446
  <div id="comic-card" class="comic-card">
447
  <div class="comic-placeholder">
448
+ <p>πŸ—―οΈ Ask for an XKCD comic to see the panel drawn here!</p>
 
449
  </div>
450
  </div>
451
  </div>
 
552
 
553
  utterance.onend = () => {
554
  orb.classList.remove('speaking');
555
+ statusBadge.textContent = "Ready!";
556
  };
557
  utterance.onerror = () => {
558
  orb.classList.remove('speaking');
559
+ statusBadge.textContent = "Ready!";
560
  };
561
 
562
  window.speechSynthesis.speak(utterance);
 
650
  if (toolName) {
651
  const toolPill = document.createElement('div');
652
  toolPill.className = 'tool-call-pill';
653
+ toolPill.innerHTML = `<span>⚑ TOOL:</span> <span>${toolName}</span>`;
654
  msg.appendChild(toolPill);
655
  }
656
 
 
665
  function renderComic(comic) {
666
  if (!comic || comic.error) return;
667
  comicCard.innerHTML = `
668
+ <div class="comic-title">#${comic.num}: ${comic.title} (${comic.year || ''})</div>
669
  <div class="comic-img-wrap">
670
  <img class="comic-img" src="${comic.img}" alt="${comic.alt || ''}" />
671
  </div>
672
+ <div class="comic-alt"><strong>πŸ—―οΈ PUNCHLINE:</strong> ${comic.alt || 'No alt text'}</div>
673
  `;
674
  }
675
 
 
696
  isListening = true;
697
  orb.classList.remove('speaking');
698
  orb.classList.add('listening');
699
+ statusBadge.textContent = "πŸŽ™οΈ Listening to you...";
700
  };
701
 
702
  recognition.onresult = (event) => {
 
707
  recognition.onend = () => {
708
  isListening = false;
709
  orb.classList.remove('listening');
710
+ statusBadge.textContent = "⚑ Processing...";
711
  };
712
 
713
  recognition.onerror = () => {
 
733
 
734
  // Initial greeting
735
  setTimeout(() => {
736
+ addMessage("πŸ—―οΈ Hey! I am your XKCD voice bot powered by PhoneLLM Alpha 1. Ask me for comics on Python, Git, SQL, or say 'Random'!", "agent");
737
  }, 400);
738
  </script>
739
  </body>