HullStarBar
PURPOSE
Shared React component that renders a gold star badge fused with a slanted XP bar. One component, two scales — hero card on the Ship Select screen, and tiny “Clash-Royale” banner clamped to the bottom edge of every inventory card. Exports HullStarBar (composite), plus StarBadge and SlantedBar (reusable parts).
OWNS
- Composite layout (
HullStarBar) — flexbox row of star + bar with a star→bar overlap of~35%ofstarSizeso the badge visually anchors the start of the track. StarBadge— square<div>rendering/star-outline.svg(gold variant) with the star level number stamped on top inCal Sanswith an 8-direction black text-shadow stroke. Font size scales fromstarSize(~0.5×, floored at 8px). Outline offset scales (~0.03×, floored at 1px).SlantedBar— direct<canvas>port ofhud.ts::_drawPremiumBar. Owns theslantedRectpath math, the slate background + outer border + top bevel hairline, the two-tone yellow fill gradient, the gloss highlight clipped to the fill, and dashed capsule segment markers.- DPR-aware repaint via a
ResizeObserveron the canvas container. - Shared component-level drop shadow filter wrapping star + bar as one silhouette (controlled by
dropShadowprop). - Negative-bottom-margin dangle (
danglePx) so the badge can hang a few px below the parent card’s content box without expanding it.
READS FROM
- Props only:
star(number, 1–5, caller clamps to ≥1)xpPct(0–100, internally re-clamped)starSize(default64)barHeight(default20; small banner callers pass8–10)dropShadow(defaulttrue)danglePx(default0)segments(capsule markers count, default4)align('center' | 'start', default'center')
- Module-level color constants
YELLOW_HI = #fde047(yellow-300),YELLOW_LO = #eab308(yellow-500). Used as the fill gradient sandwich (lo → hi at 0.35 → lo). window.devicePixelRatio(clamped to ≤2) andcontainer.getBoundingClientRect()at paint time.- Static asset
/public/star-outline.svg.
PUSHES TO
- Nothing. Pure presentational component — no stores, no events, no telemetry, no callbacks. Output is DOM + a
<canvas>bitmap.
DOES NOT
- Does not read XP from any store. Caller supplies
xpPctalready computed. - Does not enforce the
star ≥ 1clamp (header comment explicitly defers that to the caller). - Does not own the star→bar overlap as a configurable prop — it’s hard-coded at
round(starSize * 0.35). - Does not redraw on every frame — only on prop changes and container resize (via
ResizeObserver). - Does not animate the fill —
fillPctsnaps; transitions are the caller’s job (none exist today). - Does not handle pointer events —
pointerEvents: noneon the star image,userSelect: noneon the number. - Does not clamp
barHeightorstarSize— caller passes raw pixels. - Does not load the SVG via React; it’s a plain
<img src="/star-outline.svg">against the public root.
Signals
- Slant constant:
SLANT = 0.08(horizontal offset =height * 0.08). Identical tohud.ts::_drawPremiumBar. - Corner radius:
max(2, round(cssH * 0.3)). - Pad ring:
max(1, round(cssH * 0.1))of slate background visible outside the fill rect. - Gradient stops: background slate
#1e2236 → #141828 → #0c1018 → #080c14; fill yellowfillLow → fillHigh @ 0.35 → fillLow. - Bevel hairline: white @ alpha
0.12,lineWidth 0.5, top edge. - Outer border:
#334466@ alpha0.2,lineWidth 1. - Fill alpha:
0.93whenpct > 0.005(else fill is skipped entirely). - Gloss strip: white @ alpha
0.25, heightmax(1, round(cssH * 0.1)), clipped to fill path. - Markers: drawn only if
segments > 1 && cssH >= 8. Dashed[2,2], white @ alpha0.2, slanted along the sameSLANT. - DPR cap:
min(2, devicePixelRatio || 1). - Min CSS width:
max(8, floor(rect.width))— bar never collapses below 8 CSS px.
Entry points
HullStarBar({ star, xpPct, starSize?, barHeight?, dropShadow?, danglePx?, segments?, align? })— composite. Default consumer.StarBadge({ star, size? })— standalone gold star + number, no bar. Exported for callers that want just the badge.SlantedBar({ fillPct, fillHigh?, fillLow?, height?, segments? })— standalone XP bar. Exported for callers that want just the bar (defaults to the same yellow sandwich).
Pattern notes
- Shape parity with HUD.
SlantedBaris a deliberate<canvas>port of the in-game HUD’s premium bar. The shape math (slantedRect), gradient stops, bevel, gloss, and marker logic are the same so the metagame and the run HUD read as one visual language. - Two-tone yellow as default. Convention across the ship pages is yellow everywhere. Callers can override per-bar with
fillHigh/fillLow, but new screens should match the gold star unless there’s a reason not to. - Star→bar overlap is intentional. The bar’s
marginLeft: -overlap(withoverlap = round(starSize * 0.35)) and the explicitzIndexstacking make the star sit on top of the bar start. Don’t “fix” the overlap by giving the bar its own left padding — it’ll break the silhouette. - Dangle uses
marginBottom: -danglePx, not absolute positioning. Parent must keepoverflowvisible vertically. Wrapper isbox-sizing: border-boxso it never widens its parent. - Drop shadow lives on the outer wrapper, not on each piece. Wrapping both with
filter: drop-shadow(...)produces one silhouette. Putting a shadow onStarBadgealone would double-shadow the overlap region. - Repaint trigger list (
useEffectdeps):fillPct,fillHigh,fillLow,height,segments. Container width changes are picked up byResizeObserver; nothing else triggers a repaint.
EXTRACT-CANDIDATE
SLANT = 0.08, the slate background gradient stops, fill gradient sandwich, bevel/outer-border/gloss alphas, and theslantedRectpath math are duplicated between this file andhud.ts::_drawPremiumBar. Any future change to the premium-bar look needs to land in both. A shared helper (e.g.engine/rendering/premium-bar.tsexposingdrawPremiumBar(ctx, opts)) would let both call sites stay in lockstep.YELLOW_HI/YELLOW_LO(yellow-300 / yellow-500) recur across ship-page UI. Worth promoting to a shared theme constants module if a third caller appears.- The 8-direction
text-shadowoutline pattern inStarBadgeis a reusable “stamped numeral” effect; if another badge needs it, lift theoutlinebuilder into a helper.