From 955bdc7f675016d010666f91213e07c922940109 Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Tue, 26 May 2026 01:18:51 -0400 Subject: [PATCH] =?UTF-8?q?polish=20+=20bugfix=20session=20=E2=80=94=20wal?= =?UTF-8?q?let/Game=20Kit=20applet=20realign;=20my=5Fsea=20label/shadow=20?= =?UTF-8?q?polish;=20DEL/FLIP=20state=20machine;=20sig-change=20cooldown?= =?UTF-8?q?=20loophole=20closure;=20sky-wheel=20planet=20shadow;=20Fiorent?= =?UTF-8?q?ine=20additive=20numerals;=20kit-bag=20DOFF=20async=20refresh?= =?UTF-8?q?=20=E2=80=94=20TDD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit End-of-session bundle 2026-05-26 covering ~10 distinct threads atop the A.7.5-polish-8 sky-wheel mini-portal commit (9cdd2cd). A.8 room.html sprint deferred per user — waiting on image scraping for RWS + future decks so the room can apply the image-mode pattern uniformly w.o. straddling text-mode fallback for unequippable Earthman Shabby Cardstock. **(1) Game Kit + My Wallet applet realignment** — user spec "this isn't a place for tokens" / "only equippables should be there". Game Kit applet (/gameboard/, _applet-game-kit.html) drops the Free Token block — only PASS/BAND/CARTE/COIN trinkets + decks + dice remain. Free + Tithe tokens MOVED to the My Wallet applet on /dashboard/ (_applet-wallet.html rewrite). All trinkets COPIED into Wallet w. same .tt tooltip + DON/DOFF wiring so the user can equip from either surface. Stacked free/tithe icons (single icon per type) carry a .shop-badge ×N count (fa-coins for free, fa-piggy-bank for tithe — the latter standardized from outlier fa-hand-holding-dollar, now matching wallet / kit_bag / shop seed / FTs). Writs placeholder gets the same .token + .tt chrome ("Base currency unit ; Earned at the gate, spent in the shop"). 99+ cap on all badges. home_page view in apps/dashboard/views.py now passes pass/band/carte/coin + free/tithe tokens + counts + equipped_trinket_id. gameboard.js loaded on dashboard for the hover-portal tooltip system; #id_game_kit wrapper added (uses display: contents to stay transparent to the section-grid layout). Standalone game_kit.html page (_game_kit_sections.html) also reorganized — trinkets/tokens/decks each use bare .token icons w. centered flex row + 2rem gap, 1.5rem font-size to match gameboard sizing. id_game_kit outer wrapper data attrs (equipped-id, equipped-deck-id, in-use-deck-ids) feed buildMiniContent() for Equipped/Not Equipped/In-Use status. **(2) My Sea label + shadow polish (my_sea.html Cross + applet)** — user spec "labels appear below and beneath the card, w. the card's shadow obscuring the very top of the label" per the GRAVITY/LEVITY .sea-stack-name pattern. .sea-pos-label repositioning: CROWN + COVER ABOVE slot (bottom: 100%; translate(-50%, -0.4rem)), LAY + CROSS BELOW slot (top: 100%; translate(-50%, 0.3rem)), LEAVE + LOOM increased breathing room (translate -0.4rem LEFT / 0.4rem RIGHT — was 0.1rem overlap). CROWN cell translateY(-0.5rem) UP + LAY cell translateY(0.5rem) DOWN for COVER/CROSS label breathing room. Filled-card downward shadow chain (1px 2px 0 black, 0 4px 0 black-faint, 2px 5px 5px black-blur) scoped to .my-sea-cross .sea-card-slot--filled only — empty dashed placeholders stay shadowless per user spec ("only the cards that replace [slots] should [have shadows]"). Four rotation-correction overrides for box-shadow rotating w. element transform: base (0deg), reversed (180deg sign-flip), cross (90deg matrix rotation → 2px -1px), cross+reversed (270deg → -2px 1px). Saved here for future reference since the matrix derivation is non-obvious: CSS rotate(θ) CW maps offset (a, b) → screen (a·cos θ − b·sin θ, a·sin θ + b·cos θ); solving for unrotated offsets that produce screen-down-right post-rotation gives the 4 chains. My Sea applet .my-sea-slot-label (z-index 0, margin-top 0.15rem) + .my-sea-slot--filled shadow + reversed-variant shadow inversion all mirror the page treatment. **(3) DEL btn + FLIP btn state machine** — user spec: DEL un-disables as soon as ANY card drawn (was gated on hand_complete) ; FLIP btn .btn-disabled + text swap to × once hand complete. _setComplete(on) toggles FLIP btn class + label (parity w. DEL convention: × disabled / word active) ; new _setHasDrawn(on) helper extracted (was bundled in _setComplete). Wired into 4 transitions: (a) manual deposit _filled === 1, (b) initial page-load seed when _filled > 0, (c) AUTO DRAW path post-POST (CRITICAL FIX — was missing, only manual deposit synced DEL even though server already committed all cards on AUTO DRAW), (d) _resetHand spread-switch reset. Template DEL btn gates on saved_by_position (any draw); FLIP btn gates on hand_complete. Test test_partial_hand_del_btn_carries_btn_disabled inverted to test_partial_hand_del_btn_is_enabled per the new spec. **(4) Sig-change MySeaDraw RESET (cooldown loophole closure)** — user-reported revenue-stream loophole 2026-05-26: switching sig used to re-open the FREE DRAW gate + forfeit any paid-draw credit, because apps/gameboard/views.py:266's `in_cooldown = active_draw is not None` keyed entirely off the MySeaDraw row's existence (NOT off User.last_free_draw_at, which is the cooldown TIMER but doesn't drive the in_cooldown decision). Initial draft DELETED the row on sig change — turned out too aggressive: lost both the cooldown anchor (created_at via the active_draw check) AND the paid-state fields (deposit_token_id, paid_through_at). FIX: save_sign on actual sig change `.update(hand=[], significator_id=new, significator_reversed=new)` — preserves cooldown + paid revenue, just resets the hand + sig snapshot. clear_sign left untouched (sig-cleared user can't draw anyway per my_sea_lock's no_significator guard; row sits dormant until re-pick routes through save_sign's reset). Guarded w. sig_changed so re-saving the same sig is a no-op. User.last_free_draw_at was always safe — User-level field, only ever set in my_sea_lock, never cleared (user confirmed the Brief shows 11:59pm consistently). Subtle architectural note for future: the in_cooldown decision being row-existence-based rather than timestamp-based is the load-bearing implicit dependency this loophole exposed; any refactor that delete()s the row needs to either flip in_cooldown to consult last_free_draw_at OR preserve the row as we did here. **(5) Kit-bag DOFF async refresh** — user-reported 2026-05-26: deck disappears entirely from kit-bag on first DOFF; only manual page refresh restores the placeholder. Root cause: _syncKitBagDialog() in gameboard.js did card.querySelector('i') for the placeholder icon — worked for trinket/token cards (single FA ) but BROKE for image-equipped decks whose card-stack icon is (no to copy → empty placeholder div). DROP the client-side optimization, route both DOFF paths thru _refreshKitDialog() (symmetric w. DON). Single source of truth = server-rendered _kit_bag_panel.html's placeholder branch (re-renders _deck_stack_icon.html w.o. the deck arg for the empty-fill SVG). **(6) Sky-wheel planet circle shadow** — user spec "tight 1px 1px black shadow at opacity 0.7 on planet circle groups in all sky locations". Base `filter: drop-shadow(1px 1px 0 rgba(0,0,0,0.7))` on .nw-planet-group so planet badges lift off the wheel rings on /dashboard/sky/ + My Sky applet + any future surface. Hover/active state chains shadow + glow ("drop-shadow ... ; drop-shadow(0 0 5px primary-lm)") since CSS filter REPLACES rather than APPENDS — shadow has to be re-stated on the hover rule to persist during interaction. Elements/signs/houses groups keep their glow-only hover (the request was planet-specific). **(7) TarotCard suit_icon + Fiorentine additive numerals** — (a) suit_icon property pre-checks for major arcana trump 0 → fa-hat-cowboy-side (Fool/Nomad/Matto archetype) and trump 1 → fa-hat-wizard (Magician/Schizo/Bagatto archetype), pinned BEFORE the self.icon branch so even a deck seed supplying a different icon for these ranks normalizes to the convention. Earthman's seed already aligns; Minchiate (empty icon field) used to fall thru to fa-hand-dots. (b) _to_roman() adds _FIORENTINE_ADDITIVE_NUMERALS = {4:'IIII', 19:'XVIIII', 24:'XXIIII', 29:'XXVIIII', 34:'XXXIIII', 39:'XXXVIIII'} pre-check — locked-in 6-exception list per user-corrected spec (initial draft used universal additive form, user clarified "no, only these specific ones, e.g. trump 9 still prints IX + trump 14 still prints XIV per the actual Minchiate deck art"). +2 regression tests: additive overrides + non-overridden subtractive (9=IX, 14=XIV, 44=XLIV, 49=XLIX). **(8) Gear menu NVM font fix** — _my_sea_gear.html's NVM btn changed from to {% else %}{% endif %} + +

{{ pass_token.tooltip_name }}

+

{{ pass_token.tooltip_description }}

+ {% if pass_token.tooltip_shoptalk %}

{{ pass_token.tooltip_shoptalk }}

{% endif %} +

{{ pass_token.tooltip_expiry }}

+ + + {% endif %} + {% if band %} +
+ +
+
+ {% if band.pk == equipped_trinket_id %}{% else %}{% endif %} +
+

{{ band.tooltip_name }}

+

{{ band.tooltip_description }}

+ {% if band.tooltip_shoptalk %}

{{ band.tooltip_shoptalk }}

{% endif %} +

{{ band.tooltip_expiry }}

+
+
+ {% endif %} + {% if carte %} +
+ +
+
+ {% if carte.current_room %}{% elif carte.pk == equipped_trinket_id %}{% else %}{% endif %} +
+

{{ carte.tooltip_name }}

+

{{ carte.tooltip_description }}

+ {% if carte.tooltip_shoptalk %}

{{ carte.tooltip_shoptalk }}

{% endif %} +

{{ carte.tooltip_expiry }}

+
+
+ {% endif %} + {% if coin %} +
+ +
+
+ {% if coin.current_room %}{% elif coin.pk == equipped_trinket_id %}{% else %}{% endif %} +
+

{{ coin.tooltip_name }}

+

{{ coin.tooltip_description }}

+ {% if coin.tooltip_shoptalk %}

{{ coin.tooltip_shoptalk }}

{% endif %} +

{{ coin.tooltip_expiry }}

+
+
+ {% endif %} + {% if free_tokens %} + {% with free_tokens.0 as token %} +
+ + {% if free_count > 1 %}{% if free_count > 99 %}99+{% else %}{{ free_count }}{% endif %}{% endif %} +
+

{{ token.tooltip_name }}{% if free_count > 1 %} (×{% if free_count > 99 %}99+{% else %}{{ free_count }}{% endif %}){% endif %}

+

{{ token.tooltip_description }}

+ {% if token.tooltip_shoptalk %}

{{ token.tooltip_shoptalk }}

{% endif %} +

{{ token.tooltip_expiry }}

+
+
+ {% endwith %} + {% endif %} + {% if tithe_tokens %} + {% with tithe_tokens.0 as token %} +
+ + {% if tithe_count > 1 %}{% if tithe_count > 99 %}99+{% else %}{{ tithe_count }}{% endif %}{% endif %} +
+

{{ token.tooltip_name }}{% if tithe_count > 1 %} (×{% if tithe_count > 99 %}99+{% else %}{{ tithe_count }}{% endif %}){% endif %}

+

{{ token.tooltip_description }}

+

{{ token.tooltip_expiry }}

+
+
+ {% endwith %} + {% endif %} + {# Writs placeholder — currency tier (NOT a Token model row). Tooltip #} + {# mirrors the Token-style chrome so the row reads as one inventory. #} + {# Count lives in the badge (Writs count > 0); empty case still renders #} + {# the icon since Writs are the base balance unit. #} +
+ + {% if user.wallet.writs > 0 %}{% if user.wallet.writs > 99 %}99+{% else %}{{ user.wallet.writs }}{% endif %}{% endif %} +
+

Writs{% if user.wallet.writs > 0 %} (×{% if user.wallet.writs > 99 %}99+{% else %}{{ user.wallet.writs }}{% endif %}){% endif %}

+

Base currency unit

+

Earned at the gate; spent in the shop.

+
+
+ + diff --git a/src/templates/apps/dashboard/_partials/_scripts.html b/src/templates/apps/dashboard/_partials/_scripts.html index b7e3f10..000e30f 100644 --- a/src/templates/apps/dashboard/_partials/_scripts.html +++ b/src/templates/apps/dashboard/_partials/_scripts.html @@ -1,6 +1,12 @@ {% load static %} +{# `gameboard.js` powers the hover-portal tooltip system for `.token` icons #} +{# inside `#id_game_kit`. The My Wallet applet uses this scaffold so its #} +{# trinkets + tokens + writs read with the same chrome as the gameboard's #} +{# Game Kit applet. `initGameKitTooltips()` no-ops if `#id_game_kit` isn't #} +{# present (e.g. the My Wallet applet hidden via toggle). #} + + {# gameboard.js for `initGameKitTooltips()` — the gk-decks section's #} + {# `#id_game_kit` wrapper + `.token deck-variant` icons hook into its #} + {# hover-portal + DON/DOFF wiring. game-kit.js still owns the carousel #} + {# (fan modal) — clicking the icon opens the fan via .gk-deck-card. #} + {% endblock scripts %} diff --git a/src/templates/apps/gameboard/my_sea.html b/src/templates/apps/gameboard/my_sea.html index b10ae65..8da1474 100644 --- a/src/templates/apps/gameboard/my_sea.html +++ b/src/templates/apps/gameboard/my_sea.html @@ -221,18 +221,22 @@ {# Critical: this collapse is my_sea-ONLY — room.html #} {# keeps the dual layout since multiple gamers contribute #} {# (each might bring a different polarization). #} + {# FLIP btn picks up `.btn-disabled` once the hand is #} + {# complete — visual signal that no more draws remain. #} + {# JS `_setComplete(on)` toggles the same class on #} + {# live transition (last-card landed). #} {% if request.user.equipped_deck.is_polarized %}
DECKS
- +
Gravity
- +
Levity
@@ -245,7 +249,7 @@ {% if request.user.equipped_deck.has_card_images %} {% endif %} - +
@@ -266,18 +270,16 @@ class="btn btn-primary" data-state="{% if hand_complete %}gate-view{% else %}auto-draw{% endif %}" data-gate-url="{% url 'my_sea_gate' %}">{% if hand_complete %}GATE
VIEW{% else %}AUTO
DRAW{% endif %} - {# DEL starts `.btn-disabled` until the hand is #} - {# complete — per Sprint-5-iter-4c spec, the 1/day #} - {# quota is committed at first-card-draw + can't be #} - {# refunded by an early DEL. Case-by-case `×` #} - {# rendering matches game-kit tooltip + DON/DOFF #} - {# convention (user-spec 2026-05-20): a disabled btn #} - {# carries × in its own text node, not via a CSS #} - {# overlay. JS `_setComplete` swaps inner text in #} - {# lockstep with the `.btn-disabled` class toggle. #} + {# DEL gates on whether ANY card has been drawn (user #} + {# spec 2026-05-26): pre-first-draw it's disabled; as #} + {# soon as `saved_by_position` has at least one entry #} + {# the user can DEL the in-progress hand. `×` #} + {# is the disabled-state label (parity w. game-kit's #} + {# DON/DOFF disabled convention). JS `_setHasDrawn` #} + {# swaps text + class in lockstep w. live deposits. #} + class="btn btn-danger{% if not saved_by_position %} btn-disabled{% endif %}">{% if saved_by_position %}DEL{% else %}×{% endif %} {# Sea stage — portaled modal that opens on FLIP click via #} @@ -464,12 +466,14 @@ function _resetHand() { // Spread-switch reset only (mid-draw guard inside sync()). - // Iter 4c removed the explicit DEL-resets-hand pathway: - // pre-completion DEL is `.btn-disabled`; post-completion - // DEL routes to the guard portal → server-side delete. + // Post-DEL routes through the guard portal → server-side + // delete + page reload, so this path doesn't fire there. + // Re-disable DEL + re-enable FLIP for the fresh-empty state. _filled = 0; _hideOk(); _unlockSpread(); + _setHasDrawn(false); + _setComplete(false); cross.querySelectorAll( '.sea-crucifix-cell.sea-pos-crown, ' + '.sea-crucifix-cell.sea-pos-leave, ' + @@ -484,24 +488,24 @@ } function _setComplete(on) { - // Iter 4c — single-call state transition for "hand - // complete": DEL un-disables, action btn becomes GATE - // VIEW, FLIP buttons on the deck stacks render as - // disabled when shown. The deck STACKS themselves stay - // click-responsive (so the user can see the disabled - // FLIP feedback) — `_locked` gates the actual draw. - // - // DEL btn text follows the game-kit tooltip convention - // (user-spec 2026-05-20): disabled state reads × (the - // template's initial render does the same), active - // state reads DEL. Lockstep w. the `.btn-disabled` - // class so the visual + label always agree. + // State transition for "hand complete": deck-stack FLIP + // buttons render as disabled (visual signal that no more + // draws remain), action btn becomes GATE VIEW. The deck + // STACKS themselves stay click-responsive so the user + // sees the disabled-FLIP feedback — `_locked` gates the + // actual draw at the click-handler level. DEL state was + // here previously but is now driven by `_setHasDrawn` + // instead (user spec 2026-05-26: DEL unlocks at first + // draw, NOT at completion). _locked = on; picker.classList.toggle('my-sea-picker--locked', on); - if (delBtn) { - delBtn.classList.toggle('btn-disabled', !on); - delBtn.innerHTML = on ? 'DEL' : '×'; - } + picker.querySelectorAll('.sea-deck-stack .sea-stack-ok').forEach(function (btn) { + btn.classList.toggle('btn-disabled', on); + // Mirrors DEL btn convention: disabled label is × (the + // template's initial render does the same), active is + // 'FLIP'. Keeps visual + class in lockstep. + btn.innerHTML = on ? '×' : 'FLIP'; + }); if (actionBtn) { actionBtn.dataset.state = on ? 'gate-view' : 'auto-draw'; actionBtn.innerHTML = on ? 'GATE
VIEW' : 'AUTO
DRAW'; @@ -509,6 +513,19 @@ _hideOk(); } + function _setHasDrawn(on) { + // DEL btn state — un-disables as soon as ANY card has + // been drawn (user spec 2026-05-26). Pre-first-draw the + // user can't DEL an empty hand; once a card is down, + // DEL routes to the guard portal → server-side delete. + // Disabled-state label `×` mirrors the game-kit DON/DOFF + // convention. + if (delBtn) { + delBtn.classList.toggle('btn-disabled', !on); + delBtn.innerHTML = on ? 'DEL' : '×'; + } + } + // ── Deck-stack click → show FLIP → click FLIP → deposit ─ // Iter 4c — stacks remain click-responsive even after hand // is complete (so the user sees the disabled-FLIP feedback, @@ -539,7 +556,10 @@ _fillSlot(posName, card, isLevity); } _filled++; - if (_filled === 1) _lockSpread(); + if (_filled === 1) { + _lockSpread(); + _setHasDrawn(true); // first card → DEL enables + } // Per-placement upsert — server stays current // so a navigate-away mid-draw still persists // the partial hand. User-spec 2026-05-20. @@ -626,7 +646,16 @@ _setComplete(true); return; } + // First-draw transitions — mirror the manual-flip flow. + // Lock spread + un-disable DEL as soon as the POST + // commits (we KNOW ≥1 card is now server-saved). Was a + // bug: AUTO DRAW only synced DEL state at completion + // via `_setComplete(true)`, so a mid-animation refresh + // (or backing out during the staggered placeNext loop) + // would leave DEL stuck disabled even though cards were + // already saved (user-reported 2026-05-26). if (_filled === 0) _lockSpread(); + _setHasDrawn(true); var idx = 0; function placeNext() { if (idx >= autoEntries.length) { @@ -840,8 +869,10 @@ if (_filled >= _currentOrder().length) { _setComplete(true); _lockSpread(); + _setHasDrawn(true); // also enables DEL post-completion } else if (_filled > 0) { _lockSpread(); + _setHasDrawn(true); // any partial draw → DEL enabled } // Seed SeaDeal's `_seaHand` from the server-rendered