Files
python-tdd/src/static_src/scss
Disco DeDisco 1452de1a76
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful
feat: My Sign saved-sig state — --duoUser bg, centred card+stat-block, stage card auto-rotates for reversed sigs on landing. Three follow-up polish items atop the f609313 read-only-saved-sig batch.
(1) **`--duoUser` bg on the saved-sig aperture.** Per user spec — once the table hex is server-side gone (f609313's `{% if not current_significator %}` wrap), the now-mostly-empty olive aperture reads as a distinct mode vs the default landing (--priUser bg w. hex). New `.my-sign-page[data-current-card-id] { background-color: rgba(var(--duoUser), 1); }` block in `_card-deck.scss:644-696`. Keyed on `data-current-card-id` (present only when `current_significator` is set per `my_sign.html:20`) rather than the absence of `[data-phase="landing"]` — picker also lacks the hex but should keep --priUser. Mirrors how `.my-sea-page[data-phase="picker"]` swaps bg in `_gameboard.scss`.

(2) **Stage card + stat block centre in the aperture.** Default landing left-anchored the stage natural-sized at the top of the column (above the hex which filled the rest); w. the hex gone there's a wide empty page bottom. `.my-sign-page[data-current-card-id] .my-sign-stage` overrides to `flex: 1; justify-content: center; align-items: center; padding-left: 0;` — stage grows to fill, card+stat-block centre as a unit. `.my-sign-landing` collapses to `flex: 0 0 auto` + `position: static`; DEL is `position: absolute` so it walks up to `.my-sign-page` (already `position: relative`) + pins to the page corner. **2 traps caught mid-build** in the centring pass: (a) `.sig-stat-block`'s default `align-self: flex-end` (`_card-deck.scss:599`) overrode the parent's `align-items: center` on the cross axis, so the stat block floated to the bottom of the stage while the card sat at vertical-centre — forced `align-self: center` on this state. (b) `.my-sign-flip-btn`'s `left: calc(1.5rem + 0.4rem)` (`_card-deck.scss:747`) assumed the card sat flush against `.sig-stage`'s padded-left edge — true on the picker but wrong w. `justify-content: center`, FLIP landed at the stage's left edge w. the card centred ~3rem to the right of it. Re-derived left/bottom from the centred geometry: card's left edge in stage = `(100% - 2 * sig-card-w - 0.75rem) / 2` (the centred card+gap+stat group's left), card's bottom edge = `50% - sig-card-w * 0.8` from stage bottom (cardHeight = sig-card-w × 8/5 = × 1.6, half = × 0.8). `+ 0.4rem` on each lands FLIP just inside the card's bottom-left corner, same offset as the picker-side intent.

(3) **Stage card auto-rotates 180° on landing for saved-reversed sigs.** Server-side `data-polarity` attribute on `.my-sign-page` already reflected `significator_reversed` correctly (drives the polarity-themed color rules at `_card-deck.scss:917-1042` for levity/gravity ink) but the visual 180° rotation lives in the `stage-card--reversed` class which was only JS-applied via `_toggleOrientation()` (SPIN btn handler). On init w. a saved sig, `_populateStage(savedCardEl)` filled the card's data but didn't touch rotation — so saved-reversed sigs rendered upright on landing while the My Sign applet (template-driven, reads `request.user.significator_reversed` directly + conditionally adds `stage-card--reversed` per `_applet-my-sign.html:9`) correctly rotated them. Two surfaces disagreed → user read the applet as inverted ("non-reversed sig displays upside-down in the applet"). Actually the my_sign.html stage was the liar; the applet was right. Fixed at `my_sign.html:404-406` — after `_populateStage(savedCardEl) + stage.classList.add('sig-stage--frozen')`, if `revInput.value === '1'` (= saved reversed=True) call `_toggleOrientation()` once. That helper covers all three coordinated state mutations: `stageCard.classList.toggle('stage-card--reversed', on)` (visual 180° rotation), `statBlock.classList.toggle('is-reversed', on)` (swaps to reversal face per `_card-deck.scss:62-65`), `spinBtn.classList.toggle('is-reversed', on)` (visual indicator). Both surfaces now agree. Per user direction, a follow-up will lock my_sign.html SAVE to always write `reversed=False` (Tarot-tradition convention) — but the underlying rotation pipeline still has to work for room-side sig-select where reversed sigs are needed.

**TDD coverage**: no new tests — `test_landing_previews_saved_sig_on_stage` (updated in f609313) still passes as written (its assertions are around the frozen-stage + stat-block-visible + hex-absent contract, all of which hold under the centring + rotation patches). The reversed-sig-auto-rotate case is light-weight enough (one branch w. a well-known helper) to not need a dedicated FT; if it regresses, the existing room-side `_toggleOrientation` coverage in the gameboard FTs catches the helper itself + manual verify caught it here. Manual verify done on /billboard/my-sign/ w. `disco`'s saved Jack of Brands (reversed=False, renders upright + centred w. --duoUser bg + FLIP on card's bottom-left + DEL on page's bottom-right). 1211 IT/UT still green; one minor visual to chase before locking my_sign.html to non-reversed-only — verifying that a reversed-saved sig renders rotated on return (DB has none currently, will test after the follow-up).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 13:21:55 -04:00
..
.bud-duplicate-flash: auto-ease-out 3s after FYI + palette swap — note.js's Brief.showDuplicateBanner FYI handler now setTimeout(() => target.classList.remove('bud-duplicate-flash'), 3000) after the .add(); the existing transition: color 600ms ease, text-shadow 600ms ease rule on the class already covered the ease-in (default → flash), so the same rule now also covers the ease-out (flash → default) when the class drops — net behaviour: tap FYI → flash peaks → flash visibly fades back to the default text styling over ~600ms after a 3s hold, instead of persisting til page refresh; palette keys swapped per user steer — color: var(--terUser); text-shadow: var(--ninUser)color: var(--ninUser); text-shadow: var(--terUser), so the highlight reads as a lighter handle w. a gold glow rather than a gold handle w. a light glow, matching the duplicate-guard spec the user re-aligned on; affects all three flash targets uniformly (.bud-entry .bud-name on /billboard/my-buds/, .post-recipient on post.html share-flow, .gate-slot.filled on the gatekeeper invite-flow) since they all flow through the same _bud.scss .bud-duplicate-flash selector + the same Brief.showDuplicateBanner JS handler; new Jasmine spec D7b in NoteSpec.js uses jasmine.clock().install() + clock().tick(3001) to fast-forward past the dismiss window + assert the class is gone (existing D7 still pins the immediate-after-FYI peak state); existing FTs (test_bill_my_buds.test_re_add_existing_bud_shows_already_present_brief… + test_core_bud_btn duplicate-guard FTs) still green because they assert immediately after the FYI click (well inside the 3s hold) — TDD
2026-05-13 00:43:03 -04:00
btn-primary label renames + stage-card polarity color refinements — two interleaved threads from one session, committing together since both touch sig + sea stage cards ; LABEL RENAMES: PICK SIGS → SCAN SIGS (room.html #id_pick_sigs_btn), PICK SKY → CAST SKY (room.html #id_pick_sky_btn × 2), PICK SEA → DRAW SEA (room.html #id_pick_sea_btn), TAKE SIG → SAVE SIG (sig-select.js _takeSigBtn.textContent × 2 callsites + section comment) — Element IDs (id_pick_sky_btn etc.), URL names (epic:pick_sigs, epic:pick_sky), and Python state enums (TableStatus.PICK_SKY, PICK_SEA, SIG_SELECT) intentionally retained as stable identifiers; the renamed text is purely the .btn-primary user-facing label ; FT + IT mentions of the old labels swept in test_game_room_select_{sig,sky,sea,role}.py, test_billboard.py, setup_sea_session.py mgmt cmd, apps/epic/{views,utils,models,tasks,tests/integrated/test_views}.py, SigSelectSpec.js, sky_overlay/sea_overlay/dashboard/sky.html, _card-deck.scss, _sky.scss — all docstring/comment references updated for cascade-grep cleanliness ; STAGE-CARD COLOR + CLASS REFINEMENTS (earlier in session): sig-stage card text colour split per polarity — gravity gets --terUser on .fan-card-name + .fan-card-reversal-{name,qualifier} + .sig-qualifier-{above,below}, levity gets --quiUser on the same five slots; all selectors prefixed w. .sig-stage-card to match the 0,4,0 specificity of the default .sig-stage .sig-stage-card .fan-card-face .sig-qualifier-* rule (without the prefix the polarity overrides lose the cascade — .sig-qualifier-below was visibly stuck on the default --quiUser) ; .stat-face-label gets polarity-inverse colours — gravity stat-block bg is --secUser (opposite of card's --priUser) so the label takes --quiUser to stay legible; levity is the symmetric flip (label = --terUser on --priUser stat-block bg) ; levity card title/qualifier drop-shadow swapped from rgba(0,0,0,…) → rgba(255,255,255,…) — dark drop reads as harsh smudge against the inverted-frame levity --secUser bg; applied to both sig-overlay[data-polarity="levity"] stage card AND sea-stage--levity via $_sea-title-shadow-levity (former shared $_sea-title-shadow split into per-polarity {levity,gravity} variants) ; reversal-face class/content alignment so each .fan-card-reversal-* class always carries its semantic content — DOM order per arcana type controls visual layout after the 180° SPIN (DOM-second appears visually on top): Major → title in .fan-card-reversal-name @ DOM-second (visually top after spin), qualifier in .fan-card-reversal-qualifier @ DOM-first; Non-major → title in .fan-card-reversal-name @ DOM-first (visually bottom after spin), qualifier in .fan-card-reversal-qualifier @ DOM-second (preserves the original "qualifier word reads first after spin" layout for Middle/Minor arcana — e.g. "Relieving / Eight of Crowns" not "Eight of Crowns / Relieving") ; _tarot_fan.html renders per-arcana DOM order directly (Django template branches handle both layouts); sig + sea overlays render a fixed two-<p> skeleton (one DOM order) so stage-card.js's populator dynamically rewrites the two <p>s' className per arcana — Major/override branch flips DOM-second to .fan-card-reversal-name + content, DOM-first to .fan-card-reversal-qualifier; non-major branch keeps DOM-first as .fan-card-reversal-name + title, DOM-second as .fan-card-reversal-qualifier + reversalQualifier-or-polarity-fallback ; SigSelectSpec.js + SeaDealSpec.js fixtures + Major reversed-face assertion updated for the new semantic — TDD
2026-05-18 00:25:10 -04:00
tray apparatus scales w. fluid rem; sig-select 9×2 middling breakpoint — $handle-exposed was 48px fixed while #id_tray_btn is 3rem, so on big-rem viewports (clamp(14px, 2.4vmin, 22px) → up to 22px on tall screens, btn=66) the btn's flex parent (#id_tray_handle) shrank the btn from 66×66 → 48×66 via default flex-shrink:1 in portrait (elongated tall ellipse), and in landscape the btn overflowed the 48px-tall handle vertically (extending 9px past viewport top in closed state); fix: $handle-exposed: 3rem matches the btn so it fills the exposed area at every rem; $handle-rect-h: 4.5rem (was 72px) gives the visible rail thickness a touch of breathing room around the btn at every scale; landscape rules in the same partial that hard-coded 48px / 72px (#id_tray_handle { height: 48px }, #id_tray_grip { bottom: calc(48px/2 - 0.125rem); width: 72px }) now reference the variables so they track in sync — tray.js _computeBounds() swapped from _btn.offsetWidth/Height_handle.offsetWidth/Height for the same reason: even with the SCSS fix, measuring the btn would re-introduce the offset when btn and handle drift (which they shouldn't now, but the handle is the layout-defining element so measure it directly); id_kit_btn added as fallback for id_gear_btn (which no longer renders on the room page) so the open-state landscape wrap height anchors to the bottom-right kit btn instead of the full viewport — id_tray_handle cached on the module via _handle ref alongside _btn and cleared in reset() ; sig-select grid jumped straight from 6 cols (narrow landscape) → 18 cols × 3rem at min-width: 900px, but 18×3rem + 7rem modal margins needs ~1376px to clear at rem=22 so the cards spilled off the sides on common 1280-wide laptops + the previous-era 9×2 middling layout had simply been dropped; new cascade in _card-deck.scss mirrors the comment's documented intent: 6 cols default landscape (row layout, stage beside grid) → 9 cols × 3rem at min-width: 900px (column layout, stage above grid) → 18 cols × 3rem at min-width: 1400px → 18 × 5rem at min-width: 1800px (unchanged) — verified in Claudezilla across iphone-14 portrait (rem=14, btn=42 square, handle right edge at viewport right), 816×826 portrait near-landscape (rem=19.6, btn=58.75 square no longer elongated), 1149×751 landscape mid (rem=18, btn=54 square at viewport top, 9×2 grid), 1789×1111 desktop XL (rem=22, btn=66 square at viewport top, 18×1 grid)
2026-05-17 23:21:02 -04:00