← Commit history

Publish 1.9.18

John Lauer ·5830127d79 ·3mo ago ·parent 041034b
6 files changed +51−11
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-tts"-version = "1.9.17"+version = "1.9.18" edition = "2021" license = "AGPL-3.0-or-later" repository = "https://github.com/adom-inc/adom-tts"
VERSION+1−1
@@ -1 +1 @@-1.9.17+1.9.18
bin/adom-tts
⋯ 1 unchanged line ⋯
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-tts",-  "version": "1.9.17",+  "version": "1.9.18",   "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.17",+  "version": "1.9.18",   "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+34−7
@@ -68,9 +68,22 @@   .app-subtitle { font-family:'Familjen Grotesk',system-ui,sans-serif; font-weight:500; font-size:13px; color: var(--muted); }   .sep { color: var(--border); margin: 0 1px; }   .vsep { width:1px; height:20px; background: var(--border); flex: none; }-  /* 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; }+  /* Zone 2: subject — truncates when tight; hover/tap reveals the full story */+  .subject { display:flex; align-items:baseline; gap:8px; min-width:0; flex:1; overflow:visible; position:relative; cursor:default; outline:none; }+  .subject-name { font-size:14px; font-weight:600; color: var(--fg); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; flex:0 1 auto; min-width:4.5em; }+  /* rich tooltip: everything the truncated header hides, in one card */+  .subject-pop {+    position:absolute; top: calc(100% + 10px); left: 0; width: min(340px, 86vw); z-index: 30;+    background: var(--elevated); border:1px solid var(--border); border-radius:12px;+    padding:13px 15px; box-shadow: 0 14px 40px rgba(0,0,0,.5);+    opacity:0; transform: translateY(-4px); pointer-events:none; transition: opacity .14s, transform .14s;+  }+  .subject:hover .subject-pop, .subject:focus-within .subject-pop, .subject.open .subject-pop { opacity:1; transform:none; pointer-events:auto; }+  .sp-desc { font-size:13px; color: var(--fg); line-height:1.5; margin-bottom:10px; }+  .sp-row { display:flex; gap:10px; font-size:12px; margin:4px 0; }+  .sp-k { color: var(--muted); width:52px; flex:none; text-transform:uppercase; letter-spacing:.5px; font-size:10px; padding-top:1px; }+  .sp-v { color: var(--fg); min-width:0; overflow-wrap:anywhere; }+  .sp-v.mono { font-family: ui-monospace,Menlo,monospace; font-size:11.5px; }   /* 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); }@@ -241,10 +254,16 @@      <div class="vsep" aria-hidden="true"></div> -    <!-- Zone 2: SUBJECT (what's playing right now) -->-    <div class="subject">+    <!-- Zone 2: SUBJECT (what's playing right now); hover/tap for the full card -->+    <div class="subject" id="subject" tabindex="0">       <span class="subject-name" id="subjectName">waiting for a clip…</span>       <span class="subject-meta" id="subjectMeta"></span>+      <div class="subject-pop" id="subjectPop" role="tooltip">+        <div class="sp-desc" id="spDesc">No clip yet — the next `say --play` from any thread lands here.</div>+        <div class="sp-row"><span class="sp-k">thread</span><span class="sp-v" id="spThread">—</span></div>+        <div class="sp-row"><span class="sp-k">file</span><span class="sp-v mono" id="spFile">—</span></div>+        <div class="sp-row"><span class="sp-k">voice</span><span class="sp-v mono" id="spVoice">—</span></div>+      </div>     </div>      <!-- Zone 3: ACTIONS / status + live-link LED (always visible, even skinny) -->@@ -371,8 +390,11 @@     subjectName.textContent = c.label || c.title || 'audio clip';     metaBase = c.desc ? c.desc : (c.meta || '');     meta.textContent = metaBase;-    var subj = document.querySelector('.subject');-    if (subj) subj.title = (c.title || '') + (c.meta ? ' · ' + c.meta : '');+    // rich tooltip card: the untruncated story of the current clip+    document.getElementById('spDesc').textContent = c.desc || '(no description sent)';+    document.getElementById('spThread').textContent = c.label || '—';+    document.getElementById('spFile').textContent = c.title || '—';+    document.getElementById('spVoice').textContent = c.meta || '—';     threadChip.style.display = 'none';  // thread now leads the subject zone     buildTranscript(c.words || []);     // reset transport UI for the fresh clip@@ -410,6 +432,11 @@   function showPlayError(err){ lastErr=(err&&err.name||'PlayError')+(err&&err.message?': '+err.message:''); if(err&&err.name==='NotAllowedError'){setStatus('tap play ▶','paused');playBtn.classList.add('attention');setIcon('play');} else setStatus('play failed','error'); updateDiag(); ev('playreject','&msg='+encodeURIComponent(((err&&err.name)||'')+' '+((err&&err.message)||'').slice(0,60))); }   playBtn.addEventListener('click', () => { if (a.ended) { a.currentTime=0; tryPlay(); } else if (a.paused) tryPlay(); else a.pause(); }); +  // tap toggles the subject card (hover covers mouse; this covers touch)+  var subjEl = document.getElementById('subject');+  subjEl.addEventListener('click', function(e){ if (e.target.closest('.subject-pop')) return; subjEl.classList.toggle('open'); });+  document.addEventListener('click', function(e){ if (!subjEl.contains(e.target)) subjEl.classList.remove('open'); });+   seek.addEventListener('input', () => { seeking = true; if (isFinite(a.duration)) { a.currentTime = (seek.value/1000)*a.duration; curEl.textContent = fmt(a.currentTime); } });   seek.addEventListener('change', () => { seeking = false; });