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 set — Scrap + Level Up always present when Actions on; middle swaps Equip ↔ Unequip.
- 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)¶
- Card VLG
childControlHeight=true— reads each section's preferred height (readingsizeDeltawithchildControlHeight=falseis stale and mis-computes; the Figma importer defaults to false). - Fixed sections (Header, Stats, Actions) carry
LayoutElement.preferredHeight; Passives reports its own VLG preferred (noContentSizeFitteron it — the parent controls it). - Background + outline stretch to the full card rect (remove the current
-70bottom inset) so they always match the CSF-driven height — otherwise a shrunk card shows a bare strip. - Inter-section gaps live in the card VLG
spacing/padding, not inside sections, so hiding a section removes its gap too. - Section toggling =
SetActiveon 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.