fix: custom tooltip (no wiggle required), add ⓘ indicator for described units
- Replace native title-based tooltip with custom positioned div Native title only renders on mousemove; custom div fires on mouseenter after 500ms delay as intended - Tooltip styled dark with fade-in; Dan Mode gets yellow-on-black with magenta glow - Units with descriptions show a small superscript ⓘ on the result label
This commit is contained in:
@@ -62,7 +62,9 @@
|
||||
"labelPlural": "Metric fucktons",
|
||||
"symbol": "mfcktn",
|
||||
"group": "dickloads",
|
||||
"toBase": 170.2399999998
|
||||
"toBase": 170.2399999998,
|
||||
"description": "Trivia: although the name is \"metric fuckton,\" it is actually equal to two long tons!",
|
||||
"hidden": false
|
||||
},
|
||||
{
|
||||
"id": "assload",
|
||||
|
||||
27
src/app.css
27
src/app.css
@@ -448,3 +448,30 @@
|
||||
line-height: 1;
|
||||
border-radius: var(--pico-border-radius);
|
||||
}
|
||||
|
||||
/* ── Custom tooltip ────────────────────────────────────────────────────────── */
|
||||
.hu-tooltip {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
max-width: 280px;
|
||||
padding: 0.4rem 0.6rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.4;
|
||||
background: #1a1a2e;
|
||||
color: #f0f0f0;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.35);
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease;
|
||||
white-space: normal;
|
||||
}
|
||||
.hu-tooltip--visible {
|
||||
opacity: 1;
|
||||
}
|
||||
[data-theme=dan] .hu-tooltip {
|
||||
background: #000;
|
||||
color: #ffff00;
|
||||
border: 1px solid #ff00ff;
|
||||
box-shadow: 0 0 8px #ff00ff;
|
||||
}
|
||||
|
||||
@@ -73,8 +73,10 @@
|
||||
N/A
|
||||
{/if}
|
||||
</span>
|
||||
<!-- Line 2: label + symbol -->
|
||||
<span class="result-label" use:tooltip={description}>{unit.labelPlural} ({unit.symbol})</span>
|
||||
<!-- Line 2: label + symbol, with ⓘ indicator when description exists -->
|
||||
<span class="result-label" use:tooltip={description}>
|
||||
{unit.labelPlural} ({unit.symbol}){#if description}<span class="desc-indicator" aria-hidden="true">ⓘ</span>{/if}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@@ -93,4 +95,12 @@
|
||||
font-weight: 400;
|
||||
margin-right: 1px;
|
||||
}
|
||||
|
||||
.desc-indicator {
|
||||
font-size: 0.7em;
|
||||
opacity: 0.5;
|
||||
margin-left: 0.2em;
|
||||
vertical-align: super;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,15 +1,56 @@
|
||||
/**
|
||||
* Svelte action: show a native `title` tooltip after a 500ms hover delay.
|
||||
* Svelte action: show a custom tooltip after a 500ms hover delay.
|
||||
* Uses a positioned div instead of the native `title` attribute —
|
||||
* native tooltips only appear on mousemove, not on mouseenter, which
|
||||
* causes a "hover then wiggle" requirement that feels broken.
|
||||
*
|
||||
* If text is empty, does nothing.
|
||||
*/
|
||||
|
||||
const TOOLTIP_CLASS = 'hu-tooltip';
|
||||
|
||||
// Shared tooltip element — one per page, repositioned as needed.
|
||||
let tooltipEl: HTMLDivElement | null = null;
|
||||
|
||||
function getTooltipEl(): HTMLDivElement {
|
||||
if (!tooltipEl) {
|
||||
tooltipEl = document.createElement('div');
|
||||
tooltipEl.className = TOOLTIP_CLASS;
|
||||
document.body.appendChild(tooltipEl);
|
||||
}
|
||||
return tooltipEl;
|
||||
}
|
||||
|
||||
export function tooltip(node: HTMLElement, text: string) {
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
let currentText = text;
|
||||
|
||||
function enter() {
|
||||
if (!text) return;
|
||||
timer = setTimeout(() => {
|
||||
node.title = text;
|
||||
}, 500);
|
||||
function show(e: MouseEvent) {
|
||||
if (!currentText) return;
|
||||
const el = getTooltipEl();
|
||||
el.textContent = currentText;
|
||||
|
||||
// Position near cursor, clamped to viewport
|
||||
const x = Math.min(e.clientX + 12, window.innerWidth - el.offsetWidth - 8);
|
||||
const y = e.clientY + 20;
|
||||
el.style.left = `${x}px`;
|
||||
el.style.top = `${y}px`;
|
||||
el.classList.add('hu-tooltip--visible');
|
||||
}
|
||||
|
||||
function enter(e: MouseEvent) {
|
||||
if (!currentText) return;
|
||||
timer = setTimeout(() => show(e), 500);
|
||||
}
|
||||
|
||||
function move(e: MouseEvent) {
|
||||
// Keep tooltip near cursor while hovering
|
||||
if (!tooltipEl?.classList.contains('hu-tooltip--visible')) return;
|
||||
const el = getTooltipEl();
|
||||
const x = Math.min(e.clientX + 12, window.innerWidth - el.offsetWidth - 8);
|
||||
const y = e.clientY + 20;
|
||||
el.style.left = `${x}px`;
|
||||
el.style.top = `${y}px`;
|
||||
}
|
||||
|
||||
function leave() {
|
||||
@@ -17,24 +58,26 @@ export function tooltip(node: HTMLElement, text: string) {
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
}
|
||||
node.removeAttribute('title');
|
||||
tooltipEl?.classList.remove('hu-tooltip--visible');
|
||||
}
|
||||
|
||||
node.addEventListener('mouseenter', enter);
|
||||
node.addEventListener('mousemove', move);
|
||||
node.addEventListener('mouseleave', leave);
|
||||
|
||||
return {
|
||||
update(newText: string) {
|
||||
text = newText;
|
||||
// If title is currently set, update it immediately
|
||||
if (node.title) {
|
||||
node.title = newText || '';
|
||||
currentText = newText;
|
||||
if (tooltipEl?.classList.contains('hu-tooltip--visible')) {
|
||||
tooltipEl.textContent = newText;
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
if (timer !== null) clearTimeout(timer);
|
||||
node.removeEventListener('mouseenter', enter);
|
||||
node.removeEventListener('mousemove', move);
|
||||
node.removeEventListener('mouseleave', leave);
|
||||
tooltipEl?.classList.remove('hu-tooltip--visible');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user