import * as React from "react";
import type { SymbolStyle, SymbolTier } from "@spinza/game-core/client";
/* ------------------------------------------------------------------------
SymbolSvg — the exact same procedural symbol artwork the PixiJS renderer
draws on the reels (`components/game/symbol-art.ts`), ported to SVG so
lobby posters show the symbols the player will actually meet in-game.
Geometry, colours, highlights and glow follow symbol-art.ts 1:1; `size` is
the tile edge, the glyph is centred on (x, y).
------------------------------------------------------------------------ */
const INK = "#0b0d14";
const FONT = "var(--font-geist-sans), Geist, Inter, ui-sans-serif, system-ui, sans-serif";
function clamp8(n: number): number {
return Math.max(0, Math.min(255, n));
}
function parseHex(c: string): [number, number, number] {
const h = c.replace("#", "");
const full = h.length === 3 ? h.split("").map((ch) => ch + ch).join("") : h;
const n = parseInt(full, 16);
return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
}
function toHex(r: number, g: number, b: number): string {
return `#${((clamp8(r) << 16) | (clamp8(g) << 8) | clamp8(b)).toString(16).padStart(6, "0")}`;
}
/** Same arithmetic as symbol-art.ts `lighten` (adds 255*amt per channel). */
export function lightenHex(c: string, amt: number): string {
const [r, g, b] = parseHex(c);
const d = Math.round(255 * amt);
return toHex(r + d, g + d, b + d);
}
/** Same arithmetic as symbol-art.ts `darken`. */
export function darkenHex(c: string, amt: number): string {
const [r, g, b] = parseHex(c);
const d = Math.round(255 * amt);
return toHex(r - d, g - d, b - d);
}
const f = (n: number) => Math.round(n * 100) / 100;
/** Flat [x0,y0,x1,y1,…] list → SVG points string. */
function pts(list: number[]): string {
const out: string[] = [];
for (let i = 0; i < list.length; i += 2) out.push(`${f(list[i])},${f(list[i + 1])}`);
return out.join(" ");
}
function polygonPts(cx: number, cy: number, r: number, sides: number, rot = -Math.PI / 2): string {
const list: number[] = [];
for (let i = 0; i < sides; i++) {
const a = rot + (i * Math.PI * 2) / sides;
list.push(cx + Math.cos(a) * r, cy + Math.sin(a) * r);
}
return pts(list);
}
function starPts(cx: number, cy: number, outer: number, inner: number, points = 5): string {
const list: number[] = [];
for (let i = 0; i < points * 2; i++) {
const r = i % 2 === 0 ? outer : inner;
const a = -Math.PI / 2 + (i * Math.PI) / points;
list.push(cx + Math.cos(a) * r, cy + Math.sin(a) * r);
}
return pts(list);
}
function arcPath(cx: number, cy: number, r: number, a0: number, a1: number): string {
const x0 = cx + Math.cos(a0) * r;
const y0 = cy + Math.sin(a0) * r;
const x1 = cx + Math.cos(a1) * r;
const y1 = cy + Math.sin(a1) * r;
const large = Math.abs(a1 - a0) > Math.PI ? 1 : 0;
const sweep = a1 > a0 ? 1 : 0;
return `M${f(x0)} ${f(y0)} A${f(r)} ${f(r)} 0 ${large} ${sweep} ${f(x1)} ${f(y1)}`;
}
export { pts as svgPoints, polygonPts as svgPolygon, starPts as svgStar, arcPath as svgArc };
function Label({ text, fontSize, color, bold = true }: { text: string; fontSize: number; color: string; bold?: boolean }) {
return (
{text}
);
}
/* ------------------------------------------------------------------ glyph */
function Glyph({ style, size }: { style: SymbolStyle; size: number }) {
const main = style.color;
const accent = style.accent ?? lightenHex(style.color, 0.35);
const r = size * 0.36;
const sw = (k: number) => f(size * k);
switch (style.shape) {
case "gem":
return (
<>
>
);
case "hex":
return (
<>
{style.label ? : null}
>
);
case "star":
return (
<>
>
);
case "circle":
return (
<>
>
);
case "diamond":
return (
<>
>
);
case "shield":
return (
<>
>
);
case "bolt":
return (
<>
>
);
case "ring":
return (
<>
>
);
case "chip": {
let pins = "";
for (let i = -2; i <= 2; i++) {
const y = f(i * r * 0.28 - r * 0.06);
pins += `M${f(-r - r * 0.18)} ${y}h${f(r * 0.18)}v${f(r * 0.12)}h${f(-r * 0.18)}z M${f(r)} ${y}h${f(r * 0.18)}v${f(r * 0.12)}h${f(-r * 0.18)}z `;
}
return (
<>
{style.label ? : null}
>
);
}
case "crystal":
return (
<>
>
);
case "letter":
return (
<>
>
);
case "vault": {
let spokes = "";
for (let i = 0; i < 6; i++) {
const a = (i * Math.PI) / 3;
spokes += `M${f(Math.cos(a) * r * 0.25)} ${f(Math.sin(a) * r * 0.25)}L${f(Math.cos(a) * r * 0.62)} ${f(Math.sin(a) * r * 0.62)} `;
}
return (
<>
>
);
}
case "orb":
return (
<>
{style.label ? : null}
>
);
case "coin":
return (
<>
>
);
case "skull":
return (
<>
>
);
case "flame":
return (
<>
>
);
case "snow": {
let axes = "";
let branches = "";
for (let i = 0; i < 3; i++) {
const a = (i * Math.PI) / 3;
axes += `M${f(Math.cos(a) * r)} ${f(Math.sin(a) * r)}L${f(-Math.cos(a) * r)} ${f(-Math.sin(a) * r)} `;
for (const s of [1, -1]) {
const bx = Math.cos(a) * r * 0.6 * s;
const by = Math.sin(a) * r * 0.6 * s;
branches += `M${f(bx)} ${f(by)}L${f(bx + Math.cos(a + 0.6) * r * 0.3 * s)} ${f(by + Math.sin(a + 0.6) * r * 0.3 * s)} `;
branches += `M${f(bx)} ${f(by)}L${f(bx + Math.cos(a - 0.6) * r * 0.3 * s)} ${f(by + Math.sin(a - 0.6) * r * 0.3 * s)} `;
}
}
return (
<>
>
);
}
case "eye":
return (
<>
>
);
case "moon":
return (
<>
>
);
case "leaf":
return (
<>
>
);
case "atom":
return (
<>
>
);
case "crown":
return (
<>
>
);
case "wave": {
const wave = (y: number) => `M${f(-r)} ${f(y)} C${f(-r * 0.5)} ${f(y - r * 0.4)} ${f(-r * 0.2)} ${f(y + r * 0.4)} 0 ${f(y)} C${f(r * 0.2)} ${f(y - r * 0.4)} ${f(r * 0.5)} ${f(y + r * 0.4)} ${f(r)} ${f(y)}`;
return (
<>
>
);
}
case "cube":
return (
<>
{style.label ? : null}
>
);
/* ----------------------------------------------------------- themed set */
case "lock":
return (
<>
>
);
case "key":
return (
<>
>
);
case "bag":
return (
<>
>
);
case "warning":
return (
<>
>
);
case "gauge":
return (
<>
>
);
case "rocket":
return (
<>
>
);
case "chest":
return (
<>
>
);
case "planet":
return (
<>
>
);
case "comet":
return (
<>
>
);
case "satellite":
return (
<>
`M${f(x * r)} ${f(-r * 0.18)}V${f(r * 0.18)}`).join(" ")} stroke={darkenHex(main, 0.4)} strokeWidth={sw(0.012)} />
>
);
default:
return ;
}
}
/* -------------------------------------------------------------- SymbolSvg */
export interface SymbolSvgProps {
style: SymbolStyle;
/** Tile edge in user units; the glyph radius is 0.36 × size (as on the reels). */
size: number;
tier?: SymbolTier;
/** Centre position inside the parent SVG (default 0,0). */
x?: number;
y?: number;
rotate?: number;
opacity?: number;
/** Dark reel tile behind the glyph (default true, matches the in-game plate). */
plate?: boolean;
/** Coloured halo behind the glyph (default true, driven by `style.glow`). */
halo?: boolean;
className?: string;
}
/**
* Renders a reel symbol as an SVG `` centred on (x, y). Drop it inside any
* `