instant-hover tooltip + always-visible note labels

- replaced SVG <title> tooltips (which use the OS dwell-delay) with an instant custom tooltip rendered in #custom-tooltip
- note pins now also render their text inline next to the icon, so a route can be read at a glance without hovering each pin (truncated at 80 chars in the SVG; full text still in the tooltip)
This commit is contained in:
2026-04-25 23:01:43 +02:00
parent e6c457a400
commit 71bef7b8ff
2 changed files with 112 additions and 19 deletions
+92 -19
View File
@@ -223,20 +223,40 @@ function makeNotePin(note, idx) {
c.setAttribute("stroke", "#1a1208"); c.setAttribute("stroke", "#1a1208");
c.setAttribute("stroke-width", "3"); c.setAttribute("stroke-width", "3");
g.appendChild(c); g.appendChild(c);
const t = document.createElementNS(SVG_NS, "text"); const i = document.createElementNS(SVG_NS, "text");
t.setAttribute("y", 9); i.setAttribute("y", 9);
t.setAttribute("font-size", "26"); i.setAttribute("font-size", "26");
t.setAttribute("text-anchor", "middle"); i.setAttribute("text-anchor", "middle");
t.setAttribute("fill", "#1a1208"); i.setAttribute("fill", "#1a1208");
t.setAttribute("font-weight", "900"); i.setAttribute("font-weight", "900");
t.setAttribute("font-style", "italic"); i.setAttribute("font-style", "italic");
t.setAttribute("font-family", "Georgia, serif"); i.setAttribute("font-family", "Georgia, serif");
t.textContent = "i"; i.textContent = "i";
g.appendChild(t); g.appendChild(i);
const title = document.createElementNS(SVG_NS, "title"); // Always-visible label: text rendered next to the pin in image-pixel
title.textContent = note.text || "(empty)"; // space, with a dark stroke for readability over the parchment.
g.appendChild(title); if (note.text) {
const label = document.createElementNS(SVG_NS, "text");
label.setAttribute("class", "note-label");
label.setAttribute("x", 30); // sits to the right of the circle
label.setAttribute("y", 10);
label.setAttribute("font-size", "28");
label.setAttribute("font-family", "system-ui, sans-serif");
label.setAttribute("font-weight", "600");
label.setAttribute("fill", "#fff7d6");
label.setAttribute("stroke", "#1a1208");
label.setAttribute("stroke-width", "5");
label.setAttribute("paint-order", "stroke");
// Truncate very long text on the SVG side to ~80 chars so it doesn't
// overflow the map. The full text is still in the data tooltip.
const truncated = note.text.length > 80 ? note.text.slice(0, 77) + "..." : note.text;
label.textContent = truncated;
g.appendChild(label);
}
// Custom-tooltip data (instant, used by ensureTooltip).
g.dataset.tooltip = note.text || "(empty)";
// drag-to-move + right-click delete + double-click to edit text // drag-to-move + right-click delete + double-click to edit text
g.addEventListener("pointerdown", (e) => { g.addEventListener("pointerdown", (e) => {
@@ -325,10 +345,8 @@ function makeEnemyPin(e) {
t.textContent = "☠"; t.textContent = "☠";
g.appendChild(t); g.appendChild(t);
} }
const title = document.createElementNS(SVG_NS, "title");
const tag = cls === 5 ? " (rare)" : (e.skippable ? " (skippable)" : ""); const tag = cls === 5 ? " (rare)" : (e.skippable ? " (skippable)" : "");
title.textContent = e.name + tag; g.dataset.tooltip = e.name + tag;
g.appendChild(title);
return g; return g;
} }
@@ -361,9 +379,9 @@ function makeIconMarker(ic) {
g.appendChild(r); g.appendChild(r);
} }
if (ic.comment) { if (ic.comment) {
const title = document.createElementNS(SVG_NS, "title"); g.dataset.tooltip = ic.comment;
title.textContent = ic.comment; } else if (ic.type) {
g.appendChild(title); g.dataset.tooltip = ic.type;
} }
return g; return g;
} }
@@ -559,6 +577,60 @@ function zoomBy(factor, anchorX, anchorY) {
applyView(); applyView();
} }
/* --- instant tooltip ------------------------------------------------------
* SVG <title> uses the OS tooltip with a long delay. We render our own div
* so it shows the moment the cursor enters a pin/icon. Driven by a single
* pointer listener on #overlay that walks up to find any [data-tooltip].
*/
function ensureTooltip() {
let el = document.getElementById("custom-tooltip");
if (el) return el;
el = document.createElement("div");
el.id = "custom-tooltip";
el.className = "custom-tooltip";
document.body.appendChild(el);
return el;
}
function hookTooltip() {
const tip = ensureTooltip();
const host = $("canvas-host");
let activeText = "";
host.addEventListener("pointermove", (e) => {
const target = e.target.closest("[data-tooltip]");
if (!target) {
tip.classList.remove("show");
activeText = "";
return;
}
const text = target.dataset.tooltip;
if (!text) {
tip.classList.remove("show");
return;
}
if (text !== activeText) {
tip.textContent = text;
activeText = text;
}
// Position near cursor, but keep on-screen
const pad = 14;
let x = e.clientX + pad;
let y = e.clientY + pad;
const w = tip.offsetWidth, h = tip.offsetHeight;
if (x + w + pad > window.innerWidth) x = e.clientX - w - pad;
if (y + h + pad > window.innerHeight) y = e.clientY - h - pad;
tip.style.left = x + "px";
tip.style.top = y + "px";
tip.classList.add("show");
});
host.addEventListener("pointerleave", () => {
tip.classList.remove("show");
activeText = "";
});
}
function hookCanvasPanZoom() { function hookCanvasPanZoom() {
const host = $("canvas-host"); const host = $("canvas-host");
@@ -847,6 +919,7 @@ function hookEvents() {
}); });
window.addEventListener("hashchange", loadFromHash); window.addEventListener("hashchange", loadFromHash);
hookCanvasPanZoom(); hookCanvasPanZoom();
hookTooltip();
} }
init().catch((e) => { init().catch((e) => {
+20
View File
@@ -373,6 +373,26 @@ body {
} }
.waypoint-list li button:hover { color: var(--boss); } .waypoint-list li button:hover { color: var(--boss); }
/* --- instant tooltip ------------------------------------------------------ */
.custom-tooltip {
position: fixed;
pointer-events: none;
z-index: 200;
background: rgba(20, 18, 24, 0.96);
color: var(--text);
border: 1px solid var(--accent);
border-radius: 4px;
padding: 6px 10px;
font-size: 13px;
line-height: 1.35;
max-width: 360px;
box-shadow: 0 6px 18px rgba(0, 0, 0, .45);
opacity: 0;
transition: opacity .08s ease;
white-space: pre-wrap;
}
.custom-tooltip.show { opacity: 1; }
/* --- toast ---------------------------------------------------------------- */ /* --- toast ---------------------------------------------------------------- */
.toast { .toast {
position: fixed; position: fixed;