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>
This commit is contained in:
Disco DeDisco
2026-05-26 22:23:03 -04:00
parent 6809681e5a
commit 894d65fd6b
6 changed files with 194 additions and 12 deletions

View File

@@ -119,10 +119,51 @@
// 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)));
opacity: 1;
pointer-events: auto;
}
// 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 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