diff --git a/app/web/src/main.js b/app/web/src/main.js index 57b1a8a..466f93e 100644 --- a/app/web/src/main.js +++ b/app/web/src/main.js @@ -604,6 +604,10 @@ async function openRecording(id) { bubble.className = `tbubble ${scls[tu.sp] || ''} arrow-${seat.side}`; bubble.style.left = ''; bubble.style.top = ''; + // Tail x-position along the top/bottom edge = the speaker's seat mapped + // into bubble-local coordinates (bubble is centred, BUBBLE_W wide). + const ax = Math.max(8, Math.min(92, (seat.x - (50 - BUBBLE_W / 2)) / BUBBLE_W * 100)); + bubble.style.setProperty('--ax', ax.toFixed(1) + '%'); bubble.querySelector('.tb-name').textContent = spkLabel(tu.sp, i >= 0 ? i : 0); // The full turn as clickable word spans — highlighted + scrolled below. bubble.querySelector('.tb-text').innerHTML = @@ -746,11 +750,14 @@ function escapeHtml(s) { return (s || '').replace(/[&<>"]/g, c => ({ '&': '& const SEAT_LAYOUTS = { 1: [{ x: 50, y: 84, side: 'bottom' }], 2: [{ x: 50, y: 14, side: 'top' }, { x: 50, y: 84, side: 'bottom' }], - 3: [{ x: 50, y: 13, side: 'top' }, { x: 26, y: 84, side: 'bottomleft' }, { x: 74, y: 84, side: 'bottomright' }], + 3: [{ x: 50, y: 13, side: 'top' }, { x: 26, y: 84, side: 'bottom' }, { x: 74, y: 84, side: 'bottom' }], 4: [{ x: 50, y: 12, side: 'top' }, { x: 90, y: 50, side: 'right' }, { x: 50, y: 86, side: 'bottom' }, { x: 10, y: 50, side: 'left' }], 5: [{ x: 32, y: 12, side: 'top' }, { x: 68, y: 12, side: 'top' }, { x: 90, y: 60, side: 'right' }, { x: 50, y: 86, side: 'bottom' }, { x: 10, y: 60, side: 'left' }], - 6: [{ x: 32, y: 12, side: 'top' }, { x: 68, y: 12, side: 'top' }, { x: 90, y: 50, side: 'right' }, { x: 68, y: 86, side: 'bottomright' }, { x: 32, y: 86, side: 'bottomleft' }, { x: 10, y: 50, side: 'left' }], + 6: [{ x: 32, y: 12, side: 'top' }, { x: 68, y: 12, side: 'top' }, { x: 90, y: 50, side: 'right' }, { x: 68, y: 86, side: 'bottom' }, { x: 32, y: 86, side: 'bottom' }, { x: 10, y: 50, side: 'left' }], }; +// Bubble width in scene-% (kept in sync with .tbubble in style.css) — needed to +// map a seat's x onto the bubble edge so the tail points AT the speaker. +const BUBBLE_W = 54; function tableSeats(n) { return SEAT_LAYOUTS[Math.min(6, Math.max(1, n))] || SEAT_LAYOUTS[6]; } // Toggle-button icons: three people around a table / a text sheet. diff --git a/app/web/src/style.css b/app/web/src/style.css index 9a2316f..15e2043 100644 --- a/app/web/src/style.css +++ b/app/web/src/style.css @@ -316,12 +316,10 @@ audio { width: 100%; margin-bottom: 10px; } display: none; } .tbubble[class*="arrow-"]::after { display: block; } -.tbubble.arrow-top::after { top: -7px; left: 50%; transform: translateX(-50%) rotate(-135deg); } -.tbubble.arrow-bottom::after { bottom: -7px; left: 50%; transform: translateX(-50%) rotate(45deg); } +.tbubble.arrow-top::after { top: -7px; left: var(--ax, 50%); transform: translateX(-50%) rotate(-135deg); } +.tbubble.arrow-bottom::after { bottom: -7px; left: var(--ax, 50%); transform: translateX(-50%) rotate(45deg); } .tbubble.arrow-left::after { left: -7px; top: 50%; transform: translateY(-50%) rotate(135deg); } .tbubble.arrow-right::after { right: -7px; top: 50%; transform: translateY(-50%) rotate(-45deg); } -.tbubble.arrow-bottomleft::after { bottom: -7px; left: 22%; transform: rotate(45deg); } -.tbubble.arrow-bottomright::after { bottom: -7px; right: 22%; transform: rotate(45deg); } .tidle { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); font-size: 13px; text-align: center; } #viewswitch svg { display: block; } diff --git a/docs/08-changelog.md b/docs/08-changelog.md index 2668580..08febf3 100644 --- a/docs/08-changelog.md +++ b/docs/08-changelog.md @@ -3,6 +3,13 @@ Chronik der Entwicklungsschritte. **Regel: jede zukünftige Änderung bekommt hier einen Eintrag** (Datum, Was, Warum, betroffene Dateien/Backups). +## 2026-07-17 — GUI: Blasen-Oeffnung zeigt exakt auf den Sprecher + +Bei mehreren Sprechern je Tischkante (5-/6-Sitz-Layouts) zeigte der Schwanz nur auf die Kantenmitte. +Jetzt wird die Sitz-x-Position in Blasen-Koordinaten umgerechnet (CSS-Variable --ax, BUBBLE_W in +main.js synchron zur CSS-Breite) — die Oeffnung wandert entlang der Ober-/Unterkante exakt zum +Sprechenden; bottomleft/bottomright-Sonderklassen entfallen. Bundle `index-FKbNFs-A.js`. + ## 2026-07-17 — GUI: Tisch-Blase fix, Oeffnung wandert zum Sprecher Feinschliff nach User-Feedback: Die Sprechblase liegt wieder FEST auf dem Tisch (54 % Breite),