Skip to content

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.

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.

  • No change to ShopCategories, MetaState, or any def: Dictionary field — 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 NeonBackdrop component 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.

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.

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.566 positions the vertical center as a fraction of viewport height below the header; CENTER_X is always viewport_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 == 2 branch).
  • 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: past DRAG_COMMIT_FRACTION = 0.22 of 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_idx meta 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 _sel grid 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_btn press): 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.

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.

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.

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).

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.