Improve mobile UI responsiveness, touch tap-to-translate, and PWA icon
This commit is contained in:
1 parent
5f013dbdfe
commit
c3e66b4c98
6 files changed
+442
-28
No files matched your search
@@ -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 ? `<div class="hover-tt-note" style="color:#a1a1aa;font-size:0.74rem;margin-top:0.15rem;">Plural: ${info.plural}</div>` : ""}
|
||||
`;
|
||||
|
||||
const vw = window.innerWidth || 360;
|
||||
const maxW = Math.min(300, vw - 24);
|
||||
const halfW = Math.round(maxW / 2) + 12;
|
||||
const left = Math.min(
|
||||
window.innerWidth - 160,
|
||||
Math.max(160, rect.left + rect.width / 2)
|
||||
Math.max(halfW, vw - halfW),
|
||||
Math.max(halfW, rect.left + rect.width / 2)
|
||||
);
|
||||
const top = Math.max(55, rect.top);
|
||||
const top = Math.max(64, rect.top);
|
||||
|
||||
// Apply explicit inline critical styles in addition to CSS class so it is 100% immune to stale CSS cache
|
||||
Object.assign(tooltip.style, {
|
||||
@@ -374,7 +404,7 @@ class WortKraftApp {
|
||||
transform: "translate(-50%, -100%) translateY(-8px)",
|
||||
opacity: "1",
|
||||
display: "block",
|
||||
maxWidth: "310px",
|
||||
maxWidth: `${maxW}px`,
|
||||
fontSize: "0.84rem",
|
||||
lineHeight: "1.35",
|
||||
border: "1px solid rgba(255,255,255,0.16)"
|
||||
@@ -2368,7 +2398,7 @@ class WortKraftApp {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.75rem; margin: 1.25rem 0;">
|
||||
<div class="prep-options-grid">
|
||||
${optionsHtml}
|
||||
</div>
|
||||
|
||||
@@ -2416,7 +2446,7 @@ class WortKraftApp {
|
||||
<div class="cloze-en" style="margin-top: 0.3rem;">Gesuchtes Verb: <strong>${d.verb} (${d.english})</strong></div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.65rem; margin: 1.25rem 0;">
|
||||
<div class="dawo-options-grid">
|
||||
${daChoices
|
||||
.map(
|
||||
ch => `
|
||||
|
||||
Reference in new issue
Block a user