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% of starSize so 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 in Cal Sans with an 8-direction black text-shadow stroke. Font size scales from starSize (~0.5×, floored at 8px). Outline offset scales (~0.03×, floored at 1px).
  • SlantedBar — direct <canvas> port of hud.ts::_drawPremiumBar. Owns the slantedRect path 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 ResizeObserver on the canvas container.
  • Shared component-level drop shadow filter wrapping star + bar as one silhouette (controlled by dropShadow prop).
  • 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 (default 64)
    • barHeight (default 20; small banner callers pass 8–10)
    • dropShadow (default true)
    • danglePx (default 0)
    • segments (capsule markers count, default 4)
    • 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) and container.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 xpPct already computed.
  • Does not enforce the star ≥ 1 clamp (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 — fillPct snaps; transitions are the caller’s job (none exist today).
  • Does not handle pointer events — pointerEvents: none on the star image, userSelect: none on the number.
  • Does not clamp barHeight or starSize — 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 to hud.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 yellow fillLow → fillHigh @ 0.35 → fillLow.
  • Bevel hairline: white @ alpha 0.12, lineWidth 0.5, top edge.
  • Outer border: #334466 @ alpha 0.2, lineWidth 1.
  • Fill alpha: 0.93 when pct > 0.005 (else fill is skipped entirely).
  • Gloss strip: white @ alpha 0.25, height max(1, round(cssH * 0.1)), clipped to fill path.
  • Markers: drawn only if segments > 1 && cssH >= 8. Dashed [2,2], white @ alpha 0.2, slanted along the same SLANT.
  • 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. SlantedBar is 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 (with overlap = round(starSize * 0.35)) and the explicit zIndex stacking 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 keep overflow visible vertically. Wrapper is box-sizing: border-box so 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 on StarBadge alone would double-shadow the overlap region.
  • Repaint trigger list (useEffect deps): fillPct, fillHigh, fillLow, height, segments. Container width changes are picked up by ResizeObserver; nothing else triggers a repaint.

EXTRACT-CANDIDATE

  • SLANT = 0.08, the slate background gradient stops, fill gradient sandwich, bevel/outer-border/gloss alphas, and the slantedRect path math are duplicated between this file and hud.ts::_drawPremiumBar. Any future change to the premium-bar look needs to land in both. A shared helper (e.g. engine/rendering/premium-bar.ts exposing drawPremiumBar(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-shadow outline pattern in StarBadge is a reusable “stamped numeral” effect; if another badge needs it, lift the outline builder into a helper.