ArtieAI commited on
Commit
52be621
·
verified ·
1 Parent(s): f2b2a80

Add animated talking face: blinks idly, looks around while thinking, mouth moves while speaking

Browse files
Files changed (1) hide show
  1. index.html +41 -4
index.html CHANGED
@@ -21,6 +21,19 @@
21
  border-bottom:1px solid var(--border); }
22
  header .logo { width:34px; height:34px; border-radius:10px; display:flex; align-items:center; justify-content:center;
23
  font-size:20px; background:linear-gradient(135deg,var(--accent),var(--accent2)); }
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  header h1 { font-size:18px; margin:0; }
25
  header .tag { font-size:11px; color:var(--muted); border:1px solid var(--border); border-radius:20px; padding:3px 10px; }
26
  header .spacer { flex:1; }
@@ -99,7 +112,17 @@
99
  </head>
100
  <body>
101
  <header>
102
- <div class="logo">😊</div>
 
 
 
 
 
 
 
 
 
 
103
  <h1>ArtieAI</h1>
104
  <span class="tag">free · no sign-up</span>
105
  <span class="tag" id="usageTag">…</span>
@@ -291,7 +314,7 @@ async function runTurn(userText, opts){
291
  const via = opts.via || ask;
292
  const doneRe = opts.doneMarker ? new RegExp(opts.doneMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")) : null;
293
  addMsg("user", md(userText));
294
- sendBtn.disabled = true; typing.style.display = "block";
295
  try{
296
  let contextMsg = userText;
297
  if(FACTUAL.test(userText)){
@@ -353,7 +376,7 @@ async function runTurn(userText, opts){
353
  else if(m.includes("upstream")) addMsg("system", "⏳ The free AI lane is busy right now — try again in a bit. (This didn't use any of today's chats.)");
354
  else addMsg("system", "⚠️ The free AI services are both having trouble right now. Try again in a bit — it's not your connection. (" + esc(m.slice(0,160)) + ")");
355
  }
356
- typing.style.display = "none"; sendBtn.disabled = false;
357
  updateUsage();
358
  chat.scrollTop = chat.scrollHeight;
359
  }
@@ -453,14 +476,16 @@ function speak(text){
453
  if(!clean.trim()) return;
454
  const u = new SpeechSynthesisUtterance(clean);
455
  u.rate = 1; u.pitch = 1;
 
456
  synth.speak(u);
 
457
  }catch(e){}
458
  }
459
  voiceBtn.onclick = () => {
460
  if(!synth) return;
461
  voiceOut = !voiceOut;
462
  save("agentx_voice_out", voiceOut);
463
- if(!voiceOut) synth.cancel();
464
  paintVoiceBtn();
465
  addMsg("system", voiceOut ? "<b>Voice ON</b> — I'll read replies aloud." : "<b>Voice OFF</b>.");
466
  };
@@ -488,6 +513,18 @@ if(!SR){
488
  };
489
  }
490
 
 
 
 
 
 
 
 
 
 
 
 
 
491
  /* ---------- modal helpers ---------- */
492
  const back = $("modalBack"), modal = $("modal");
493
  function openModal(html){ modal.innerHTML = '<span class="close-x" id="mx">×</span>' + html; back.classList.add("open");
 
21
  border-bottom:1px solid var(--border); }
22
  header .logo { width:34px; height:34px; border-radius:10px; display:flex; align-items:center; justify-content:center;
23
  font-size:20px; background:linear-gradient(135deg,var(--accent),var(--accent2)); }
24
+ /* Talking face: blinks idly, looks around while thinking, mouth moves while speaking. Pure CSS/SVG. */
25
+ #face svg { width:100%; height:100%; display:block; }
26
+ #face .eye { fill:#fff; }
27
+ #face .pupil { fill:#1e1b4b; }
28
+ #face .mouth { fill:#fff; transform-box:fill-box; transform-origin:center; }
29
+ #face .eyes-blink { transform-box:fill-box; transform-origin:center; animation:faceBlink 4.6s infinite; }
30
+ @keyframes faceBlink { 0%,92%,100% { transform:scaleY(1); } 95.5% { transform:scaleY(.08); } }
31
+ #face.thinking .eyes-look { animation:faceLook 1.2s ease-in-out infinite; }
32
+ @keyframes faceLook { 0%,100% { transform:translateX(0); } 30% { transform:translateX(-1.8px); } 70% { transform:translateX(1.8px); } }
33
+ #face.thinking svg { animation:faceTilt 1.2s ease-in-out infinite; }
34
+ @keyframes faceTilt { 0%,100% { transform:rotate(-5deg); } 50% { transform:rotate(5deg); } }
35
+ #face.talking .mouth { animation:faceTalk .34s ease-in-out infinite; }
36
+ @keyframes faceTalk { 0%,100% { transform:scaleY(.3); } 50% { transform:scaleY(1.5); } }
37
  header h1 { font-size:18px; margin:0; }
38
  header .tag { font-size:11px; color:var(--muted); border:1px solid var(--border); border-radius:20px; padding:3px 10px; }
39
  header .spacer { flex:1; }
 
112
  </head>
113
  <body>
114
  <header>
115
+ <div class="logo" id="face" title="Artie">
116
+ <svg viewBox="0 0 40 40" aria-hidden="true">
117
+ <g class="eyes-blink"><g class="eyes-look">
118
+ <ellipse class="eye" cx="13.5" cy="15" rx="3.4" ry="4.2"/>
119
+ <ellipse class="eye" cx="26.5" cy="15" rx="3.4" ry="4.2"/>
120
+ <circle class="pupil" cx="13.5" cy="16" r="1.5"/>
121
+ <circle class="pupil" cx="26.5" cy="16" r="1.5"/>
122
+ </g></g>
123
+ <rect class="mouth" x="13" y="24.5" width="14" height="3.4" rx="1.7"/>
124
+ </svg>
125
+ </div>
126
  <h1>ArtieAI</h1>
127
  <span class="tag">free · no sign-up</span>
128
  <span class="tag" id="usageTag">…</span>
 
314
  const via = opts.via || ask;
315
  const doneRe = opts.doneMarker ? new RegExp(opts.doneMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")) : null;
316
  addMsg("user", md(userText));
317
+ sendBtn.disabled = true; typing.style.display = "block"; faceThinking(true);
318
  try{
319
  let contextMsg = userText;
320
  if(FACTUAL.test(userText)){
 
376
  else if(m.includes("upstream")) addMsg("system", "⏳ The free AI lane is busy right now — try again in a bit. (This didn't use any of today's chats.)");
377
  else addMsg("system", "⚠️ The free AI services are both having trouble right now. Try again in a bit — it's not your connection. (" + esc(m.slice(0,160)) + ")");
378
  }
379
+ typing.style.display = "none"; sendBtn.disabled = false; faceThinking(false);
380
  updateUsage();
381
  chat.scrollTop = chat.scrollHeight;
382
  }
 
476
  if(!clean.trim()) return;
477
  const u = new SpeechSynthesisUtterance(clean);
478
  u.rate = 1; u.pitch = 1;
479
+ u.onend = u.onerror = () => faceTalking(false);
480
  synth.speak(u);
481
+ faceTalking(true);
482
  }catch(e){}
483
  }
484
  voiceBtn.onclick = () => {
485
  if(!synth) return;
486
  voiceOut = !voiceOut;
487
  save("agentx_voice_out", voiceOut);
488
+ if(!voiceOut){ synth.cancel(); faceTalking(false); }
489
  paintVoiceBtn();
490
  addMsg("system", voiceOut ? "<b>Voice ON</b> — I'll read replies aloud." : "<b>Voice OFF</b>.");
491
  };
 
513
  };
514
  }
515
 
516
+ /* ---------- Talking face state ---------- */
517
+ const face = $("face");
518
+ let talkWatch = null;
519
+ function faceThinking(on){ face.classList.toggle("thinking", !!on); }
520
+ function faceTalking(on){
521
+ face.classList.toggle("talking", !!on);
522
+ if(talkWatch){ clearInterval(talkWatch); talkWatch = null; }
523
+ if(on && synth){
524
+ talkWatch = setInterval(() => { if(!synth.speaking) faceTalking(false); }, 400);
525
+ }
526
+ }
527
+
528
  /* ---------- modal helpers ---------- */
529
  const back = $("modalBack"), modal = $("modal");
530
  function openModal(html){ modal.innerHTML = '<span class="close-x" id="mx">×</span>' + html; back.classList.add("open");