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;

View File

@@ -449,7 +449,9 @@ html.natus-open .natus-modal-wrap {
.nw-element--air { fill: rgba(var(--priCy, 64, 144, 176), 0.92); stroke: rgba(var(--quaUser), 1); stroke-width: 0.5px; }
.nw-element--water { fill: rgba(var(--priId, 80, 80, 160), 0.92); stroke: rgba(var(--quaUser), 1); stroke-width: 0.5px; }
// ── Planet hover tooltip — uses .tt base styles; overrides position + z ───────
// ── Planet hover tooltip — must live outside any ancestor with transform or
// container-type (both break position:fixed). Placed as a direct sibling of
// .natus-overlay in room.html; alongside #id_tooltip_portal in home.html. ──
#id_natus_tooltip {
position: fixed;
@@ -459,6 +461,7 @@ html.natus-open .natus-modal-wrap {
.tt-title { font-size: 1rem; font-weight: 700; }
.tt-description { font-size: 0.75rem; }
.tt-sign-icon { fill: currentColor; vertical-align: middle; margin-bottom: 0.1em; }
// Planet title colors — senary (brightest) tier on dark palettes
.tt-title--au { color: rgba(var(--sixAu), 1); } // Sun

View File

@@ -74,7 +74,6 @@
{% endif %}
</section>
<div id="id_natus_tooltip" class="tt" style="display:none;"></div>
<script src="{% static 'apps/gameboard/d3.min.js' %}"></script>
<script src="{% static 'apps/gameboard/natus-wheel.js' %}"></script>
<script>

View File

@@ -21,5 +21,6 @@
{% include "apps/applets/_partials/_gear.html" with menu_id="id_dash_applet_menu" %}
</div>
<div id="id_tooltip_portal" class="token-tooltip" style="display:none;"></div>
<div id="id_natus_tooltip" class="tt" style="display:none;"></div>
{% endif %}
{% endblock content %}

View File

@@ -99,9 +99,6 @@
</div>{# /.natus-modal-wrap #}
</div>{# /.natus-overlay #}
{# Planet hover tooltip — position:fixed so it escapes overflow:hidden on the modal #}
<div id="id_natus_tooltip" class="tt" style="display:none;"></div>
<script src="{% static 'apps/gameboard/d3.min.js' %}"></script>
<script src="{% static 'apps/gameboard/natus-wheel.js' %}"></script>
<script>

View File

@@ -71,6 +71,10 @@
{% if room.table_status == "SKY_SELECT" %}
{% include "apps/gameboard/_partials/_natus_overlay.html" %}
{% endif %}
{# Natus tooltip: sibling of .natus-overlay, not inside .natus-modal-wrap (which has transform) #}
{% if room.table_status == "SKY_SELECT" %}
<div id="id_natus_tooltip" class="tt" style="display:none;"></div>
{% endif %}
{% if room.gate_status != "RENEWAL_DUE" and room.table_status != "SIG_SELECT" %}
{% include "apps/gameboard/_partials/_table_positions.html" %}