Files
python-tdd/src/static_src/scss/_burger.scss

198 lines
7.3 KiB
SCSS
Raw Normal View History

room.html burger btn + 5-fan; universal landscape btn refactor; kit_bag_dialog vertical bar — TDD Major feature push staging room.html for sprint A.8 — adds the burger btn + fan-of-five sub-btns affordance, then rotates the whole landscape btn layout to make room for it. The landscape refactor is universal (not .room-page-scoped) so every page that hosts these btns reads consistently. ## Burger btn + fan-of-five (room.html only) `templates/apps/gameboard/_partials/_room_burger.html` (NEW) — `#id_burger_btn` (.fa-burger) + `#id_burger_fan` containing 5 sub-btns: `#id_voice_btn` (headset), `#id_sky_btn` (cloud), `#id_earth_btn` (earth-americas), `#id_sea_btn` (bridge-water), `#id_text_btn` (keyboard). Pure scaffolding — no click handlers in this sprint; wire-up lands later as each surface matures. `apps/epic/static/apps/epic/burger-btn.js` (NEW) — toggle `.active` on click; Escape + click-outside close; opening burger auto-closes the kit dialog (`#id_kit_bag_dialog`) + the bud slide-out panel (`html.bud-open`) by dispatching a click to the owning btn (routes through that btn's own toggle/close path — no fetch on close). `bindBurger()` returns an `AbortController` so test code (+ any future re-bind callers) can detach all listeners cleanly via `ac.abort()`. `static_src/scss/_burger.scss` (NEW) — burger sits parallel to gear-above-kit but on the bud-side. Portrait: bottom:4.2rem; left:0.5rem (above #id_bud_btn). Landscape: bottom:0.5rem; right:4.2rem (to the LEFT of #id_bud_btn). Fan is a CSS radial menu w. each sub-btn's `--angle = --base + --i * 30deg`. Portrait `--base: 0deg` (arc 12→4 o'clock), landscape `--base: -90deg` (arc 9→1 o'clock). Sub-btn radius `--r: 7.75rem`. Indices: voice=0, sky=1, earth=2, sea=3, text=4 (user-spec'd clockwise order). room.html includes the partial + the script; the burger renders unconditionally regardless of `gate_status` / `table_status`. ## Universal landscape btn refactor Sprint replaces the prior centred-in-sidebar landscape arrangement w. a kit-at-top + bud-at-bottom + gear/burger as horizontal partners. Applies to every page in landscape (was scoped to .room-page in the first iteration). `_game-kit.scss` — kit_btn landscape moves to top:0.5rem; right:0.5rem (was bottom:0.5rem centred in sidebar). The 0.5rem right literal (not the calc((--sidebar-w - 3rem)/2)=1rem) produces a 0.7rem edge-to-edge gap w. gear at right:4.2rem — matching the portrait gear-above-kit gap exactly. `_bud.scss` — bud_btn landscape moves to bottom:0.5rem; right:0.5rem (was top:0.5rem centred in sidebar). Same 0.5rem literal as kit_btn. bud_panel #id_recipient relocates to bottom:0.5rem (was top:0.5rem) + transform-origin flips to right center. .bud-suggestions rise upward from above the panel (bottom:4rem) instead of dropping from below. `_applets.scss` — .gear-btn landscape moves to top:0.5rem; right:4.2rem (was centred bottom:3.95rem). All applet menus anchor at top:2.6rem; right:4.2rem (beneath the gear's leftward arc) extending DOWN-LEFT into the viewport. #id_room_menu joins the shared portrait position list (was bespoke in _room.scss). `_room.scss` — bespoke #id_room_menu rule deleted entirely. The menu now inherits %applet-menu + the shared portrait position list — same chrome + behaviour as #id_post_menu / #id_billscroll_menu. Earlier iteration tried flex-direction:row in landscape; reverted per user request — "lose all scss specificity" wins. `_card-deck.scss` — obsolete `#id_room_menu { right: 2.5rem; }` override in the XL+landscape block deleted. Was a same-specificity hack to beat _applets.scss's old centred position; no longer needed w. the consolidated rule. ## kit_bag_dialog vertical bar in landscape `_game-kit.scss` — when open in landscape, dialog covers the right sidebar (top:0; bottom:0; right:0; width: var(--sidebar-w)). Slides in from off-viewport right by animating max-width 0 → var(--sidebar-w). Opaque bg (rgba(--priUser, 1) — was 0.97). z-index: 319 (above burger at 318) so it lands in front of the burger btn when open. Top-edge border → left-edge border. Inner content flips to `flex-direction: column-reverse` so DOM order Deck→Dice→Trinket→Tokens paints visually bottom→top. .kit-bag-section also column-reverse → icon row above label. .kit-bag-label drops vertical-rl + the rotate(180deg) scaleX(1.3) transform, reads horizontally. .kit-bag-row--scroll flips to column + overflow-y for the Tokens scrollable row. `game-kit.js attachTooltip()` — 2-axis tooltip clamp matching sky-wheel.js + wallet.js's pattern. Horizontal: left edge stays within [1rem, viewport-ttW-1rem]. Vertical: prefer ABOVE the element; flip BELOW when tooltip is too tall to fit above (e.g. landscape kit bar w. Tokens row near top). Resets top/bottom on mouseleave so next show measures fresh. ## Tests `apps/epic/tests/integrated/test_views.py` (+1 class, 6 ITs) — RoomBurgerBtnRenderTest: burger_btn renders, fan container renders, 5 sub-btns w. correct ids, icons match spec, burger-btn.js loaded, burger persists thru table_status. `static_src/tests/BurgerSpec.js` (NEW, 19 Jasmine specs) — bindBurger() returns AbortController; click toggle; Escape close; click-outside close; opening burger closes kit dialog + bud panel when set; AbortController teardown removes listeners. All 1356 IT+UT green (+6 new from RoomBurgerBtnRenderTest, was 1350). Jasmine suite green (was 220-something specs, +19 BurgerSpec). Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 20:40:33 -04:00
// ── Burger btn + fan-of-5 (room.html only) ───────────────────────────
//
// Sits parallel to .gear-btn-above-#id_kit_btn, but on the bud-side:
// portrait : bottom-left, above #id_bud_btn
// landscape: right sidebar, to the LEFT of #id_bud_btn (same horizontal
// pair as gear ← kit at the top)
//
// Active state opens a fanning arc of 5 sub-btns 30deg apart, each
// centred --r from the burger centre. R=7.75rem reads as a comfortable
// spread w. ~0.4rem edge-to-edge gaps between adjacent sub-btns.
// portrait : arc 12→4 o'clock (sky, earth, sea, voice, text)
// landscape: arc 9→1 o'clock (same icon order, rotated CCW 90°)
//
// Sub-btns are pure scaffolding — no click handlers in this sprint.
//
// Landscape btn relocation rules for kit / gear / bud / kit_dialog / bud
// panel / applet menus DON'T live in this partial — they're owned by
// their respective `_game-kit.scss`, `_bud.scss`, `_applets.scss`,
// `_room.scss` partials. _burger.scss only owns burger-specific styling.
#id_burger_btn {
position: fixed;
bottom: 4.2rem;
left: 0.5rem;
burger z-index drop 318→314 to match .gear-btn; FT base dismiss_brief_if_present helper — TDD Follow-up to 3ca986f. Lands the FT fixes the burger sprint surfaced + tightens the burger's z-stack so the existing kit_btn / bud_btn / bud_panel / dialog naturally cover it on overlap (vs. the explicit opacity-fade rules the first iteration tried). ## Burger z-index drop `static_src/scss/_burger.scss`: - `#id_burger_btn` z 318 → 314 (matches the page-level `.gear-btn` z). Below kit_btn + bud_btn (318), bud_panel (317), kit_bag_dialog (316) — so every overlapping surface visually covers the burger when it appears. The earlier `html.bud-open #id_burger_btn { opacity: 0 }` + `html:has(#id_kit_bag_dialog[open]) #id_burger_btn { opacity: 0 }` rules are now redundant + deleted. - `#id_burger_fan` z 317 → 313 (stays just below burger so burger remains clickable when fan is open). `static_src/scss/_game-kit.scss`: - `#id_kit_bag_dialog` z 319 → 316 (reverts an earlier iteration that bumped it above burger). 316 keeps the dialog BELOW kit_btn + bud_btn so those stay visible + clickable when dialog opens — user re-clicks kit_btn to close. Resolves "kit btn disappears when dialog open" reported on iPad portrait. `static_src/scss/_bud.scss`: - `html.bud-open #id_kit_btn { opacity: 0 }` now wrapped in `@media (orientation: portrait)`. In landscape kit_btn lives at the TOP of the right sidebar + bud_panel sits at the BOTTOM — no visual conflict, kit stays visible. ## FT base — dismiss_brief_if_present() `functional_tests/base.py`: - New helper on both `FunctionalTest` + `ChannelsFunctionalTest`: ```python def dismiss_brief_if_present(self, banner_selector=".note-banner", browser=None): ``` - Removes any matching Brief banner from the DOM via `execute_script`. No-op if absent. Default selector matches every Brief shape; pass a specific selector to target one kind. DOM-removal rather than NVM-click bypasses the dismiss_url POST flow that some Briefs (FREE/PAID DRAW) wire up — use this when the test cares about the page state AFTER a Brief, not the dismissal mechanics. ## FT — test_trinket_coin_on_a_string.py Two methods (`test_coin_deposit_unequips_from_kit_bag_and_fills_one_slot`, `test_coin_in_use_game_kit_shows_room_attribution_and_btn_disabled`) updated: 1. `self.dismiss_brief_if_present()` right after `wait_for(id_game_kit)`. `coin@test.io` is created fresh w/o a significator, so the `_my_sea_sign_gate_brief.html` auto-spawns on `/gameboard/` + intercepts the create-game btn click. Dismissing the banner clears the runway. 2. `self.wait_for(... dialog.rect["width"] > 50 ...)` after `kit_btn.click()` + before interacting w. tokens inside the dialog. The landscape kit_bag_dialog animates `max-width 0 → 5rem` over 0.25s; the existing wait_for(find_token).click() found the COIN .token in the DOM immediately + raced the animation — Selenium's scrollIntoView fails on a 0-width container. Waiting for the rect to widen past 50px (≈ 3rem) confirms layout has rendered. ## Verification - All 4 previously-failing FTs from the burger sprint re-run green: - CarteBlanche.test_carte_blanche_equip_and_multi_slot_gatekeeper (was flaking; passed on retry — pre-existing tooltip-population race, not in scope) - CoinOnAString.test_coin_deposit_unequips_from_kit_bag_and_fills_one_slot - CoinOnAString.test_coin_in_use_game_kit_shows_room_attribution_and_btn_disabled - GatekeeperTest.test_second_gamer_drops_token_into_open_slot - IT+UT suite still 1356 green (no touches to model/view code). Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 21:39:36 -04:00
// Match .gear-btn page-level z (314) — keeps burger BELOW kit_btn +
// bud_btn (both z-318) + bud_panel (z-317) so those naturally cover
// burger when they overlap. Dialog (z-316) covers burger too.
z-index: 314;
room.html burger btn + 5-fan; universal landscape btn refactor; kit_bag_dialog vertical bar — TDD Major feature push staging room.html for sprint A.8 — adds the burger btn + fan-of-five sub-btns affordance, then rotates the whole landscape btn layout to make room for it. The landscape refactor is universal (not .room-page-scoped) so every page that hosts these btns reads consistently. ## Burger btn + fan-of-five (room.html only) `templates/apps/gameboard/_partials/_room_burger.html` (NEW) — `#id_burger_btn` (.fa-burger) + `#id_burger_fan` containing 5 sub-btns: `#id_voice_btn` (headset), `#id_sky_btn` (cloud), `#id_earth_btn` (earth-americas), `#id_sea_btn` (bridge-water), `#id_text_btn` (keyboard). Pure scaffolding — no click handlers in this sprint; wire-up lands later as each surface matures. `apps/epic/static/apps/epic/burger-btn.js` (NEW) — toggle `.active` on click; Escape + click-outside close; opening burger auto-closes the kit dialog (`#id_kit_bag_dialog`) + the bud slide-out panel (`html.bud-open`) by dispatching a click to the owning btn (routes through that btn's own toggle/close path — no fetch on close). `bindBurger()` returns an `AbortController` so test code (+ any future re-bind callers) can detach all listeners cleanly via `ac.abort()`. `static_src/scss/_burger.scss` (NEW) — burger sits parallel to gear-above-kit but on the bud-side. Portrait: bottom:4.2rem; left:0.5rem (above #id_bud_btn). Landscape: bottom:0.5rem; right:4.2rem (to the LEFT of #id_bud_btn). Fan is a CSS radial menu w. each sub-btn's `--angle = --base + --i * 30deg`. Portrait `--base: 0deg` (arc 12→4 o'clock), landscape `--base: -90deg` (arc 9→1 o'clock). Sub-btn radius `--r: 7.75rem`. Indices: voice=0, sky=1, earth=2, sea=3, text=4 (user-spec'd clockwise order). room.html includes the partial + the script; the burger renders unconditionally regardless of `gate_status` / `table_status`. ## Universal landscape btn refactor Sprint replaces the prior centred-in-sidebar landscape arrangement w. a kit-at-top + bud-at-bottom + gear/burger as horizontal partners. Applies to every page in landscape (was scoped to .room-page in the first iteration). `_game-kit.scss` — kit_btn landscape moves to top:0.5rem; right:0.5rem (was bottom:0.5rem centred in sidebar). The 0.5rem right literal (not the calc((--sidebar-w - 3rem)/2)=1rem) produces a 0.7rem edge-to-edge gap w. gear at right:4.2rem — matching the portrait gear-above-kit gap exactly. `_bud.scss` — bud_btn landscape moves to bottom:0.5rem; right:0.5rem (was top:0.5rem centred in sidebar). Same 0.5rem literal as kit_btn. bud_panel #id_recipient relocates to bottom:0.5rem (was top:0.5rem) + transform-origin flips to right center. .bud-suggestions rise upward from above the panel (bottom:4rem) instead of dropping from below. `_applets.scss` — .gear-btn landscape moves to top:0.5rem; right:4.2rem (was centred bottom:3.95rem). All applet menus anchor at top:2.6rem; right:4.2rem (beneath the gear's leftward arc) extending DOWN-LEFT into the viewport. #id_room_menu joins the shared portrait position list (was bespoke in _room.scss). `_room.scss` — bespoke #id_room_menu rule deleted entirely. The menu now inherits %applet-menu + the shared portrait position list — same chrome + behaviour as #id_post_menu / #id_billscroll_menu. Earlier iteration tried flex-direction:row in landscape; reverted per user request — "lose all scss specificity" wins. `_card-deck.scss` — obsolete `#id_room_menu { right: 2.5rem; }` override in the XL+landscape block deleted. Was a same-specificity hack to beat _applets.scss's old centred position; no longer needed w. the consolidated rule. ## kit_bag_dialog vertical bar in landscape `_game-kit.scss` — when open in landscape, dialog covers the right sidebar (top:0; bottom:0; right:0; width: var(--sidebar-w)). Slides in from off-viewport right by animating max-width 0 → var(--sidebar-w). Opaque bg (rgba(--priUser, 1) — was 0.97). z-index: 319 (above burger at 318) so it lands in front of the burger btn when open. Top-edge border → left-edge border. Inner content flips to `flex-direction: column-reverse` so DOM order Deck→Dice→Trinket→Tokens paints visually bottom→top. .kit-bag-section also column-reverse → icon row above label. .kit-bag-label drops vertical-rl + the rotate(180deg) scaleX(1.3) transform, reads horizontally. .kit-bag-row--scroll flips to column + overflow-y for the Tokens scrollable row. `game-kit.js attachTooltip()` — 2-axis tooltip clamp matching sky-wheel.js + wallet.js's pattern. Horizontal: left edge stays within [1rem, viewport-ttW-1rem]. Vertical: prefer ABOVE the element; flip BELOW when tooltip is too tall to fit above (e.g. landscape kit bar w. Tokens row near top). Resets top/bottom on mouseleave so next show measures fresh. ## Tests `apps/epic/tests/integrated/test_views.py` (+1 class, 6 ITs) — RoomBurgerBtnRenderTest: burger_btn renders, fan container renders, 5 sub-btns w. correct ids, icons match spec, burger-btn.js loaded, burger persists thru table_status. `static_src/tests/BurgerSpec.js` (NEW, 19 Jasmine specs) — bindBurger() returns AbortController; click toggle; Escape close; click-outside close; opening burger closes kit dialog + bud panel when set; AbortController teardown removes listeners. All 1356 IT+UT green (+6 new from RoomBurgerBtnRenderTest, was 1350). Jasmine suite green (was 220-something specs, +19 BurgerSpec). Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 20:40:33 -04:00
font-size: 1.75rem;
cursor: pointer;
color: rgba(var(--secUser), 1);
display: inline-flex;
align-items: center;
justify-content: center;
width: 3rem;
height: 3rem;
border-radius: 50%;
background-color: rgba(var(--priUser), 1);
border: 0.15rem solid rgba(var(--secUser), 1);
transition: opacity 0.15s ease;
&.active {
color: rgba(var(--quaUser), 1);
border-color: rgba(var(--quaUser), 1);
}
// Landscape: burger sits to the LEFT of #id_bud_btn (same bottom
// anchor as bud). Mirrors gear-to-the-LEFT-of-kit at the top of the
// right sidebar (see _applets.scss for the gear placement).
@media (orientation: landscape) {
left: auto;
right: 4.2rem; // bud_btn right (0.5) + 3.7rem centre-to-centre delta
bottom: 0.5rem;
top: auto;
}
}
// Fan anchor — sits at the burger centre. Sub-btns inside translate
// outward radially when #id_burger_btn carries the .active class.
#id_burger_fan {
position: fixed;
bottom: 5.7rem; // burger bottom (4.2) + half-btn (1.5)
left: 2rem; // burger left (0.5) + half-btn (1.5)
width: 0;
height: 0;
burger z-index drop 318→314 to match .gear-btn; FT base dismiss_brief_if_present helper — TDD Follow-up to 3ca986f. Lands the FT fixes the burger sprint surfaced + tightens the burger's z-stack so the existing kit_btn / bud_btn / bud_panel / dialog naturally cover it on overlap (vs. the explicit opacity-fade rules the first iteration tried). ## Burger z-index drop `static_src/scss/_burger.scss`: - `#id_burger_btn` z 318 → 314 (matches the page-level `.gear-btn` z). Below kit_btn + bud_btn (318), bud_panel (317), kit_bag_dialog (316) — so every overlapping surface visually covers the burger when it appears. The earlier `html.bud-open #id_burger_btn { opacity: 0 }` + `html:has(#id_kit_bag_dialog[open]) #id_burger_btn { opacity: 0 }` rules are now redundant + deleted. - `#id_burger_fan` z 317 → 313 (stays just below burger so burger remains clickable when fan is open). `static_src/scss/_game-kit.scss`: - `#id_kit_bag_dialog` z 319 → 316 (reverts an earlier iteration that bumped it above burger). 316 keeps the dialog BELOW kit_btn + bud_btn so those stay visible + clickable when dialog opens — user re-clicks kit_btn to close. Resolves "kit btn disappears when dialog open" reported on iPad portrait. `static_src/scss/_bud.scss`: - `html.bud-open #id_kit_btn { opacity: 0 }` now wrapped in `@media (orientation: portrait)`. In landscape kit_btn lives at the TOP of the right sidebar + bud_panel sits at the BOTTOM — no visual conflict, kit stays visible. ## FT base — dismiss_brief_if_present() `functional_tests/base.py`: - New helper on both `FunctionalTest` + `ChannelsFunctionalTest`: ```python def dismiss_brief_if_present(self, banner_selector=".note-banner", browser=None): ``` - Removes any matching Brief banner from the DOM via `execute_script`. No-op if absent. Default selector matches every Brief shape; pass a specific selector to target one kind. DOM-removal rather than NVM-click bypasses the dismiss_url POST flow that some Briefs (FREE/PAID DRAW) wire up — use this when the test cares about the page state AFTER a Brief, not the dismissal mechanics. ## FT — test_trinket_coin_on_a_string.py Two methods (`test_coin_deposit_unequips_from_kit_bag_and_fills_one_slot`, `test_coin_in_use_game_kit_shows_room_attribution_and_btn_disabled`) updated: 1. `self.dismiss_brief_if_present()` right after `wait_for(id_game_kit)`. `coin@test.io` is created fresh w/o a significator, so the `_my_sea_sign_gate_brief.html` auto-spawns on `/gameboard/` + intercepts the create-game btn click. Dismissing the banner clears the runway. 2. `self.wait_for(... dialog.rect["width"] > 50 ...)` after `kit_btn.click()` + before interacting w. tokens inside the dialog. The landscape kit_bag_dialog animates `max-width 0 → 5rem` over 0.25s; the existing wait_for(find_token).click() found the COIN .token in the DOM immediately + raced the animation — Selenium's scrollIntoView fails on a 0-width container. Waiting for the rect to widen past 50px (≈ 3rem) confirms layout has rendered. ## Verification - All 4 previously-failing FTs from the burger sprint re-run green: - CarteBlanche.test_carte_blanche_equip_and_multi_slot_gatekeeper (was flaking; passed on retry — pre-existing tooltip-population race, not in scope) - CoinOnAString.test_coin_deposit_unequips_from_kit_bag_and_fills_one_slot - CoinOnAString.test_coin_in_use_game_kit_shows_room_attribution_and_btn_disabled - GatekeeperTest.test_second_gamer_drops_token_into_open_slot - IT+UT suite still 1356 green (no touches to model/view code). Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 21:39:36 -04:00
z-index: 313; // below #id_burger_btn (314) so burger stays clickable
room.html burger btn + 5-fan; universal landscape btn refactor; kit_bag_dialog vertical bar — TDD Major feature push staging room.html for sprint A.8 — adds the burger btn + fan-of-five sub-btns affordance, then rotates the whole landscape btn layout to make room for it. The landscape refactor is universal (not .room-page-scoped) so every page that hosts these btns reads consistently. ## Burger btn + fan-of-five (room.html only) `templates/apps/gameboard/_partials/_room_burger.html` (NEW) — `#id_burger_btn` (.fa-burger) + `#id_burger_fan` containing 5 sub-btns: `#id_voice_btn` (headset), `#id_sky_btn` (cloud), `#id_earth_btn` (earth-americas), `#id_sea_btn` (bridge-water), `#id_text_btn` (keyboard). Pure scaffolding — no click handlers in this sprint; wire-up lands later as each surface matures. `apps/epic/static/apps/epic/burger-btn.js` (NEW) — toggle `.active` on click; Escape + click-outside close; opening burger auto-closes the kit dialog (`#id_kit_bag_dialog`) + the bud slide-out panel (`html.bud-open`) by dispatching a click to the owning btn (routes through that btn's own toggle/close path — no fetch on close). `bindBurger()` returns an `AbortController` so test code (+ any future re-bind callers) can detach all listeners cleanly via `ac.abort()`. `static_src/scss/_burger.scss` (NEW) — burger sits parallel to gear-above-kit but on the bud-side. Portrait: bottom:4.2rem; left:0.5rem (above #id_bud_btn). Landscape: bottom:0.5rem; right:4.2rem (to the LEFT of #id_bud_btn). Fan is a CSS radial menu w. each sub-btn's `--angle = --base + --i * 30deg`. Portrait `--base: 0deg` (arc 12→4 o'clock), landscape `--base: -90deg` (arc 9→1 o'clock). Sub-btn radius `--r: 7.75rem`. Indices: voice=0, sky=1, earth=2, sea=3, text=4 (user-spec'd clockwise order). room.html includes the partial + the script; the burger renders unconditionally regardless of `gate_status` / `table_status`. ## Universal landscape btn refactor Sprint replaces the prior centred-in-sidebar landscape arrangement w. a kit-at-top + bud-at-bottom + gear/burger as horizontal partners. Applies to every page in landscape (was scoped to .room-page in the first iteration). `_game-kit.scss` — kit_btn landscape moves to top:0.5rem; right:0.5rem (was bottom:0.5rem centred in sidebar). The 0.5rem right literal (not the calc((--sidebar-w - 3rem)/2)=1rem) produces a 0.7rem edge-to-edge gap w. gear at right:4.2rem — matching the portrait gear-above-kit gap exactly. `_bud.scss` — bud_btn landscape moves to bottom:0.5rem; right:0.5rem (was top:0.5rem centred in sidebar). Same 0.5rem literal as kit_btn. bud_panel #id_recipient relocates to bottom:0.5rem (was top:0.5rem) + transform-origin flips to right center. .bud-suggestions rise upward from above the panel (bottom:4rem) instead of dropping from below. `_applets.scss` — .gear-btn landscape moves to top:0.5rem; right:4.2rem (was centred bottom:3.95rem). All applet menus anchor at top:2.6rem; right:4.2rem (beneath the gear's leftward arc) extending DOWN-LEFT into the viewport. #id_room_menu joins the shared portrait position list (was bespoke in _room.scss). `_room.scss` — bespoke #id_room_menu rule deleted entirely. The menu now inherits %applet-menu + the shared portrait position list — same chrome + behaviour as #id_post_menu / #id_billscroll_menu. Earlier iteration tried flex-direction:row in landscape; reverted per user request — "lose all scss specificity" wins. `_card-deck.scss` — obsolete `#id_room_menu { right: 2.5rem; }` override in the XL+landscape block deleted. Was a same-specificity hack to beat _applets.scss's old centred position; no longer needed w. the consolidated rule. ## kit_bag_dialog vertical bar in landscape `_game-kit.scss` — when open in landscape, dialog covers the right sidebar (top:0; bottom:0; right:0; width: var(--sidebar-w)). Slides in from off-viewport right by animating max-width 0 → var(--sidebar-w). Opaque bg (rgba(--priUser, 1) — was 0.97). z-index: 319 (above burger at 318) so it lands in front of the burger btn when open. Top-edge border → left-edge border. Inner content flips to `flex-direction: column-reverse` so DOM order Deck→Dice→Trinket→Tokens paints visually bottom→top. .kit-bag-section also column-reverse → icon row above label. .kit-bag-label drops vertical-rl + the rotate(180deg) scaleX(1.3) transform, reads horizontally. .kit-bag-row--scroll flips to column + overflow-y for the Tokens scrollable row. `game-kit.js attachTooltip()` — 2-axis tooltip clamp matching sky-wheel.js + wallet.js's pattern. Horizontal: left edge stays within [1rem, viewport-ttW-1rem]. Vertical: prefer ABOVE the element; flip BELOW when tooltip is too tall to fit above (e.g. landscape kit bar w. Tokens row near top). Resets top/bottom on mouseleave so next show measures fresh. ## Tests `apps/epic/tests/integrated/test_views.py` (+1 class, 6 ITs) — RoomBurgerBtnRenderTest: burger_btn renders, fan container renders, 5 sub-btns w. correct ids, icons match spec, burger-btn.js loaded, burger persists thru table_status. `static_src/tests/BurgerSpec.js` (NEW, 19 Jasmine specs) — bindBurger() returns AbortController; click toggle; Escape close; click-outside close; opening burger closes kit dialog + bud panel when set; AbortController teardown removes listeners. All 1356 IT+UT green (+6 new from RoomBurgerBtnRenderTest, was 1350). Jasmine suite green (was 220-something specs, +19 BurgerSpec). Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 20:40:33 -04:00
pointer-events: none;
@media (orientation: landscape) {
left: auto;
right: 5.7rem; // burger right (4.2) + half-btn (1.5) → burger centre
bottom: 2rem; // burger bottom (0.5) + half-btn (1.5) → burger centre
top: auto;
}
}
.burger-fan-btn {
--r: 7.75rem;
--i: 0;
--base: 0deg; // portrait: arc starts at 12 o'clock
--angle: calc(var(--base) + var(--i) * 30deg);
position: absolute;
left: -1.5rem; // centre 3rem btn on parent's 0,0 origin
top: -1.5rem;
width: 3rem;
height: 3rem;
border-radius: 50%;
background-color: rgba(var(--priUser), 1);
border: 0.15rem solid rgba(var(--secUser), 1);
color: rgba(var(--secUser), 1);
font-size: 1.25rem;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
// Closed state — collapsed onto burger centre, invisible
transform: rotate(var(--angle)) translateY(0) rotate(calc(-1 * var(--angle)));
opacity: 0;
pointer-events: none;
transition: transform 0.25s ease-out, opacity 0.2s ease;
// Landscape: arc rotates CCW 90° — start at 9 o'clock instead of 12.
@media (orientation: landscape) {
--base: -90deg;
}
}
// Per-btn arc index (clockwise from arc start) — voice leads, then sky,
// earth, sea, text. Portrait arc: voice at 12, text at 4 o'clock.
// Landscape arc: voice at 9, text at 1 o'clock.
#id_voice_btn { --i: 0; }
#id_sky_btn { --i: 1; }
#id_earth_btn { --i: 2; }
#id_sea_btn { --i: 3; }
#id_text_btn { --i: 4; }
// Open state — sub-btns swing out to their arc positions.
#id_burger_btn.active ~ #id_burger_fan .burger-fan-btn {
transform: rotate(var(--angle)) translateY(calc(-1 * var(--r))) rotate(calc(-1 * var(--angle)));
pointer-events: auto;
}
burger z-index drop 318→314 to match .gear-btn; FT base dismiss_brief_if_present helper — TDD Follow-up to 3ca986f. Lands the FT fixes the burger sprint surfaced + tightens the burger's z-stack so the existing kit_btn / bud_btn / bud_panel / dialog naturally cover it on overlap (vs. the explicit opacity-fade rules the first iteration tried). ## Burger z-index drop `static_src/scss/_burger.scss`: - `#id_burger_btn` z 318 → 314 (matches the page-level `.gear-btn` z). Below kit_btn + bud_btn (318), bud_panel (317), kit_bag_dialog (316) — so every overlapping surface visually covers the burger when it appears. The earlier `html.bud-open #id_burger_btn { opacity: 0 }` + `html:has(#id_kit_bag_dialog[open]) #id_burger_btn { opacity: 0 }` rules are now redundant + deleted. - `#id_burger_fan` z 317 → 313 (stays just below burger so burger remains clickable when fan is open). `static_src/scss/_game-kit.scss`: - `#id_kit_bag_dialog` z 319 → 316 (reverts an earlier iteration that bumped it above burger). 316 keeps the dialog BELOW kit_btn + bud_btn so those stay visible + clickable when dialog opens — user re-clicks kit_btn to close. Resolves "kit btn disappears when dialog open" reported on iPad portrait. `static_src/scss/_bud.scss`: - `html.bud-open #id_kit_btn { opacity: 0 }` now wrapped in `@media (orientation: portrait)`. In landscape kit_btn lives at the TOP of the right sidebar + bud_panel sits at the BOTTOM — no visual conflict, kit stays visible. ## FT base — dismiss_brief_if_present() `functional_tests/base.py`: - New helper on both `FunctionalTest` + `ChannelsFunctionalTest`: ```python def dismiss_brief_if_present(self, banner_selector=".note-banner", browser=None): ``` - Removes any matching Brief banner from the DOM via `execute_script`. No-op if absent. Default selector matches every Brief shape; pass a specific selector to target one kind. DOM-removal rather than NVM-click bypasses the dismiss_url POST flow that some Briefs (FREE/PAID DRAW) wire up — use this when the test cares about the page state AFTER a Brief, not the dismissal mechanics. ## FT — test_trinket_coin_on_a_string.py Two methods (`test_coin_deposit_unequips_from_kit_bag_and_fills_one_slot`, `test_coin_in_use_game_kit_shows_room_attribution_and_btn_disabled`) updated: 1. `self.dismiss_brief_if_present()` right after `wait_for(id_game_kit)`. `coin@test.io` is created fresh w/o a significator, so the `_my_sea_sign_gate_brief.html` auto-spawns on `/gameboard/` + intercepts the create-game btn click. Dismissing the banner clears the runway. 2. `self.wait_for(... dialog.rect["width"] > 50 ...)` after `kit_btn.click()` + before interacting w. tokens inside the dialog. The landscape kit_bag_dialog animates `max-width 0 → 5rem` over 0.25s; the existing wait_for(find_token).click() found the COIN .token in the DOM immediately + raced the animation — Selenium's scrollIntoView fails on a 0-width container. Waiting for the rect to widen past 50px (≈ 3rem) confirms layout has rendered. ## Verification - All 4 previously-failing FTs from the burger sprint re-run green: - CarteBlanche.test_carte_blanche_equip_and_multi_slot_gatekeeper (was flaking; passed on retry — pre-existing tooltip-population race, not in scope) - CoinOnAString.test_coin_deposit_unequips_from_kit_bag_and_fills_one_slot - CoinOnAString.test_coin_in_use_game_kit_shows_room_attribution_and_btn_disabled - GatekeeperTest.test_second_gamer_drops_token_into_open_slot - IT+UT suite still 1356 green (no touches to model/view code). Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 21:39:36 -04:00
burger sub-btns: opacity-0.6 inactive default + 2-pulse --priRd flash w. fa-ban swap on click — TDD Adds the active/inactive distinction to the 5 burger fan sub-btns. Default state is INACTIVE (opacity 0.6, real icon visible); active conditions get wired one-by-one in later sprints as each surface matures. ## Markup (templates/apps/gameboard/_partials/_burger.html) Each sub-btn now renders BOTH icons: - `<i class="fa-solid fa-<real> burger-fan-icon--on">` (sky/earth/sea/voice/text) - `<i class="fa-solid fa-ban burger-fan-icon--off">` CSS keeps the real icon visible by default in both .active + inactive states. The fa-ban only surfaces during the .flash-inactive pulse below (icon swap is tied to the pulse class, not to inactive state per se — user-spec'd). ## SCSS (static_src/scss/_burger.scss) - `#id_burger_btn.active ~ ... .burger-fan-btn.active { opacity: 1 }` — active sub-btn fully visible. - `#id_burger_btn.active ~ ... .burger-fan-btn:not(.active) { opacity: 0.6 }` — inactive default. - `.burger-fan-icon--on / --off` stacked absolute-position so the swap doesn't shift the layout box. - `.burger-fan-btn.flash-inactive` — adds --priRd border + glow (box-shadow modeled on sig-select's SAVE SIG countdown but lighter), AND swaps to fa-ban via `.burger-fan-icon--on { display: none } / --off { display: inline-block }`. The `#id_burger_btn` itself (the trigger btn) is explicitly NOT subject to inactive/active opacity treatment — only the sub-btns. ## JS (apps/epic/static/apps/epic/burger-btn.js) Delegated click handler on `#id_burger_fan`: any `.burger-fan-btn` click that DOESN'T carry `.active` runs `_flashInactive(subBtn)` — 2 pulses, 180ms ON / 120ms OFF (tighter than sig-select's 600ms cadence per user spec). Active sub-btns will route to their per-feature handlers in later sprints; for now they no-op. ## Tests - `apps/epic/tests/integrated/test_views.py::RoomBurgerBtnRenderTest::test_each_sub_btn_renders_dual_icon_for_inactive_flash_swap` — asserts `burger-fan-icon--on` + `--off` appear 5 times each (one per sub-btn). fa-ban itself isn't counted directly — `_table_positions.html` also renders fa-ban for non-starter seats — but the burger-fan-icon classes are unique to the fan. - `static_src/tests/BurgerSpec.js` — 5 new specs under `describe("inactive sub-btn flash")`: - adds .flash-inactive on click - removes after ~180ms (first ON window) - re-adds after ~480ms (second ON window during the 2nd pulse) - settles back to default after ~800ms (full 2-pulse cycle) - does NOT flash when sub-btn carries .active Uses `jasmine.clock()` for deterministic timing. Mirror-copied to `static/tests/BurgerSpec.js` for the Jasmine FT runner. ## Verification 1358 IT+UT green. Jasmine FT runs all specs (incl. the 5 new flash specs) green. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 22:23:03 -04:00
// Active sub-btn = fully visible; inactive (default) = 0.6 opacity hint.
// Active conditions are wired one-by-one in later sprints.
#id_burger_btn.active ~ #id_burger_fan .burger-fan-btn.active {
opacity: 1;
}
#id_burger_btn.active ~ #id_burger_fan .burger-fan-btn:not(.active) {
opacity: 0.6;
}
// Icon swap — every sub-btn renders BOTH the real icon (.burger-fan-icon--on)
// + a fa-ban placeholder (.burger-fan-icon--off). Real icon shows by
// default in BOTH .active + inactive states; fa-ban only surfaces during
// the .flash-inactive pulse below. Stacked absolute-position so swapping
// doesn't shift the layout box.
.burger-fan-btn {
.burger-fan-icon--on,
.burger-fan-icon--off {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.burger-fan-icon--on { display: inline-block; }
.burger-fan-icon--off { display: none; }
}
// Click-while-inactive flash — burger-btn.js toggles .flash-inactive
// twice in quick succession (~180ms on / 120ms off) for a brief --priRd
// glow on the border + icon, AND swaps the real icon for fa-ban during
// each pulse-on phase. Modeled on sig-select.js's SAVE SIG countdown
// glow but tighter cadence + only 2 pulses.
.burger-fan-btn.flash-inactive {
border-color: rgba(var(--priRd), 1);
color: rgba(var(--priRd), 1);
box-shadow:
0 0 0.5rem 0.1rem rgba(var(--priRd), 0.75),
0 0 1.2rem 0.3rem rgba(var(--priRd), 0.35);
.burger-fan-icon--on { display: none; }
.burger-fan-icon--off { display: inline-block; }
}
burger Sea sub-btn: first-draw --priYl glow handoff (phase 3/3) — TDD Final slice of the Sea sub-btn rollout (phase 1 = .active wiring 3ae85b9; phase 2 = modal extraction + CONT DRAW 6fbeed7). Adds a --priYl + --ninUser glow that rides the affordance chain to teach the user where to click pre-first-draw. ## The handoff chain burger → click → sea_btn → click → .sea-select → click → end - Modal close (Esc / backdrop / DEL guard-OK) restarts the cycle on the burger. - Burger fan close w/o sea_btn click ALSO restarts on the burger. - AUTO DRAW guard-OK ends the cycle permanently (user found the path). - `#id_sea_action_btn` data-state → 'gate-view' (last card landed via ANY path — AUTO DRAW or manual FLIP) ALSO ends permanently. ## SCSS `static_src/scss/_burger.scss` — `.glow-handoff` on burger / sea_btn = --priYl color + border + --ninUser glow. `static_src/scss/_gameboard.scss` — `.glow-handoff` on .sea-select = --terUser border + --ninUser glow (no font-color change per spec). ## Server side `apps/gameboard/views.py` — new `sea_first_draw_pending = show_picker and not hand_non_empty`. True when picker is active w. an empty hand (paid-draw entry, or page reload of a freshly-entered picker). The FREE-DRAW → picker transition fires client-side w. show_picker=False on the rendered template, so the FREE DRAW JS handler seeds the burger glow itself in that path. `templates/apps/gameboard/_partials/_burger.html` — `#id_burger_btn` conditionally renders `class="glow-handoff"` when `sea_first_draw_pending`. `templates/apps/gameboard/my_sea.html` — FREE DRAW transition handler adds `.glow-handoff` to burger at the same SEAT_ANIM_MS moment data-phase swaps to 'picker' (covers the client-side path). ## JS state machine `templates/apps/gameboard/my_sea.html` — new inline IIFE owns the .glow-handoff transitions: - `burger.click` → if .glow-handoff on burger, transfer to sea_btn. - `sea_btn.click` → if .glow-handoff on sea_btn, transfer to .sea-select. - `.sea-select.click` → end this cycle (just clear the glow; cycle restarts on next modal open). - AUTO DRAW guard-OK (via doc-level click listener) → sets `autoDrawConfirmed`. - Modal `hidden`-attr observer: AUTO DRAW path → endPermanently; any other close (Esc / backdrop / DEL) → startOnBurger (skip if glow already permanently ended). - Burger `class`-attr observer: fan closes (`.active` removed) while glow on sea_btn → restart on burger. - `#id_sea_action_btn` `data-state`-attr observer: flips to 'gate-view' (last card landed via ANY path — AUTO DRAW finishing OR manual FLIP filling the final slot) → endPermanently. The data-state observer makes the "stop glowing when all slots filled" guarantee async + decoupled from how the cards arrived. ## CONT DRAW polish (drag-in from prior commit's spec gap) `apps/gameboard/views.py` — `show_cont_draw` now additionally requires `bool(active_draw.hand)` (at least one card drawn). Pre-draw NVM-to-landing falls through to the existing 3-way state machine (PAID DRAW / GATE VIEW / FREE DRAW) instead of misleading w. CONT DRAW that lands back on an empty picker. ## Tests (4 new ITs) `apps/gameboard/tests/integrated/test_views.py::MySeaViewTest`: - `test_burger_renders_glow_handoff_class_when_sea_first_draw_pending` — paid-draw entry to picker w. empty hand → burger has .glow-handoff. - `test_burger_omits_glow_handoff_when_hand_non_empty` — mid-draw → no .glow-handoff. - `test_burger_omits_glow_handoff_on_landing` — landing → no .glow-handoff (FREE DRAW handler seeds client-side instead). - `test_force_landing_hides_cont_draw_when_hand_empty` — pre-first-draw NVM → no CONT DRAW. (JS state-machine behaviour is verified visually; not Jasmine-tested since the IIFE lives inline on my_sea.html, not as a separate module.) ## Verification All 1374 IT+UT green (+4 from Phase 3). Visual verification of glow handoff + hand-complete auto-end confirmed. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 00:39:46 -04:00
// ── First-draw glow handoff (phase 3 of the Sea sub-btn rollout) ─────
//
// Pre-first-draw nudge for the my-sea picker: a --priYl + --ninUser glow
// rides the affordance chain to teach the user where to click.
//
// burger → click → sea_btn → click → .sea-select → click → end
//
// Modal close (Esc / backdrop / DEL) w/o AUTO DRAW restarts the cycle on
// the burger. AUTO DRAW's guard-OK ends the cycle permanently (the user
// has actually drawn — they've found the path). Owner: my_sea.html JS.
#id_burger_btn.glow-handoff,
#id_sea_btn.glow-handoff {
color: rgba(var(--priYl), 1);
border-color: rgba(var(--priYl), 1);
box-shadow:
0 0 0.5rem 0.1rem rgba(var(--ninUser), 0.75),
0 0 1.2rem 0.3rem rgba(var(--ninUser), 0.35);
}
my_sea_gate burger; _bud_apparatus shared shell; CI #344 tray-anchor fix — TDD Continuation of the burger sprint into the my-sea gatekeeper + a couple companion cleanups the visual + CI runs surfaced. ## my_sea_gate.html burger `templates/apps/gameboard/_partials/_room_burger.html` → `_burger.html` (git mv) — now lives at a non-room-scoped path since it's reused across templates. Updated room.html's include path. `templates/apps/gameboard/my_sea_gate.html` — includes `_burger.html` + loads `burger-btn.js`. Burger renders unconditionally on the my-sea gatekeeper, same affordance as the room gatekeeper. `apps/gameboard/tests/integrated/test_views.py::MySeaGateViewTest` — new `test_gate_view_renders_burger_btn_and_fan` IT asserts burger_btn + burger_fan + 5 sub-btns w. correct ids + burger-btn.js loaded on `/gameboard/my-sea/gate/`. ## Burger fade rule reinstated `static_src/scss/_burger.scss` — `html.bud-open #id_burger_btn { opacity: 0; pointer-events: none; }` reinstated, scoped to landscape only. User-confirmed: even after the z-index drop the burger needs to disappear when bud_panel is open in landscape (panel + bud_ok races vs. burger pointer-events). Portrait keeps burger visible since the panel sits BELOW the burger w. no overlap. ## "friend@example.com" → "bud@example.com" placeholder Renamed in 4 bud-panel templates + the FT asserting it: - `templates/apps/gameboard/_partials/_my_sea_bud_panel.html` - `templates/apps/billboard/_partials/_bud_panel.html` - `templates/apps/billboard/_partials/_bud_invite_panel.html` - `templates/apps/billboard/_partials/_bud_add_panel.html` - `functional_tests/test_core_sharing.py` "bud" matches the broader naming convention (bud_btn, my-buds, share-w.-a-bud, etc.) — `friend` was an outlier. ## _bud_apparatus.html shared shell refactor New `templates/apps/billboard/_partials/_bud_apparatus.html` — single shared markup partial for the four bud-btn use cases. Contains btn + panel + input + OK + (optional) suggestions div + bud-btn.js script. Each of the 4 specific partials becomes a thin wrapper that `{% include %}`s the shell + renders its own `<script>bindBudBtn({...})</script>` block w. per-use-case submitUrl / onSuccess / duplicateTargetSelector. Context vars accepted by the shell: - `aria_label` — string for #id_bud_btn aria-label - `sharer_name` — optional; renders `data-sharer-name=` on #id_bud_panel (post-share only) - `include_suggestions` — bool; renders suggestions div + autocomplete script (false on my_buds where the pool == request.user.buds == nothing useful to suggest) Per-call wrappers are now ~10-50 lines instead of 30-120 lines of duplicate markup. Behaviour is identical; only DRY-ed up. ## CI #344 tray-anchor regression fix `apps/epic/static/apps/epic/tray.js` — `_computeBounds` in landscape used `id_gear_btn || id_kit_btn` as the bottom anchor (wrap height = anchor.top). After the burger sprint relocated kit_btn to the TOP of the right sidebar (top:0.5rem), kit_btn.top ≈ 8px → wrap.height collapsed to 8px → tray couldn't slide → `test_dragging_tray_btn_down_opens_tray_in_landscape` failed. Fix: anchor fallback chain is now `id_burger_btn || id_bud_btn` (the new bottom-anchored btns) → `window.innerHeight - 3.5rem` reserved fallback (for pages that have neither). Burger renders unconditionally on room.html so the SIG_SELECT tray test now finds its anchor + lays out the wrap correctly. ## Verification - IT+UT 1357 green (+1 from MySeaGateViewTest burger). - Jasmine specs green. - `test_dragging_tray_btn_down_opens_tray_in_landscape` green (was the CI #344 failure). Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 21:57:45 -04:00
// Burger hides when bud_panel is open — LANDSCAPE only. In portrait the
// burger sits ABOVE the bud panel (bottom:4.2rem vs panel at bottom:0.5
// + height:3rem); no visual conflict. In landscape they share the
// bottom-right area + the bud OK btn ends up obscured / pointer-events
// races against the burger even at the lower z-index — explicit fade
// keeps the bud_panel apparatus clean.
@media (orientation: landscape) {
html.bud-open #id_burger_btn {
opacity: 0;
pointer-events: none;
}
}