diff --git a/data/units.json b/data/units.json
index 3eb4160..19907d3 100644
--- a/data/units.json
+++ b/data/units.json
@@ -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",
diff --git a/src/app.css b/src/app.css
index 21760fc..4f901e0 100644
--- a/src/app.css
+++ b/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;
+}
diff --git a/src/components/ConversionResult.svelte b/src/components/ConversionResult.svelte
index c3a677f..98fe5c9 100644
--- a/src/components/ConversionResult.svelte
+++ b/src/components/ConversionResult.svelte
@@ -73,8 +73,10 @@
N/A
{/if}
-
- {unit.labelPlural} ({unit.symbol})
+
+
+ {unit.labelPlural} ({unit.symbol}){#if description}ⓘ{/if}
+
diff --git a/src/lib/actions/tooltip.ts b/src/lib/actions/tooltip.ts
index 2fb00d1..9344be4 100644
--- a/src/lib/actions/tooltip.ts
+++ b/src/lib/actions/tooltip.ts
@@ -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 | 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');
}
};
}