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;
|
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;
|
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;
|
|
|
|
|
}
|
2026-05-26 21:39:36 -04:00
|
|
|
|
2026-05-26 22:23:03 -04:00
|
|
|
// Active sub-btn = fully visible; inactive (default) = 0.6 opacity hint.
|
bud landing page: /billboard/buds/<id>/ + my_buds tooltip portal + @mailman post-attribution anchor — TDD
Replaces the @mailman invite Line's inline OK/BYE block w. a dedicated per-bud surface. Three new FTs (test_bill_bud_page, test_bill_my_buds_tooltip, test_bill_mailman_invite_post — landed red 2026-05-27 PM) drive: per-bud landing page rendering 4-btn apparatus + shoptalk textarea + invite-cascade glow handoff; my-buds row tooltip portal w. .tt-title/.tt-description/.tt-email/.tt-shoptalk/.tt-milestone slots; mailman Brief surfacing on any authenticated page-load via context processor + base.html JSON-script.
Models: new `BudshipNote(user, bud, shoptalk[CharField max=160], edited_at)` w. unique_together — per-relation personal note about a bud, never visible to the bud. Lazy-created on first shoptalk save so absence of a row reads as 'never edited' (drives .tt-milestone slot presence).
URLs (billboard): `buds/<uuid:bud_id>/` (bud_page), `buds/<uuid:bud_id>/shoptalk` (save_bud_shoptalk), `buds/<uuid:bud_id>/delete` (delete_bud).
Views: bud_page auto-adds the bud on first visit (mirrors share_post implicit-add); resolves `pending_invite` as non-expired PENDING SeaInvite(owner=bud, invitee=request.user) → drives `sea_btn_active` + `sea_first_draw_pending` flags that _burger.html already reads on my_sea + room. my_buds enriches each bud w. `.shoptalk_text` + `.milestone_dt` so the row template can render data-tt-* attrs without an extra template tag.
mail.py: INVITE_TEMPLATE now interpolates `owner_id` into an `<a class="post-attribution" href="/billboard/buds/{owner_id}/">{handle}</a>` wrapper around the owner's handle. post.html's existing safe-filter branch (gated on author username == 'mailman') passes it through unescaped. Removed the {% if line.sea_invite %} include path — _invite_actions.html left in place for archival.
Templates: new bud.html (header + shoptalk form + apparatus + gear + burger fan + sea_btn nav inline JS); new _bud_gear.html (NVM→my_buds, DEL→guard portal "Delete this bud?" → POST delete_bud); new _bud_tooltip.html (portal w. .tt-* slots); _my_buds_item.html wraps `@handle` in an anchor to bud_page + carries data-tt-* attrs + " the {{ active_title_display }}"; my_buds.html includes the tooltip portal + loads my-buds-tooltip.js.
JS: new my-buds-tooltip.js binds row clicks → .row-locked + populates #id_tooltip_portal from data-tt-* attrs; anchor clicks pass through to navigate; .tt-milestone is removed from DOM (not just emptied) when never-edited so the FT can distinguish absent vs cleared-after-edit.
SCSS: extend landscape gear-btn rule + #id_*_menu rule w. `.bud-page` + `#id_bud_menu` (otherwise gear-btn collided w. bud-btn in landscape on bud.html). Bump active burger sub-btn z-index to 1 so click hit-test picks the active sub-btn during the 0.25s fan arc-out animation (otherwise a later-in-DOM inactive btn obscured the active target during transition).
Cross-page Brief surface: new `mail_brief_payload` context processor injects the user's oldest unread MAIL_ACCEPTANCE Brief into every authenticated response; base.html renders the JSON-script + auto-fires Brief.showBanner. Mark-read still rides view_post's existing GET unread-flip — no new endpoint.
Pre-existing MySeaInvitePostRenderTest (test_sea_invite_views.py) inverted to match the new contract: the .invite-actions sweep is unconditional (PENDING / ACCEPTED / DECLINED all carry prose only); pinned the post-attribution anchor + bud-page href in its place.
1518 ITs green (1475 app ITs + 43 sprint ITs), 23 sprint FTs green (5 my_buds tooltip + 13 bud page + 5 mailman invite post). Jasmine specs from sprint plan deferred — FT coverage of burger-glow / row-lock / portal-populate paths suffices and the textarea blur-POST flow isn't implemented in this sprint (form is server-action only, save-on-blur AJAX is a follow-on).
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 11:45:20 -04:00
|
|
|
// `z-index: 1` raises the active sub-btn above its inactive siblings so
|
|
|
|
|
// click hit-testing picks the active one even while the fan is mid-
|
|
|
|
|
// transition (during the 0.25s arc-out animation all 5 sub-btns share
|
|
|
|
|
// the fan-centre origin, and a later-in-DOM inactive btn would otherwise
|
|
|
|
|
// obscure the active target). Active conditions are wired one-by-one in
|
|
|
|
|
// later sprints.
|
2026-05-26 22:23:03 -04:00
|
|
|
#id_burger_btn.active ~ #id_burger_fan .burger-fan-btn.active {
|
|
|
|
|
opacity: 1;
|
bud landing page: /billboard/buds/<id>/ + my_buds tooltip portal + @mailman post-attribution anchor — TDD
Replaces the @mailman invite Line's inline OK/BYE block w. a dedicated per-bud surface. Three new FTs (test_bill_bud_page, test_bill_my_buds_tooltip, test_bill_mailman_invite_post — landed red 2026-05-27 PM) drive: per-bud landing page rendering 4-btn apparatus + shoptalk textarea + invite-cascade glow handoff; my-buds row tooltip portal w. .tt-title/.tt-description/.tt-email/.tt-shoptalk/.tt-milestone slots; mailman Brief surfacing on any authenticated page-load via context processor + base.html JSON-script.
Models: new `BudshipNote(user, bud, shoptalk[CharField max=160], edited_at)` w. unique_together — per-relation personal note about a bud, never visible to the bud. Lazy-created on first shoptalk save so absence of a row reads as 'never edited' (drives .tt-milestone slot presence).
URLs (billboard): `buds/<uuid:bud_id>/` (bud_page), `buds/<uuid:bud_id>/shoptalk` (save_bud_shoptalk), `buds/<uuid:bud_id>/delete` (delete_bud).
Views: bud_page auto-adds the bud on first visit (mirrors share_post implicit-add); resolves `pending_invite` as non-expired PENDING SeaInvite(owner=bud, invitee=request.user) → drives `sea_btn_active` + `sea_first_draw_pending` flags that _burger.html already reads on my_sea + room. my_buds enriches each bud w. `.shoptalk_text` + `.milestone_dt` so the row template can render data-tt-* attrs without an extra template tag.
mail.py: INVITE_TEMPLATE now interpolates `owner_id` into an `<a class="post-attribution" href="/billboard/buds/{owner_id}/">{handle}</a>` wrapper around the owner's handle. post.html's existing safe-filter branch (gated on author username == 'mailman') passes it through unescaped. Removed the {% if line.sea_invite %} include path — _invite_actions.html left in place for archival.
Templates: new bud.html (header + shoptalk form + apparatus + gear + burger fan + sea_btn nav inline JS); new _bud_gear.html (NVM→my_buds, DEL→guard portal "Delete this bud?" → POST delete_bud); new _bud_tooltip.html (portal w. .tt-* slots); _my_buds_item.html wraps `@handle` in an anchor to bud_page + carries data-tt-* attrs + " the {{ active_title_display }}"; my_buds.html includes the tooltip portal + loads my-buds-tooltip.js.
JS: new my-buds-tooltip.js binds row clicks → .row-locked + populates #id_tooltip_portal from data-tt-* attrs; anchor clicks pass through to navigate; .tt-milestone is removed from DOM (not just emptied) when never-edited so the FT can distinguish absent vs cleared-after-edit.
SCSS: extend landscape gear-btn rule + #id_*_menu rule w. `.bud-page` + `#id_bud_menu` (otherwise gear-btn collided w. bud-btn in landscape on bud.html). Bump active burger sub-btn z-index to 1 so click hit-test picks the active sub-btn during the 0.25s fan arc-out animation (otherwise a later-in-DOM inactive btn obscured the active target during transition).
Cross-page Brief surface: new `mail_brief_payload` context processor injects the user's oldest unread MAIL_ACCEPTANCE Brief into every authenticated response; base.html renders the JSON-script + auto-fires Brief.showBanner. Mark-read still rides view_post's existing GET unread-flip — no new endpoint.
Pre-existing MySeaInvitePostRenderTest (test_sea_invite_views.py) inverted to match the new contract: the .invite-actions sweep is unconditional (PENDING / ACCEPTED / DECLINED all carry prose only); pinned the post-attribution anchor + bud-page href in its place.
1518 ITs green (1475 app ITs + 43 sprint ITs), 23 sprint FTs green (5 my_buds tooltip + 13 bud page + 5 mailman invite post). Jasmine specs from sprint plan deferred — FT coverage of burger-glow / row-lock / portal-populate paths suffices and the textarea blur-POST flow isn't implemented in this sprint (form is server-action only, save-on-blur AJAX is a follow-on).
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 11:45:20 -04:00
|
|
|
z-index: 1;
|
2026-05-26 22:23:03 -04:00
|
|
|
}
|
|
|
|
|
#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; }
|
|
|
|
|
}
|
|
|
|
|
|
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 voice: voice-btn glow/pulse state machine (sea-precedence, pulse-while-alone, 2x on 2nd party) — TDD
Phase 3 of the my-sea voice batch (user-spec 2026-05-29). A --quaUser/--ninUser
glow + pulse machine for the burger btn + its voice sub-btn, driven by the
voice sub-btn availability (voice_active) + the live mesh state.
- voice-mesh.js: VoiceRoom gains a state-change hook — setOnStateChange(cb) +
peerCount() + _notify({inCall, peerCount, muted}), fired on join, every peer
add/drop, mute toggle, and teardown. No behaviour change without a subscriber
(VoiceMeshSpec stays green).
- voice-glow.js (new): the glow machine. PRE-JOIN nudge — burger glows when the
fan is closed (sea draw-nudge keeps burger precedence; voice reclaims it once
the sea glow clears), voice sub-btn glows when the fan opens. LIVE — the glow
PULSES on whichever surface shows (voice sub-btn fan-open, burger fan-closed):
base 2s cadence while alone, doubled (.voice-pulse--fast) once a 2nd party
connects (equalizer stand-in; a true volume-reactive equalizer is a live-only
enhancement). Class writes are reconciled (idempotent) so the burger-class
MutationObserver doesn't feed back on itself.
- _burger.scss: .voice-glow + @keyframes voice-pulse + .voice-pulse(--fast).
- loaded on my_sea.html + my_sea_visit.html (after burger-btn.js).
- VoiceGlowSpec.js (18 specs) + registered in SpecRunner; MySeaSeatsSpec flare
window updated 1.5s → 2s (Phase 2 bump). 428 Jasmine specs green.
Live-verify on staging: the actual glow colours/cadence + the equalizer
upgrade (item 5) and disconnect states (item 7) land in later phases.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 21:06:02 -04:00
|
|
|
// ── Voice affordance glow + pulse (Phase 3, my-sea voice) ─────────────
|
|
|
|
|
//
|
|
|
|
|
// Distinct from the sea-btn's --priYl `.glow-handoff` draw nudge: voice uses
|
|
|
|
|
// a --quaUser tint + --ninUser halo. voice-glow.js owns the class transitions
|
|
|
|
|
// (sea takes burger precedence; the voice glow surfaces on the voice sub-btn
|
|
|
|
|
// when the fan is open, or on the burger once the sea nudge clears). Once the
|
|
|
|
|
// mic is live the glow PULSES — base cadence while alone, doubled once a 2nd
|
|
|
|
|
// party connects (equalizer stand-in).
|
|
|
|
|
#id_voice_btn.voice-glow,
|
|
|
|
|
#id_burger_btn.voice-glow {
|
|
|
|
|
color: rgba(var(--quaUser), 1);
|
|
|
|
|
border-color: rgba(var(--quaUser), 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes voice-pulse {
|
|
|
|
|
0%, 100% {
|
|
|
|
|
box-shadow:
|
|
|
|
|
0 0 0.35rem 0.05rem rgba(var(--ninUser), 0.35),
|
|
|
|
|
0 0 0.7rem 0.1rem rgba(var(--ninUser), 0.2);
|
|
|
|
|
}
|
|
|
|
|
50% {
|
|
|
|
|
box-shadow:
|
|
|
|
|
0 0 0.7rem 0.2rem rgba(var(--ninUser), 0.9),
|
|
|
|
|
0 0 1.5rem 0.5rem rgba(var(--ninUser), 0.45);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Base cadence — one full ease in+out per 2s (1s each way) while the visiting
|
|
|
|
|
// user is alone in the voice room.
|
|
|
|
|
#id_voice_btn.voice-pulse,
|
|
|
|
|
#id_burger_btn.voice-pulse {
|
|
|
|
|
animation: voice-pulse 2s ease-in-out infinite;
|
|
|
|
|
}
|
|
|
|
|
// Doubled cadence once a 2nd party is connected.
|
|
|
|
|
#id_voice_btn.voice-pulse--fast,
|
|
|
|
|
#id_burger_btn.voice-pulse--fast {
|
|
|
|
|
animation: voice-pulse 1s ease-in-out infinite;
|
|
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|