From 2d453dbc784e0c1424572e9317e869457b373355 Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Sun, 15 Mar 2026 01:17:09 -0400 Subject: [PATCH] new _kit_bag_panel.html partial in core to allow user to manage equipped kit items from anywhere on site; #id_kit_btn moved from _footer.html partial directly into a base.html include; new trinket for superusers now incl. in apps.lyric.models; apps.gameboard.views handles this new type of PASS token; apps.epic.views allows payment with several different token types based on rarity & expiration hierarchy; kit bag and PASS functionality now handled in apps.dashboard.views; /kit-bag/ now pathed in .urls; styles abound; fully passing test suite (tho much work to be done, chiefly with stacking like coins in FEFO order) --- src/apps/dashboard/urls.py | 1 + src/apps/dashboard/views.py | 8 + src/apps/epic/views.py | 15 +- src/apps/gameboard/views.py | 3 + src/apps/lyric/models.py | 4 +- src/functional_tests/test_game_kit.py | 61 ++++++++ src/functional_tests/test_gatekeeper.py | 79 ++++++++++ src/static_src/scss/_applets.scss | 21 +++ src/static_src/scss/_game-kit.scss | 123 +++++++++++++++ src/static_src/scss/_room.scss | 145 ++++++++++++++++-- src/static_src/scss/core.scss | 1 + src/templates/apps/dashboard/home.html | 2 +- .../gameboard/_partials/_applet-game-kit.html | 12 +- .../_partials/_applet-wallet-tokens.html | 13 +- .../core/_partials/_kit_bag_panel.html | 48 ++++++ src/templates/core/base.html | 8 + 16 files changed, 521 insertions(+), 23 deletions(-) create mode 100644 src/functional_tests/test_game_kit.py create mode 100644 src/static_src/scss/_game-kit.scss create mode 100644 src/templates/core/_partials/_kit_bag_panel.html diff --git a/src/apps/dashboard/urls.py b/src/apps/dashboard/urls.py index f9c9559..a9d43b8 100644 --- a/src/apps/dashboard/urls.py +++ b/src/apps/dashboard/urls.py @@ -13,4 +13,5 @@ urlpatterns = [ path('wallet/toggle-applets', views.toggle_wallet_applets, name='toggle_wallet_applets'), path('wallet/setup-intent', views.setup_intent, name='setup_intent'), path('wallet/save-payment-method', views.save_payment_method, name='save_payment_method'), + path('kit-bag/', views.kit_bag, name='kit_bag'), ] diff --git a/src/apps/dashboard/views.py b/src/apps/dashboard/views.py index e899dfc..8a48b99 100644 --- a/src/apps/dashboard/views.py +++ b/src/apps/dashboard/views.py @@ -146,6 +146,7 @@ def toggle_applets(request): def wallet(request): return render(request, "apps/dashboard/wallet.html", { "wallet": request.user.wallet, + "pass_token": request.user.tokens.filter(token_type=Token.PASS).first(), "coin": request.user.tokens.filter(token_type=Token.COIN).first(), "free_tokens": list(request.user.tokens.filter(token_type=Token.FREE)), "tithe_tokens": list(request.user.tokens.filter(token_type=Token.TITHE)), @@ -153,6 +154,12 @@ def wallet(request): "page_class": "page-wallet", }) + +@login_required(login_url="/") +def kit_bag(request): + tokens = list(request.user.tokens.all()) + return render(request, "core/_partials/_kit_bag_panel.html", {"tokens": tokens}) + @login_required(login_url="/") def toggle_wallet_applets(request): checked = request.POST.getlist("applets") @@ -166,6 +173,7 @@ def toggle_wallet_applets(request): return render(request, "apps/wallet/_partials/_applets.html", { "applets": applet_context(request.user, "wallet"), "wallet": request.user.wallet, + "pass_token": request.user.tokens.filter(token_type=Token.PASS).first(), "coin": request.user.tokens.filter(token_type=Token.COIN).first(), "free_tokens": list(request.user.tokens.filter(token_type=Token.FREE)), "tithe_tokens": list(request.user.tokens.filter(token_type=Token.TITHE)), diff --git a/src/apps/epic/views.py b/src/apps/epic/views.py index 419e215..29399f3 100644 --- a/src/apps/epic/views.py +++ b/src/apps/epic/views.py @@ -78,7 +78,12 @@ def drop_token(request, room_id): return redirect("epic:gatekeeper", room_id=room_id) if room.gate_slots.filter(gamer=request.user, status=GateSlot.FILLED).exists(): return redirect("epic:gatekeeper", room_id=room_id) - if select_token(request.user) is None: + token_id = request.POST.get("token_id") + if token_id: + token = request.user.tokens.filter(id=token_id).first() + else: + token = select_token(request.user) + if token is None: return HttpResponse(status=402) slot = room.gate_slots.filter( status=GateSlot.EMPTY @@ -88,6 +93,7 @@ def drop_token(request, room_id): slot.status = GateSlot.RESERVED slot.reserved_at = timezone.now() slot.save() + request.session["kit_token_id"] = str(token.id) return redirect("epic:gatekeeper", room_id=room_id) @@ -99,7 +105,12 @@ def confirm_token(request, room_id): gamer=request.user, status=GateSlot.RESERVED ).first() if slot: - token = select_token(request.user) + token_id = request.session.pop("kit_token_id", None) + token = None + if token_id: + token = request.user.tokens.filter(id=token_id).first() + if not token: + token = select_token(request.user) if token: debit_token(request.user, slot, token) return redirect("epic:gatekeeper", room_id=room_id) diff --git a/src/apps/gameboard/views.py b/src/apps/gameboard/views.py index b65b5b6..87bddba 100644 --- a/src/apps/gameboard/views.py +++ b/src/apps/gameboard/views.py @@ -17,10 +17,12 @@ GAMEBOARD_APPLET_ORDER = [ @login_required(login_url="/") def gameboard(request): + pass_token = request.user.tokens.filter(token_type=Token.PASS).first() if request.user.is_staff else None coin = request.user.tokens.filter(token_type=Token.COIN).first() free_tokens = list(request.user.tokens.filter(token_type=Token.FREE)) return render( request, "apps/gameboard/gameboard.html", { + "pass_token": pass_token, "coin": coin, "free_tokens": free_tokens, "applets": applet_context(request.user, "gameboard"), @@ -45,6 +47,7 @@ def toggle_game_applets(request): if request.headers.get("HX-Request"): return render(request, "apps/gameboard/_partials/_applets.html", { "applets": applet_context(request.user, "gameboard"), + "pass_token": request.user.tokens.filter(token_type=Token.PASS).first() if request.user.is_staff else None, "coin": request.user.tokens.filter(token_type=Token.COIN).first(), "free_tokens": list(request.user.tokens.filter(token_type=Token.FREE)), "my_games": Room.objects.filter( diff --git a/src/apps/lyric/models.py b/src/apps/lyric/models.py index a771ef6..8f16434 100644 --- a/src/apps/lyric/models.py +++ b/src/apps/lyric/models.py @@ -92,7 +92,7 @@ class Token(models.Model): return self.get_token_type_display() def tooltip_description(self): - if self.token_type in (self.COIN, self.FREE): + if self.token_type in (self.COIN, self.FREE, self.PASS): return "Admit 1 Entry" if self.token_type == self.TITHE: return "+ Writ bonus" @@ -116,6 +116,8 @@ class Token(models.Model): def tooltip_shoptalk(self): if self.token_type == self.COIN: return "\u2026and another after that, and another after that\u2026" + if self.token_type == self.PASS: + return "\u2018Entry fee\u2019? Do you know who you\u2019re talking to?" return None def tooltip_text(self): diff --git a/src/functional_tests/test_game_kit.py b/src/functional_tests/test_game_kit.py new file mode 100644 index 0000000..0d7ba22 --- /dev/null +++ b/src/functional_tests/test_game_kit.py @@ -0,0 +1,61 @@ +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys + +from .base import FunctionalTest +from apps.epic.models import Room +from apps.lyric.models import Token, User + + +class GameKitTest(FunctionalTest): + """Game Kit : opens from footer, shows token cards, dismisses.""" + + def setUp(self): + super().setUp() + self.create_pre_authenticated_session("gamer@kit.io") + self.gamer = User.objects.get(email="gamer@kit.io") + self.token = self.gamer.tokens.filter(token_type=Token.COIN).first() + self.room = Room.objects.create(name="Kit Room", owner=self.gamer) + self.gate_url = self.live_server_url + f"/gameboard/room/{self.room.id}/gate/" + + def test_kit_btn_in_footer_opens_dialog(self): + self.browser.get(self.gate_url) + kit_btn = self.wait_for( + lambda: self.browser.find_element(By.ID, "id_kit_btn") + ) + self.assertTrue(kit_btn.is_displayed()) + kit_btn.click() + dialog = self.wait_for( + lambda: self.browser.find_element(By.ID, "id_kit_bag_dialog") + ) + self.assertTrue(dialog.is_displayed()) + + def test_kit_dialog_shows_token_cards(self): + self.browser.get(self.gate_url) + self.browser.find_element(By.ID, "id_kit_btn").click() + self.wait_for( + lambda: self.browser.find_element( + By.CSS_SELECTOR, + f"#id_kit_bag_dialog [data-token-id='{self.token.id}']", + ) + ) + + def test_kit_dialog_closes_on_escape(self): + self.browser.get(self.gate_url) + self.browser.find_element(By.ID, "id_kit_btn").click() + dialog = self.wait_for( + lambda: self.browser.find_element(By.ID, "id_kit_bag_dialog") + ) + self.assertTrue(dialog.is_displayed()) + dialog.send_keys(Keys.ESCAPE) + self.wait_for( + lambda: self.assertFalse( + self.browser.find_element(By.ID, "id_kit_bag_dialog").is_displayed() + ) + ) + + def test_kit_btn_visible_outside_room(self): + self.browser.get(self.live_server_url + "/") + kit_btn = self.wait_for( + lambda: self.browser.find_element(By.ID, "id_kit_btn") + ) + self.assertTrue(kit_btn.is_displayed()) diff --git a/src/functional_tests/test_gatekeeper.py b/src/functional_tests/test_gatekeeper.py index bae50a6..67b388b 100644 --- a/src/functional_tests/test_gatekeeper.py +++ b/src/functional_tests/test_gatekeeper.py @@ -501,3 +501,82 @@ class TokenPriorityTest(FunctionalTest): self.assertTrue(Token.objects.filter(pk=pass_token.pk).exists()) self.coin.refresh_from_db() self.assertIsNone(self.coin.current_room) + + +class GameKitInsertTest(FunctionalTest): + """Token selected from Game Kit, inserted via token-slot click.""" + + def setUp(self): + super().setUp() + self.create_pre_authenticated_session("gamer@insert.io") + self.gamer = User.objects.get(email="gamer@insert.io") + self.coin = self.gamer.tokens.filter(token_type=Token.COIN).first() + self.room = Room.objects.create(name="Insert Room", owner=self.gamer) + self.gate_url = self.live_server_url + f"/gameboard/room/{self.room.id}/gate/" + + def _select_token_from_kit(self, token): + self.browser.find_element(By.ID, "id_kit_btn").click() + self.wait_for( + lambda: self.browser.find_element( + By.CSS_SELECTOR, f"[data-token-id='{token.id}']" + ).click() + ) + self.wait_for( + lambda: self.browser.find_element(By.CSS_SELECTOR, ".token-slot.ready") + ) + + def test_coin_insert_via_kit_reserves_slot(self): + self.browser.get(self.gate_url) + self._select_token_from_kit(self.coin) + self.browser.find_element(By.CSS_SELECTOR, "button.token-rails").click() + self.wait_for( + lambda: self.browser.find_element(By.CSS_SELECTOR, ".gate-slot.reserved") + ) + self.assertEqual(self.browser.current_url, self.gate_url) + + def test_free_token_insert_via_kit_consumed_on_confirm(self): + token = Token.objects.create( + user=self.gamer, + token_type=Token.FREE, + expires_at=timezone.now() + timedelta(days=7), + ) + self.browser.get(self.gate_url) + self._select_token_from_kit(token) + self.browser.find_element(By.CSS_SELECTOR, "button.token-rails").click() + self.wait_for( + lambda: self.browser.find_element( + By.CSS_SELECTOR, ".gate-slot[data-slot='1'] .btn-confirm" + ) + ).click() + self.wait_for( + lambda: self.browser.find_element(By.CSS_SELECTOR, ".gate-slot.filled") + ) + self.assertFalse(Token.objects.filter(id=token.id).exists()) + + def test_tithe_token_insert_via_kit_consumed_on_confirm(self): + token = Token.objects.create(user=self.gamer, token_type=Token.TITHE) + self.browser.get(self.gate_url) + self._select_token_from_kit(token) + self.browser.find_element(By.CSS_SELECTOR, "button.token-rails").click() + self.wait_for( + lambda: self.browser.find_element( + By.CSS_SELECTOR, ".gate-slot[data-slot='1'] .btn-confirm" + ) + ).click() + self.wait_for( + lambda: self.browser.find_element(By.CSS_SELECTOR, ".gate-slot.filled") + ) + self.assertFalse(Token.objects.filter(id=token.id).exists()) + + def test_pass_token_insert_via_kit_not_consumed(self): + self.gamer.is_staff = True + self.gamer.save() + pass_token = Token.objects.create(user=self.gamer, token_type=Token.PASS) + self.browser.get(self.gate_url) + self._select_token_from_kit(pass_token) + self.browser.find_element(By.CSS_SELECTOR, "button.token-rails").click() + self.wait_for( + lambda: self.browser.find_element(By.CSS_SELECTOR, ".gate-slot.reserved") + ) + self.assertTrue(Token.objects.filter(id=pass_token.id).exists()) + self.assertEqual(self.browser.current_url, self.gate_url) diff --git a/src/static_src/scss/_applets.scss b/src/static_src/scss/_applets.scss index 56a08e6..daec6d1 100644 --- a/src/static_src/scss/_applets.scss +++ b/src/static_src/scss/_applets.scss @@ -77,6 +77,27 @@ #id_wallet_applet_menu { @extend %applet-menu; } #id_room_menu { @extend %applet-menu; } +// Page-level gear buttons — fixed to viewport bottom-right +.gameboard-page, +.dashboard-page, +.wallet-page { + > .gear-btn { + position: fixed; + bottom: 4.2rem; + right: 0.5rem; + z-index: 202; + } +} + +#id_dash_applet_menu, +#id_game_applet_menu, +#id_wallet_applet_menu { + position: fixed; + bottom: 6.6rem; + right: 1rem; + z-index: 202; +} + // ── Applets grid (shared across all boards) ──────────────── %applets-grid { container-type: inline-size; diff --git a/src/static_src/scss/_game-kit.scss b/src/static_src/scss/_game-kit.scss new file mode 100644 index 0000000..906b0c9 --- /dev/null +++ b/src/static_src/scss/_game-kit.scss @@ -0,0 +1,123 @@ +#id_kit_btn { + position: fixed; + bottom: 0.5rem; + right: 0.5rem; + z-index: 205; + font-size: 1.75rem; + cursor: pointer; + color: rgba(var(--secUser), 1); + display: inline-flex; + align-items: center; + justify-content: center; + width: 3rem; + height: 3rem; + border-radius: 50%; + background-color: rgba(var(--priUser), 1); + border: 0.15rem solid rgba(var(--secUser), 1); + + &:hover, + &.active { + color: rgba(var(--quaUser), 1); + border-color: rgba(var(--quaUser), 1); + } +} + +#id_kit_bag_dialog { + // Override dialog's native display:none so we can drive visibility via max-height + display: block !important; + position: fixed; + bottom: 0; + left: 0; + right: 0; + width: 100%; + max-width: none; + margin: 0; + padding: 0; + border: none; + border-top: 0.1rem solid rgba(var(--terUser), 0.3); + background: rgba(var(--priUser), 0.97); + z-index: 204; + overflow: hidden; + // Closed state + max-height: 0; + visibility: hidden; + transition: max-height 0.25s ease-out, visibility 0s 0.25s; + + &[open] { + max-height: 5rem; + visibility: visible; + transition: max-height 0.25s ease-out, visibility 0s; + display: flex !important; + flex-direction: row; + gap: 1.5rem; + align-items: center; + padding: 0.4rem 1rem; + } +} + +.kit-bag-section { + display: flex; + flex-direction: row; + align-items: center; + gap: 0.5rem; + flex-shrink: 0; +} + +.kit-bag-label { + font-size: 0.55rem; + text-transform: uppercase; + letter-spacing: 0.12em; + color: rgba(var(--secUser), 0.35); + writing-mode: vertical-rl; + text-orientation: mixed; + transform: rotate(180deg); +} + +.kit-bag-row { + display: flex; + flex-direction: row; + gap: 0.4rem; +} + +.kit-card { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.15rem; + padding: 0.3rem 0.4rem; + border: 0.1rem solid rgba(var(--terUser), 0.35); + border-radius: 0.4rem; + cursor: pointer; + min-width: 3rem; + transition: border-color 0.15s, box-shadow 0.15s; + + i { + font-size: 1.1rem; + color: rgba(var(--terUser), 0.7); + } + + .kit-card-label { + font-size: 0.5rem; + color: rgba(var(--secUser), 0.45); + text-align: center; + white-space: nowrap; + } + + &:hover { + border-color: rgba(var(--terUser), 0.7); + } + + &.selected { + border-color: rgba(var(--terUser), 1); + box-shadow: + 0 0 0.4rem rgba(var(--terUser), 0.5), + 0 0 1rem rgba(var(--terUser), 0.2) + ; + i { color: rgba(var(--terUser), 1); } + } +} + +.kit-bag-empty { + font-size: 0.7rem; + color: rgba(var(--secUser), 0.4); +} diff --git a/src/static_src/scss/_room.scss b/src/static_src/scss/_room.scss index 07ab2ab..3e6f12a 100644 --- a/src/static_src/scss/_room.scss +++ b/src/static_src/scss/_room.scss @@ -10,11 +10,49 @@ $gate-line: 2px; min-height: 60vh; } -.room-page .gear-btn, -#id_room_menu { +.room-page .gear-btn { z-index: 101; } +#id_room_menu { + position: absolute; + bottom: 3.5rem; + right: 0.5rem; + z-index: 101; + background-color: rgba(var(--priUser), 0.95); + border: 0.15rem solid rgba(var(--secUser), 1); + box-shadow: + 0 0 0.5rem rgba(var(--secUser), 0.75), + 0.12rem 0.12rem 0.5rem rgba(0, 0, 0, 0.25) + ; + border-radius: 0.75rem; + padding: 1rem; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +body:has(.gate-overlay) { + overflow: hidden; + + // Pin gear controls to the visual viewport, + // bypassing iOS 100vh chrome-inclusion bug. + // Offset upward so gear btn clears the kit btn below it. + .room-page .gear-btn { + position: fixed; + bottom: 4.2rem; + right: 0.5rem; + z-index: 202; + } + + #id_room_menu { + position: fixed; + bottom: 6.6rem; + right: 0.5rem; + z-index: 202; + } +} + .gate-overlay { position: fixed; inset: 0; @@ -24,6 +62,9 @@ $gate-line: 2px; background: rgba(0, 0, 0, 0.7); backdrop-filter: blur(4px); z-index: 100; + overflow-y: auto; + overscroll-behavior: contain; + -webkit-overflow-scrolling: touch; } .gate-modal { @@ -43,7 +84,7 @@ $gate-line: 2px; color: rgba(var(--secUser), 0.6); margin-bottom: 1rem; text-align: justify; - text-align-last: justify; + text-align-last: center; text-justify: inter-character; text-transform: uppercase; text-shadow: @@ -92,6 +133,14 @@ $gate-line: 2px; pointer-events: none; } + &.ready { + border-color: rgba(var(--terUser), 1); + box-shadow: + 0 0 0.6rem rgba(var(--terUser), 0.6), + 0 0 1.6rem rgba(var(--terUser), 0.25) + ; + } + &.pending, &.claimed { box-shadow: @@ -264,21 +313,85 @@ $gate-line: 2px; } } -// Mobile: 2×3 grid, both rows left-to-right +// Narrow viewport — scale down, 2×3 slot grid (portrait mobile + narrow desktop) @media (max-width: 550px) { - .gate-modal .gate-slots { - display: grid; - grid-template-columns: repeat(3, $gate-node); - grid-template-rows: repeat(2, $gate-node); - gap: $gate-gap; + .gate-modal { + padding: 1.25rem 1.5rem; - .gate-slot { - &:nth-child(1) { grid-column: 1; grid-row: 1; } - &:nth-child(2) { grid-column: 2; grid-row: 1; } - &:nth-child(3) { grid-column: 3; grid-row: 1; } - &:nth-child(4) { grid-column: 1; grid-row: 2; } - &:nth-child(5) { grid-column: 2; grid-row: 2; } - &:nth-child(6) { grid-column: 3; grid-row: 2; } + .gate-header { + h1 { font-size: 1.5rem; } + .gate-status-wrap { margin-bottom: 0.5rem; } + } + + .token-slot { min-width: 150px; } + + .gate-slots { + display: grid; + grid-template-columns: repeat(3, 52px); + grid-template-rows: repeat(2, 52px); + gap: 24px; + + .gate-slot { + width: 52px; + height: 52px; + &:nth-child(1) { grid-column: 1; grid-row: 1; } + &:nth-child(2) { grid-column: 2; grid-row: 1; } + &:nth-child(3) { grid-column: 3; grid-row: 1; } + &:nth-child(4) { grid-column: 1; grid-row: 2; } + &:nth-child(5) { grid-column: 2; grid-row: 2; } + &:nth-child(6) { grid-column: 3; grid-row: 2; } + } + } + } +} + +// Landscape mobile — aggressively scale down to fit short viewport +@media (orientation: landscape) and (max-width: 1023px) { + .room-page .gear-btn { + bottom: 3.5rem; + } + + .gate-modal { + padding: 0.6rem 1.25rem; + + .gate-header { + h1 { font-size: 1rem; margin: 0 0 0.25rem; } + .gate-status-wrap { font-size: 0.65em; margin-bottom: 0.35rem; } + } + + .token-slot { + min-width: 130px; + + .token-rails, + button.token-rails { padding: 0.4rem 0.35rem; } + + .token-panel { + padding: 0.3rem 0.5rem; + + .token-denomination { font-size: 1.1em; } + } + } + + .gate-slots { + gap: 14px; + + .gate-slot { + width: 40px; + height: 40px; + + .slot-number { font-size: 0.6em; } + } + } + + .form-container { + h3 { font-size: 0.85rem; margin: 0.25rem 0; } + + form { gap: 0.35rem; } + + .form-control-lg { + --_pad-v: 0.4rem; + font-size: 0.9rem; + } } } } diff --git a/src/static_src/scss/core.scss b/src/static_src/scss/core.scss index 5cbbfc6..f215bb5 100644 --- a/src/static_src/scss/core.scss +++ b/src/static_src/scss/core.scss @@ -6,6 +6,7 @@ @import 'gameboard'; @import 'palette-picker'; @import 'room'; +@import 'game-kit'; @import 'wallet-tokens'; diff --git a/src/templates/apps/dashboard/home.html b/src/templates/apps/dashboard/home.html index 2e241a3..95859c5 100644 --- a/src/templates/apps/dashboard/home.html +++ b/src/templates/apps/dashboard/home.html @@ -10,7 +10,7 @@ {% block content %} {% if user.is_authenticated %} -
+
{% include "apps/applets/_partials/_gear.html" with menu_id="id_dash_applet_menu" %} {% include "apps/dashboard/_partials/_applets.html" %}
diff --git a/src/templates/apps/gameboard/_partials/_applet-game-kit.html b/src/templates/apps/gameboard/_partials/_applet-game-kit.html index 4622d66..9abe694 100644 --- a/src/templates/apps/gameboard/_partials/_applet-game-kit.html +++ b/src/templates/apps/gameboard/_partials/_applet-game-kit.html @@ -4,9 +4,19 @@ >

Game Kit

+ {% if pass_token %} +
+ +
+

{{ pass_token.tooltip_name }}

+

{{ pass_token.tooltip_description }}

+

{{ pass_token.tooltip_expiry }}

+
+
+ {% endif %} {% if coin %}
- +

{{ coin.tooltip_name }}

diff --git a/src/templates/apps/wallet/_partials/_applet-wallet-tokens.html b/src/templates/apps/wallet/_partials/_applet-wallet-tokens.html index 3bf34d3..c377c4d 100644 --- a/src/templates/apps/wallet/_partials/_applet-wallet-tokens.html +++ b/src/templates/apps/wallet/_partials/_applet-wallet-tokens.html @@ -4,9 +4,18 @@ >

Tokens

- {% if coin %} + {% if pass_token %} +
+ +
+

{{ pass_token.tooltip_name }}

+

{{ pass_token.tooltip_description }}

+

{{ pass_token.tooltip_expiry }}

+
+
+ {% elif coin %}
- +

{{ coin.tooltip_name }}

{{ coin.tooltip_description }}

diff --git a/src/templates/core/_partials/_kit_bag_panel.html b/src/templates/core/_partials/_kit_bag_panel.html new file mode 100644 index 0000000..4562427 --- /dev/null +++ b/src/templates/core/_partials/_kit_bag_panel.html @@ -0,0 +1,48 @@ +{% if tokens %} +
+ Trinkets +
+ {% for token in tokens %} + {% if token.token_type == "coin" or token.token_type == "pass" %} +
+ {% if token.token_type == "coin" %} + + {% else %} + + {% endif %} + {{ token.tooltip_name }} +
+ {% endif %} + {% endfor %} +
+
+
+ Tokens +
+ {% for token in tokens %} + {% if token.token_type == "Free" or token.token_type == "tithe" %} +
+ {% if token.token_type == "Free" %} + + {% else %} + + {% endif %} + {{ token.tooltip_name }} +
+ {% endif %} + {% endfor %} +
+
+{% else %} +

Kit bag empty.

+{% endif %} diff --git a/src/templates/core/base.html b/src/templates/core/base.html index 007267a..64e8615 100644 --- a/src/templates/core/base.html +++ b/src/templates/core/base.html @@ -50,10 +50,18 @@ {% include "core/_partials/_footer.html" %} + {% if user.is_authenticated %} + + {% endif %} + + {% block scripts %} {% endblock scripts %} +