Compare commits
4 Commits
71ef3dcb7f
...
db9ac9cb24
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db9ac9cb24 | ||
|
|
d3e4638233 | ||
|
|
10a6809dcf | ||
|
|
de4ac60aec |
@@ -60,7 +60,7 @@
|
|||||||
|
|
||||||
function attachTooltip(el) {
|
function attachTooltip(el) {
|
||||||
el.addEventListener('mouseenter', function () {
|
el.addEventListener('mouseenter', function () {
|
||||||
var tooltip = el.querySelector('.token-tooltip');
|
var tooltip = el.querySelector('.tt');
|
||||||
if (!tooltip) return;
|
if (!tooltip) return;
|
||||||
var rect = el.getBoundingClientRect();
|
var rect = el.getBoundingClientRect();
|
||||||
tooltip.style.position = 'fixed';
|
tooltip.style.position = 'fixed';
|
||||||
@@ -69,11 +69,16 @@
|
|||||||
tooltip.style.display = 'block';
|
tooltip.style.display = 'block';
|
||||||
});
|
});
|
||||||
el.addEventListener('mouseleave', function () {
|
el.addEventListener('mouseleave', function () {
|
||||||
var tooltip = el.querySelector('.token-tooltip');
|
var tooltip = el.querySelector('.tt');
|
||||||
if (tooltip) tooltip.style.display = '';
|
if (tooltip) tooltip.style.display = '';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gameboard.js re-fetches dialog content after DON and fires this event.
|
||||||
|
dialog.addEventListener('kit-content-refreshed', function () {
|
||||||
|
attachCardListeners();
|
||||||
|
});
|
||||||
|
|
||||||
function attachCardListeners() {
|
function attachCardListeners() {
|
||||||
dialog.querySelectorAll('.token[data-token-id]').forEach(function (card) {
|
dialog.querySelectorAll('.token[data-token-id]').forEach(function (card) {
|
||||||
card.addEventListener('click', function () {
|
card.addEventListener('click', function () {
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ function initWalletTooltips() {
|
|||||||
if (!portal) return;
|
if (!portal) return;
|
||||||
|
|
||||||
document.querySelectorAll('.wallet-tokens .token').forEach(token => {
|
document.querySelectorAll('.wallet-tokens .token').forEach(token => {
|
||||||
const tooltip = token.querySelector('.token-tooltip');
|
const tooltip = token.querySelector('.tt');
|
||||||
if (!tooltip) return;
|
if (!tooltip) return;
|
||||||
|
|
||||||
token.addEventListener('mouseenter', () => {
|
token.addEventListener('mouseenter', () => {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ function initGameKitTooltips() {
|
|||||||
portal.style.display = 'none';
|
portal.style.display = 'none';
|
||||||
miniPortal.style.display = 'none';
|
miniPortal.style.display = 'none';
|
||||||
|
|
||||||
let equippedId = gameKit.dataset.equippedId || '';
|
|
||||||
let activeToken = null;
|
let activeToken = null;
|
||||||
let equipping = false;
|
let equipping = false;
|
||||||
|
|
||||||
@@ -32,7 +31,16 @@ function initGameKitTooltips() {
|
|||||||
|
|
||||||
document.addEventListener('mousemove', (e) => {
|
document.addEventListener('mousemove', (e) => {
|
||||||
if (portal.classList.contains('active') && activeToken) {
|
if (portal.classList.contains('active') && activeToken) {
|
||||||
const rects = [activeToken.getBoundingClientRect(), portal.getBoundingClientRect()];
|
const tokenRect = activeToken.getBoundingClientRect();
|
||||||
|
const portalRect = portal.getBoundingClientRect();
|
||||||
|
// Expand left to cover button overflow outside portal edge
|
||||||
|
const expandedPortalRect = {
|
||||||
|
left: portalRect.left - 24,
|
||||||
|
top: portalRect.top,
|
||||||
|
right: portalRect.right,
|
||||||
|
bottom: portalRect.bottom,
|
||||||
|
};
|
||||||
|
const rects = [tokenRect, expandedPortalRect];
|
||||||
if (miniPortal.classList.contains('active')) rects.push(miniPortal.getBoundingClientRect());
|
if (miniPortal.classList.contains('active')) rects.push(miniPortal.getBoundingClientRect());
|
||||||
const left = Math.min(...rects.map(r => r.left));
|
const left = Math.min(...rects.map(r => r.left));
|
||||||
const top = Math.min(...rects.map(r => r.top));
|
const top = Math.min(...rects.map(r => r.top));
|
||||||
@@ -41,7 +49,7 @@ function initGameKitTooltips() {
|
|||||||
if (!inRect(e.clientX, e.clientY, { left, top, right, bottom })) closePortals();
|
if (!inRect(e.clientX, e.clientY, { left, top, right, bottom })) closePortals();
|
||||||
} else if (!portal.classList.contains('active')) {
|
} else if (!portal.classList.contains('active')) {
|
||||||
for (const tokenEl of gameKit.querySelectorAll('.token')) {
|
for (const tokenEl of gameKit.querySelectorAll('.token')) {
|
||||||
if (!tokenEl.querySelector('.token-tooltip')) continue;
|
if (!tokenEl.querySelector('.tt')) continue;
|
||||||
if (inRect(e.clientX, e.clientY, tokenEl.getBoundingClientRect())) {
|
if (inRect(e.clientX, e.clientY, tokenEl.getBoundingClientRect())) {
|
||||||
showPortals(tokenEl);
|
showPortals(tokenEl);
|
||||||
break;
|
break;
|
||||||
@@ -50,73 +58,161 @@ function initGameKitTooltips() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// buildMiniContent takes the full element so it can inspect data-deck-id vs data-token-id.
|
// buildMiniContent — text-only status; DON/DOFF buttons live in the main portal.
|
||||||
function buildMiniContent(token) {
|
function buildMiniContent(token) {
|
||||||
const deckId = token.dataset.deckId;
|
const deckId = token.dataset.deckId;
|
||||||
const tokenId = token.dataset.tokenId;
|
const tokenId = token.dataset.tokenId;
|
||||||
|
const equippedId = gameKit.dataset.equippedId || '';
|
||||||
if (deckId) {
|
|
||||||
const equippedDeckId = gameKit.dataset.equippedDeckId || '';
|
const equippedDeckId = gameKit.dataset.equippedDeckId || '';
|
||||||
if (equippedDeckId && deckId === equippedDeckId) {
|
if (deckId) {
|
||||||
miniPortal.textContent = 'Equipped';
|
miniPortal.textContent = (equippedDeckId && deckId === equippedDeckId) ? 'Equipped' : 'Not Equipped';
|
||||||
|
} else if (tokenId) {
|
||||||
|
miniPortal.textContent = (equippedId && tokenId === equippedId) ? 'Equipped' : 'Not Equipped';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update DON/DOFF button pair to reflect new equipped state.
|
||||||
|
function _setEquipState(donBtn, doffBtn, isEquipped) {
|
||||||
|
if (isEquipped) {
|
||||||
|
donBtn.classList.add('btn-disabled'); donBtn.textContent = '×';
|
||||||
|
doffBtn.classList.remove('btn-disabled'); doffBtn.textContent = 'DOFF';
|
||||||
} else {
|
} else {
|
||||||
const btn = document.createElement('button');
|
donBtn.classList.remove('btn-disabled'); donBtn.textContent = 'DON';
|
||||||
btn.className = 'equip-deck-btn';
|
doffBtn.classList.add('btn-disabled'); doffBtn.textContent = '×';
|
||||||
btn.textContent = 'Equip Deck?';
|
}
|
||||||
btn.addEventListener('click', (e) => {
|
}
|
||||||
|
|
||||||
|
// Sync all tokens' .tt DON/DOFF buttons after an equip state change.
|
||||||
|
function _syncTokenButtons(kind, newEquippedId) {
|
||||||
|
const selector = kind === 'deck' ? '[data-deck-id]' : '[data-token-id]';
|
||||||
|
gameKit.querySelectorAll('.token' + selector).forEach(tokenEl => {
|
||||||
|
const id = kind === 'deck' ? tokenEl.dataset.deckId : tokenEl.dataset.tokenId;
|
||||||
|
const tt = tokenEl.querySelector('.tt');
|
||||||
|
if (!tt) return;
|
||||||
|
const don = tt.querySelector('.btn-equip');
|
||||||
|
const doff = tt.querySelector('.btn-unequip');
|
||||||
|
if (!don || !doff) return;
|
||||||
|
_setEquipState(don, doff, id === newEquippedId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the kit bag dialog is open, re-fetch its content (used after DON so the
|
||||||
|
// newly-equipped card appears without the user having to close+reopen).
|
||||||
|
function _refreshKitDialog() {
|
||||||
|
const dialog = document.getElementById('id_kit_bag_dialog');
|
||||||
|
const kitBtn = document.getElementById('id_kit_btn');
|
||||||
|
if (!dialog || !dialog.hasAttribute('open') || !kitBtn || !kitBtn.dataset.kitUrl) return;
|
||||||
|
fetch(kitBtn.dataset.kitUrl, {
|
||||||
|
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||||
|
})
|
||||||
|
.then(r => r.text())
|
||||||
|
.then(html => {
|
||||||
|
dialog.innerHTML = html;
|
||||||
|
dialog.dispatchEvent(new CustomEvent('kit-content-refreshed'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the kit bag dialog is open, replace the matching card with a placeholder.
|
||||||
|
function _syncKitBagDialog(kind, id) {
|
||||||
|
const dialog = document.getElementById('id_kit_bag_dialog');
|
||||||
|
if (!dialog || !dialog.hasAttribute('open')) return;
|
||||||
|
const selector = kind === 'deck'
|
||||||
|
? `.kit-bag-deck[data-deck-id="${id}"]`
|
||||||
|
: `.token[data-token-id="${id}"]`;
|
||||||
|
const card = dialog.querySelector(selector);
|
||||||
|
if (!card) return;
|
||||||
|
const placeholder = document.createElement('div');
|
||||||
|
placeholder.className = 'kit-bag-placeholder';
|
||||||
|
const icon = card.querySelector('i');
|
||||||
|
if (icon) placeholder.innerHTML = icon.outerHTML;
|
||||||
|
card.parentNode.insertBefore(placeholder, card);
|
||||||
|
card.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function wireDonDoff(token) {
|
||||||
|
const donBtn = portal.querySelector('.btn-equip');
|
||||||
|
const doffBtn = portal.querySelector('.btn-unequip');
|
||||||
|
if (!donBtn || !doffBtn) return;
|
||||||
|
|
||||||
|
donBtn.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
if (donBtn.classList.contains('btn-disabled') || equipping) return;
|
||||||
equipping = true;
|
equipping = true;
|
||||||
|
const tokenId = donBtn.dataset.tokenId;
|
||||||
|
const deckId = donBtn.dataset.deckId;
|
||||||
|
if (tokenId) {
|
||||||
|
gameKit.dataset.equippedId = tokenId;
|
||||||
|
fetch(`/gameboard/equip-trinket/${tokenId}/`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'X-CSRFToken': getCsrfToken()},
|
||||||
|
}).then(r => {
|
||||||
|
equipping = false;
|
||||||
|
if (r.ok) {
|
||||||
|
_setEquipState(donBtn, doffBtn, true);
|
||||||
|
_syncTokenButtons('trinket', tokenId);
|
||||||
|
buildMiniContent(token);
|
||||||
|
_refreshKitDialog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (deckId) {
|
||||||
gameKit.dataset.equippedDeckId = deckId;
|
gameKit.dataset.equippedDeckId = deckId;
|
||||||
fetch(`/gameboard/equip-deck/${deckId}/`, {
|
fetch(`/gameboard/equip-deck/${deckId}/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'X-CSRFToken': getCsrfToken()},
|
headers: {'X-CSRFToken': getCsrfToken()},
|
||||||
}).then(r => {
|
}).then(r => {
|
||||||
if (r.ok && equipping) {
|
|
||||||
equipping = false;
|
|
||||||
closePortals();
|
|
||||||
} else {
|
|
||||||
equipping = false;
|
equipping = false;
|
||||||
|
if (r.ok) {
|
||||||
|
_setEquipState(donBtn, doffBtn, true);
|
||||||
|
_syncTokenButtons('deck', deckId);
|
||||||
|
buildMiniContent(token);
|
||||||
|
_refreshKitDialog();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
miniPortal.innerHTML = '';
|
|
||||||
miniPortal.appendChild(btn);
|
|
||||||
}
|
}
|
||||||
} else if (tokenId) {
|
});
|
||||||
if (equippedId && tokenId === equippedId) {
|
|
||||||
miniPortal.textContent = 'Equipped';
|
doffBtn.addEventListener('click', (e) => {
|
||||||
} else {
|
|
||||||
const btn = document.createElement('button');
|
|
||||||
btn.className = 'equip-trinket-btn';
|
|
||||||
btn.dataset.tokenId = tokenId;
|
|
||||||
btn.textContent = 'Equip Trinket?';
|
|
||||||
btn.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
if (doffBtn.classList.contains('btn-disabled') || equipping) return;
|
||||||
equipping = true;
|
equipping = true;
|
||||||
equippedId = tokenId;
|
const tokenId = doffBtn.dataset.tokenId;
|
||||||
gameKit.dataset.equippedId = equippedId;
|
const deckId = doffBtn.dataset.deckId;
|
||||||
fetch(`/gameboard/equip-trinket/${tokenId}/`, {
|
if (tokenId) {
|
||||||
|
gameKit.dataset.equippedId = '';
|
||||||
|
fetch(`/gameboard/unequip-trinket/${tokenId}/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'X-CSRFToken': getCsrfToken()},
|
headers: {'X-CSRFToken': getCsrfToken()},
|
||||||
}).then(r => {
|
}).then(r => {
|
||||||
if (r.ok && equipping) {
|
|
||||||
equipping = false;
|
|
||||||
closePortals();
|
|
||||||
} else {
|
|
||||||
equipping = false;
|
equipping = false;
|
||||||
|
if (r.ok) {
|
||||||
|
_setEquipState(donBtn, doffBtn, false);
|
||||||
|
_syncTokenButtons('trinket', '');
|
||||||
|
buildMiniContent(token);
|
||||||
|
_syncKitBagDialog('token', tokenId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else if (deckId) {
|
||||||
|
gameKit.dataset.equippedDeckId = '';
|
||||||
|
fetch(`/gameboard/unequip-deck/${deckId}/`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'X-CSRFToken': getCsrfToken()},
|
||||||
|
}).then(r => {
|
||||||
|
equipping = false;
|
||||||
|
if (r.ok) {
|
||||||
|
_setEquipState(donBtn, doffBtn, false);
|
||||||
|
_syncTokenButtons('deck', '');
|
||||||
|
buildMiniContent(token);
|
||||||
|
_syncKitBagDialog('deck', deckId);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
miniPortal.innerHTML = '';
|
|
||||||
miniPortal.appendChild(btn);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showPortals(token) {
|
function showPortals(token) {
|
||||||
equipping = false;
|
equipping = false;
|
||||||
activeToken = token;
|
activeToken = token;
|
||||||
const tooltip = token.querySelector('.token-tooltip');
|
const tooltip = token.querySelector('.tt');
|
||||||
portal.innerHTML = tooltip.innerHTML;
|
portal.innerHTML = tooltip.innerHTML;
|
||||||
portal.classList.add('active');
|
portal.classList.add('active');
|
||||||
portal.style.display = 'block';
|
portal.style.display = 'block';
|
||||||
@@ -129,6 +225,7 @@ function initGameKitTooltips() {
|
|||||||
miniPortal.classList.add('active');
|
miniPortal.classList.add('active');
|
||||||
miniPortal.style.display = 'block';
|
miniPortal.style.display = 'block';
|
||||||
miniHeight = miniPortal.offsetHeight + 4;
|
miniHeight = miniPortal.offsetHeight + 4;
|
||||||
|
wireDonDoff(token);
|
||||||
} else {
|
} else {
|
||||||
miniPortal.classList.remove('active');
|
miniPortal.classList.remove('active');
|
||||||
miniPortal.style.display = 'none';
|
miniPortal.style.display = 'none';
|
||||||
@@ -154,21 +251,24 @@ function initGameKitTooltips() {
|
|||||||
|
|
||||||
if (isEquippable) {
|
if (isEquippable) {
|
||||||
const mainRect = portal.getBoundingClientRect();
|
const mainRect = portal.getBoundingClientRect();
|
||||||
miniPortal.style.left = (mainRect.right - miniPortal.offsetWidth) + 'px';
|
// Pin the right edge of the mini-portal to the right edge of the main portal.
|
||||||
|
// Using `right` (not `left`) means text width changes grow/shrink leftward.
|
||||||
|
miniPortal.style.left = '';
|
||||||
|
miniPortal.style.right = Math.round(window.innerWidth - mainRect.right) + 'px';
|
||||||
miniPortal.style.top = (mainRect.bottom + 4) + 'px';
|
miniPortal.style.top = (mainRect.bottom + 4) + 'px';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('mouseover', (e) => {
|
document.addEventListener('mouseover', (e) => {
|
||||||
const tokenEl = e.target.closest('#id_game_kit .token');
|
const tokenEl = e.target.closest('#id_game_kit .token');
|
||||||
if (!tokenEl || !tokenEl.querySelector('.token-tooltip')) return;
|
if (!tokenEl || !tokenEl.querySelector('.tt')) return;
|
||||||
if (!portal.classList.contains('active') || activeToken !== tokenEl) {
|
if (!portal.classList.contains('active') || activeToken !== tokenEl) {
|
||||||
showPortals(tokenEl);
|
showPortals(tokenEl);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
gameKit.querySelectorAll('.token').forEach(tokenEl => {
|
gameKit.querySelectorAll('.token').forEach(tokenEl => {
|
||||||
if (!tokenEl.querySelector('.token-tooltip')) return;
|
if (!tokenEl.querySelector('.tt')) return;
|
||||||
tokenEl.addEventListener('mouseenter', () => {
|
tokenEl.addEventListener('mouseenter', () => {
|
||||||
if (!portal.classList.contains('active') || activeToken !== tokenEl) {
|
if (!portal.classList.contains('active') || activeToken !== tokenEl) {
|
||||||
showPortals(tokenEl);
|
showPortals(tokenEl);
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ urlpatterns = [
|
|||||||
path('toggle-applets', views.toggle_game_applets, name='toggle_game_applets'),
|
path('toggle-applets', views.toggle_game_applets, name='toggle_game_applets'),
|
||||||
path('equip-trinket/<int:token_id>/', views.equip_trinket, name='equip_trinket'),
|
path('equip-trinket/<int:token_id>/', views.equip_trinket, name='equip_trinket'),
|
||||||
path('equip-deck/<int:deck_id>/', views.equip_deck, name='equip_deck'),
|
path('equip-deck/<int:deck_id>/', views.equip_deck, name='equip_deck'),
|
||||||
|
path('unequip-trinket/<int:token_id>/', views.unequip_trinket, name='unequip_trinket'),
|
||||||
|
path('unequip-deck/<int:deck_id>/', views.unequip_deck, name='unequip_deck'),
|
||||||
path('game-kit/', views.game_kit, name='game_kit'),
|
path('game-kit/', views.game_kit, name='game_kit'),
|
||||||
path('game-kit/toggle-sections', views.toggle_game_kit_sections, name='toggle_game_kit_sections'),
|
path('game-kit/toggle-sections', views.toggle_game_kit_sections, name='toggle_game_kit_sections'),
|
||||||
path('game-kit/deck/<int:deck_id>/', views.tarot_fan, name='tarot_fan'),
|
path('game-kit/deck/<int:deck_id>/', views.tarot_fan, name='tarot_fan'),
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ def gameboard(request):
|
|||||||
"pass_token": pass_token,
|
"pass_token": pass_token,
|
||||||
"coin": coin,
|
"coin": coin,
|
||||||
"carte": carte,
|
"carte": carte,
|
||||||
"equipped_trinket_id": str(request.user.equipped_trinket_id or ""),
|
"equipped_trinket_id": request.user.equipped_trinket_id,
|
||||||
"equipped_deck_id": str(request.user.equipped_deck_id or ""),
|
"equipped_deck_id": request.user.equipped_deck_id,
|
||||||
"deck_variants": list(request.user.unlocked_decks.all()),
|
"deck_variants": list(request.user.unlocked_decks.all()),
|
||||||
"free_tokens": free_tokens,
|
"free_tokens": free_tokens,
|
||||||
"free_count": len(free_tokens),
|
"free_count": len(free_tokens),
|
||||||
@@ -60,8 +60,8 @@ def toggle_game_applets(request):
|
|||||||
"pass_token": request.user.tokens.filter(token_type=Token.PASS).first() if request.user.is_staff else None,
|
"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(),
|
"coin": request.user.tokens.filter(token_type=Token.COIN).first(),
|
||||||
"carte": request.user.tokens.filter(token_type=Token.CARTE).first(),
|
"carte": request.user.tokens.filter(token_type=Token.CARTE).first(),
|
||||||
"equipped_trinket_id": str(request.user.equipped_trinket_id or ""),
|
"equipped_trinket_id": request.user.equipped_trinket_id,
|
||||||
"equipped_deck_id": str(request.user.equipped_deck_id or ""),
|
"equipped_deck_id": request.user.equipped_deck_id,
|
||||||
"deck_variants": list(request.user.unlocked_decks.all()),
|
"deck_variants": list(request.user.unlocked_decks.all()),
|
||||||
"free_tokens": list(request.user.tokens.filter(
|
"free_tokens": list(request.user.tokens.filter(
|
||||||
token_type=Token.FREE, expires_at__gt=timezone.now()
|
token_type=Token.FREE, expires_at__gt=timezone.now()
|
||||||
@@ -102,6 +102,28 @@ def equip_deck(request, deck_id):
|
|||||||
return HttpResponse(status=405)
|
return HttpResponse(status=405)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required(login_url="/")
|
||||||
|
def unequip_trinket(request, token_id):
|
||||||
|
token = get_object_or_404(Token, pk=token_id, user=request.user)
|
||||||
|
if request.method == "POST":
|
||||||
|
if request.user.equipped_trinket_id == token.pk:
|
||||||
|
request.user.equipped_trinket = None
|
||||||
|
request.user.save(update_fields=["equipped_trinket"])
|
||||||
|
return HttpResponse(status=204)
|
||||||
|
return HttpResponse(status=405)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required(login_url="/")
|
||||||
|
def unequip_deck(request, deck_id):
|
||||||
|
get_object_or_404(DeckVariant, pk=deck_id)
|
||||||
|
if request.method == "POST":
|
||||||
|
if request.user.equipped_deck_id == deck_id:
|
||||||
|
request.user.equipped_deck = None
|
||||||
|
request.user.save(update_fields=["equipped_deck"])
|
||||||
|
return HttpResponse(status=204)
|
||||||
|
return HttpResponse(status=405)
|
||||||
|
|
||||||
|
|
||||||
def _game_kit_context(user):
|
def _game_kit_context(user):
|
||||||
coin = user.tokens.filter(token_type=Token.COIN).first()
|
coin = user.tokens.filter(token_type=Token.COIN).first()
|
||||||
pass_token = user.tokens.filter(token_type=Token.PASS).first() if user.is_staff else None
|
pass_token = user.tokens.filter(token_type=Token.PASS).first() if user.is_staff else None
|
||||||
|
|||||||
0
src/apps/tooltips/__init__.py
Normal file
0
src/apps/tooltips/__init__.py
Normal file
10
src/apps/tooltips/admin.py
Normal file
10
src/apps/tooltips/admin.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import TooltipContent
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(TooltipContent)
|
||||||
|
class TooltipContentAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('slug', 'title', 'type_label', 'symbol')
|
||||||
|
search_fields = ('slug', 'title', 'type_label')
|
||||||
|
list_filter = ('type_label',)
|
||||||
6
src/apps/tooltips/apps.py
Normal file
6
src/apps/tooltips/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class TooltipsConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'apps.tooltips'
|
||||||
33
src/apps/tooltips/migrations/0001_initial.py
Normal file
33
src/apps/tooltips/migrations/0001_initial.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 6.0 on 2026-04-16 00:38
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TooltipContent',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('slug', models.SlugField(unique=True)),
|
||||||
|
('title', models.CharField(max_length=200)),
|
||||||
|
('type_label', models.CharField(blank=True, max_length=100)),
|
||||||
|
('symbol', models.CharField(blank=True, max_length=10)),
|
||||||
|
('degree_str', models.CharField(blank=True, max_length=40)),
|
||||||
|
('description', models.TextField(blank=True)),
|
||||||
|
('shoptalk', models.TextField(blank=True)),
|
||||||
|
('expiry', models.CharField(blank=True, max_length=200)),
|
||||||
|
('effect', models.TextField(blank=True)),
|
||||||
|
('extras', models.JSONField(default=dict)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['slug'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
0
src/apps/tooltips/migrations/__init__.py
Normal file
0
src/apps/tooltips/migrations/__init__.py
Normal file
38
src/apps/tooltips/models.py
Normal file
38
src/apps/tooltips/models.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class TooltipContent(models.Model):
|
||||||
|
"""
|
||||||
|
Static tooltip content for game objects — planets, signs, houses, cards, etc.
|
||||||
|
|
||||||
|
Required: slug (unique lookup key), title (display name).
|
||||||
|
All other fields are optional; omitted fields render nothing in the template.
|
||||||
|
|
||||||
|
extras (JSONField) stores structured data that varies by tooltip type:
|
||||||
|
dignities — dict {role: body/sign string, …}
|
||||||
|
aspects — list [{symbol, type, body, orb}, …]
|
||||||
|
keywords_up / keywords_rev — list of strings (card upright/reversed keywords)
|
||||||
|
cautions — list [{title, type_label, shoptalk, effect}, …]
|
||||||
|
nav — dict {prv: slug, nxt: slug} for PRV/NXT navigation
|
||||||
|
"""
|
||||||
|
|
||||||
|
slug = models.SlugField(unique=True)
|
||||||
|
title = models.CharField(max_length=200)
|
||||||
|
|
||||||
|
# Optional display fields
|
||||||
|
type_label = models.CharField(max_length=100, blank=True)
|
||||||
|
symbol = models.CharField(max_length=10, blank=True)
|
||||||
|
degree_str = models.CharField(max_length=40, blank=True)
|
||||||
|
description = models.TextField(blank=True)
|
||||||
|
shoptalk = models.TextField(blank=True)
|
||||||
|
expiry = models.CharField(max_length=200, blank=True)
|
||||||
|
effect = models.TextField(blank=True)
|
||||||
|
|
||||||
|
# Structured data for tabular / list / nav sections
|
||||||
|
extras = models.JSONField(default=dict)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['slug']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
0
src/apps/tooltips/templatetags/__init__.py
Normal file
0
src/apps/tooltips/templatetags/__init__.py
Normal file
32
src/apps/tooltips/templatetags/tooltip_tags.py
Normal file
32
src/apps/tooltips/templatetags/tooltip_tags.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
from django import template
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.inclusion_tag('apps/tooltips/_tooltip.html')
|
||||||
|
def tooltip(data):
|
||||||
|
"""Render the unified tooltip partial from a data dict.
|
||||||
|
|
||||||
|
Minimum required key: 'title'.
|
||||||
|
All other keys are optional; missing keys render nothing.
|
||||||
|
extras (dict) may contain: dignities, aspects, keywords_up,
|
||||||
|
keywords_rev, cautions, nav.
|
||||||
|
"""
|
||||||
|
extras = data.get('extras', {}) or {}
|
||||||
|
return {
|
||||||
|
'title': data.get('title', ''),
|
||||||
|
'type_label': data.get('type_label', ''),
|
||||||
|
'symbol': data.get('symbol', ''),
|
||||||
|
'degree_str': data.get('degree_str', ''),
|
||||||
|
'description': data.get('description', ''),
|
||||||
|
'shoptalk': data.get('shoptalk', ''),
|
||||||
|
'expiry': data.get('expiry', ''),
|
||||||
|
'effect': data.get('effect', ''),
|
||||||
|
'dignities': extras.get('dignities'),
|
||||||
|
'aspects': extras.get('aspects'),
|
||||||
|
'keywords_up': extras.get('keywords_up'),
|
||||||
|
'keywords_rev':extras.get('keywords_rev'),
|
||||||
|
'cautions': extras.get('cautions'),
|
||||||
|
'nav': extras.get('nav'),
|
||||||
|
'equippable': data.get('equippable'),
|
||||||
|
}
|
||||||
0
src/apps/tooltips/tests/__init__.py
Normal file
0
src/apps/tooltips/tests/__init__.py
Normal file
0
src/apps/tooltips/tests/integrated/__init__.py
Normal file
0
src/apps/tooltips/tests/integrated/__init__.py
Normal file
127
src/apps/tooltips/tests/integrated/test_models.py
Normal file
127
src/apps/tooltips/tests/integrated/test_models.py
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
from django.db import IntegrityError
|
||||||
|
|
||||||
|
from apps.tooltips.models import TooltipContent
|
||||||
|
|
||||||
|
|
||||||
|
class TooltipContentModelTest(TestCase):
|
||||||
|
|
||||||
|
# ── Required fields ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def test_can_create_with_slug_and_title_only(self):
|
||||||
|
tt = TooltipContent.objects.create(slug='sun', title='Sun')
|
||||||
|
self.assertEqual(TooltipContent.objects.count(), 1)
|
||||||
|
self.assertEqual(tt.slug, 'sun')
|
||||||
|
self.assertEqual(tt.title, 'Sun')
|
||||||
|
|
||||||
|
def test_slug_is_unique(self):
|
||||||
|
TooltipContent.objects.create(slug='sun', title='Sun')
|
||||||
|
with self.assertRaises(IntegrityError):
|
||||||
|
TooltipContent.objects.create(slug='sun', title='Another Sun')
|
||||||
|
|
||||||
|
def test_str_returns_title(self):
|
||||||
|
tt = TooltipContent(slug='moon', title='Moon')
|
||||||
|
self.assertEqual(str(tt), 'Moon')
|
||||||
|
|
||||||
|
# ── Optional text fields ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def test_optional_text_fields_default_blank(self):
|
||||||
|
tt = TooltipContent.objects.create(slug='mars', title='Mars')
|
||||||
|
for field in ('type_label', 'symbol', 'degree_str',
|
||||||
|
'description', 'shoptalk', 'expiry', 'effect'):
|
||||||
|
self.assertEqual(getattr(tt, field), '', msg=f'{field} should default blank')
|
||||||
|
|
||||||
|
def test_can_set_all_text_fields(self):
|
||||||
|
tt = TooltipContent.objects.create(
|
||||||
|
slug='jupiter',
|
||||||
|
title='Jupiter',
|
||||||
|
type_label='Planet',
|
||||||
|
symbol='♃',
|
||||||
|
degree_str='14° 22′ Scorpio',
|
||||||
|
description='Planet of expansion and fortune.',
|
||||||
|
shoptalk='Ruler of Sagittarius.',
|
||||||
|
expiry='',
|
||||||
|
effect='Amplifies whichever house it occupies.',
|
||||||
|
)
|
||||||
|
self.assertEqual(tt.type_label, 'Planet')
|
||||||
|
self.assertEqual(tt.symbol, '♃')
|
||||||
|
self.assertEqual(tt.degree_str, '14° 22′ Scorpio')
|
||||||
|
|
||||||
|
# ── extras JSONField ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def test_extras_defaults_to_empty_dict(self):
|
||||||
|
tt = TooltipContent.objects.create(slug='venus', title='Venus')
|
||||||
|
self.assertEqual(tt.extras, {})
|
||||||
|
|
||||||
|
def test_extras_stores_dignities(self):
|
||||||
|
tt = TooltipContent.objects.create(
|
||||||
|
slug='saturn',
|
||||||
|
title='Saturn',
|
||||||
|
extras={
|
||||||
|
'dignities': {
|
||||||
|
'Domicile': 'Capricorn / Aquarius',
|
||||||
|
'Exalted': 'Libra',
|
||||||
|
'Exile': 'Cancer / Leo',
|
||||||
|
'Fallen': 'Aries',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
tt.refresh_from_db()
|
||||||
|
self.assertEqual(tt.extras['dignities']['Exalted'], 'Libra')
|
||||||
|
|
||||||
|
def test_extras_stores_aspects(self):
|
||||||
|
tt = TooltipContent.objects.create(
|
||||||
|
slug='mercury',
|
||||||
|
title='Mercury',
|
||||||
|
extras={
|
||||||
|
'aspects': [
|
||||||
|
{'symbol': '△', 'type': 'Trine', 'body': 'Venus', 'orb': '2° 14′'},
|
||||||
|
{'symbol': '□', 'type': 'Square', 'body': 'Mars', 'orb': '0° 42′'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
tt.refresh_from_db()
|
||||||
|
self.assertEqual(len(tt.extras['aspects']), 2)
|
||||||
|
self.assertEqual(tt.extras['aspects'][0]['type'], 'Trine')
|
||||||
|
|
||||||
|
def test_extras_stores_keywords(self):
|
||||||
|
tt = TooltipContent.objects.create(
|
||||||
|
slug='the-schizo',
|
||||||
|
title='The Schizo, Leavened',
|
||||||
|
extras={
|
||||||
|
'keywords_up': ['willpower', 'skill', 'resourcefulness'],
|
||||||
|
'keywords_rev': ['hubris', 'overreach'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
tt.refresh_from_db()
|
||||||
|
self.assertIn('willpower', tt.extras['keywords_up'])
|
||||||
|
|
||||||
|
def test_extras_stores_cautions(self):
|
||||||
|
tt = TooltipContent.objects.create(
|
||||||
|
slug='the-schizo-cautions',
|
||||||
|
title='The Schizo, Leavened',
|
||||||
|
extras={
|
||||||
|
'cautions': [
|
||||||
|
{
|
||||||
|
'title': 'Caution!',
|
||||||
|
'type_label': 'Rival Interaction',
|
||||||
|
'shoptalk': '[Shoptalk forthcoming]',
|
||||||
|
'effect': 'This card will reverse into I. The Pervert...',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
tt.refresh_from_db()
|
||||||
|
self.assertEqual(tt.extras['cautions'][0]['type_label'], 'Rival Interaction')
|
||||||
|
|
||||||
|
def test_extras_stores_nav(self):
|
||||||
|
"""PRV/NXT nav dict for paired or sequenced tooltips."""
|
||||||
|
tt = TooltipContent.objects.create(
|
||||||
|
slug='house-01',
|
||||||
|
title='House of Self',
|
||||||
|
extras={
|
||||||
|
'nav': {'prv': 'house-12', 'nxt': 'house-02'},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
tt.refresh_from_db()
|
||||||
|
self.assertEqual(tt.extras['nav']['nxt'], 'house-02')
|
||||||
172
src/apps/tooltips/tests/integrated/test_templatetags.py
Normal file
172
src/apps/tooltips/tests/integrated/test_templatetags.py
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
from django.template import Context, Template
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
def render_tooltip(data):
|
||||||
|
"""Helper: render the {% tooltip %} tag with the given data dict."""
|
||||||
|
tpl = Template("{% load tooltip_tags %}{% tooltip data %}")
|
||||||
|
return tpl.render(Context({'data': data}))
|
||||||
|
|
||||||
|
|
||||||
|
class TooltipTagRequiredFieldsTest(TestCase):
|
||||||
|
|
||||||
|
def test_renders_title(self):
|
||||||
|
html = render_tooltip({'title': 'Sun'})
|
||||||
|
self.assertIn('Sun', html)
|
||||||
|
self.assertIn('class="tt-title"', html)
|
||||||
|
|
||||||
|
def test_wrapper_has_tt_class(self):
|
||||||
|
html = render_tooltip({'title': 'Moon'})
|
||||||
|
self.assertIn('class="tt"', html)
|
||||||
|
|
||||||
|
|
||||||
|
class TooltipTagOptionalTextFieldsTest(TestCase):
|
||||||
|
|
||||||
|
def test_renders_type_label_when_present(self):
|
||||||
|
html = render_tooltip({'title': 'Sun', 'type_label': 'Planet'})
|
||||||
|
self.assertIn('Planet', html)
|
||||||
|
self.assertIn('tt-type', html)
|
||||||
|
|
||||||
|
def test_omits_type_label_when_absent(self):
|
||||||
|
html = render_tooltip({'title': 'Sun'})
|
||||||
|
self.assertNotIn('tt-type', html)
|
||||||
|
|
||||||
|
def test_renders_symbol_when_present(self):
|
||||||
|
html = render_tooltip({'title': 'Sun', 'symbol': '☉'})
|
||||||
|
self.assertIn('☉', html)
|
||||||
|
self.assertIn('tt-symbol', html)
|
||||||
|
|
||||||
|
def test_omits_symbol_when_absent(self):
|
||||||
|
html = render_tooltip({'title': 'Sun'})
|
||||||
|
self.assertNotIn('tt-symbol', html)
|
||||||
|
|
||||||
|
def test_renders_degree_str_when_present(self):
|
||||||
|
html = render_tooltip({'title': 'Sun', 'degree_str': '14° 22′ Scorpio'})
|
||||||
|
self.assertIn('14° 22′ Scorpio', html)
|
||||||
|
self.assertIn('tt-degree', html)
|
||||||
|
|
||||||
|
def test_omits_degree_str_when_absent(self):
|
||||||
|
html = render_tooltip({'title': 'Sun'})
|
||||||
|
self.assertNotIn('tt-degree', html)
|
||||||
|
|
||||||
|
def test_renders_description_when_present(self):
|
||||||
|
html = render_tooltip({'title': 'Sun', 'description': 'Planet of vitality.'})
|
||||||
|
self.assertIn('Planet of vitality.', html)
|
||||||
|
self.assertIn('tt-description', html)
|
||||||
|
|
||||||
|
def test_renders_shoptalk_in_em_when_present(self):
|
||||||
|
html = render_tooltip({'title': 'Sun', 'shoptalk': 'Ruler of Leo.'})
|
||||||
|
self.assertIn('Ruler of Leo.', html)
|
||||||
|
self.assertIn('tt-shoptalk', html)
|
||||||
|
self.assertIn('<em>', html)
|
||||||
|
|
||||||
|
def test_renders_expiry_when_present(self):
|
||||||
|
html = render_tooltip({'title': 'Pass', 'expiry': 'no expiry'})
|
||||||
|
self.assertIn('no expiry', html)
|
||||||
|
self.assertIn('tt-expiry', html)
|
||||||
|
|
||||||
|
def test_renders_effect_when_present(self):
|
||||||
|
html = render_tooltip({'title': 'Pass', 'effect': 'Admit All Entry'})
|
||||||
|
self.assertIn('Admit All Entry', html)
|
||||||
|
self.assertIn('tt-effect', html)
|
||||||
|
|
||||||
|
|
||||||
|
class TooltipTagExtrasTest(TestCase):
|
||||||
|
|
||||||
|
def test_renders_dignities_table_when_present(self):
|
||||||
|
data = {
|
||||||
|
'title': 'Saturn',
|
||||||
|
'extras': {
|
||||||
|
'dignities': {
|
||||||
|
'Domicile': 'Capricorn',
|
||||||
|
'Exalted': 'Libra',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
html = render_tooltip(data)
|
||||||
|
self.assertIn('tt-table--dignities', html)
|
||||||
|
self.assertIn('Capricorn', html)
|
||||||
|
self.assertIn('Exalted', html)
|
||||||
|
|
||||||
|
def test_omits_dignities_when_absent(self):
|
||||||
|
html = render_tooltip({'title': 'Sun', 'extras': {}})
|
||||||
|
self.assertNotIn('tt-table--dignities', html)
|
||||||
|
|
||||||
|
def test_renders_aspects_list_when_present(self):
|
||||||
|
data = {
|
||||||
|
'title': 'Mercury',
|
||||||
|
'extras': {
|
||||||
|
'aspects': [
|
||||||
|
{'symbol': '△', 'type': 'Trine', 'body': 'Venus', 'orb': '2° 14′'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
html = render_tooltip(data)
|
||||||
|
self.assertIn('tt-aspects', html)
|
||||||
|
self.assertIn('Trine', html)
|
||||||
|
self.assertIn('Venus', html)
|
||||||
|
|
||||||
|
def test_omits_aspects_when_absent(self):
|
||||||
|
html = render_tooltip({'title': 'Sun', 'extras': {}})
|
||||||
|
self.assertNotIn('tt-aspects', html)
|
||||||
|
|
||||||
|
def test_renders_keyword_lists_when_present(self):
|
||||||
|
data = {
|
||||||
|
'title': 'The Schizo',
|
||||||
|
'extras': {
|
||||||
|
'keywords_up': ['willpower', 'skill'],
|
||||||
|
'keywords_rev': ['hubris'],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
html = render_tooltip(data)
|
||||||
|
self.assertIn('tt-keywords', html)
|
||||||
|
self.assertIn('willpower', html)
|
||||||
|
self.assertIn('hubris', html)
|
||||||
|
|
||||||
|
def test_omits_keywords_when_absent(self):
|
||||||
|
html = render_tooltip({'title': 'Sun', 'extras': {}})
|
||||||
|
self.assertNotIn('tt-keywords', html)
|
||||||
|
|
||||||
|
def test_renders_fyi_cautions_section_when_present(self):
|
||||||
|
data = {
|
||||||
|
'title': 'The Schizo',
|
||||||
|
'extras': {
|
||||||
|
'cautions': [
|
||||||
|
{
|
||||||
|
'title': 'Caution!',
|
||||||
|
'type_label': 'Rival Interaction',
|
||||||
|
'shoptalk': '[Shoptalk forthcoming]',
|
||||||
|
'effect': 'This card will reverse...',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
html = render_tooltip(data)
|
||||||
|
self.assertIn('tt-fyi', html)
|
||||||
|
self.assertIn('tt-fyi--cautions', html)
|
||||||
|
self.assertIn('Rival Interaction', html)
|
||||||
|
|
||||||
|
def test_omits_cautions_section_when_absent(self):
|
||||||
|
html = render_tooltip({'title': 'Sun', 'extras': {}})
|
||||||
|
self.assertNotIn('tt-fyi--cautions', html)
|
||||||
|
|
||||||
|
def test_renders_nav_when_present(self):
|
||||||
|
data = {
|
||||||
|
'title': 'House of Self',
|
||||||
|
'extras': {
|
||||||
|
'nav': {'prv': 'house-12', 'nxt': 'house-02'},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
html = render_tooltip(data)
|
||||||
|
self.assertIn('tt-nav', html)
|
||||||
|
self.assertIn('house-02', html)
|
||||||
|
self.assertIn('house-12', html)
|
||||||
|
|
||||||
|
def test_omits_nav_when_absent(self):
|
||||||
|
html = render_tooltip({'title': 'Sun', 'extras': {}})
|
||||||
|
self.assertNotIn('tt-nav', html)
|
||||||
|
|
||||||
|
def test_no_extras_key_does_not_error(self):
|
||||||
|
"""Tag should handle data with no 'extras' key gracefully."""
|
||||||
|
html = render_tooltip({'title': 'Sun'})
|
||||||
|
self.assertIn('Sun', html)
|
||||||
@@ -63,6 +63,7 @@ INSTALLED_APPS = [
|
|||||||
'apps.epic',
|
'apps.epic',
|
||||||
'apps.drama',
|
'apps.drama',
|
||||||
# Custom apps
|
# Custom apps
|
||||||
|
'apps.tooltips',
|
||||||
'apps.ap',
|
'apps.ap',
|
||||||
'apps.api',
|
'apps.api',
|
||||||
'apps.applets',
|
'apps.applets',
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class GameKitTest(FunctionalTest):
|
|||||||
"arguments[0].dispatchEvent(new Event('mouseenter'))", deck_el
|
"arguments[0].dispatchEvent(new Event('mouseenter'))", deck_el
|
||||||
)
|
)
|
||||||
tooltip = self.browser.find_element(
|
tooltip = self.browser.find_element(
|
||||||
By.CSS_SELECTOR, "#id_kit_bag_dialog .kit-bag-deck .token-tooltip"
|
By.CSS_SELECTOR, "#id_kit_bag_dialog .kit-bag-deck .tt"
|
||||||
)
|
)
|
||||||
self.wait_for(lambda: self.assertTrue(tooltip.is_displayed()))
|
self.wait_for(lambda: self.assertTrue(tooltip.is_displayed()))
|
||||||
text = tooltip.text
|
text = tooltip.text
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ from selenium.webdriver.common.by import By
|
|||||||
|
|
||||||
from .base import FunctionalTest
|
from .base import FunctionalTest
|
||||||
from apps.applets.models import Applet
|
from apps.applets.models import Applet
|
||||||
|
from apps.epic.models import DeckVariant, Room
|
||||||
|
from apps.lyric.models import Token, User
|
||||||
|
|
||||||
|
|
||||||
class GameboardNavigationTest(FunctionalTest):
|
class GameboardNavigationTest(FunctionalTest):
|
||||||
@@ -156,3 +158,139 @@ class GameboardAppletMenuTest(FunctionalTest):
|
|||||||
)
|
)
|
||||||
# 7. Assert no full page reload occurred
|
# 7. Assert no full page reload occurred
|
||||||
self.assertTrue(self.browser.execute_script("return window.__no_reload_marker === true"))
|
self.assertTrue(self.browser.execute_script("return window.__no_reload_marker === true"))
|
||||||
|
|
||||||
|
|
||||||
|
class GameKitEquipTest(FunctionalTest):
|
||||||
|
"""DON|DOFF equip buttons in the game kit applet portal tooltip."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.earthman, _ = DeckVariant.objects.get_or_create(
|
||||||
|
slug="earthman",
|
||||||
|
defaults={"name": "Earthman Deck", "card_count": 108, "is_default": True},
|
||||||
|
)
|
||||||
|
Applet.objects.get_or_create(slug="game-kit", defaults={"name": "Game Kit", "context": "gameboard"})
|
||||||
|
self.create_pre_authenticated_session("gamer@equip.io")
|
||||||
|
self.gamer = User.objects.get(email="gamer@equip.io")
|
||||||
|
# Promote to staff so the pass_token appears in the game kit applet.
|
||||||
|
self.gamer.is_staff = True
|
||||||
|
self.gamer.save(update_fields=["is_staff"])
|
||||||
|
self.gamer.unlocked_decks.add(self.earthman)
|
||||||
|
self.coin = self.gamer.tokens.filter(token_type=Token.COIN).first()
|
||||||
|
# Create a PASS token manually — starts unequipped (coin is auto-equipped).
|
||||||
|
self.pass_token = Token.objects.create(user=self.gamer, token_type=Token.PASS)
|
||||||
|
self.browser.set_window_size(1200, 900)
|
||||||
|
self.browser.get(self.live_server_url + "/gameboard/")
|
||||||
|
|
||||||
|
def _hover_game_kit_token(self, token_el):
|
||||||
|
"""Hover token, wait for portal, return portal element."""
|
||||||
|
ActionChains(self.browser).move_to_element(token_el).perform()
|
||||||
|
self.wait_for(
|
||||||
|
lambda: self.assertTrue(
|
||||||
|
self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return self.browser.find_element(By.ID, "id_tooltip_portal")
|
||||||
|
|
||||||
|
def test_unequipped_token_shows_don_active_doff_disabled(self):
|
||||||
|
"""Backstage Pass — naturally unequipped (coin is auto-equipped): DON active, DOFF is ×."""
|
||||||
|
pass_el = self.wait_for(
|
||||||
|
lambda: self.browser.find_element(By.ID, "id_kit_pass")
|
||||||
|
)
|
||||||
|
portal = self._hover_game_kit_token(pass_el)
|
||||||
|
don = portal.find_element(By.CSS_SELECTOR, ".btn-equip")
|
||||||
|
doff = portal.find_element(By.CSS_SELECTOR, ".btn-unequip")
|
||||||
|
self.assertNotIn("btn-disabled", don.get_attribute("class"))
|
||||||
|
self.assertIn("btn-disabled", doff.get_attribute("class"))
|
||||||
|
self.assertEqual(doff.text, "×")
|
||||||
|
|
||||||
|
def test_equipped_token_shows_doff_active_don_disabled(self):
|
||||||
|
"""Auto-equipped coin: DOFF active, DON is ×; mini-portal says Equipped."""
|
||||||
|
coin = self.wait_for(
|
||||||
|
lambda: self.browser.find_element(By.ID, "id_kit_coin_on_a_string")
|
||||||
|
)
|
||||||
|
portal = self._hover_game_kit_token(coin)
|
||||||
|
don = portal.find_element(By.CSS_SELECTOR, ".btn-equip")
|
||||||
|
doff = portal.find_element(By.CSS_SELECTOR, ".btn-unequip")
|
||||||
|
self.assertIn("btn-disabled", don.get_attribute("class"))
|
||||||
|
self.assertEqual(don.text, "×")
|
||||||
|
self.assertNotIn("btn-disabled", doff.get_attribute("class"))
|
||||||
|
mini = self.browser.find_element(By.ID, "id_mini_tooltip_portal")
|
||||||
|
self.assertEqual(mini.text, "Equipped")
|
||||||
|
self.assertEqual(len(mini.find_elements(By.CSS_SELECTOR, "button")), 0)
|
||||||
|
|
||||||
|
def test_doff_then_don_roundtrip(self):
|
||||||
|
"""Full roundtrip: DOFF unequips (portal stays open, buttons swap, mini updates);
|
||||||
|
DON re-equips (buttons swap back, mini updates back, DB confirms)."""
|
||||||
|
coin = self.wait_for(
|
||||||
|
lambda: self.browser.find_element(By.ID, "id_kit_coin_on_a_string")
|
||||||
|
)
|
||||||
|
portal = self._hover_game_kit_token(coin)
|
||||||
|
|
||||||
|
# — DOFF —
|
||||||
|
portal.find_element(By.CSS_SELECTOR, ".btn-unequip").click()
|
||||||
|
self.wait_for(
|
||||||
|
lambda: self.assertIn(
|
||||||
|
"btn-disabled",
|
||||||
|
portal.find_element(By.CSS_SELECTOR, ".btn-unequip").get_attribute("class"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertEqual(portal.find_element(By.CSS_SELECTOR, ".btn-unequip").text, "×")
|
||||||
|
self.assertNotIn(
|
||||||
|
"btn-disabled",
|
||||||
|
portal.find_element(By.CSS_SELECTOR, ".btn-equip").get_attribute("class"),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.browser.find_element(By.ID, "id_mini_tooltip_portal").text, "Not Equipped"
|
||||||
|
)
|
||||||
|
self.gamer.refresh_from_db()
|
||||||
|
self.assertIsNone(self.gamer.equipped_trinket)
|
||||||
|
|
||||||
|
# — DON —
|
||||||
|
portal.find_element(By.CSS_SELECTOR, ".btn-equip").click()
|
||||||
|
self.wait_for(
|
||||||
|
lambda: self.assertIn(
|
||||||
|
"btn-disabled",
|
||||||
|
portal.find_element(By.CSS_SELECTOR, ".btn-equip").get_attribute("class"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertEqual(portal.find_element(By.CSS_SELECTOR, ".btn-equip").text, "×")
|
||||||
|
self.assertNotIn(
|
||||||
|
"btn-disabled",
|
||||||
|
portal.find_element(By.CSS_SELECTOR, ".btn-unequip").get_attribute("class"),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.browser.find_element(By.ID, "id_mini_tooltip_portal").text, "Equipped"
|
||||||
|
)
|
||||||
|
self.gamer.refresh_from_db()
|
||||||
|
self.assertEqual(self.gamer.equipped_trinket, self.coin)
|
||||||
|
|
||||||
|
def test_doff_updates_open_kit_bag_dialog(self):
|
||||||
|
"""DOFF from game kit applet replaces trinket card in currently-open kit bag dialog."""
|
||||||
|
self.gamer.equipped_trinket = self.coin
|
||||||
|
self.gamer.save(update_fields=["equipped_trinket"])
|
||||||
|
self.browser.refresh()
|
||||||
|
# Open kit bag dialog
|
||||||
|
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.coin.id}"]',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# While dialog is open, hover coin and DOFF from game kit
|
||||||
|
coin = self.wait_for(
|
||||||
|
lambda: self.browser.find_element(By.ID, "id_kit_coin_on_a_string")
|
||||||
|
)
|
||||||
|
portal = self._hover_game_kit_token(coin)
|
||||||
|
portal.find_element(By.CSS_SELECTOR, ".btn-unequip").click()
|
||||||
|
# Dialog updates: trinket card replaced with placeholder
|
||||||
|
self.wait_for(
|
||||||
|
lambda: self.assertEqual(
|
||||||
|
len(self.browser.find_elements(
|
||||||
|
By.CSS_SELECTOR,
|
||||||
|
f'#id_kit_bag_dialog [data-token-id="{self.coin.id}"]',
|
||||||
|
)), 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.browser.find_element(By.CSS_SELECTOR, "#id_kit_bag_dialog .kit-bag-placeholder")
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ class CarteBlancheTest(FunctionalTest):
|
|||||||
# relying on hover visibility in headless Firefox.
|
# relying on hover visibility in headless Firefox.
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
"The Long Room",
|
"The Long Room",
|
||||||
carte_in_bag.find_element(By.CSS_SELECTOR, ".token-tooltip").get_attribute("textContent"),
|
carte_in_bag.find_element(By.CSS_SELECTOR, ".tt").get_attribute("textContent"),
|
||||||
)
|
)
|
||||||
# Close kit bag
|
# Close kit bag
|
||||||
self.browser.find_element(By.ID, "id_kit_btn").click()
|
self.browser.find_element(By.ID, "id_kit_btn").click()
|
||||||
@@ -265,7 +265,7 @@ class CarteBlancheTest(FunctionalTest):
|
|||||||
)
|
)
|
||||||
self.assertNotIn(
|
self.assertNotIn(
|
||||||
"The Long Room",
|
"The Long Room",
|
||||||
carte_in_bag.find_element(By.CSS_SELECTOR, ".token-tooltip").get_attribute("textContent"),
|
carte_in_bag.find_element(By.CSS_SELECTOR, ".tt").get_attribute("textContent"),
|
||||||
)
|
)
|
||||||
self.browser.find_element(By.ID, "id_kit_btn").click()
|
self.browser.find_element(By.ID, "id_kit_btn").click()
|
||||||
|
|
||||||
|
|||||||
@@ -286,44 +286,6 @@
|
|||||||
font-size: 0.75rem; // 0.63rem × 1.2
|
font-size: 0.75rem; // 0.63rem × 1.2
|
||||||
}
|
}
|
||||||
|
|
||||||
&.btn-disabled {
|
|
||||||
cursor: default !important;
|
|
||||||
pointer-events: none;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
padding-bottom: 0.1rem;
|
|
||||||
color: rgba(var(--secUser), 0.25) !important;
|
|
||||||
background-color: rgba(var(--priUser), 1) !important;
|
|
||||||
border-color: rgba(var(--secUser), 0.25) !important;
|
|
||||||
box-shadow:
|
|
||||||
0.1rem 0.1rem 0.12rem rgba(var(--priUser), 0.5),
|
|
||||||
0.12rem 0.12rem 0.25rem rgba(0, 0, 0, 0.25),
|
|
||||||
0.25rem 0.25rem 0.25rem rgba(var(--secUser), 0.12)
|
|
||||||
;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-shadow:
|
|
||||||
0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.25),
|
|
||||||
0 0 1rem rgba(var(--priUser), 1)
|
|
||||||
;
|
|
||||||
box-shadow:
|
|
||||||
0.12rem 0.12rem 0.5rem rgba(0, 0, 0, 0.25),
|
|
||||||
0 0 0.5rem rgba(var(--priUser), 0.12)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
text-shadow:
|
|
||||||
-0.1rem -0.1rem 0.25rem rgba(0, 0, 0, 0.25),
|
|
||||||
0 0 0.12rem rgba(var(--priUser), 1)
|
|
||||||
;
|
|
||||||
box-shadow:
|
|
||||||
-0.1rem -0.1rem 0.12rem rgba(var(--priUser), 0.75),
|
|
||||||
-0.1rem -0.1rem 0.12rem rgba(0, 0, 0, 0.25),
|
|
||||||
0 0 0.5rem rgba(var(--secUser), 0.12)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.btn-nav-left {
|
&.btn-nav-left {
|
||||||
color: rgba(var(--priFs), 1);
|
color: rgba(var(--priFs), 1);
|
||||||
border-color: rgba(var(--priFs), 1);
|
border-color: rgba(var(--priFs), 1);
|
||||||
@@ -394,6 +356,76 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.btn-equip {
|
||||||
|
color: rgba(var(--priTk), 1);
|
||||||
|
border-color: rgba(var(--priTk), 1);
|
||||||
|
background-color: rgba(var(--terTk), 1);
|
||||||
|
box-shadow:
|
||||||
|
0.1rem 0.1rem 0.12rem rgba(var(--terTk), 0.25),
|
||||||
|
0.12rem 0.12rem 0.25rem rgba(0, 0, 0, 0.25),
|
||||||
|
0.25rem 0.25rem 0.25rem rgba(var(--terTk), 0.12)
|
||||||
|
;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-shadow:
|
||||||
|
0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.25),
|
||||||
|
0 0 1rem rgba(var(--priTk), 1)
|
||||||
|
;
|
||||||
|
box-shadow:
|
||||||
|
0.12rem 0.12rem 0.5rem rgba(0, 0, 0, 0.25),
|
||||||
|
0 0 0.5rem rgba(var(--priTk), 0.12)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
border: 0.18rem solid rgba(var(--priTk), 1);
|
||||||
|
text-shadow:
|
||||||
|
-0.1rem -0.1rem 0.25rem rgba(0, 0, 0, 0.25),
|
||||||
|
0 0 0.12rem rgba(var(--priTk), 1)
|
||||||
|
;
|
||||||
|
box-shadow:
|
||||||
|
-0.1rem -0.1rem 0.12rem rgba(var(--terTk), 0.25),
|
||||||
|
-0.1rem -0.1rem 0.12rem rgba(0, 0, 0, 0.25),
|
||||||
|
0 0 0.5rem rgba(var(--priTk), 0.12)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.btn-unequip {
|
||||||
|
color: rgba(var(--priMe), 1);
|
||||||
|
border-color: rgba(var(--priMe), 1);
|
||||||
|
background-color: rgba(var(--terMe), 1);
|
||||||
|
box-shadow:
|
||||||
|
0.1rem 0.1rem 0.12rem rgba(var(--terMe), 0.25),
|
||||||
|
0.12rem 0.12rem 0.25rem rgba(0, 0, 0, 0.25),
|
||||||
|
0.25rem 0.25rem 0.25rem rgba(var(--terMe), 0.12)
|
||||||
|
;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-shadow:
|
||||||
|
0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.25),
|
||||||
|
0 0 1rem rgba(var(--priMe), 1)
|
||||||
|
;
|
||||||
|
box-shadow:
|
||||||
|
0.12rem 0.12rem 0.5rem rgba(0, 0, 0, 0.25),
|
||||||
|
0 0 0.5rem rgba(var(--priMe), 0.12)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
border: 0.18rem solid rgba(var(--priMe), 1);
|
||||||
|
text-shadow:
|
||||||
|
-0.1rem -0.1rem 0.25rem rgba(0, 0, 0, 0.25),
|
||||||
|
0 0 0.12rem rgba(var(--priMe), 1)
|
||||||
|
;
|
||||||
|
box-shadow:
|
||||||
|
-0.1rem -0.1rem 0.12rem rgba(var(--terMe), 0.25),
|
||||||
|
-0.1rem -0.1rem 0.12rem rgba(0, 0, 0, 0.25),
|
||||||
|
0 0 0.5rem rgba(var(--priMe), 0.12)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.btn-reverse {
|
&.btn-reverse {
|
||||||
color: rgba(var(--priCy), 1);
|
color: rgba(var(--priCy), 1);
|
||||||
border-color: rgba(var(--priCy), 1);
|
border-color: rgba(var(--priCy), 1);
|
||||||
@@ -463,4 +495,43 @@
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dead last — wins over all color modifiers by source order.
|
||||||
|
&.btn-disabled {
|
||||||
|
cursor: default !important;
|
||||||
|
pointer-events: none;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
padding-bottom: 0.1rem;
|
||||||
|
color: rgba(var(--secUser), 0.25) !important;
|
||||||
|
background-color: rgba(var(--priUser), 1) !important;
|
||||||
|
border-color: rgba(var(--secUser), 0.25) !important;
|
||||||
|
box-shadow:
|
||||||
|
0.1rem 0.1rem 0.12rem rgba(var(--priUser), 0.5),
|
||||||
|
0.12rem 0.12rem 0.25rem rgba(0, 0, 0, 0.25),
|
||||||
|
0.25rem 0.25rem 0.25rem rgba(var(--secUser), 0.12)
|
||||||
|
;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-shadow:
|
||||||
|
0.1rem 0.1rem 0.1rem rgba(0, 0, 0, 0.25),
|
||||||
|
0 0 1rem rgba(var(--priUser), 1)
|
||||||
|
;
|
||||||
|
box-shadow:
|
||||||
|
0.12rem 0.12rem 0.5rem rgba(0, 0, 0, 0.25),
|
||||||
|
0 0 0.5rem rgba(var(--priUser), 0.12)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
text-shadow:
|
||||||
|
-0.1rem -0.1rem 0.25rem rgba(0, 0, 0, 0.25),
|
||||||
|
0 0 0.12rem rgba(var(--priUser), 1)
|
||||||
|
;
|
||||||
|
box-shadow:
|
||||||
|
-0.1rem -0.1rem 0.12rem rgba(var(--priUser), 0.75),
|
||||||
|
-0.1rem -0.1rem 0.12rem rgba(0, 0, 0, 0.25),
|
||||||
|
0 0 0.5rem rgba(var(--secUser), 0.12)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,11 +105,26 @@
|
|||||||
transition: filter 0.15s;
|
transition: filter 0.15s;
|
||||||
padding: 0 0.125rem;
|
padding: 0 0.125rem;
|
||||||
|
|
||||||
&:hover .token-tooltip { display: none; } // JS positions these as fixed
|
&:hover .token-tooltip,
|
||||||
|
&:hover .tt { display: none; } // JS positions these as fixed
|
||||||
}
|
}
|
||||||
|
|
||||||
.token-tooltip {
|
.token-tooltip,
|
||||||
|
.tt {
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
|
|
||||||
|
// Buttons positioned on left edge of the fixed inline tooltip
|
||||||
|
.tt-equip-btns {
|
||||||
|
position: absolute;
|
||||||
|
left: -1rem;
|
||||||
|
top: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.25rem;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.btn { margin: 0; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.kit-bag-deck {
|
.kit-bag-deck {
|
||||||
|
|||||||
@@ -78,7 +78,8 @@ body.page-gameboard {
|
|||||||
|
|
||||||
.token { position: static; }
|
.token { position: static; }
|
||||||
|
|
||||||
.token:hover .token-tooltip { display: none; }
|
.token:hover .token-tooltip,
|
||||||
|
.token:hover .tt { display: none; } // JS portal handles show/hide
|
||||||
|
|
||||||
.token,
|
.token,
|
||||||
.kit-item { font-size: 1.5rem; }
|
.kit-item { font-size: 1.5rem; }
|
||||||
@@ -121,6 +122,25 @@ body.page-gameboard {
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
|
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
|
||||||
|
.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); }
|
||||||
|
|
||||||
|
.tt-equip-btns {
|
||||||
|
position: absolute;
|
||||||
|
left: -1rem;
|
||||||
|
top: -1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.btn { margin: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
&.active { display: block; }
|
&.active { display: block; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
.token-tooltip {
|
.token-tooltip,
|
||||||
|
.tt {
|
||||||
display: none;
|
display: none;
|
||||||
width: 16rem;
|
width: 16rem;
|
||||||
max-width: 16rem;
|
max-width: 16rem;
|
||||||
@@ -67,7 +68,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover .token-tooltip {
|
&:hover .token-tooltip {
|
||||||
display: block;
|
display: block; // legacy fallback; .tt is JS-portal-only (no CSS hover)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,49 +3,52 @@
|
|||||||
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
||||||
>
|
>
|
||||||
<h2><a href="{% url 'game_kit' %}">Game Kit</a></h2>
|
<h2><a href="{% url 'game_kit' %}">Game Kit</a></h2>
|
||||||
<div id="id_game_kit" data-equipped-id="{{ equipped_trinket_id }}" data-equipped-deck-id="{{ equipped_deck_id }}">
|
<div id="id_game_kit" data-equipped-id="{{ equipped_trinket_id|default:'' }}" data-equipped-deck-id="{{ equipped_deck_id|default:'' }}">
|
||||||
{% if pass_token %}
|
{% if pass_token %}
|
||||||
<div id="id_kit_pass" class="token" data-token-id="{{ pass_token.pk }}">
|
<div id="id_kit_pass" class="token" data-token-id="{{ pass_token.pk }}">
|
||||||
<i class="fa-solid fa-clipboard"></i>
|
<i class="fa-solid fa-clipboard"></i>
|
||||||
<div class="token-tooltip">
|
<div class="tt">
|
||||||
<div class="token-tooltip-body">
|
<div class="tt-equip-btns">
|
||||||
<h4>{{ pass_token.tooltip_name }}</h4>
|
{% if pass_token.pk == equipped_trinket_id %}<button class="btn btn-equip btn-disabled" data-token-id="{{ pass_token.pk }}">×</button><button class="btn btn-unequip" data-token-id="{{ pass_token.pk }}">DOFF</button>{% else %}<button class="btn btn-equip" data-token-id="{{ pass_token.pk }}">DON</button><button class="btn btn-unequip btn-disabled" data-token-id="{{ pass_token.pk }}">×</button>{% endif %}
|
||||||
<p>{{ pass_token.tooltip_description }}</p>
|
|
||||||
{% if pass_token.tooltip_shoptalk %}
|
|
||||||
<small><em>{{ pass_token.tooltip_shoptalk }}</em></small>
|
|
||||||
{% endif %}
|
|
||||||
<p class="expiry">{{ pass_token.tooltip_expiry }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<h4 class="tt-title">{{ pass_token.tooltip_name }}</h4>
|
||||||
|
<p class="tt-description">{{ pass_token.tooltip_description }}</p>
|
||||||
|
{% if pass_token.tooltip_shoptalk %}
|
||||||
|
<p class="tt-shoptalk"><em>{{ pass_token.tooltip_shoptalk }}</em></p>
|
||||||
|
{% endif %}
|
||||||
|
<p class="tt-expiry">{{ pass_token.tooltip_expiry }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if carte %}
|
{% if carte %}
|
||||||
<div id="id_kit_carte_blanche" class="token" data-token-id="{{ carte.pk }}">
|
<div id="id_kit_carte_blanche" class="token" data-token-id="{{ carte.pk }}">
|
||||||
<i class="fa-solid fa-money-check"></i>
|
<i class="fa-solid fa-money-check"></i>
|
||||||
<div class="token-tooltip">
|
<div class="tt">
|
||||||
<div class="token-tooltip-body">
|
<div class="tt-equip-btns">
|
||||||
<h4>{{ carte.tooltip_name }}</h4>
|
{% if carte.pk == equipped_trinket_id %}<button class="btn btn-equip btn-disabled" data-token-id="{{ carte.pk }}">×</button><button class="btn btn-unequip" data-token-id="{{ carte.pk }}">DOFF</button>{% else %}<button class="btn btn-equip" data-token-id="{{ carte.pk }}">DON</button><button class="btn btn-unequip btn-disabled" data-token-id="{{ carte.pk }}">×</button>{% endif %}
|
||||||
<p>{{ carte.tooltip_description }}</p>
|
|
||||||
{% if carte.tooltip_shoptalk %}
|
|
||||||
<small><em>{{ carte.tooltip_shoptalk }}</em></small>
|
|
||||||
{% endif %}
|
|
||||||
<p class="expiry">{{ carte.tooltip_expiry }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<h4 class="tt-title">{{ carte.tooltip_name }}</h4>
|
||||||
|
<p class="tt-description">{{ carte.tooltip_description }}</p>
|
||||||
|
{% if carte.tooltip_shoptalk %}
|
||||||
|
<p class="tt-shoptalk"><em>{{ carte.tooltip_shoptalk }}</em></p>
|
||||||
|
{% endif %}
|
||||||
|
<p class="tt-expiry">{{ carte.tooltip_expiry }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if coin %}
|
{% if coin %}
|
||||||
<div id="id_kit_coin_on_a_string" class="token" data-token-id="{{ coin.pk }}">
|
<div id="id_kit_coin_on_a_string" class="token" data-token-id="{{ coin.pk }}">
|
||||||
<i class="fa-solid fa-medal"></i>
|
<i class="fa-solid fa-medal"></i>
|
||||||
<div class="token-tooltip">
|
<div class="tt">
|
||||||
<div class="token-tooltip-body">
|
<div class="tt-equip-btns">
|
||||||
<h4>{{ coin.tooltip_name }}</h4>
|
{% if coin.pk == equipped_trinket_id %}<button class="btn btn-equip btn-disabled" data-token-id="{{ coin.pk }}">×</button><button class="btn btn-unequip" data-token-id="{{ coin.pk }}">DOFF</button>{% else %}<button class="btn btn-equip" data-token-id="{{ coin.pk }}">DON</button><button class="btn btn-unequip btn-disabled" data-token-id="{{ coin.pk }}">×</button>{% endif %}
|
||||||
<p>{{ coin.tooltip_description }}</p>
|
|
||||||
{% if coin.tooltip_shoptalk %}
|
|
||||||
<small><em>{{ coin.tooltip_shoptalk }}</em></small>
|
|
||||||
{% endif %}
|
|
||||||
<p class="expiry">{{ coin.tooltip_expiry }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<h4 class="tt-title">{{ coin.tooltip_name }}</h4>
|
||||||
|
<p class="tt-description">{{ coin.tooltip_description }}</p>
|
||||||
|
{% if coin.tooltip_shoptalk %}
|
||||||
|
<p class="tt-shoptalk"><em>{{ coin.tooltip_shoptalk }}</em></p>
|
||||||
|
{% endif %}
|
||||||
|
<p class="tt-expiry">{{ coin.tooltip_expiry }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -53,15 +56,13 @@
|
|||||||
{% with free_tokens.0 as token %}
|
{% with free_tokens.0 as token %}
|
||||||
<div id="id_kit_free_token" class="token">
|
<div id="id_kit_free_token" class="token">
|
||||||
<i class="fa-solid fa-coins"></i>
|
<i class="fa-solid fa-coins"></i>
|
||||||
<div class="token-tooltip">
|
<div class="tt">
|
||||||
<div class="token-tooltip-body">
|
<h4 class="tt-title">{{ token.tooltip_name }}{% if free_count > 1 %} <span class="token-count">(×{{ free_count }})</span>{% endif %}</h4>
|
||||||
<h4>{{ token.tooltip_name }}{% if free_count > 1 %} <span class="token-count">(×{{ free_count }})</span>{% endif %}</h4>
|
<p class="tt-description">{{ token.tooltip_description }}</p>
|
||||||
<p>{{ token.tooltip_description }}</p>
|
|
||||||
{% if token.tooltip_shoptalk %}
|
{% if token.tooltip_shoptalk %}
|
||||||
<small><em>{{ token.tooltip_shoptalk }}</em></small>
|
<p class="tt-shoptalk"><em>{{ token.tooltip_shoptalk }}</em></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="expiry">{{ token.tooltip_expiry }}</p>
|
<p class="tt-expiry">{{ token.tooltip_expiry }}</p>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
@@ -69,11 +70,12 @@
|
|||||||
{% for deck in deck_variants %}
|
{% for deck in deck_variants %}
|
||||||
<div id="id_kit_{{ deck.short_key }}_deck" class="token deck-variant" data-deck-id="{{ deck.pk }}">
|
<div id="id_kit_{{ deck.short_key }}_deck" class="token deck-variant" data-deck-id="{{ deck.pk }}">
|
||||||
<i class="fa-regular fa-id-badge"></i>
|
<i class="fa-regular fa-id-badge"></i>
|
||||||
<div class="token-tooltip">
|
<div class="tt">
|
||||||
<div class="token-tooltip-body">
|
<div class="tt-equip-btns">
|
||||||
<h4>{{ deck.name }}</h4>
|
{% if deck.pk == equipped_deck_id %}<button class="btn btn-equip btn-disabled" data-deck-id="{{ deck.pk }}">×</button><button class="btn btn-unequip" data-deck-id="{{ deck.pk }}">DOFF</button>{% else %}<button class="btn btn-equip" data-deck-id="{{ deck.pk }}">DON</button><button class="btn btn-unequip btn-disabled" data-deck-id="{{ deck.pk }}">×</button>{% endif %}
|
||||||
<p>{{ deck.card_count }} cards</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<h4 class="tt-title">{{ deck.name }}</h4>
|
||||||
|
<p class="tt-description">{{ deck.card_count }} cards</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
|
|||||||
81
src/templates/apps/tooltips/_tooltip.html
Normal file
81
src/templates/apps/tooltips/_tooltip.html
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<div class="tt">
|
||||||
|
|
||||||
|
{% if equippable %}
|
||||||
|
<div class="tt-equip-btns">
|
||||||
|
{% if equippable.is_equipped %}
|
||||||
|
<button class="btn btn-equip btn-disabled"{% if equippable.token_id %} data-token-id="{{ equippable.token_id }}"{% endif %}{% if equippable.deck_id %} data-deck-id="{{ equippable.deck_id }}"{% endif %}>×</button>
|
||||||
|
<button class="btn btn-unequip"{% if equippable.token_id %} data-token-id="{{ equippable.token_id }}"{% endif %}{% if equippable.deck_id %} data-deck-id="{{ equippable.deck_id }}"{% endif %}>DOFF</button>
|
||||||
|
{% else %}
|
||||||
|
<button class="btn btn-equip"{% if equippable.token_id %} data-token-id="{{ equippable.token_id }}"{% endif %}{% if equippable.deck_id %} data-deck-id="{{ equippable.deck_id }}"{% endif %}>DON</button>
|
||||||
|
<button class="btn btn-unequip btn-disabled"{% if equippable.token_id %} data-token-id="{{ equippable.token_id }}"{% endif %}{% if equippable.deck_id %} data-deck-id="{{ equippable.deck_id }}"{% endif %}>×</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<h4 class="tt-title">{{ title }}</h4>
|
||||||
|
|
||||||
|
{% if type_label %}<p class="tt-type">{{ type_label }}</p>{% endif %}
|
||||||
|
|
||||||
|
{% if symbol %}<span class="tt-symbol">{{ symbol }}</span>{% endif %}
|
||||||
|
|
||||||
|
{% if degree_str %}<p class="tt-degree">{{ degree_str }}</p>{% endif %}
|
||||||
|
|
||||||
|
{% if description %}<p class="tt-description">{{ description }}</p>{% endif %}
|
||||||
|
|
||||||
|
{% if shoptalk %}<p class="tt-shoptalk"><em>{{ shoptalk }}</em></p>{% endif %}
|
||||||
|
|
||||||
|
{% if expiry %}<p class="tt-expiry">{{ expiry }}</p>{% endif %}
|
||||||
|
|
||||||
|
{% if effect %}<p class="tt-effect">{{ effect }}</p>{% endif %}
|
||||||
|
|
||||||
|
{% if dignities %}
|
||||||
|
<table class="tt-table tt-table--dignities">
|
||||||
|
{% for key, value in dignities.items %}
|
||||||
|
<tr><th>{{ key }}</th><td>{{ value }}</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if aspects %}
|
||||||
|
<ul class="tt-aspects">
|
||||||
|
{% for asp in aspects %}
|
||||||
|
<li><span class="tt-asp-symbol">{{ asp.symbol }}</span> {{ asp.type }} {{ asp.body }} <span class="tt-asp-orb">{{ asp.orb }}</span></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if keywords_up or keywords_rev %}
|
||||||
|
<div class="tt-keywords">
|
||||||
|
{% if keywords_up %}
|
||||||
|
<ul class="tt-keywords-up">
|
||||||
|
{% for kw in keywords_up %}<li>{{ kw }}</li>{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% if keywords_rev %}
|
||||||
|
<ul class="tt-keywords-rev">
|
||||||
|
{% for kw in keywords_rev %}<li>{{ kw }}</li>{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if cautions %}
|
||||||
|
<div class="tt-fyi tt-fyi--cautions">
|
||||||
|
{% for caution in cautions %}
|
||||||
|
<div class="tt-fyi-item">
|
||||||
|
{% if caution.type_label %}<p class="tt-fyi-type">{{ caution.type_label }}</p>{% endif %}
|
||||||
|
{% if caution.shoptalk %}<p class="tt-fyi-shoptalk">{{ caution.shoptalk }}</p>{% endif %}
|
||||||
|
{% if caution.effect %}<p class="tt-fyi-effect">{{ caution.effect }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if nav %}
|
||||||
|
<div class="tt-nav">
|
||||||
|
<span class="tt-nav-prv">{{ nav.prv }}</span>
|
||||||
|
<span class="tt-nav-nxt">{{ nav.nxt }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -7,25 +7,25 @@
|
|||||||
{% if pass_token %}
|
{% if pass_token %}
|
||||||
<div id="id_pass_token" class="token">
|
<div id="id_pass_token" class="token">
|
||||||
<i class="fa-solid fa-clipboard"></i>
|
<i class="fa-solid fa-clipboard"></i>
|
||||||
<div class="token-tooltip">
|
<div class="tt">
|
||||||
<h4>{{ pass_token.tooltip_name }}</h4>
|
<h4 class="tt-title">{{ pass_token.tooltip_name }}</h4>
|
||||||
<p>{{ pass_token.tooltip_description }}</p>
|
<p class="tt-description">{{ pass_token.tooltip_description }}</p>
|
||||||
{% if pass_token.tooltip_shoptalk %}
|
{% if pass_token.tooltip_shoptalk %}
|
||||||
<small><em>{{ pass_token.tooltip_shoptalk }}</em></small>
|
<p class="tt-shoptalk"><em>{{ pass_token.tooltip_shoptalk }}</em></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="expiry">{{ pass_token.tooltip_expiry }}</p>
|
<p class="tt-expiry">{{ pass_token.tooltip_expiry }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% elif coin %}
|
{% elif coin %}
|
||||||
<div id="id_coin_on_a_string" class="token">
|
<div id="id_coin_on_a_string" class="token">
|
||||||
<i class="fa-solid fa-medal"></i>
|
<i class="fa-solid fa-medal"></i>
|
||||||
<div class="token-tooltip">
|
<div class="tt">
|
||||||
<h4>{{ coin.tooltip_name }}</h4>
|
<h4 class="tt-title">{{ coin.tooltip_name }}</h4>
|
||||||
<p>{{ coin.tooltip_description }}</p>
|
<p class="tt-description">{{ coin.tooltip_description }}</p>
|
||||||
{% if coin.tooltip_shoptalk %}
|
{% if coin.tooltip_shoptalk %}
|
||||||
<small><em>{{ coin.tooltip_shoptalk }}</em></small>
|
<p class="tt-shoptalk"><em>{{ coin.tooltip_shoptalk }}</em></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="expiry">{{ coin.tooltip_expiry }}</p>
|
<p class="tt-expiry">{{ coin.tooltip_expiry }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -33,23 +33,23 @@
|
|||||||
{% with free_tokens.0 as token %}
|
{% with free_tokens.0 as token %}
|
||||||
<div id="id_free_token" class="token">
|
<div id="id_free_token" class="token">
|
||||||
<i class="fa-solid fa-coins"></i>
|
<i class="fa-solid fa-coins"></i>
|
||||||
<div class="token-tooltip">
|
<div class="tt">
|
||||||
<h4>{{ token.tooltip_name }}{% if free_count > 1 %} <span class="token-count">(×{{ free_count }})</span>{% endif %}</h4>
|
<h4 class="tt-title">{{ token.tooltip_name }}{% if free_count > 1 %} <span class="token-count">(×{{ free_count }})</span>{% endif %}</h4>
|
||||||
<p>{{ token.tooltip_description }}</p>
|
<p class="tt-description">{{ token.tooltip_description }}</p>
|
||||||
{% if token.tooltip_shoptalk %}
|
{% if token.tooltip_shoptalk %}
|
||||||
<small><em>{{ token.tooltip_shoptalk }}</em></small>
|
<p class="tt-shoptalk"><em>{{ token.tooltip_shoptalk }}</em></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="expiry">{{ token.tooltip_expiry }}</p>
|
<p class="tt-expiry">{{ token.tooltip_expiry }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div id="id_free_token_empty" class="token token--empty">
|
<div id="id_free_token_empty" class="token token--empty">
|
||||||
<i class="fa-solid fa-coins"></i>
|
<i class="fa-solid fa-coins"></i>
|
||||||
<div class="token-tooltip">
|
<div class="tt">
|
||||||
<h4>Free Token</h4>
|
<h4 class="tt-title">Free Token</h4>
|
||||||
<p>0 owned</p>
|
<p class="tt-description">0 owned</p>
|
||||||
<p class="expiry">find one around</p>
|
<p class="tt-expiry">find one around</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -57,20 +57,20 @@
|
|||||||
{% with tithe_tokens.0 as token %}
|
{% with tithe_tokens.0 as token %}
|
||||||
<div id="id_tithe_token" class="token">
|
<div id="id_tithe_token" class="token">
|
||||||
<i class="fa-solid fa-piggy-bank"></i>
|
<i class="fa-solid fa-piggy-bank"></i>
|
||||||
<div class="token-tooltip">
|
<div class="tt">
|
||||||
<h4>{{ token.tooltip_name }}{% if tithe_count > 1 %} <span class="token-count">(×{{ tithe_count }})</span>{% endif %}</h4>
|
<h4 class="tt-title">{{ token.tooltip_name }}{% if tithe_count > 1 %} <span class="token-count">(×{{ tithe_count }})</span>{% endif %}</h4>
|
||||||
<p>{{ token.tooltip_description }}</p>
|
<p class="tt-description">{{ token.tooltip_description }}</p>
|
||||||
<p class="expiry">{{ token.tooltip_expiry }}</p>
|
<p class="tt-expiry">{{ token.tooltip_expiry }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div id="id_tithe_token_empty" class="token token--empty">
|
<div id="id_tithe_token_empty" class="token token--empty">
|
||||||
<i class="fa-solid fa-piggy-bank"></i>
|
<i class="fa-solid fa-piggy-bank"></i>
|
||||||
<div class="token-tooltip">
|
<div class="tt">
|
||||||
<h4>Tithe Token</h4>
|
<h4 class="tt-title">Tithe Token</h4>
|
||||||
<p>0 owned</p>
|
<p class="tt-description">0 owned</p>
|
||||||
<p class="expiry">purchase one above</p>
|
<p class="tt-expiry">purchase one above</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
<div class="kit-bag-row">
|
<div class="kit-bag-row">
|
||||||
<div class="kit-bag-deck" data-deck-id="{{ equipped_deck.pk }}">
|
<div class="kit-bag-deck" data-deck-id="{{ equipped_deck.pk }}">
|
||||||
<i class="fa-regular fa-id-badge"></i>
|
<i class="fa-regular fa-id-badge"></i>
|
||||||
<div class="token-tooltip">
|
<div class="tt">
|
||||||
<h4>{{ equipped_deck.name }}{% if equipped_deck.is_default %} <span class="token-count">(Default)</span>{% endif %}</h4>
|
<h4 class="tt-title">{{ equipped_deck.name }}{% if equipped_deck.is_default %} <span class="token-count">(Default)</span>{% endif %}</h4>
|
||||||
<p>{{ equipped_deck.card_count }}-card Tarot deck</p>
|
<p class="tt-description">{{ equipped_deck.card_count }}-card Tarot deck</p>
|
||||||
<small><em>placeholder comment</em></small>
|
<p class="tt-shoptalk"><em>placeholder comment</em></p>
|
||||||
<p class="availability">active</p>
|
<p class="tt-effect">active</p>
|
||||||
<p class="stock-version">Stock version</p>
|
<p class="tt-expiry">Stock version</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -25,10 +25,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if equipped_trinket %}
|
|
||||||
<div class="kit-bag-section">
|
<div class="kit-bag-section">
|
||||||
<span class="kit-bag-label">Trinket</span>
|
<span class="kit-bag-label">Trinket</span>
|
||||||
<div class="kit-bag-row">
|
<div class="kit-bag-row">
|
||||||
|
{% if equipped_trinket %}
|
||||||
{% with token=equipped_trinket %}
|
{% with token=equipped_trinket %}
|
||||||
<div
|
<div
|
||||||
class="token"
|
class="token"
|
||||||
@@ -43,24 +43,27 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<i class="fa-solid fa-clipboard"></i>
|
<i class="fa-solid fa-clipboard"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="token-tooltip">
|
<div class="tt">
|
||||||
<h4>{{ token.tooltip_name }}</h4>
|
<h4 class="tt-title">{{ token.tooltip_name }}</h4>
|
||||||
<p>{{ token.tooltip_description }}</p>
|
<p class="tt-description">{{ token.tooltip_description }}</p>
|
||||||
{% if token.tooltip_shoptalk %}
|
{% if token.tooltip_shoptalk %}
|
||||||
<small><em>{{ token.tooltip_shoptalk }}</em></small>
|
<p class="tt-shoptalk"><em>{{ token.tooltip_shoptalk }}</em></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="expiry">{{ token.tooltip_expiry }}</p>
|
<p class="tt-expiry">{{ token.tooltip_expiry }}</p>
|
||||||
{% with room_html=token.tooltip_room_html %}
|
{% with room_html=token.tooltip_room_html %}
|
||||||
{% if room_html %}{{ room_html|safe }}{% endif %}
|
{% if room_html %}{{ room_html|safe }}{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
{% else %}
|
||||||
|
<div class="kit-bag-placeholder">
|
||||||
|
<i class="fa-solid fa-medal"></i>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if free_token or tithe_token %}
|
|
||||||
<div class="kit-bag-section kit-bag-section--tokens">
|
<div class="kit-bag-section kit-bag-section--tokens">
|
||||||
<span class="kit-bag-label">Tokens</span>
|
<span class="kit-bag-label">Tokens</span>
|
||||||
<div class="kit-bag-row kit-bag-row--scroll">
|
<div class="kit-bag-row kit-bag-row--scroll">
|
||||||
@@ -72,13 +75,13 @@
|
|||||||
data-token-type="{{ free_token.token_type }}"
|
data-token-type="{{ free_token.token_type }}"
|
||||||
>
|
>
|
||||||
<i class="fa-solid fa-coins"></i>
|
<i class="fa-solid fa-coins"></i>
|
||||||
<div class="token-tooltip">
|
<div class="tt">
|
||||||
<h4>{{ free_token.tooltip_name }}{% if free_count > 1 %} <span class="token-count">(×{{ free_count }})</span>{% endif %}</h4>
|
<h4 class="tt-title">{{ free_token.tooltip_name }}{% if free_count > 1 %} <span class="token-count">(×{{ free_count }})</span>{% endif %}</h4>
|
||||||
<p>{{ free_token.tooltip_description }}</p>
|
<p class="tt-description">{{ free_token.tooltip_description }}</p>
|
||||||
{% if free_token.tooltip_shoptalk %}
|
{% if free_token.tooltip_shoptalk %}
|
||||||
<small><em>{{ free_token.tooltip_shoptalk }}</em></small>
|
<p class="tt-shoptalk"><em>{{ free_token.tooltip_shoptalk }}</em></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="expiry">{{ free_token.tooltip_expiry }}</p>
|
<p class="tt-expiry">{{ free_token.tooltip_expiry }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -90,13 +93,17 @@
|
|||||||
data-token-type="{{ tithe_token.token_type }}"
|
data-token-type="{{ tithe_token.token_type }}"
|
||||||
>
|
>
|
||||||
<i class="fa-solid fa-piggy-bank"></i>
|
<i class="fa-solid fa-piggy-bank"></i>
|
||||||
<div class="token-tooltip">
|
<div class="tt">
|
||||||
<h4>{{ tithe_token.tooltip_name }}{% if tithe_count > 1 %} <span class="token-count">(×{{ tithe_count }})</span>{% endif %}</h4>
|
<h4 class="tt-title">{{ tithe_token.tooltip_name }}{% if tithe_count > 1 %} <span class="token-count">(×{{ tithe_count }})</span>{% endif %}</h4>
|
||||||
<p>{{ tithe_token.tooltip_description }}</p>
|
<p class="tt-description">{{ tithe_token.tooltip_description }}</p>
|
||||||
<p class="expiry">{{ tithe_token.tooltip_expiry }}</p>
|
<p class="tt-expiry">{{ tithe_token.tooltip_expiry }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if not free_token and not tithe_token %}
|
||||||
|
<div class="kit-bag-placeholder">
|
||||||
|
<i class="fa-solid fa-coins"></i>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user