Files
python-tdd/src/templates/apps/billboard/my_sign.html
Disco DeDisco 66b2947e8c My Sign: Brief banner + Earthman [Shabby Cardstock] backup deck when no equipped — TDD
User-reported gap on /billboard/my-sign/ — admin user's only deck was in-use as `TableSeat.deck_variant` in another room (Wonderbeard) → `equipped_deck` cleared → previous my-sign template showed "Equip a card deck first…" w. no actionable next step. User scoped fix: don't force equip, just nudge via a Brief banner "Look!—no deck is equipped. Navigate to the Game Kit to equip one (FYI) or (NVM) proceed with the Earthman [Shabby Cardstock] deck.", title "Default deck warning". NVM dismisses + picker proceeds against an Earthman card pile labeled in-copy as the temporary backup; FYI links to /gameboard/ (Game Kit equip). User-modified line text in template: "Paperboard" → "Cardstock" mid-session ; **helper fallback** (epic/models.py): `personal_sig_cards(user)` now falls back to `DeckVariant.objects.filter(slug='earthman').first()` when `user.equipped_deck` is None — same 16-or-18 card pile, just sourced from the canonical Earthman deck rather than the empty FK. No new DeckVariant row needed; "Shabby Cardstock" is purely UX framing (cards are the same TarotCard records the room sig-select uses). Preserves the existing helper signature so no callers had to change ; **view + template** (billboard/views.py + my_sign.html): view passes `no_equipped_deck` + `show_backup_intro_banner` flags. Template removes the old `{% if not equipped_deck %}` forced-equip branch — picker now renders unconditionally w. cards from the backup helper when no deck is equipped. Brief banner fires via `Brief.showBanner({...})` on DOMContentLoaded when `show_backup_intro_banner` is true — gets h2-overlay positioning + NVM behavior + portal styling for free (per [[sprint-baltimorean-note-unlock-may18]] portrait h2 measurement in note.js's `_alignToH2`). Added `<script src="note.js">` to my_sign.html since the page didn't load it before. Post-render JS tags the Brief w. a `.my-sign-intro-banner` class so FTs (and any future my-sign-specific styling) can distinguish this nudge from other Briefs on the page ; **TDD trail** — 4 new FTs in `MySignBackupDeckTest` (test_bill_my_sign.py): T1 banner renders w. "Default deck warning" title + "no deck is equipped" + "Shabby Cardstock" copy + both action btns visible; T2 picker still populates 16 cards from backup; T3 NVM click removes the banner from the DOM; T4 FYI href ends w. /gameboard/. Initial reds (`NoSuchElementException` on all 4) confirmed before implementation. Plus 1 new IT in `PersonalSigCardsTest` pinning the helper fallback (16 cards w. all `c.deck_variant.slug == "earthman"`) ; pre-existing change picked up: `static_src/scss/rootvars.scss` (user-modified mid-session) ; 1020 IT/UT green; 7 FTs green (3 picker happy-path + 4 backup deck) in 56s. Sprint 4a-follow complete — primary deferral from Sprint 4a (deck-source fallback UX) now landed. Unblocks Sprint 4b (My Sea gating w. --terUser link to /billboard/my-sign/ when no sig set)

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:49:49 -04:00

142 lines
7.3 KiB
HTML

{% extends "core/base.html" %}
{% load static %}
{% block title_text %}Game Sign{% endblock title_text %}
{% block header_text %}<span>Game</span><span>Sign</span>{% endblock header_text %}
{% block content %}
{# Solo lift of `_sig_select_overlay.html`. Same card-grid + stage-card #}
{# choreography as the room sig-select, minus countdown / WebSocket / #}
{# polarity / multi-user. FLIP btn (.spin-btn) lets the user choose the #}
{# card's orientation; SAVE SIG persists it on the User model. #}
{# "Significator" is preserved at the storage layer (User.significator) + #}
{# game-room context; this billboard surface re-brands to "Sign". #}
<div class="my-sign-page"
data-save-url="{% url 'billboard:save_sign' %}"
{% if current_significator %}data-current-card-id="{{ current_significator.id }}"{% endif %}
data-current-reversed="{{ current_significator_reversed|yesno:'true,false' }}">
<div class="my-sign-stage">
<div class="sig-stage-card" style="display:none">
<div class="fan-card-corner fan-card-corner--tl">
<span class="fan-corner-rank"></span>
<i class="fa-solid stage-suit-icon" style="display:none"></i>
</div>
<div class="fan-card-face">
<div class="fan-card-face-upright">
<p class="fan-card-name-group"></p>
<p class="sig-qualifier-above"></p>
<p class="fan-card-name"></p>
<p class="sig-qualifier-below"></p>
</div>
<p class="fan-card-arcana"></p>
<div class="fan-card-face-reversal">
<p class="fan-card-reversal-name"></p>
<p class="fan-card-reversal-qualifier"></p>
</div>
</div>
<div class="fan-card-corner fan-card-corner--br">
<span class="fan-corner-rank"></span>
<i class="fa-solid stage-suit-icon" style="display:none"></i>
</div>
</div>
<div class="sig-stat-block">
<button class="btn btn-reverse spin-btn" type="button">FLIP</button>
<div class="stat-face stat-face--upright">
<p class="stat-face-label">Emanation</p>
<ul class="stat-keywords"></ul>
</div>
<div class="stat-face stat-face--reversed">
<p class="stat-face-label">Reversal</p>
<ul class="stat-keywords"></ul>
</div>
</div>
</div>
<div class="my-sign-deck-grid">
{% for card in cards %}
<div class="sig-card"
data-card-id="{{ card.id }}"
data-suit-icon="{{ card.suit_icon }}"
data-corner-rank="{{ card.corner_rank }}"
data-name-group="{{ card.name_group }}"
data-name-title="{{ card.name_title }}"
data-arcana="{{ card.get_arcana_display }}"
data-keywords-upright="{{ card.keywords_upright|join:',' }}"
data-keywords-reversed="{{ card.keywords_reversed|join:',' }}"
data-reversal-qualifier="{{ card.reversal_qualifier }}">
<div class="fan-card-corner fan-card-corner--tl">
<span class="fan-corner-rank">{{ card.corner_rank }}</span>
{% if card.suit_icon %}<i class="fa-solid {{ card.suit_icon }}"></i>{% endif %}
</div>
</div>
{% endfor %}
</div>
<form id="id_save_sign_form" method="POST" action="{% url 'billboard:save_sign' %}">
{% csrf_token %}
<input type="hidden" name="card_id" id="id_save_sign_card_id" value="{{ current_significator.id|default:'' }}">
<input type="hidden" name="reversed" id="id_save_sign_reversed" value="{{ current_significator_reversed|yesno:'1,0' }}">
<button type="submit" id="id_save_sign_btn" class="btn btn-primary"{% if not current_significator %} disabled{% endif %}>SAVE SIGN</button>
</form>
{# Minimal picker JS — click .sig-card to pick + enable SAVE SIGN. #}
{# FLIP-btn integration (reversed toggle) lands w. the stage-card #}
{# preview JS in a Sprint 4a-follow-up; for now we just record #}
{# the chosen card_id + the FLIP state. #}
<script>
(function () {
var grid = document.querySelector('.my-sign-deck-grid');
if (!grid) return;
var cardIdInput = document.getElementById('id_save_sign_card_id');
var saveBtn = document.getElementById('id_save_sign_btn');
var flipBtn = document.querySelector('.my-sign-stage .spin-btn');
var revInput = document.getElementById('id_save_sign_reversed');
grid.addEventListener('click', function (e) {
var card = e.target.closest('.sig-card');
if (!card) return;
grid.querySelectorAll('.sig-card.sig-focused').forEach(function (c) {
c.classList.remove('sig-focused');
});
card.classList.add('sig-focused');
cardIdInput.value = card.dataset.cardId;
saveBtn.removeAttribute('disabled');
});
if (flipBtn) {
flipBtn.addEventListener('click', function () {
var current = revInput.value === '1';
revInput.value = current ? '0' : '1';
flipBtn.classList.toggle('is-reversed', !current);
});
}
}());
</script>
{# No equipped deck + no saved sig — fire a Brief banner via the #}
{# shared note.js API so positioning + NVM behavior + h2-overlay #}
{# styling matches every other Brief on the site (per #}
{# [[sprint-baltimorean-note-unlock-may18]] portrait h2 measurement). #}
{# FYI links to /gameboard/ (Game Kit equip); NVM dismisses + the #}
{# picker proceeds against the Earthman [Shabby Paperboard] fallback. #}
<script src="{% static 'apps/dashboard/note.js' %}"></script>
{% if show_backup_intro_banner %}
<script>
document.addEventListener('DOMContentLoaded', function () {
if (!window.Brief || !Brief.showBanner) return;
Brief.showBanner({
title: 'Default deck warning',
line_text: 'Look!—no deck is equipped. Navigate to the Game Kit to equip one (FYI) or (NVM) proceed with the Earthman [Shabby Cardstock] deck.',
post_url: '{% url "gameboard" %}',
created_at: '',
kind: 'NUDGE',
});
// Tag the banner so FTs (and any my-sign-specific styling) can
// distinguish this intro nudge from other Briefs on the page.
var banner = document.querySelector('.note-banner');
if (banner) banner.classList.add('my-sign-intro-banner');
});
</script>
{% endif %}
</div>
{% endblock content %}