From d2861077a4ad55d34594fb4d85aaff0569ea1eb9 Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Mon, 9 Mar 2026 23:48:20 -0400 Subject: [PATCH] tooltips now fully styled, appearing above applet container to avoid clipping issues; new methods added to apps.lyric.models.Token --- .../static/apps/gameboard/gameboard.js | 24 +++++++++ src/apps/lyric/models.py | 37 +++++++++----- src/functional_tests/test_gameboard.py | 16 ++---- src/static_src/scss/_gameboard.scss | 49 +++++++++--------- src/static_src/scss/_wallet-tokens.scss | 51 ++++++++++++++----- .../gameboard/_partials/_applet-game-kit.html | 22 +++++++- src/templates/apps/gameboard/gameboard.html | 8 ++- 7 files changed, 143 insertions(+), 64 deletions(-) diff --git a/src/apps/gameboard/static/apps/gameboard/gameboard.js b/src/apps/gameboard/static/apps/gameboard/gameboard.js index e69de29..82304e6 100644 --- a/src/apps/gameboard/static/apps/gameboard/gameboard.js +++ b/src/apps/gameboard/static/apps/gameboard/gameboard.js @@ -0,0 +1,24 @@ +function initGameKitTooltips() { + const portal = document.getElementById('id_tooltip_portal'); + if (!portal) return; + + document.querySelectorAll('#id_game_kit .token').forEach(token => { + const tooltip = token.querySelector('.token-tooltip'); + if (!tooltip) return; + + token.addEventListener('mouseenter', () => { + const rect = token.getBoundingClientRect(); + portal.innerHTML = tooltip.innerHTML; + portal.style.left = Math.round(rect.left + rect.width / 2) + 'px'; + portal.style.top = Math.round(rect.top) + 'px'; + portal.style.transform = 'translate(-50%, calc(-100% - 0.5rem))'; + portal.classList.add('active'); + }); + + token.addEventListener('mouseleave', () => { + portal.classList.remove('active'); + }); + }); +} + +document.addEventListener('DOMContentLoaded', initGameKitTooltips); diff --git a/src/apps/lyric/models.py b/src/apps/lyric/models.py index cd8cb57..a5d5a76 100644 --- a/src/apps/lyric/models.py +++ b/src/apps/lyric/models.py @@ -65,20 +65,33 @@ class Token(models.Model): token_type = models.CharField(max_length=8, choices=TOKEN_TYPE_CHOICES) expires_at = models.DateTimeField(null=True, blank=True) - def tooltip_text(self): - if self.token_type == self.COIN: - return ( - "Coin-on-a-String: Admit 1 Entry" - " (and another after that, and another after that\u2026)" - " \u2014 no expiry" - ) - if self.token_type == self.FREE: - return ( - f"Free Token: Admit 1 Entry" - f" \u2014 Expires {self.expires_at.strftime('%Y-%m-%d')}" - ) + def tooltip_name(self): return self.get_token_type_display() + def tooltip_description(self): + if self.token_type in (self.COIN, self.FREE): + return "Admit 1 Entry" + return "" + + def tooltip_expiry(self): + if self.token_type == self.COIN: + return "no expiry" + if self.expires_at: + return f"Expires {self.expires_at.strftime('%Y-%m-%d')}" + return "" + + def tooltip_shoptalk(self): + if self.token_type == self.COIN: + return "\u2026and another after that, and another after that\u2026" + return None + + def tooltip_text(self): + text = f"{self.tooltip_name()}: {self.tooltip_description()}" + if self.tooltip_shoptalk(): + text += f" ({self.tooltip_shoptalk()})" + text += f" \u2014 {self.tooltip_expiry()}" + return text + class PaymentMethod(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="payment_methods") stripe_pm_id = models.CharField(max_length=255) diff --git a/src/functional_tests/test_gameboard.py b/src/functional_tests/test_gameboard.py index 5cc8584..8b8635c 100644 --- a/src/functional_tests/test_gameboard.py +++ b/src/functional_tests/test_gameboard.py @@ -61,14 +61,10 @@ class GameboardNavigationTest(FunctionalTest): ActionChains(self.browser).move_to_element(coin).perform() self.wait_for( lambda: self.assertTrue( - self.browser.find_element( - By.CSS_SELECTOR, "#id_kit_coin_on_a_string .token-tooltip" - ).is_displayed() + self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed() ) ) - coin_tooltip = self.browser.find_element( - By.CSS_SELECTOR, "#id_kit_coin_on_a_string .token-tooltip" - ).text + coin_tooltip = self.browser.find_element(By.ID, "id_tooltip_portal").text self.assertIn("Coin-on-a-String", coin_tooltip) self.assertIn("Admit 1 Entry", coin_tooltip) self.assertIn("and another after that", coin_tooltip) @@ -78,14 +74,10 @@ class GameboardNavigationTest(FunctionalTest): ActionChains(self.browser).move_to_element(free_token).perform() self.wait_for( lambda: self.assertTrue( - self.browser.find_element( - By.CSS_SELECTOR, "#id_kit_free_token_0 .token-tooltip" - ).is_displayed() + self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed() ) ) - free_tooltip = self.browser.find_element( - By.CSS_SELECTOR, "#id_kit_free_token_0 .token-tooltip" - ).text + free_tooltip = self.browser.find_element(By.ID, "id_tooltip_portal").text self.assertIn("Free Token", free_tooltip) self.assertIn("Admit 1 Entry", free_tooltip) self.assertIn("Expires", free_tooltip) diff --git a/src/static_src/scss/_gameboard.scss b/src/static_src/scss/_gameboard.scss index f84f11e..e4865ea 100644 --- a/src/static_src/scss/_gameboard.scss +++ b/src/static_src/scss/_gameboard.scss @@ -41,35 +41,29 @@ body.page-gameboard { } } -#id_game_applets_container { - #id_applet_game_kit { +#id_applet_game_kit { + display: flex; + flex-direction: column; + + #id_game_kit { + flex: 1; + position: relative; display: flex; - flex-direction: column; - overflow: visible; + flex-direction: row; + align-items: center; + justify-content: space-evenly; + overflow-x: visible; + scrollbar-width: none; + &::-webkit-scrollbar { display: none; } - #id_game_kit { - flex: 1; - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-evenly; - overflow-x: visible; - scrollbar-width: none; - &::-webkit-scrollbar { display: none; } + .token { position: static; } - .token-tooltip { - z-index: 1000; - } + .token:hover .token-tooltip { display: none; } - .token, - .kit-item { - font-size: 1.5rem; - } + .token, + .kit-item { font-size: 1.5rem; } - .kit-item { - opacity: 0.6; - } - } + .kit-item { opacity: 0.6; } } } @@ -94,6 +88,13 @@ body.page-gameboard { } } +#id_tooltip_portal { + position: fixed; + z-index: 9999; + + &.active { display: block; } +} + @media (max-height: 500px) { body.page-gameboard { .container { diff --git a/src/static_src/scss/_wallet-tokens.scss b/src/static_src/scss/_wallet-tokens.scss index d1a97d5..e0b388c 100644 --- a/src/static_src/scss/_wallet-tokens.scss +++ b/src/static_src/scss/_wallet-tokens.scss @@ -1,3 +1,38 @@ +.token-tooltip { + display: none; + width: 16rem; + max-width: 16rem; + white-space: normal; + background-color: rgba(0, 0, 0, 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); + } + + p { + margin: 0 0 0.2rem 0; + + &.expiry { + color: rgba(var(--priRd), 1); + } + } + + small { + display: block; + font-size: 0.6rem; + opacity: 0.6; + } +} + .token { position: relative; display: inline-block; @@ -5,23 +40,13 @@ color: rgba(var(--terUser), 1); .token-tooltip { - display: none; position: absolute; bottom: 125%; - left: 0; - width: 16rem; - max-width: 16rem; - white-space: normal; - background-color: rgba(var(--priUser), 0.95); - 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; + left: 50%; + transform: translateX(-50%); } &:hover .token-tooltip { display: block; } -} \ No newline at end of file +} diff --git a/src/templates/apps/gameboard/_partials/_applet-game-kit.html b/src/templates/apps/gameboard/_partials/_applet-game-kit.html index 0f75f09..4622d66 100644 --- a/src/templates/apps/gameboard/_partials/_applet-game-kit.html +++ b/src/templates/apps/gameboard/_partials/_applet-game-kit.html @@ -7,13 +7,31 @@ {% if coin %}
- {{ coin.tooltip_text }} +
+

{{ coin.tooltip_name }}

+

+ {{ coin.tooltip_description }} +

+ {% if coin.tooltip_shoptalk %} + {{ coin.tooltip_shoptalk }} + {% endif %} +

{{ coin.tooltip_expiry }}

+
{% endif %} {% for token in free_tokens %}
- {{ token.tooltip_text }} +
+

{{ token.tooltip_name }}

+

+ {{ token.tooltip_description }} +

+ {% if token.tooltip_shoptalk %} + {{ token.tooltip_shoptalk }} + {% endif %} +

{{ token.tooltip_expiry }}

+
{% endfor %}
diff --git a/src/templates/apps/gameboard/gameboard.html b/src/templates/apps/gameboard/gameboard.html index e96b5d6..e1d42b7 100644 --- a/src/templates/apps/gameboard/gameboard.html +++ b/src/templates/apps/gameboard/gameboard.html @@ -1,4 +1,5 @@ {% extends "core/base.html" %} +{% load static %} {% block title_text %}Gameboard{% endblock title_text %} {% block header_text %}Gameboard{% endblock header_text %} @@ -8,4 +9,9 @@ {% include "apps/applets/_partials/_gear.html" with menu_id="id_game_applet_menu" %} {% include "apps/gameboard/_partials/_applets.html" %} -{% endblock content %} \ No newline at end of file +
+{% endblock content %} + +{% block scripts %} + +{% endblock scripts %}