natus tooltip: fix portal placement + viewport clamping + SVG sign icon
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful

- Move #id_natus_tooltip out of #id_applets_container (container-type:
  inline-size breaks position:fixed) → add to home.html alongside
  #id_tooltip_portal
- Move #id_natus_tooltip out of .natus-modal-wrap (transform breaks
  position:fixed) → place as sibling of .natus-overlay in room.html
- Add _positionTooltip() helper in natus-wheel.js: flips tooltip to
  left of cursor when it would overflow right edge; clamps both axes
- Replace hardcoded 280px in dashboard.js palette tooltip with measured
  offsetWidth; add left-edge floor (Math.max margin)
- Planet tooltip format: @14.0° Capricorn (<svg-icon>) using preloaded
  _signPaths; falls back to unicode symbol if not yet loaded
- Add .tt-sign-icon SCSS: fill:currentColor, vertical-align:middle

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-04-18 14:02:49 -04:00
parent f15b17f7bd
commit 09ed64080b
7 changed files with 40 additions and 17 deletions

View File

@@ -49,8 +49,10 @@ const bindPaletteSwatches = () => {
portal.style.display = 'block';
portal.style.position = 'fixed';
portal.style.top = `${rect.bottom + 8}px`;
portal.style.left = `${Math.min(rect.left, window.innerWidth - 280)}px`;
portal.style.zIndex = '9999';
const margin = 8;
const ttW = portal.offsetWidth;
portal.style.left = `${Math.max(margin, Math.min(rect.left, window.innerWidth - ttW - margin))}px`;
}
function hideTooltip() {

View File

@@ -113,6 +113,27 @@ const NatusWheel = (() => {
return ((ecliptic % 360) + 360) % 360 % 30;
}
/** Inline SVG for a zodiac sign, sized to 1em, using current text colour. */
function _signIconSvg(signName) {
const d = _signPaths[signName];
if (!d) return '';
return `<svg viewBox="0 0 640 640" width="1em" height="1em" class="tt-sign-icon" aria-hidden="true"><path d="${d}"/></svg>`;
}
/** Position tooltip near cursor, clamped so it never overflows the viewport. */
function _positionTooltip(tooltip, event) {
const margin = 8;
tooltip.style.display = 'block';
const ttW = tooltip.offsetWidth;
const ttH = tooltip.offsetHeight;
let left = event.clientX + 14;
let top = event.clientY - 10;
if (left + ttW + margin > window.innerWidth) left = event.clientX - ttW - 14;
if (top + ttH + margin > window.innerHeight) top = event.clientY - ttH - 10;
tooltip.style.left = Math.max(margin, left) + 'px';
tooltip.style.top = Math.max(margin, top) + 'px';
}
function _layout(svgEl) {
const rect = svgEl.getBoundingClientRect();
const size = Math.min(rect.width || 400, rect.height || 400);
@@ -295,17 +316,15 @@ const NatusWheel = (() => {
d3.select(this).classed('nw-planet--hover', true);
const tooltip = document.getElementById('id_natus_tooltip');
if (!tooltip) return;
const sym = PLANET_SYMBOLS[name] || name[0];
const sym = PLANET_SYMBOLS[name] || name[0];
const signData = SIGNS.find(s => s.name === pdata.sign) || {};
const signSym = signData.symbol || '';
const inDeg = _inSignDeg(pdata.degree).toFixed(1);
const rx = pdata.retrograde ? ' ℞' : '';
const inDeg = _inSignDeg(pdata.degree).toFixed(1);
const rx = pdata.retrograde ? ' ℞' : '';
const icon = _signIconSvg(pdata.sign) || signData.symbol || '';
tooltip.innerHTML =
`<div class="tt-title tt-title--${el}">${name} (${sym})</div>` +
`<div class="tt-description">${inDeg}° ${pdata.sign} ${signSym}${rx}</div>`;
tooltip.style.left = (event.clientX + 14) + 'px';
tooltip.style.top = (event.clientY - 10) + 'px';
tooltip.style.display = 'block';
`<div class="tt-description">@${inDeg}° ${pdata.sign} (${icon})${rx}</div>`;
_positionTooltip(tooltip, event);
})
.on('mouseout', function (event) {
// Ignore mouseout when moving between children of this group
@@ -427,9 +446,7 @@ const NatusWheel = (() => {
tooltip.innerHTML =
`<div class="tt-title tt-title--el-${elKey}">[${info.abbr}] ${info.name}</div>` +
`<div class="tt-description">${info.classical} · ${count} (${pct}%)</div>`;
tooltip.style.left = (event.clientX + 14) + 'px';
tooltip.style.top = (event.clientY - 10) + 'px';
tooltip.style.display = 'block';
_positionTooltip(tooltip, event);
})
.on('mouseout', function (event) {
if (sliceGroup.node().contains(event.relatedTarget)) return;