← Commit history

Publish 1.9.15

John Lauer ·7db303a6c0 ·3mo ago ·parent e32dcd1
6 files changed +49−19
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-tts"-version = "1.9.14"+version = "1.9.15" edition = "2021" license = "AGPL-3.0-or-later" repository = "https://github.com/adom-inc/adom-tts"
VERSION+1−1
@@ -1 +1 @@-1.9.14+1.9.15
bin/adom-tts
⋯ 1 unchanged line ⋯
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-tts",-  "version": "1.9.14",+  "version": "1.9.15",   "type": "app",   "description": "Adom's shared text-to-speech CLI + Playground. Reads your AI answers back to you while driving, and renders perfectly-timed voiceover tracks for demo videos.",   "license": "MIT",
page.json+14−1
@@ -4,7 +4,7 @@   "slug": "adom-tts",   "title": "Adom TTS — Text to Speech",   "brief": "Adom's shared voice service. Reads your AI's answers back to you while you drive, and renders perfectly-timed voiceover tracks for your demo videos — so you never record your own voice.",-  "version": "1.9.14",+  "version": "1.9.15",   "license": "MIT",   "tags": [     "tts",@@ -12,6 +12,7 @@     "audio",     "demo",     "accessibility",+    "cli",     "open-source",     "mit"   ],@@ -64,5 +65,17 @@   ],   "install": {     "binary_name": "adom-tts"+  },+  "description": "Adom's shared text-to-speech CLI + Playground. Reads your AI answers back to you while driving, and renders perfectly-timed voiceover tracks for demo videos.",+  "dependencies": {},+  "scripts": {+    "install": "./install.sh",+    "uninstall": "./uninstall.sh"+  },+  "needs_sudo": false,+  "org": "adom",+  "author": {+    "name": "John Lauer",+    "email": "[email protected]"   } }\ No newline at end of file
src/player.html+32−15
@@ -14,8 +14,20 @@     --blue: #0061EF; --purple: #8C6BF7;     --amber: #d4a017; --red: #e0524d;   }-  * { box-sizing: border-box; }-  html, body { margin: 0; height: 100%; }+  * { box-sizing: border-box; min-width: 0; }+  /* HARD RULE: the outermost html NEVER shows a horizontal scrollbar. A truly+     fluid layout never overflows sideways; `clip` guarantees a stray pixel+     (negative margin, long token) can't summon one either. */+  html, body { margin: 0; height: 100%; overflow-x: clip; }++  /* Pretty scrollbars everywhere (transcript, history, page) — slim, rounded,+     quiet until hovered. Never the chunky default. */+  * { scrollbar-width: thin; scrollbar-color: rgba(0,184,177,.35) transparent; }+  *::-webkit-scrollbar { width: 7px; height: 7px; }+  *::-webkit-scrollbar-track { background: transparent; }+  *::-webkit-scrollbar-thumb { background: rgba(0,184,177,.28); border-radius: 999px; }+  *::-webkit-scrollbar-thumb:hover { background: rgba(0,184,177,.55); }+  *::-webkit-scrollbar-corner { background: transparent; }   body {     background:       radial-gradient(900px 420px at 85% -10%, rgba(140,107,247,0.12), transparent 60%),@@ -59,9 +71,9 @@   /* Zone 2: subject */   .subject { display:flex; align-items:baseline; gap:8px; min-width:0; flex:1; overflow:hidden; }   .subject-name { font-size:14px; font-weight:600; color: var(--fg); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }-  .subject-meta { font-family: ui-monospace,Menlo,monospace; font-size:11.5px; color: var(--muted); white-space:nowrap; }+  /* the clip DESCRIPTION (human sentence) — always visible, ellipsized when tight */+  .subject-meta { font-size:12.5px; color: var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; flex: 1 1 auto; }   .subject-meta .sep { color: var(--border); }-  @media (max-width: 720px){ .subject-meta { display:none; } }   /* Zone 3: actions */   .actions { display:flex; align-items:center; gap:8px; margin-left:auto; flex:none; }   .queue-chip { font-size:11.5px; color: var(--blue); border:1px solid rgba(0,97,239,0.5); background: rgba(0,97,239,0.12); padding:3px 9px; border-radius:999px; white-space:nowrap; }@@ -69,11 +81,15 @@   .thread-chip { font-size:11.5px; color: var(--purple); border:1px solid rgba(140,107,247,0.5); background: rgba(140,107,247,0.12); padding:3px 10px; border-radius:999px; white-space:nowrap; }   /* live-link LED — proves the front-end has a live pipe to the AI/back-end.      green=connected, amber=degraded, red/gray=disconnected (dead/orphaned tab). */-  .led { width:11px; height:11px; border-radius:50%; flex:none; margin-left:3px; transition: background .2s, box-shadow .2s, transform .15s; }-  .led.ok   { background:#3fb950; box-shadow:0 0 9px #3fb950; }-  .led.warn { background:var(--amber); box-shadow:0 0 9px var(--amber); }-  .led.down { background:#e0524d; box-shadow:0 0 8px rgba(224,82,77,.7); }-  .led.beat { transform: scale(1.5); }   /* quick pulse on each successful ping = "talking now" */+  .led { width:11px; height:11px; border-radius:50%; flex:none; margin-left:3px; transition: background .4s, box-shadow .4s; }+  /* GENTLE liveness: a slow breathing glow, never a jumpy per-ping pulse. */+  .led.ok   { background:#3fb950; animation: ledbreathe 3.2s ease-in-out infinite; }+  .led.warn { background:var(--amber); box-shadow:0 0 7px rgba(212,160,23,.5); }+  .led.down { background:#e0524d; box-shadow:0 0 6px rgba(224,82,77,.55); }+  @keyframes ledbreathe {+    0%,100% { box-shadow: 0 0 4px rgba(63,185,80,.35); }+    50%     { box-shadow: 0 0 10px rgba(63,185,80,.75); }+  }   .pill {     display: inline-flex; align-items: center; gap: 7px;     padding: 4px 11px; background: var(--elevated); border: 1px solid var(--border);@@ -350,12 +366,14 @@   let blobUrl = null;   function applyClip(c){     curClipId = c.id;-    subjectName.textContent = c.title || 'audio clip';-    // show WHAT this clip is about (the sender's --desc) next to the filename+    // Subject = WHO is speaking (AI thread) + WHAT the clip says (--desc).+    // The mp3 filename means nothing to a human — it lives in the tooltip only.+    subjectName.textContent = c.label || c.title || 'audio clip';     metaBase = c.desc ? c.desc : (c.meta || '');     meta.textContent = metaBase;-    if (c.label && c.label.toLowerCase() !== APP_NAME) { threadChip.textContent = c.label; threadChip.style.display = ''; }-    else { threadChip.style.display = 'none'; }+    var subj = document.querySelector('.subject');+    if (subj) subj.title = (c.title || '') + (c.meta ? ' · ' + c.meta : '');+    threadChip.style.display = 'none';  // thread now leads the subject zone     buildTranscript(c.words || []);     // reset transport UI for the fresh clip     lastErr = ''; seek.value = 0; curEl.textContent = '0:00'; durEl.textContent = '0:00';@@ -488,7 +506,6 @@       + '&done=' + (a.ended ? 1 : 0);     fetch(q, { cache: 'no-store' }).then(r => r.json()).then(j => {       lastPing = Date.now();-      led.classList.add('beat'); setTimeout(() => led.classList.remove('beat'), 160);       if (!j) return;       // queue depth chip       if (typeof j.queued === 'number' && j.queued > 0) { queueChip.textContent = '+' + j.queued + ' queued'; queueChip.classList.remove('hidden'); }@@ -514,7 +531,7 @@   setInterval(() => {     const age = Date.now() - lastPing;     const s = age < 2500 ? 'ok' : age < 6000 ? 'warn' : 'down';-    led.className = 'led ' + s + (led.classList.contains('beat') ? ' beat' : '');+    led.className = 'led ' + s;     led.title = 'AI ↔ player link — ' + (s === 'ok' ? 'live' : s === 'warn' ? 'degraded' : 'disconnected (dead tab)');   }, 1000); </script>