diff --git a/CLAUDE.md b/CLAUDE.md index c9e3cca..13340d4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -124,7 +124,7 @@ Test runner is `core.runner.RobustCompressorTestRunner` — handles the Windows - Push to `main` triggers Woodpecker → deploys to staging ## SCSS Import Order -`core.scss`: `rootvars → applets → base → button-pad → dashboard → gameboard → palette-picker → room → billboard → game-kit → wallet-tokens` +`core.scss`: `rootvars → applets → base → button-pad → dashboard → gameboard → palette-picker → room → card-deck → natus → tray → billboard → tooltips → game-kit → wallet-tokens` ## Critical Gotchas diff --git a/src/apps/gameboard/static/apps/gameboard/natus-wheel.js b/src/apps/gameboard/static/apps/gameboard/natus-wheel.js index 116f3c2..15f455f 100644 --- a/src/apps/gameboard/static/apps/gameboard/natus-wheel.js +++ b/src/apps/gameboard/static/apps/gameboard/natus-wheel.js @@ -52,6 +52,16 @@ const NatusWheel = (() => { Jupiter: 'sn', Saturn: 'pb', Uranus: 'u', Neptune: 'np', Pluto: 'pu', }; + // EarthmanRPG element names + classical equivalents + ring segment color var + const ELEMENT_INFO = { + Fire: { abbr: 'Ar', name: 'Ardor', classical: 'fire', titleVar: '--priRd' }, + Stone: { abbr: 'Om', name: 'Ossum', classical: 'stone', titleVar: '--priFs' }, + Time: { abbr: 'Tp', name: 'Tempo', classical: 'time', titleVar: '--priYl' }, + Space: { abbr: 'Nx', name: 'Nexus', classical: 'space', titleVar: '--priGn' }, + Air: { abbr: 'Pn', name: 'Pneuma', classical: 'air', titleVar: '--priCy' }, + Water: { abbr: 'Hm', name: 'Humor', classical: 'water', titleVar: '--priId' }, + }; + // Aspect stroke colors remain in JS — they are data-driven, not stylistic. const ASPECT_COLORS = { Conjunction: 'var(--priYl, #f0e060)', @@ -287,7 +297,7 @@ const NatusWheel = (() => { const inDeg = _inSignDeg(pdata.degree).toFixed(1); const rx = pdata.retrograde ? ' ℞' : ''; tooltip.innerHTML = - `
${name} (${sym})
` + + `
${name} (${sym})
` + `
${inDeg}° ${pdata.sign} ${signSym}${rx}
`; tooltip.style.left = (event.clientX + 14) + 'px'; tooltip.style.top = (event.clientY - 10) + 'px'; @@ -383,12 +393,12 @@ const NatusWheel = (() => { function _drawElements(g, data) { const el = data.elements; - const total = (el.Fire || 0) + (el.Stone || 0) + (el.Air || 0) + (el.Water || 0); + // Deasil order: Fire → Stone → Time → Space → Air → Water + const ELEMENT_ORDER = ['Fire', 'Stone', 'Time', 'Space', 'Air', 'Water']; + const total = ELEMENT_ORDER.reduce((s, k) => s + (el[k] || 0), 0); if (total === 0) return; - const pieData = ['Fire', 'Stone', 'Air', 'Water'].map(k => ({ - key: k, value: el[k] || 0, - })); + const pieData = ELEMENT_ORDER.map(k => ({ key: k, value: el[k] || 0 })); const pie = d3.pie().value(d => d.value).sort(null)(pieData); const arc = d3.arc().innerRadius(R.elementInner).outerRadius(R.elementOuter); @@ -397,24 +407,36 @@ const NatusWheel = (() => { .attr('class', 'nw-elements') .attr('transform', `translate(${_cx},${_cy})`); - elGroup.selectAll('path') - .data(pie) - .join('path') - .attr('d', arc) - .attr('class', d => `nw-element--${d.data.key.toLowerCase()}`); + // Per-slice group: carries hover events + glow, arc path inside + pie.forEach(slice => { + const info = ELEMENT_INFO[slice.data.key] || {}; - // Time + Space emergent counts as text - ['Time', 'Space'].forEach((key, i) => { - const count = el[key] || 0; - if (count === 0) return; - g.append('text') - .attr('x', _cx + (i === 0 ? -1 : 1) * R.elementInner * 0.6) - .attr('y', _cy) - .attr('text-anchor', 'middle') - .attr('dominant-baseline', 'middle') - .attr('font-size', `${_r * 0.045}px`) - .attr('class', `nw-element-label--${key.toLowerCase()}`) - .text(`${key[0]}${count}`); + const sliceGroup = elGroup.append('g') + .attr('class', 'nw-element-group') + .on('mouseover', function (event) { + d3.select(this).classed('nw-element--hover', true); + const tooltip = document.getElementById('id_natus_tooltip'); + if (!tooltip) return; + const count = slice.data.value; + const pct = total > 0 ? Math.round((count / total) * 100) : 0; + const elKey = slice.data.key.toLowerCase(); + tooltip.innerHTML = + `
[${info.abbr}] ${info.name}
` + + `
${info.classical} · ${count} (${pct}%)
`; + tooltip.style.left = (event.clientX + 14) + 'px'; + tooltip.style.top = (event.clientY - 10) + 'px'; + tooltip.style.display = 'block'; + }) + .on('mouseout', function (event) { + if (sliceGroup.node().contains(event.relatedTarget)) return; + d3.select(this).classed('nw-element--hover', false); + const tooltip = document.getElementById('id_natus_tooltip'); + if (tooltip) tooltip.style.display = 'none'; + }); + + sliceGroup.append('path') + .attr('d', arc(slice)) + .attr('class', `nw-element--${slice.data.key.toLowerCase()}`); }); } diff --git a/src/static_src/scss/_game-kit.scss b/src/static_src/scss/_game-kit.scss index 4c0b434..746e62f 100644 --- a/src/static_src/scss/_game-kit.scss +++ b/src/static_src/scss/_game-kit.scss @@ -113,6 +113,11 @@ .tt { z-index: 9999; + .tt-title { font-size: 1rem; } + .tt-description { padding: 0.125rem; font-size: 0.75rem; } + .tt-shoptalk { font-size: 0.75rem; opacity: 0.75; } + .tt-expiry { font-size: 1rem; color: rgba(var(--priRd), 1); } + // Buttons positioned on left edge of the fixed inline tooltip .tt-equip-btns { position: absolute; diff --git a/src/static_src/scss/_natus.scss b/src/static_src/scss/_natus.scss index 626c1d7..6604c7d 100644 --- a/src/static_src/scss/_natus.scss +++ b/src/static_src/scss/_natus.scss @@ -431,8 +431,9 @@ html.natus-open .natus-modal-wrap { .nw-planet-label--pu { fill: rgba(var(--sixPu), 1); stroke: rgba(var(--sixPu), 0.6); } .nw-rx { fill: rgba(var(--terUser), 1); } -// Planet hover glow (--ninUser) -.nw-planet--hover { +// Hover glow (--ninUser) — shared by planet groups and element slice groups +.nw-planet--hover, +.nw-element--hover { filter: drop-shadow(0 0 5px rgba(var(--ninUser), 0.9)); cursor: pointer; } @@ -440,22 +441,70 @@ html.natus-open .natus-modal-wrap { // Aspects .nw-aspects { opacity: 0.8; } -// Element pie +// Element pie — deasil order: Fire → Stone → Time → Space → Air → Water .nw-element--fire { fill: rgba(var(--priRd, 192, 64, 64), 0.92); stroke: rgba(var(--quaUser), 1); stroke-width: 0.5px; } .nw-element--stone { fill: rgba(var(--priFs, 122, 96, 64), 0.92); stroke: rgba(var(--quaUser), 1); stroke-width: 0.5px; } +.nw-element--time { fill: rgba(var(--priYl, 192, 160, 48), 0.92); stroke: rgba(var(--quaUser), 1); stroke-width: 0.5px; } +.nw-element--space { fill: rgba(var(--priGn, 64, 96, 64), 0.92); stroke: rgba(var(--quaUser), 1); stroke-width: 0.5px; } .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; } -// Time / Space emergent labels -.nw-element-label--time { fill: rgba(var(--priYl, 192, 160, 48), 1); } -.nw-element-label--space { fill: rgba(var(--priGn, 64, 96, 64), 1); } - // ── Planet hover tooltip — uses .tt base styles; overrides position + z ─────── #id_natus_tooltip { position: fixed; z-index: 200; pointer-events: none; + padding: 0.75rem 1.5rem; + + .tt-title { font-size: 1rem; font-weight: 700; } + .tt-description { font-size: 0.75rem; } + + // Planet title colors — senary (brightest) tier on dark palettes + .tt-title--au { color: rgba(var(--sixAu), 1); } // Sun + .tt-title--ag { color: rgba(var(--sixAg), 1); } // Moon + .tt-title--hg { color: rgba(var(--sixHg), 1); } // Mercury + .tt-title--cu { color: rgba(var(--sixCu), 1); } // Venus + .tt-title--fe { color: rgba(var(--sixFe), 1); } // Mars + .tt-title--sn { color: rgba(var(--sixSn), 1); } // Jupiter + .tt-title--pb { color: rgba(var(--sixPb), 1); } // Saturn + .tt-title--u { color: rgba(var(--sixU), 1); } // Uranus + .tt-title--np { color: rgba(var(--sixNp), 1); } // Neptune + .tt-title--pu { color: rgba(var(--sixPu), 1); } // Pluto +} + +// Element title colors — primary tier on dark palettes +#id_natus_tooltip { + .tt-title--el-fire { color: rgba(var(--priRd), 1); } + .tt-title--el-stone { color: rgba(var(--priFs), 1); } + .tt-title--el-time { color: rgba(var(--priYl), 1); } + .tt-title--el-space { color: rgba(var(--priGn), 1); } + .tt-title--el-air { color: rgba(var(--priCy), 1); } + .tt-title--el-water { color: rgba(var(--priId), 1); } +} + +// On light palettes — switch to tertiary tier for legibility +body[class*="-light"] #id_natus_tooltip { + .tt-title--el-fire { color: rgba(var(--terRd), 1); } + .tt-title--el-stone { color: rgba(var(--terFs), 1); } + .tt-title--el-time { color: rgba(var(--terYl), 1); } + .tt-title--el-space { color: rgba(var(--terGn), 1); } + .tt-title--el-air { color: rgba(var(--terCy), 1); } + .tt-title--el-water { color: rgba(var(--terId), 1); } +} + +// On light palettes — switch to primary (darkest) tier for legibility +body[class*="-light"] #id_natus_tooltip { + .tt-title--au { color: rgba(var(--priAu), 1); } + .tt-title--ag { color: rgba(var(--priAg), 1); } + .tt-title--hg { color: rgba(var(--priHg), 1); } + .tt-title--cu { color: rgba(var(--priCu), 1); } + .tt-title--fe { color: rgba(var(--priFe), 1); } + .tt-title--sn { color: rgba(var(--priSn), 1); } + .tt-title--pb { color: rgba(var(--priPb), 1); } + .tt-title--u { color: rgba(var(--priU), 1); } + .tt-title--np { color: rgba(var(--priNp), 1); } + .tt-title--pu { color: rgba(var(--priPu), 1); } } // ── Sidebar z-index sink (landscape sidebars must go below backdrop) ─────────── diff --git a/src/static_src/scss/_tooltips.scss b/src/static_src/scss/_tooltips.scss new file mode 100644 index 0000000..bdf9777 --- /dev/null +++ b/src/static_src/scss/_tooltips.scss @@ -0,0 +1,61 @@ +// ── Tooltip base styles ─────────────────────────────────────────────────────── +// Shared by wallet tokens, game-kit kit bag, and natus wheel tooltips. +// Portal tooltips (#id_tooltip_portal, #id_natus_tooltip) are position:fixed +// and override z-index; inline .tt cards use position:absolute within their +// parent token container. + +.token-tooltip, +.tt { + display: none; + width: 16rem; + max-width: 16rem; + white-space: normal; + background-color: rgba(var(--tooltip-bg), 0.75); + backdrop-filter: blur(6px); + border: 0.1rem solid rgba(var(--secUser), 0.5); + color: rgba(var(--secUser), 1); + padding: 0.5rem 0.75rem; + border-radius: 0.5rem; + z-index: 10; + font-size: 0.875rem; + + h4 { + font-size: 0.95rem; + margin: 0 0 0.3rem 0; + color: rgba(var(--terUser), 1); + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 0.5rem; + + .token-count { + font-size: 0.75rem; + opacity: 0.65; + font-weight: normal; + flex-shrink: 0; + } + } + + p { + margin: 0 0 0.2rem 0; + + &.expiry { + color: rgba(var(--priRd), 1); + } + + &.availability { + color: rgba(var(--priRd), 1); + } + + &.stock-version { + font-weight: 700; + color: rgba(var(--terUser), 1); + } + } + + small { + display: block; + font-size: 0.6rem; + opacity: 0.6; + } +} diff --git a/src/static_src/scss/_wallet-tokens.scss b/src/static_src/scss/_wallet-tokens.scss index 25151b9..6252d11 100644 --- a/src/static_src/scss/_wallet-tokens.scss +++ b/src/static_src/scss/_wallet-tokens.scss @@ -1,59 +1,3 @@ -.token-tooltip, -.tt { - display: none; - width: 16rem; - max-width: 16rem; - white-space: normal; - background-color: rgba(var(--tooltip-bg), 0.5); - backdrop-filter: blur(6px); - border: 0.1rem solid rgba(var(--secUser), 0.5); - color: rgba(var(--secUser), 1); - padding: 0.5rem 0.75rem; - border-radius: 0.5rem; - z-index: 10; - font-size: 0.875rem; - - h4 { - font-size: 0.95rem; - margin: 0 0 0.3rem 0; - color: rgba(var(--terUser), 1); - display: flex; - justify-content: space-between; - align-items: baseline; - gap: 0.5rem; - - .token-count { - font-size: 0.75rem; - opacity: 0.65; - font-weight: normal; - flex-shrink: 0; - } - } - - p { - margin: 0 0 0.2rem 0; - - &.expiry { - color: rgba(var(--priRd), 1); - } - - &.availability { - color: rgba(var(--priRd), 1); - } - - &.stock-version { - font-weight: 700; - color: rgba(var(--terUser), 1); - } - } - - small { - display: block; - font-size: 0.6rem; - opacity: 0.6; - } -} - .token { position: relative; display: inline-block; diff --git a/src/static_src/scss/core.scss b/src/static_src/scss/core.scss index cf0f2d4..e323788 100644 --- a/src/static_src/scss/core.scss +++ b/src/static_src/scss/core.scss @@ -10,6 +10,7 @@ @import 'natus'; @import 'tray'; @import 'billboard'; +@import 'tooltips'; @import 'game-kit'; @import 'wallet-tokens';