Shop carousel + Hologram Corner Brackets — design
Shop carousel + Hologram Corner Brackets — design
Section titled “Shop carousel + Hologram Corner Brackets — design”Replace the shop panel’s grid-based card layout and 2D navigation with a centered,
3-visible-cards-at-a-time carousel, styled as Hologram Corner Brackets (no fill, corner
brackets only, the shared NeonBackdrop visible straight through every card). This
applies to every view the panel renders: root categories, Drones sub-categories, and
every upgrade list.
Context
Section titled “Context”This follows a same-session exploration arc: an earlier “glassy ambient” restyle shipped
and was later reverted (translucency-over-near-black didn’t read as intended on a real
device); a Gemini-generated glass-texture card material was tried and also reverted (a
visible seam between the texture’s own edge and the procedural border, plus reduced
icon/text contrast). Chris then asked for a broad survey of procedural card styles
(tools/shop_lab/, 8 styles spanning StyleBoxFlat-only, shader gradients, real
SCREEN_TEXTURE blur, neumorphic, flat-minimal, hologram brackets, procedural grain, and
animated shimmer) and picked Hologram Corner Brackets — thin corner brackets, no
fill, so the backdrop shows straight through — combined with a carousel layout
(always-centered, 3 cards visible) that was prototyped and iterated live on his phone
across several rounds: bigger center card, live-follow drag animation (replacing an
initial discrete snap-on-release), and a centering bug fix (the lab’s CENTER_X/CENTER_Y
were hardcoded against a desktop test window and were measurably off-center on the real
iPhone’s different aspect ratio).
This design ports that validated lab prototype (tools/shop_lab/shop_lab.gd) into the
real, shipped ui/meta_shop_panel.gd — reusing its real content/data model instead of
the lab’s fake categories/upgrades, and reconciling with the real panel’s existing
tvOS/gamepad navigation, close-button handling, and test suite.
Non-goals
Section titled “Non-goals”- No change to
ShopCategories,MetaState, or anydef: Dictionaryfield — the carousel is a rendering/navigation change only, not a data-model change. - No change to
_close_btn/_go_back()— back/close handling is already independent of the grid and needs no modification. - No change to the shared
NeonBackdropcomponent or the start menu. - Not reintroducing the reverted “glassy ambient” translucency or the Gemini glass texture — Hologram Corner Brackets has no fill at all, so those approaches don’t apply here.
- Up/down gamepad navigation is dropped, not repurposed — see Navigation below.
Design
Section titled “Design”1. Visual style — Hologram Corner Brackets
Section titled “1. Visual style — Hologram Corner Brackets”Every card (root tile, Drones sub-category tile, upgrade row) becomes: no background
fill, no border-as-a-full-rectangle — just four L-shaped corner brackets (per-card accent
color) drawn via a custom _draw(), plus a very faint (alpha ~0.04) accent-tinted full-
card wash so the card’s clickable area still reads as a coherent shape. This is a direct,
unmodified port of tools/shop_lab/shop_lab.gd’s _style_hologram/_HologramBrackets
inner class. _make_card’s current StyleBoxFlat-based construction is replaced with
this; _card_box() (still used by _close_btn) is untouched.
2. Carousel layout & sizing
Section titled “2. Carousel layout & sizing”Exactly one item is “centered” (full scale, full opacity) at a time; its immediate neighbors (previous/next in the current view’s item list) render smaller and dimmed on either side; anything beyond that is not rendered at all. Constants, carried over from the validated lab prototype:
CENTER_SCALE = 1.85,SIDE_SCALE = 0.62,SIDE_ALPHA = 0.4,SIDE_GAP = 26.0.- Centering is computed fresh at render time from
get_viewport().get_visible_rect().size(not a fixed design-space constant) — this is the fix for the “not quite center” bug found when testing on a real iPhone, whose aspect ratio differs from the lab’s desktop test window.CENTER_Y_FRAC = 0.566positions the vertical center as a fraction of viewport height below the header;CENTER_Xis alwaysviewport_width * 0.5. - Root tiles keep their square aspect (
ROOT_CARD_SIZE); Drones sub-category tiles and upgrade rows keep their wide aspect (LIST_CARD_MAX_W-capped width). The carousel positions whichever shape the current view already builds — it doesn’t change card shape, only how many are visible and where they sit. - Fewer-than-3-item views (root only ever has 2–4 categories; some upgrade lists — e.g.
Arsenal’s 1 upgrade + Back — have as few as 2 total items) fall back to showing only
what exists: 1 item shows only the center; 2 items show center + one side (no
duplicate-item-on-both-sides case, ported from the lab’s
elif n == 2branch).
3. Animation
Section titled “3. Animation”- Touch/mouse drag: live-follow — visible cards track the finger/pointer in real
time (
InputEventScreenDrag/InputEventMouseMotion), a 4th “incoming” card is built lazily once drag direction is clear and fades in proportional to drag progress. On release: pastDRAG_COMMIT_FRACTION = 0.22of one slot’s spacing, the drag completes (tweens the remaining distance,ANIM_DURATION = 0.3s,Tween.TRANS_CUBIC/EASE_OUT); short of that, it springs back to the pre-drag layout. - Gamepad/tvOS D-pad or Siri Remote left/right: triggers the same tween-based transition a completed drag produces (slide + cross-fade), just without a live-drag phase — this is the direct equivalent of tapping a side card, driven by a discrete input instead of a gesture.
- Tap a dimmed side card: jumps straight to it (animated, same transition).
- Tap/confirm the centered card: activates it (drills into a category, or triggers
the existing buy/equip logic for an upgrade card) — no animation of its own, this
reuses whatever
_activate(id)already does today. - Cards are matched across a transition by an
item_idxmeta tag (the def’s position in the current view’s item list), not by tracking which physical node plays which visual role — this is what let the lab’s commit/cancel logic stay simple regardless of exactly how far a drag got before release, and carries over unchanged.
4. Navigation model (touch, mouse, gamepad/tvOS)
Section titled “4. Navigation model (touch, mouse, gamepad/tvOS)”- Confirm (
ui_accept/JOY_BUTTON_A/tap-center): unchanged trigger; now activates whatever item is currently centered instead of whatever_selgrid index was focused. - Left/Right (
MenuNav.is_left(event)/is_right(event), already used elsewhere in this codebase for shared gamepad/Siri-Remote direction detection): steps the carousel one item, debounced the same way (NAV_DEBOUNCE_MS = 200,_last_nav_ms) as today’s grid nav. - Up/Down: dropped entirely, not repurposed. A carousel has no second row — the
_columns/row-clamp math this replaces is deleted, not left in as dead code. - Back (
ui_cancel/JOY_BUTTON_B/KEY_ESCAPE/_close_btnpress): completely unchanged —_go_back()and_close_btn’s toggle-label behavior sit outside the carousel already and need no modification. - Existing audio hook points (whatever the current grid-nav path calls on selection change / activation) are preserved at the equivalent carousel transition points — the implementer should grep the current file for the exact call sites rather than assume a name, since this spec doesn’t pin one down.
5. Data model — unchanged
Section titled “5. Data model — unchanged”Every view’s item list keeps coming from exactly where it does today:
ShopCategories.present(_defs) (root), ShopCategories.drone_subcategories_present(_defs)
(Drones), _current_upgrade_defs() (any upgrade list). Per-item content (icon, name,
count/cost/state string, accent = GOLD if afford else STEEL, interactive-only-if-
affordable-or-owned-decoy) is unchanged — only the container each item renders into
changes from a flat StyleBoxFlat card to a Hologram Corner Brackets card, and only the
positioning changes from a GridContainer cell to a carousel slot.
6. Removed grid infrastructure
Section titled “6. Removed grid infrastructure”Deleted as part of this change (superseded, not left dormant): _columns, _columns_for(),
_card_width(), LIST_CARD_MAX_W-driven column math, the GridContainer itself, and the
row/column _sel math in the current _input() override. NAV_GUTTER/NAV_RIGHT_W/the
124px default gutter become irrelevant once cards no longer need to reserve space for a
neighbor in the same row — carried-over content-building helpers (_set_card_text,
_set_root_card_content) keep their text/icon layout logic but stop taking a gutter
parameter tuned for a grid row.
Testing
Section titled “Testing”Kept unchanged (behavior, not layout, so they should pass without modification once
the carousel is wired to the same data calls): all buy-logic tests
(tests/test_meta_shop.gd), close-button tests (test_close_button_exists_and_is_not_a_grid_card,
test_close_button_label_reflects_root_vs_category,
test_close_button_press_steps_category_to_root_then_closes), Drones drill-down/back
tests (test_drones_category_shows_subcategory_tiles_not_45_upgrades,
test_drilling_into_a_drone_subcategory_shows_its_upgrades,
test_back_from_drone_subcategory_upgrades_returns_to_subcategory_tiles).
Removed (test a grid mechanism this design deletes): test_columns_scale_with_count,
test_columns_for_four_cards_is_one_full_row, test_columns_for_seven_avoids_a_lone_orphan_row,
test_root_view_uses_a_centered_2x2_grid_of_square_icon_tiles (superseded by a carousel-
specific centering test), test_non_root_views_shrink_center_the_grid_too,
test_sparse_category_cards_are_width_capped_not_stretched_huge,
test_nav_gutter_reclaims_width_for_long_category_names,
test_drones_subcategory_cards_use_the_nav_gutter_not_the_upgrade_gutter,
test_focused_card_draws_above_its_neighbours (z_index-on-focus was a grid-specific
affordance; the carousel’s own scale/alpha difference is the “what’s selected” cue),
test_category_view_card_width_fits_viewport. Also updates needed:
test_root_cards_have_a_category_icon (icon still exists, but the child-index it reads
will shift once the card’s internal child structure changes for the hologram style —
same category of fix as earlier get_children()[N] index updates this session),
test_card_fill_is_translucent_glass_not_opaque/test_card_box_still_keeps_its_neon_border_and_glow_shadow
(both assert properties of the old flat-neon _card_box-based fill, which no longer
applies to hologram cards — remove or replace with hologram-specific assertions),
test_shop_background_is_the_shared_neon_backdrop (kept — NeonBackdrop itself is
unaffected).
New tests: center/side scale+alpha values for a rendered carousel; item_idx
tagging present on every rendered card; a completed drag past
DRAG_COMMIT_FRACTION advances the centered item and a short drag springs back
(mirroring the lab’s own verified drag logic); MenuNav.is_left/is_right step the
carousel one item with debounce; wraparound at both ends for a 2-item and a 3+-item
list; tapping a dimmed side card jumps to it; tapping/confirming the centered card
activates it; up/down input is a no-op (doesn’t crash, doesn’t change _carousel_index).
Rollout
Section titled “Rollout”This ships through the same bh-dev-chunk/full-suite/determinism/boot-check discipline
as every other change to this file — it’s a render/UI-only change (no /sim files), so
the pinned determinism baseline should hold by construction. Given the panel is used
identically across iPhone, iPad, Apple TV, and Mac, and Chris explicitly flagged the lab
version as “a bit glitchy,” the plan should include a real-device verification pass
(devicectl install to the iPhone, matching this session’s established pattern) before
considering this done — not just a headless test pass.