Game Kit fan stage + FLIP/SPIN; sig/sea/fan refactor — TDD
- fan modal: stage block w. idle-reveal/careen-out; carousel shifts left so focused card sits left-of-center; SPIN rotates whole card via Element.animate(); FLIP toggles polarity (Levity ↔ Gravity) via perspective rotateY w. mid-flip repaint; SPIN state retained across FLIP; FLIP btn hover-revealed only when focused card or btn is hovered (:has) - mobile breakpoints: --fan-card-w / --fan-card-h / --fan-stage-shift / --fan-carousel-step lifted to CSS vars on .tarot-fan-wrap; portrait ≤ 480px @ 150×230, landscape ≤ 500h @ 150×235; corners + face text/padding scale w. card width - shared StageCard JS module (apps/epic/stage-card.js): fromDataset, populateCard, populateKeywords, buildInfoData, renderFyi — sig/sea/fan all delegate; ~150 lines de-duplicated - shared @mixin stat-block-shared (SCSS) lifts duplicated stat-face / stat-keywords / sig-info rules; @mixin stage-card-polarity unifies sea-stage--levity/--gravity + fan[data-polarity] coloring - model rename: TarotCard.reversal → reversal_qualifier (migration 0014); render-time fallback to current polarity's qualifier when blank - class unification: .sig-info-open / .sea-info-open / .fyi-open → .fyi-open (on stat block); .sig-flip-btn / .sea-spin-btn / .fan-spin-btn → .spin-btn; same for .fyi-btn / .fyi-prev / .fyi-next - custom combobox (apps/epic/combobox.js) replaces native <select> for PICK SEA spread picker — keyboard nav, click-outside-close, aria roles; Firefox/Chrome OS-rendered <option> ignored CSS - Jasmine: FanStageSpec.js w. idle-reveal / population / SPIN / FYI / FLIP specs; sig + sea fixtures + IT view assertions updated for renamed classes - 748 ITs + Jasmine green Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -62,16 +62,36 @@
|
||||
|
||||
<div class="sea-form-main">
|
||||
<div class="sea-field">
|
||||
<label for="id_sea_spread">Spread</label>
|
||||
<select id="id_sea_spread" name="spread" class="sea-select">
|
||||
{% if user_polarity == "levity" %}
|
||||
<option value="waite-smith" selected>Celtic Cross, Waite-Smith</option>
|
||||
<option value="escape-velocity">Celtic Cross, Escape Velocity</option>
|
||||
{% else %}
|
||||
<option value="escape-velocity" selected>Celtic Cross, Escape Velocity</option>
|
||||
<option value="waite-smith">Celtic Cross, Waite-Smith</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
<label for="id_sea_spread" id="id_sea_spread_label">Spread</label>
|
||||
{% comment %}
|
||||
Custom combobox — native <select> dropdowns ignore most CSS on
|
||||
Firefox/Chrome (OS-rendered list); this gives full styling control.
|
||||
combobox.js wires up the keyboard nav, click-outside-to-close, and
|
||||
writes the chosen value to the hidden <input id="id_sea_spread"> so
|
||||
sea.js's existing `spreadSel.value` read still works.
|
||||
{% endcomment %}
|
||||
<input type="hidden" id="id_sea_spread" name="spread"
|
||||
value="{% if user_polarity == 'levity' %}waite-smith{% else %}escape-velocity{% endif %}">
|
||||
<div class="sea-select"
|
||||
data-combobox
|
||||
data-combobox-target="id_sea_spread"
|
||||
role="combobox"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-labelledby="id_sea_spread_label"
|
||||
tabindex="0">
|
||||
<span class="sea-select-current">{% if user_polarity == "levity" %}Celtic Cross, Waite-Smith{% else %}Celtic Cross, Escape Velocity{% endif %}</span>
|
||||
<span class="sea-select-arrow" aria-hidden="true">▾</span>
|
||||
<ul class="sea-select-list" role="listbox">
|
||||
{% if user_polarity == "levity" %}
|
||||
<li role="option" data-value="waite-smith" aria-selected="true">Celtic Cross, Waite-Smith</li>
|
||||
<li role="option" data-value="escape-velocity" aria-selected="false">Celtic Cross, Escape Velocity</li>
|
||||
{% else %}
|
||||
<li role="option" data-value="escape-velocity" aria-selected="true">Celtic Cross, Escape Velocity</li>
|
||||
<li role="option" data-value="waite-smith" aria-selected="false">Celtic Cross, Waite-Smith</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Two face-down deck piles — tap to proffer OK #}
|
||||
@@ -114,8 +134,8 @@
|
||||
{# ── Sea stage — big card viewer ─────────────────────────────────────────── #}
|
||||
<div class="sea-stage" id="id_sea_stage" style="display:none">
|
||||
<div class="sea-stage-backdrop"></div>
|
||||
<div class="sea-stage-content">
|
||||
<div class="sig-stage-card sea-stage-card" style="--sig-card-w:140px">
|
||||
<div class="sea-stage-content" style="--sig-card-w:180px">
|
||||
<div class="sig-stage-card sea-stage-card">
|
||||
<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>
|
||||
@@ -139,8 +159,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="sig-stat-block sea-stat-block">
|
||||
<button class="btn btn-reverse sea-spin-btn" type="button">SPIN</button>
|
||||
<button class="btn btn-info sea-fyi-btn" type="button">FYI</button>
|
||||
<button class="btn btn-reverse spin-btn" type="button">SPIN</button>
|
||||
<button class="btn btn-info fyi-btn" type="button">FYI</button>
|
||||
<div class="stat-face stat-face--upright">
|
||||
<p class="stat-face-label">Emanation</p>
|
||||
<ul class="stat-keywords" id="id_sea_stat_upright"></ul>
|
||||
@@ -157,8 +177,8 @@
|
||||
<p class="sig-info-effect"></p>
|
||||
<span class="sig-info-index"></span>
|
||||
</div>
|
||||
<button class="btn btn-nav-left sea-fyi-prev" type="button">PRV</button>
|
||||
<button class="btn btn-nav-right sea-fyi-next" type="button">NXT</button>
|
||||
<button class="btn btn-nav-left fyi-prev" type="button">PRV</button>
|
||||
<button class="btn btn-nav-right fyi-next" type="button">NXT</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -42,8 +42,8 @@ Context: sig_cards, user_polarity, user_seat, sig_reserve_url, sig_reservations_
|
||||
</div>
|
||||
</div>
|
||||
<div class="sig-stat-block">
|
||||
<button class="btn btn-reverse sig-flip-btn" type="button">SPIN</button>
|
||||
<button class="btn btn-info sig-info-btn" type="button">FYI</button>
|
||||
<button class="btn btn-reverse spin-btn" type="button">SPIN</button>
|
||||
<button class="btn btn-info fyi-btn" type="button">FYI</button>
|
||||
<div class="stat-face stat-face--upright">
|
||||
<p class="stat-face-label">Emanation</p>
|
||||
<ul class="stat-keywords" id="id_stat_keywords_upright"></ul>
|
||||
@@ -60,8 +60,8 @@ Context: sig_cards, user_polarity, user_seat, sig_reserve_url, sig_reservations_
|
||||
<p class="sig-info-effect"></p>
|
||||
<span class="sig-info-index"></span>
|
||||
</div>
|
||||
<button class="btn btn-nav-left sig-info-prev" type="button">PRV</button>
|
||||
<button class="btn btn-nav-right sig-info-next" type="button">NXT</button>
|
||||
<button class="btn btn-nav-left fyi-prev" type="button">PRV</button>
|
||||
<button class="btn btn-nav-right fyi-next" type="button">NXT</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -81,7 +81,7 @@ Context: sig_cards, user_polarity, user_seat, sig_reserve_url, sig_reservations_
|
||||
data-operations="{{ card.operations_json }}"
|
||||
data-levity-qualifier="{{ card.levity_qualifier }}"
|
||||
data-gravity-qualifier="{{ card.gravity_qualifier }}"
|
||||
data-reversal="{{ card.reversal }}">
|
||||
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 %}
|
||||
|
||||
@@ -1,16 +1,44 @@
|
||||
{% for card in cards %}
|
||||
<div class="fan-card" data-index="{{ forloop.counter0 }}">
|
||||
<div class="fan-card"
|
||||
data-index="{{ forloop.counter0 }}"
|
||||
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-correspondence="{{ card.correspondence|default:'' }}"
|
||||
data-keywords-upright="{{ card.keywords_upright|join:',' }}"
|
||||
data-keywords-reversed="{{ card.keywords_reversed|join:',' }}"
|
||||
data-energies="{{ card.energies_json }}"
|
||||
data-operations="{{ card.operations_json }}"
|
||||
data-levity-qualifier="{{ card.levity_qualifier }}"
|
||||
data-gravity-qualifier="{{ card.gravity_qualifier }}"
|
||||
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 class="fan-card-face">
|
||||
{% if card.name_group %}<p class="fan-card-name-group">{{ card.name_group }}</p>{% endif %}
|
||||
<h3 class="fan-card-name">{{ card.name_title }}</h3>
|
||||
<div class="fan-card-face-upright">
|
||||
{% if card.name_group %}<p class="fan-card-name-group">{{ card.name_group }}</p>{% endif %}
|
||||
{% if card.arcana != "MAJOR" and card.levity_qualifier %}
|
||||
<p class="sig-qualifier-above">{{ card.levity_qualifier }}</p>
|
||||
{% endif %}
|
||||
<h3 class="fan-card-name">{{ card.name_title }}{% if card.arcana == "MAJOR" and card.levity_qualifier %},{% endif %}</h3>
|
||||
{% if card.arcana == "MAJOR" and card.levity_qualifier %}
|
||||
<p class="sig-qualifier-below">{{ card.levity_qualifier }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<p class="fan-card-arcana">{{ card.get_arcana_display }}</p>
|
||||
{% if card.correspondence %}
|
||||
<p class="fan-card-correspondence">{{ card.correspondence }}</p>
|
||||
{% endif %}
|
||||
<div class="fan-card-face-reversal">
|
||||
{% if card.arcana == "MAJOR" %}
|
||||
<p class="fan-card-reversal-name">{{ card.levity_qualifier|default:card.gravity_qualifier }}</p>
|
||||
<p class="fan-card-reversal-qualifier">{{ card.name_title }}{% if card.levity_qualifier %},{% endif %}</p>
|
||||
{% else %}
|
||||
<p class="fan-card-reversal-name">{{ card.name_title }}</p>
|
||||
<p class="fan-card-reversal-qualifier">{{ card.reversal_qualifier|default:card.gravity_qualifier }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="fan-card-corner fan-card-corner--br">
|
||||
<span class="fan-corner-rank">{{ card.corner_rank }}</span>
|
||||
|
||||
@@ -17,11 +17,35 @@
|
||||
<div class="tarot-fan-wrap">
|
||||
<button id="id_fan_prev" class="fan-nav fan-nav--prev" aria-label="Previous card">‹</button>
|
||||
<div id="id_fan_content" class="tarot-fan"></div>
|
||||
<button id="id_fan_flip" class="btn btn-reveal fan-flip-btn" type="button">FLIP</button>
|
||||
<div class="fan-stage-block sig-stat-block" id="id_fan_stage_block">
|
||||
<button class="btn btn-reverse spin-btn" type="button">SPIN</button>
|
||||
<button class="btn btn-info fyi-btn" type="button">FYI</button>
|
||||
<div class="stat-face stat-face--upright">
|
||||
<p class="stat-face-label">Emanation</p>
|
||||
<ul class="stat-keywords" id="id_fan_stat_upright"></ul>
|
||||
</div>
|
||||
<div class="stat-face stat-face--reversed">
|
||||
<p class="stat-face-label">Reversal</p>
|
||||
<ul class="stat-keywords" id="id_fan_stat_reversed"></ul>
|
||||
</div>
|
||||
<div class="sig-info" id="id_fan_fyi_panel" style="display:none">
|
||||
<div class="sig-info-header">
|
||||
<h4 class="sig-info-title"></h4>
|
||||
<p class="sig-info-type"></p>
|
||||
</div>
|
||||
<p class="sig-info-effect"></p>
|
||||
<span class="sig-info-index"></span>
|
||||
</div>
|
||||
<button class="btn btn-nav-left fyi-prev" type="button">PRV</button>
|
||||
<button class="btn btn-nav-right fyi-next" type="button">NXT</button>
|
||||
</div>
|
||||
<button id="id_fan_next" class="fan-nav fan-nav--next" aria-label="Next card">›</button>
|
||||
</div>
|
||||
</dialog>
|
||||
{% endblock content %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{% static 'apps/epic/stage-card.js' %}"></script>
|
||||
<script src="{% static 'apps/gameboard/game-kit.js' %}"></script>
|
||||
{% endblock scripts %}
|
||||
|
||||
@@ -112,6 +112,8 @@
|
||||
<script src="{% static 'apps/epic/room.js' %}"></script>
|
||||
<script src="{% static 'apps/epic/gatekeeper.js' %}"></script>
|
||||
<script src="{% static 'apps/epic/role-select.js' %}"></script>
|
||||
<script src="{% static 'apps/epic/stage-card.js' %}"></script>
|
||||
<script src="{% static 'apps/epic/combobox.js' %}"></script>
|
||||
<script src="{% static 'apps/epic/sig-select.js' %}"></script>
|
||||
<script src="{% static 'apps/epic/sea.js' %}"></script>
|
||||
<script src="{% static 'apps/epic/tray.js' %}"></script>
|
||||
|
||||
Reference in New Issue
Block a user