CollectibleCard
PURPOSE
Shared v4 ship card component used by the Ships screen and ShipSelectOverlay. Renders a rarity-frame card containing the real ship sprite, hull name, star row, XP bar, and a locked overlay. Mobile-first sizing — readable at 100px wide. Supports a compact variant for the bottom-sheet picker.
OWNS
CollectibleCardReact functional component (default per-file export, named).- Module-scope side effect that injects a
<style>tag with idcollectible-selected-pulse-keyframesintodocument.head, defining thecollectibleSelectedPulseCSS keyframes (white-border + outer-glow pulse for the selected state). - Inline-style derivation of: stacked card shadow with optional rarity glow, dual rarity gradient vs locked dark gradient background, locked-vs-selected-vs-default border treatments, hull-name 8-direction black stroke + drop shadow, sprite-area drop shadow + colored frame glow filter, sprite grayscale on lock, the “CURRENTLY SELECTED” blackout overlay.
- Width-of-current-tier XP percentage calculation via
xp / (xp + xpToNextStar(xp)), clamped to 100 whenstar >= 5.
READS FROM
useInventoryStore(@starship-survivors/stores/inventoryStore) —currentXp(hull)andcurrentStar(hull)selectors.getShipDef,RARITY_COLORS,displayHullNamefrom@starship-survivors/data/ships.xpToNextStarfrom@starship-survivors/data/ship-progression.getShipV4SpritePathfrom@starship-survivors/engine/rendering/ships-v4-loader.rarityGradientCss,RARITY_BADGE_LETTER,RARITY_BADGE_FILL,RARITY_ACCENT,RARITY_EFFECT_TIER,CARD_SHADOW_CSS,CARD_BADGE, and theRaritytype from@starship-survivors/engine/rendering/card-theme.- Sibling components
RarityBadgeandTrimmedShipImage. HullStarBarfrom@starship-survivors/components/HullStarBar.- Props:
hull(string),locked(boolean),selected?,onClick?,compact?.
PUSHES TO
- DOM: appends a single
<style>element todocument.headon first module load (guarded by id lookup to prevent duplicates). - Caller via
onClickcallback, suppressed whenlockedis true. - Render tree:
RarityBadge,TrimmedShipImage,HullStarBar.
DOES NOT
- Does not mutate inventory, XP, or star state — read-only against
useInventoryStore. - Does not fetch sprites or load images directly; delegates to
TrimmedShipImagewith a resolved path fromgetShipV4SpritePath. - Does not render the rarity badge or “CURRENTLY SELECTED” overlay when
lockedis true. - Does not render the star row / XP bar when
lockedis true. - Does not own layout for collections of cards (grid placement, ordering, filtering live in the parent screen / overlay).
- Does not handle keyboard navigation beyond the native disabled
<button>semantics. - Does not animate on hover or press — only the selected-state pulse keyframe runs.
Signals
lockedprop disables the button, swaps background to a dark gradient (#2a2a30→#14141a), suppresses the rarity badge and star-bar banner, appliesgrayscale(1) brightness(0.35)to the sprite, overlays a lock glyph, and sets cursornot-allowed.selectedprop (when not locked) swaps the border to a 5px white outline, applies thecollectibleSelectedPulse1.4s infinite ease-in-out animation, and renders a full-card 85%-opacity black overlay with a “CURRENTLY SELECTED” label.- Rarity drives: outer border color (
RARITY_ACCENT), gradient background (rarityGradientCss), optional outer glow whose radius and alpha scale withRARITY_EFFECT_TIER(tier ≥ 1 enables glow; tier ≥ 4 bumps alpha from88tocc), badge letter and fill, and sprite drop-shadow tint (frame.color55). compactprop reduces padding (8 → 6), name font size (15 → 13), gap (5 → 3), star size (30 → 24), bar height (11 → 8), dangle offset (6 → 4), selected-label font size (17 → 14), and suppresses no other rendering. Naming aside, the XP bar is still rendered in compact mode — the file header’s “no XP bar” comment is descriptive of the compact variant’s smaller bar treatment viaHullStarBar, not removal.isMax(star >= 5) pinsxpBarPctto 100.
Entry points
- Default usage:
<CollectibleCard hull={hull} locked={locked} selected={selected} onClick={onClick} />. - Compact usage: pass
compactfor bottom-sheet picker contexts (e.g.ShipSelectOverlay). - Module import side effect: importing the module once registers the
collectibleSelectedPulsekeyframes globally. Safe under SSR via thetypeof document !== 'undefined'guard.
Pattern notes
- All styling is inline; no CSS modules or styled-components. The one exception is the keyframes block, which must live in a stylesheet to drive a CSS animation.
- The keyframes injection is idempotent: it checks
document.getElementById('collectible-selected-pulse-keyframes')before creating the element, so reloading the module (HMR) does not duplicate the style tag. - The card uses
all: 'unset'on the<button>to drop user-agent styling, then rebuilds layout from scratch as a flex column.boxSizing: 'border-box'is set explicitly becauseall: 'unset'can reset it. overflow: 'visible'on the root is required so the rarity badge can dangle outside the upper-left corner via negativetop/left(CARD_BADGE.dangleY/dangleX).marginBottom: 12reserves space below the card so the 10px hard drop shadow does not collide with adjacent cards in a grid.- The star-bar wrapper uses negative
marginTop(-10 compact, -14 default) to pull the star glyph up so it overlaps the bottom of the sprite art — a deliberate visual anchor mirroring the hero card on the Ships screen. - The hull-name
textShadowis an 8-direction 1.5px black stroke plus a vertical drop shadow, kept inline rather than abstracted because it’s a one-off effect tuned to this card’s gradient. - Rarity-specific visuals are looked up through
card-themeconstants (RARITY_ACCENT,RARITY_EFFECT_TIER,CARD_BADGE,CARD_SHADOW_CSS,rarityGradientCss) rather than hand-coded per rarity — the file pulls every rarity-driven number from that theme module. TrimmedShipImageis invoked withzoom={1.35}to deliberately crop the widest hulls so every ship reads large and dominant within the card.- The locked lock glyph is rendered with a literal emoji character inline; the rest of the file is emoji-free.
- The
selected && !lockedoverlay sits atposition: absolute; inset: 0and usespointerEvents: 'none'so clicks still hit the underlying button.