diff --git a/src/apps/epic/static/apps/epic/tray.js b/src/apps/epic/static/apps/epic/tray.js index 2d3a2ad..c8abce5 100644 --- a/src/apps/epic/static/apps/epic/tray.js +++ b/src/apps/epic/static/apps/epic/tray.js @@ -100,14 +100,20 @@ var Tray = (function () { // meets the gear button when open. Tray is flex:1 and fills the rest. // Open: wrap top = 0 (pinned to viewport top). // Closed: wrap top = -(gearBtnTop - handleH) = tray fully above viewport. - // Anchor: id_gear_btn historically; id_kit_btn is the live fallback so - // the open-state handle bottom lands at the bottom-right anchor instead - // of overlapping it (no id_gear_btn renders on the room page today). - var gearBtn = document.getElementById('id_gear_btn') - || document.getElementById('id_kit_btn'); - var gearBtnTop = window.innerHeight; - if (gearBtn) { - gearBtnTop = Math.round(gearBtn.getBoundingClientRect().top); + // Anchor: the bottom-most btn on the right sidebar in landscape. + // After the burger sprint, kit_btn + gear_btn relocated to the TOP + // of the sidebar — so using them as anchors collapses wrap height + // to ~8px. Burger (room.html) + bud (post.html) are now the only + // btns reliably anchored at the BOTTOM. Fall back to a fixed + // reserved 3.5rem if neither is present on the page. + var anchor = document.getElementById('id_burger_btn') + || document.getElementById('id_bud_btn'); + var gearBtnTop; + if (anchor) { + gearBtnTop = Math.round(anchor.getBoundingClientRect().top); + } else { + var rem = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16; + gearBtnTop = window.innerHeight - 3.5 * rem; } // handleH = #id_tray_handle's height (48px CSS), NOT _btn.offsetHeight. // The 3rem btn can be larger than the 48px handle on big-rem viewports diff --git a/src/apps/gameboard/tests/integrated/test_views.py b/src/apps/gameboard/tests/integrated/test_views.py index 720d7aa..a27ae5d 100644 --- a/src/apps/gameboard/tests/integrated/test_views.py +++ b/src/apps/gameboard/tests/integrated/test_views.py @@ -2154,6 +2154,17 @@ class MySeaGateViewTest(TestCase): self.assertContains(response, "id_my_sea_paid_draw_btn") self.assertContains(response, reverse("my_sea_refund_token")) + def test_gate_view_renders_burger_btn_and_fan(self): + response = self.client.get(reverse("my_sea_gate")) + self.assertContains(response, 'id="id_burger_btn"') + self.assertContains(response, 'id="id_burger_fan"') + for btn_id in ( + "id_voice_btn", "id_sky_btn", "id_earth_btn", + "id_sea_btn", "id_text_btn", + ): + self.assertContains(response, f'id="{btn_id}"') + self.assertContains(response, "burger-btn.js") + class MySeaInsertTokenViewTest(TestCase): """Sprint 6 iter 6a — POST `/gameboard/my-sea/insert` reserves the diff --git a/src/functional_tests/test_core_sharing.py b/src/functional_tests/test_core_sharing.py index 173662c..4f2b662 100644 --- a/src/functional_tests/test_core_sharing.py +++ b/src/functional_tests/test_core_sharing.py @@ -41,7 +41,7 @@ class SharingTest(FunctionalTest): share_box = post_page.get_share_box() self.assertEqual( share_box.get_attribute("placeholder"), - "friend@example.com or username", + "bud@example.com or username", ) post_page.share_post_with("alice@test.io") diff --git a/src/static_src/scss/_burger.scss b/src/static_src/scss/_burger.scss index a07a9bf..3ff3b3e 100644 --- a/src/static_src/scss/_burger.scss +++ b/src/static_src/scss/_burger.scss @@ -123,8 +123,15 @@ pointer-events: auto; } -// No explicit bud_panel / kit_dialog hide rules needed — burger's z=314 -// sits BELOW kit_btn / bud_btn (318), bud_panel (317), + kit_dialog (316), -// so all those naturally cover the burger when they overlap. Earlier -// iterations explicitly faded the burger via opacity; rendered redundant -// by the z-index drop. +// 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; + } +} diff --git a/src/templates/apps/billboard/_partials/_bud_add_panel.html b/src/templates/apps/billboard/_partials/_bud_add_panel.html index 93d673e..d05fc8e 100644 --- a/src/templates/apps/billboard/_partials/_bud_add_panel.html +++ b/src/templates/apps/billboard/_partials/_bud_add_panel.html @@ -1,30 +1,8 @@ -{% load static %} -{# ─────────────────────────────────────────────────────────────────────── #} -{# _bud_add_panel.html — bottom-left handshake btn + slide-out add-bud #} -{# field. Skeleton (open/close/POST) owned by bud-btn.js; this partial #} -{# wires the add-bud success callback: {bud} → append .bud-entry to #} -{# #id_buds_list. #} -{# Included by my_buds.html only. #} -{# #} -{# No autocomplete — the bud-autocomplete pool is request.user.buds, #} -{# which is precisely the set you can't usefully re-add. Post-share + #} -{# gatekeeper-invite panels DO bind autocomplete. #} -{# ─────────────────────────────────────────────────────────────────────── #} - - - -
- - -
- - +{# _bud_add_panel.html — handshake btn + slide-out add-bud field on #} +{# my_buds.html. No autocomplete — the pool is request.user.buds, which #} +{# is precisely the set you can't usefully re-add. On success, appends #} +{# a .bud-entry to #id_buds_list. #} +{% include "apps/billboard/_partials/_bud_apparatus.html" with aria_label="Add a bud" include_suggestions=False %} block carrying the per-use-case #} +{# submitUrl + onSuccess + (optional) duplicateTargetSelector. The shell #} +{# loads bud-btn.js + autocomplete (when `include_suggestions` is true) #} +{# so the caller can call bindBudBtn() inline. #} +{# #} +{# Context vars: #} +{# 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 #id_bud_suggestions + autocompete #} +{# script (false only for my_buds, where the #} +{# autocomplete pool == request.user.buds, which #} +{# is precisely what you can't usefully re-add) #} +{# ─────────────────────────────────────────────────────────────────────── #} + + + +
+ + +
+ +{% if include_suggestions %} + + +{% endif %} + + diff --git a/src/templates/apps/billboard/_partials/_bud_invite_panel.html b/src/templates/apps/billboard/_partials/_bud_invite_panel.html index 6046800..4e6621c 100644 --- a/src/templates/apps/billboard/_partials/_bud_invite_panel.html +++ b/src/templates/apps/billboard/_partials/_bud_invite_panel.html @@ -1,33 +1,8 @@ -{% load static %} -{# ─────────────────────────────────────────────────────────────────────── #} -{# _bud_invite_panel.html — bud btn + slide-out for the gatekeeper game- #} -{# invite flow. Skeleton (open/close/POST + autocomplete) owned by #} -{# bud-btn.js; this partial wires the invite success callback: server #} -{# returns {brief, recipient_display} — no line_text (no Post to append a #} -{# Line to). JS just shows the Brief banner. #} -{# #} -{# Caller must pass `room` in context. #} -{# ─────────────────────────────────────────────────────────────────────── #} - - - -
- - -
- -{# Autocomplete suggestions — sibling because the panel has overflow:hidden #} -{# for the slide-in scaleX animation. Pulls from request.user.buds. #} - - - - +{# _bud_invite_panel.html — bud btn + slide-out for the gatekeeper #} +{# game-invite flow on room.html. Server returns {brief, #} +{# recipient_display} — no line_text (no Post to append a Line to). JS #} +{# just shows the Brief banner. Caller must pass `room` in context. #} +{% include "apps/billboard/_partials/_bud_apparatus.html" with aria_label="Invite a friend" include_suggestions=True %} - +{# _bud_panel.html — bud btn + slide-out for the post-share flow on #} +{# post.html. On success: appends a Line to #id_post_table, fires #} +{# Brief.showBanner, + updates the .post-header shared-with prose. #} +{# `sharer_name` (rendered onto data-sharer-name) is the author of the #} +{# optimistically-appended Line so it matches the persisted post-refresh #} +{# state where Line.author == request.user. #} +{% include "apps/billboard/_partials/_bud_apparatus.html" with aria_label="Share with a bud" sharer_name=request.user|at_handle include_suggestions=True %} - +{% include "apps/billboard/_partials/_bud_apparatus.html" with aria_label="Invite a friend" include_suggestions=True %} + {% endblock scripts %} diff --git a/src/templates/apps/gameboard/room.html b/src/templates/apps/gameboard/room.html index ef92801..8d8d68f 100644 --- a/src/templates/apps/gameboard/room.html +++ b/src/templates/apps/gameboard/room.html @@ -119,7 +119,7 @@ {% endif %} {% include "apps/gameboard/_partials/_room_gear.html" %} - {% include "apps/gameboard/_partials/_room_burger.html" %} + {% include "apps/gameboard/_partials/_burger.html" %} {% endblock content %}