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>
387 lines
12 KiB
SCSS
387 lines
12 KiB
SCSS
#id_kit_btn {
|
|
position: fixed;
|
|
bottom: 0.5rem;
|
|
right: 0.5rem;
|
|
|
|
// In landscape, kit btn relocates to the TOP of the right sidebar
|
|
// — anchored at right: 0.5rem (same as portrait), so the gear-to-the-
|
|
// LEFT-of-kit gap (in _applets.scss) reads as 0.7rem edge-to-edge,
|
|
// matching portrait's vertical gear-above-kit gap. Sidebar-centring
|
|
// via calc((--sidebar-w - 3rem) / 2) = 1rem would tighten that gap
|
|
// to 0.2rem, which reads visually wider than portrait.
|
|
@media (orientation: landscape) {
|
|
right: 0.5rem;
|
|
top: 0.5rem;
|
|
bottom: auto;
|
|
}
|
|
|
|
z-index: 318;
|
|
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);
|
|
|
|
&.active {
|
|
color: rgba(var(--quaUser), 1);
|
|
border-color: rgba(var(--quaUser), 1);
|
|
}
|
|
}
|
|
|
|
#id_kit_bag_dialog {
|
|
// Override dialog's native display:none so we can drive visibility via max-height
|
|
display: block !important;
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
width: 100%;
|
|
max-width: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
border: none;
|
|
border-top: 0.1rem solid rgba(var(--quaUser), 1);
|
|
background: rgba(var(--priUser), 0.97);
|
|
// Below kit_btn + burger_btn (both z-318) so those btns stay visible
|
|
// when the dialog opens — user can re-click kit_btn to close + the
|
|
// burger fan stays accessible alongside an open dialog.
|
|
z-index: 316;
|
|
overflow: hidden;
|
|
|
|
// Closed state — portrait: grow from below the footer (max-height)
|
|
max-height: 0;
|
|
visibility: hidden;
|
|
transition: max-height 0.25s ease-out, visibility 0s 0.25s;
|
|
|
|
&[open] {
|
|
max-height: 5rem;
|
|
visibility: visible;
|
|
transition: max-height 0.25s ease-out, visibility 0s;
|
|
display: flex !important;
|
|
flex-direction: row;
|
|
gap: 1.5rem;
|
|
align-items: center;
|
|
padding: 0.4rem 1rem;
|
|
}
|
|
|
|
// In landscape, the dialog slides in from off-viewport to the RIGHT,
|
|
// landing ATOP the right sidebar (covers kit + gear + burger + bud
|
|
// when open). Full viewport height. Width matches sidebar-w so the
|
|
// closed→open width animation reads like the portrait grow-UPWARD-
|
|
// from-bottom rotated 90° — closed has zero width, open spans the
|
|
// sidebar column from right edge inward.
|
|
@media (orientation: landscape) {
|
|
top: 0;
|
|
bottom: 0;
|
|
right: 0;
|
|
left: auto;
|
|
width: var(--sidebar-w);
|
|
height: auto;
|
|
|
|
// Opaque in landscape — the dialog covers the sidebar btns + the
|
|
// burger + bud area when open; transparency would let those btns
|
|
// bleed through the dialog content.
|
|
background: rgba(var(--priUser), 0.97);
|
|
|
|
// Closed state — width animates from 0 (grows leftward from right)
|
|
max-width: 0;
|
|
max-height: none;
|
|
transition: max-width 0.25s ease-out, visibility 0s 0.25s;
|
|
|
|
// Top-edge accent → left-edge accent (border on the aperture-facing side)
|
|
border-top: none;
|
|
border-left: 0.1rem solid rgba(var(--quaUser), 1);
|
|
|
|
&[open] {
|
|
max-width: var(--sidebar-w);
|
|
max-height: none;
|
|
transition: max-width 0.25s ease-out, visibility 0s;
|
|
display: flex !important;
|
|
// DOM order is Deck→Dice→Trinket→Tokens; column-reverse paints
|
|
// them visually bottom→top so Deck rests at the bottom of the
|
|
// bar and Tokens at the top.
|
|
flex-direction: column-reverse;
|
|
gap: 1rem;
|
|
align-items: center;
|
|
padding: 1rem 0.25rem;
|
|
}
|
|
}
|
|
}
|
|
|
|
.kit-bag-section {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
flex-shrink: 0;
|
|
|
|
// Landscape — stack icon row ABOVE label. DOM order is label → row;
|
|
// column-reverse paints row at top + label at bottom (icon above title).
|
|
@media (orientation: landscape) {
|
|
flex-direction: column-reverse;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
}
|
|
}
|
|
|
|
.kit-bag-label {
|
|
font-size: 0.6rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
text-decoration: underline;
|
|
letter-spacing: 0.2em;
|
|
color: rgba(var(--secUser), 0.5);
|
|
writing-mode: vertical-rl;
|
|
text-orientation: mixed;
|
|
transform: rotate(180deg) scaleX(1.3);
|
|
padding: 0 0.25rem 0 0.5rem;
|
|
|
|
// Landscape — undo vertical writing-mode + rotation; label reads
|
|
// horizontally beneath its icon in the column-reverse section.
|
|
@media (orientation: landscape) {
|
|
writing-mode: horizontal-tb;
|
|
text-orientation: mixed;
|
|
transform: none;
|
|
padding: 0;
|
|
font-size: 0.5rem;
|
|
text-align: center;
|
|
}
|
|
}
|
|
|
|
.kit-bag-row {
|
|
display: flex;
|
|
flex-direction: row;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
#id_kit_bag_dialog {
|
|
.token {
|
|
font-size: 1.5rem;
|
|
cursor: pointer;
|
|
transition: filter 0.15s;
|
|
padding: 0 0.125rem;
|
|
|
|
&:hover .token-tooltip,
|
|
&:hover .tt { display: none; } // JS positions these as fixed
|
|
}
|
|
|
|
.token-tooltip,
|
|
.tt {
|
|
z-index: 9999;
|
|
|
|
@extend %tt-token-fields;
|
|
|
|
// Buttons positioned on left edge of the fixed inline tooltip
|
|
.tt-equip-btns {
|
|
position: absolute;
|
|
left: -1rem;
|
|
top: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1.25rem;
|
|
z-index: 1;
|
|
|
|
.btn { margin: 0; }
|
|
}
|
|
}
|
|
|
|
.kit-bag-deck {
|
|
font-size: 1.5rem;
|
|
cursor: pointer;
|
|
padding: 0 0.125rem;
|
|
color: rgba(var(--terUser), 1);
|
|
}
|
|
|
|
.kit-bag-placeholder {
|
|
font-size: 1.5rem;
|
|
padding: 0 0.125rem;
|
|
color: rgba(var(--quaUser), 0.3);
|
|
}
|
|
}
|
|
|
|
.kit-bag-section--tokens {
|
|
flex: 1;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.kit-bag-row--scroll {
|
|
overflow-x: auto;
|
|
flex-wrap: nowrap;
|
|
scrollbar-width: none;
|
|
&::-webkit-scrollbar { display: none; }
|
|
|
|
// Landscape — the dialog runs vertically, so the Tokens row also
|
|
// flips to a vertical column + scrolls along the y-axis instead.
|
|
@media (orientation: landscape) {
|
|
flex-direction: column;
|
|
overflow-x: visible;
|
|
overflow-y: auto;
|
|
}
|
|
}
|
|
|
|
.kit-bag-empty {
|
|
font-size: 0.7rem;
|
|
color: rgba(var(--secUser), 0.4);
|
|
}
|
|
|
|
// ── Game Kit page ────────────────────────────────────────────────────────────
|
|
|
|
#id_game_kit_applets_container {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
#id_game_kit_applets_container section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
h2 { flex-shrink: 0; }
|
|
.gk-items { flex: 1; overflow-y: auto; }
|
|
}
|
|
|
|
.gk-items {
|
|
display: flex;
|
|
flex-direction: row;
|
|
flex-wrap: wrap;
|
|
gap: 1rem;
|
|
align-items: center;
|
|
}
|
|
|
|
// `.gk-deck-card` + `.gk-trinket-card` + `.gk-token-card` dropped from this
|
|
// multi-class rule — the gk-decks/trinkets/tokens sections all render as
|
|
// bare `.token` icons (FA glyph or SVG card-stack) sized by their own rules
|
|
// in `_gameboard.scss`; no border + no padding-shell since the hover-portal
|
|
// tooltip carries the name/count/description. Pronoun cards still use the
|
|
// bordered card-shell shape via this rule.
|
|
//
|
|
// Icon-row treatment for Trinkets / Tokens / Card Decks — the small icons
|
|
// look lonely huddled on the left edge of the wider applet rectangle, so
|
|
// center them along the main axis + bump the gap. Scoped to the 3 specific
|
|
// section IDs so it doesn't bleed to Pronouns (whose cards fill the row +
|
|
// look fine left-anchored).
|
|
#id_gk_trinkets .gk-items,
|
|
#id_gk_tokens .gk-items,
|
|
#id_gk_decks .gk-items {
|
|
justify-content: center;
|
|
gap: 2rem;
|
|
}
|
|
|
|
// `#id_game_kit` wraps every section in `_game_kit_sections.html` so
|
|
// `gameboard.js`'s `getElementById('id_game_kit')` finds ONE scope covering
|
|
// trinkets + tokens + decks. But that wrapper sits between `#id_gk_sections
|
|
// _container` (the CSS-grid parent — see `_applets.scss:268`) and its
|
|
// `<section>` children, breaking the `grid-column: span var(--applet-cols)`
|
|
// layout that needs the sections as DIRECT children. `display: contents`
|
|
// makes the wrapper transparent to layout — sections still resolve as grid
|
|
// children of `#id_gk_sections_container` — while keeping the wrapper a
|
|
// real DOM element JS can query + read data-attrs off.
|
|
#id_gk_sections_container > #id_game_kit { display: contents; }
|
|
|
|
// My Wallet applet (`_applet-wallet.html` on /dashboard/) uses the same
|
|
// `#id_game_kit` wrapper so `gameboard.js` picks up its `.token` icons +
|
|
// fires the hover-portal tooltips. Lay items out as a centered flex row
|
|
// (parity w. the standalone game_kit.html sections — see `#id_gk_trinkets
|
|
// .gk-items, #id_gk_tokens .gk-items, #id_gk_decks .gk-items` above).
|
|
// `height: 100%` keeps the row vertically centered inside the applet box
|
|
// since the section's body has the h2 absolute-positioned + claims full
|
|
// box height.
|
|
#id_applet_wallet > #id_game_kit.wallet-items {
|
|
display: flex;
|
|
flex-direction: row;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 2rem;
|
|
height: 100%;
|
|
}
|
|
|
|
// `.token` is a flex child of `.gk-items` — make it a positioning context
|
|
// so the `.shop-badge` (position: absolute, top: -0.8rem, right: -1.2rem)
|
|
// anchors off the icon, NOT off some grandparent. Also pin `font-size:
|
|
// 1.5rem` so the standalone game_kit.html icons match the gameboard's
|
|
// Game Kit applet sizing — `_gameboard.scss:79` sets this for `#id_applet
|
|
// _game_kit #id_game_kit .token` (only fires on /gameboard/ since `#id_
|
|
// applet_game_kit` doesn't exist on this page); we re-state at the same
|
|
// 1.5rem so the visual weight is identical across the two surfaces.
|
|
#id_game_kit .token {
|
|
position: relative;
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.gk-pronoun-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
padding: 0.75rem 1rem;
|
|
border-radius: 0.5rem;
|
|
border: 0.1rem solid rgba(var(--secUser), 0.3);
|
|
cursor: pointer;
|
|
font-size: 1.5rem;
|
|
min-width: 6rem;
|
|
text-align: center;
|
|
transition: border-color 0.15s;
|
|
|
|
span { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.08em; }
|
|
small { font-size: 0.6rem; opacity: 0.5; }
|
|
|
|
&:hover { border-color: rgba(var(--secUser), 0.8); }
|
|
}
|
|
|
|
.gk-pronoun-card {
|
|
// Card shows the ideology slug (pluralism, bawlmorese, …) in italic;
|
|
// the guard portal previews the actual slash trio above OK|NVM via JS.
|
|
// Active card is filled with the secondary tint so the user can see at a
|
|
// glance which preference is currently in effect.
|
|
.gk-pronoun-label {
|
|
font-size: 0.85rem;
|
|
letter-spacing: 0.05em;
|
|
text-transform: lowercase;
|
|
font-style: italic;
|
|
}
|
|
&.active {
|
|
border-color: rgba(var(--secUser), 1);
|
|
background: rgba(var(--secUser), 0.18);
|
|
}
|
|
}
|
|
|
|
#id_guard_portal .guard-pronoun-trio {
|
|
display: block;
|
|
margin-top: 0.25rem;
|
|
font-size: 0.75rem;
|
|
letter-spacing: 0.06em;
|
|
opacity: 0.75;
|
|
text-align: center;
|
|
}
|
|
|
|
.gk-placeholder {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
padding: 0.75rem 1rem;
|
|
border-radius: 0.5rem;
|
|
border: 0.1rem dashed rgba(var(--secUser), 0.2);
|
|
font-size: 1.5rem;
|
|
min-width: 6rem;
|
|
text-align: center;
|
|
opacity: 0.4;
|
|
|
|
span { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.08em; }
|
|
}
|
|
|
|
.gk-empty {
|
|
font-size: 0.8rem;
|
|
opacity: 0.45;
|
|
}
|
|
|