Skip to content

Item Card — reusable multi-state design

Date: 2026-07-02 Prefab: Assets/Prefabs/UI/InventoryEquipped.prefab → the card node InventoryItemCard (reusable on its own; the compare screen enables/disables sections and stacks two instances).

Goal

One reusable item-card that expresses every state below by enabling/disabling sections and setting data — no runtime layout code. The card self-sizes vertically to whatever sections are active, ending flush after the last visible section (no reserved gaps).

States (complete — confirmed with user)

# Role Badge Stat deltas Passives Actions Middle button
1 Equipped (tapped) Equipped item (neutral) none 1 on Unequip
2 Equipped (compare ref) Equipped item none 0 off
3 Equipped (compare ref) Equipped item none 3 off
4 Candidate (better) Better! (green) ↑ green 3 on Equip
5 Candidate (worse) Worse (red) ↓ red 1 on Equip

Axes of variation - Badge — text + color: Equipped item / Better! / Worse. - Power + each stat — optional delta arrow: none / ↑ green / ↓ red. - Passives — 0…N rows (self-sizing list). - Actions — cost labels (+scrap / level cost) and the button row shown/hidden as one unit. - Button setScrap + Level Up always present when Actions on; middle swaps EquipUnequip. - Outline color — item rarity (data-driven), independent of state.

Header and Stats are always present. No hidden stats row; no variable button count beyond the middle swap.

Structure (Approach A — section stack)

InventoryItemCard      [VerticalLayoutGroup: ctrlH=true, ctrlW=false, spacing+padding] [ContentSizeFitter: vFit=Preferred]  pivot(0,1)
├─ img_cardbg          [LayoutElement ignoreLayout]  anchors 0→1, ZERO offsets → always fills the card
│   └─ img_cardoutline [stretch]                     rarity color (data)
├─ badge               [LayoutElement ignoreLayout]  pinned top-right   (text + color = state)
├─ Header   (always)   [LayoutElement preferredHeight]  icon · name · power(+Δ)
├─ Stats    (always)   [LayoutElement preferredHeight]  4 stat chips, each optional Δ arrow
├─ Passives (0…N)      [own VerticalLayoutGroup]        rows self-size; SetActive(false) at count 0
└─ Actions  (on/off)   [LayoutElement preferredHeight]  cost labels + buttons row
       └─ buttons: Scrap · [Equip | Unequip] · Level Up

Correctness rules (learned from the earlier adaptive pass)

  1. Card VLG childControlHeight=true — reads each section's preferred height (reading sizeDelta with childControlHeight=false is stale and mis-computes; the Figma importer defaults to false).
  2. Fixed sections (Header, Stats, Actions) carry LayoutElement.preferredHeight; Passives reports its own VLG preferred (no ContentSizeFitter on it — the parent controls it).
  3. Background + outline stretch to the full card rect (remove the current -70 bottom inset) so they always match the CSF-driven height — otherwise a shrunk card shows a bare strip.
  4. Inter-section gaps live in the card VLG spacing/padding, not inside sections, so hiding a section removes its gap too.
  5. Section toggling = SetActive on the section root (VLG skips inactive children → card CSF reshrinks on the next rebuild).

Target section heights (from the current authored geometry)

  • Header ≈ icon block (160) + top padding → tune to match panel_item (top −55, 160 tall).
  • Stats = 60 (layout_stats).
  • Passive row = 59, spacing 16.
  • Actions ≈ 251 (cost labels + buttons; matches the current BottomBlock).

Exact spacing/padding tuned during build to reproduce the mockup pixel positions, then verified per state via RectTransform.rect / GetWorldCorners.

Out of scope

Runtime controller wiring (populating data, choosing which sections to enable) — a later task; this spec covers only the prefab's layout so the controller only needs SetActive + set fields.