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:
+92
-19
@@ -223,20 +223,40 @@ function makeNotePin(note, idx) {
|
||||
c.setAttribute("stroke", "#1a1208");
|
||||
c.setAttribute("stroke-width", "3");
|
||||
g.appendChild(c);
|
||||
const t = document.createElementNS(SVG_NS, "text");
|
||||
t.setAttribute("y", 9);
|
||||
t.setAttribute("font-size", "26");
|
||||
t.setAttribute("text-anchor", "middle");
|
||||
t.setAttribute("fill", "#1a1208");
|
||||
t.setAttribute("font-weight", "900");
|
||||
t.setAttribute("font-style", "italic");
|
||||
t.setAttribute("font-family", "Georgia, serif");
|
||||
t.textContent = "i";
|
||||
g.appendChild(t);
|
||||
const i = document.createElementNS(SVG_NS, "text");
|
||||
i.setAttribute("y", 9);
|
||||
i.setAttribute("font-size", "26");
|
||||
i.setAttribute("text-anchor", "middle");
|
||||
i.setAttribute("fill", "#1a1208");
|
||||
i.setAttribute("font-weight", "900");
|
||||
i.setAttribute("font-style", "italic");
|
||||
i.setAttribute("font-family", "Georgia, serif");
|
||||
i.textContent = "i";
|
||||
g.appendChild(i);
|
||||
|
||||
const title = document.createElementNS(SVG_NS, "title");
|
||||
title.textContent = note.text || "(empty)";
|
||||
g.appendChild(title);
|
||||
// Always-visible label: text rendered next to the pin in image-pixel
|
||||
// space, with a dark stroke for readability over the parchment.
|
||||
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
|
||||
g.addEventListener("pointerdown", (e) => {
|
||||
@@ -325,10 +345,8 @@ function makeEnemyPin(e) {
|
||||
t.textContent = "☠";
|
||||
g.appendChild(t);
|
||||
}
|
||||
const title = document.createElementNS(SVG_NS, "title");
|
||||
const tag = cls === 5 ? " (rare)" : (e.skippable ? " (skippable)" : "");
|
||||
title.textContent = e.name + tag;
|
||||
g.appendChild(title);
|
||||
g.dataset.tooltip = e.name + tag;
|
||||
return g;
|
||||
}
|
||||
|
||||
@@ -361,9 +379,9 @@ function makeIconMarker(ic) {
|
||||
g.appendChild(r);
|
||||
}
|
||||
if (ic.comment) {
|
||||
const title = document.createElementNS(SVG_NS, "title");
|
||||
title.textContent = ic.comment;
|
||||
g.appendChild(title);
|
||||
g.dataset.tooltip = ic.comment;
|
||||
} else if (ic.type) {
|
||||
g.dataset.tooltip = ic.type;
|
||||
}
|
||||
return g;
|
||||
}
|
||||
@@ -559,6 +577,60 @@ function zoomBy(factor, anchorX, anchorY) {
|
||||
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() {
|
||||
const host = $("canvas-host");
|
||||
|
||||
@@ -847,6 +919,7 @@ function hookEvents() {
|
||||
});
|
||||
window.addEventListener("hashchange", loadFromHash);
|
||||
hookCanvasPanZoom();
|
||||
hookTooltip();
|
||||
}
|
||||
|
||||
init().catch((e) => {
|
||||
|
||||
@@ -373,6 +373,26 @@ body {
|
||||
}
|
||||
.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 {
|
||||
position: fixed;
|
||||
|
||||
Reference in New Issue
Block a user