diff --git a/Dockerfile b/Dockerfile index 31a4131..ad0c97c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ ENV DATA_DIR=/app/store COPY package*.json ./ RUN npm install --omit=dev -COPY index.html styles.css app.js ./ +COPY index.html styles.css app.js icon.svg ./ COPY data/ ./data/ COPY server/ ./server/ diff --git a/app.js b/app.js index 70b1078..c01cb2c 100644 --- a/app.js +++ b/app.js @@ -218,38 +218,65 @@ class WortKraftApp { setupHoverListeners() { let lastHoveredWord = null; - document.addEventListener("mousemove", e => { - if (!this.hoverTranslateEnabled) return; + const handlePointLookup = (clientX, clientY, targetEl) => { + if (!this.hoverTranslateEnabled) return false; - // 1. Check if directly over an explicit .word-token span - const tokenEl = e.target.closest?.(".word-token"); + // 1. Check if directly over/tapping an explicit .word-token span + const tokenEl = targetEl?.closest?.(".word-token"); if (tokenEl) { const word = tokenEl.dataset.word || tokenEl.textContent.trim(); if (word) { this.showHoverTooltipForRect(tokenEl.getBoundingClientRect(), word, true); lastHoveredWord = word; - return; + return true; } } - // 2. Universal German word detector under cursor (works on headings, cards, buttons, chips, etc.) - const hit = this.getWordAtPoint(e.clientX, e.clientY); + // 2. Universal German word detector under cursor/tap + const hit = this.getWordAtPoint(clientX, clientY); if (hit && hit.word) { const info = this.lookupWordInfo(hit.word, null); - // Show tooltip if word is in our German dictionaries if (!info.needsLiveTranslate) { this.showHoverTooltipForRect(hit.rect, hit.word, false); lastHoveredWord = hit.word; - return; + return true; } } + return false; + }; - if (lastHoveredWord) { + document.addEventListener("mousemove", e => { + if (!this.hoverTranslateEnabled) return; + const matched = handlePointLookup(e.clientX, e.clientY, e.target); + if (!matched && lastHoveredWord) { lastHoveredWord = null; this.hideHoverTooltip(); } }); + // Mobile & Touch support: tap a word to show translation tooltip, tap elsewhere to dismiss + document.addEventListener( + "touchstart", + e => { + if (!this.hoverTranslateEnabled) return; + const touch = e.touches?.[0]; + if (!touch) return; + // Ignore taps on interactive controls like buttons/inputs unless it's a .word-token + const isToken = e.target?.closest?.(".word-token"); + const isInteractiveControl = e.target?.closest?.("button, input, select, textarea, a"); + if (isInteractiveControl && !isToken) { + this.hideHoverTooltip(); + return; + } + const matched = handlePointLookup(touch.clientX, touch.clientY, e.target); + if (!matched) { + lastHoveredWord = null; + this.hideHoverTooltip(); + } + }, + { passive: true } + ); + window.addEventListener("scroll", () => this.hideHoverTooltip(), { passive: true }); } @@ -353,11 +380,14 @@ class WortKraftApp { ${info.plural ? `