SPB Git

spb/spinza

Public

games: theme-specific symbol vocabulary (45 new shapes) replaces generic royals/cubes across the library; presentation exposed on game cards

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Sep 8, 2026) parent f61a85f Browse files

21 changed files +1,672 −632

modified apps/web/src/components/game/symbol-art.ts +356 −0
@@ -310,6 +310,362 @@ export function drawSymbol(style: SymbolStyle, opts: SymbolArtOptions): Containe
310 g.poly([0, 0, r * 0.87, -r * 0.5, r * 0.87, r * 0.5, 0, r]).fill(darken(style.color, 0.3)); 310 g.poly([0, 0, r * 0.87, -r * 0.5, r * 0.87, r * 0.5, 0, r]).fill(darken(style.color, 0.3));
311 break; 311 break;
312 } 312 }
313+ /* ----------------------------------------------------------- themed set */
314+ case "lock": {
315+ g.roundRect(-r * 0.75, -r * 0.1, r * 1.5, r * 1.1, r * 0.2).fill(main);
316+ g.arc(0, -r * 0.15, r * 0.5, Math.PI, 0).stroke({ color: accent, width: size * 0.05 });
317+ g.circle(0, r * 0.35, r * 0.16).fill(0x0b0d14);
318+ g.rect(-r * 0.06, r * 0.35, r * 0.12, r * 0.35).fill(0x0b0d14);
319+ break;
320+ }
321+ case "key": {
322+ g.circle(-r * 0.45, -r * 0.35, r * 0.42).stroke({ color: main, width: size * 0.06 });
323+ g.moveTo(-r * 0.15, -r * 0.05).lineTo(r * 0.85, r * 0.95).stroke({ color: main, width: size * 0.06 });
324+ g.moveTo(r * 0.45, r * 0.55).lineTo(r * 0.7, r * 0.3).stroke({ color: accent, width: size * 0.05 });
325+ g.moveTo(r * 0.65, r * 0.75).lineTo(r * 0.9, r * 0.5).stroke({ color: accent, width: size * 0.05 });
326+ break;
327+ }
328+ case "code": {
329+ g.roundRect(-r, -r * 0.7, r * 2, r * 1.4, r * 0.15).fill({ color: darken(style.color, 0.45), alpha: 0.95 });
330+ g.roundRect(-r, -r * 0.7, r * 2, r * 1.4, r * 0.15).stroke({ color: main, width: size * 0.02 });
331+ c.addChild(label(style.label ?? "</>", size * 0.26, main, true));
332+ break;
333+ }
334+ case "badge": {
335+ g.roundRect(-r * 0.7, -r, r * 1.4, r * 2, r * 0.18).fill(main);
336+ g.circle(0, -r * 0.4, r * 0.28).fill(0x0b0d14);
337+ g.roundRect(-r * 0.45, r * 0.05, r * 0.9, r * 0.14, r * 0.05).fill(accent);
338+ g.roundRect(-r * 0.45, r * 0.35, r * 0.6, r * 0.14, r * 0.05).fill(accent);
339+ break;
340+ }
341+ case "mask": {
342+ g.ellipse(0, 0, r, r * 0.6).fill(main);
343+ g.ellipse(-r * 0.4, -r * 0.05, r * 0.28, r * 0.18).fill(0x0b0d14);
344+ g.ellipse(r * 0.4, -r * 0.05, r * 0.28, r * 0.18).fill(0x0b0d14);
345+ g.moveTo(-r * 0.95, -r * 0.1).lineTo(-r * 1.3, -r * 0.6).stroke({ color: accent, width: size * 0.025 });
346+ g.moveTo(r * 0.95, -r * 0.1).lineTo(r * 1.3, -r * 0.6).stroke({ color: accent, width: size * 0.025 });
347+ break;
348+ }
349+ case "glove": {
350+ g.roundRect(-r * 0.55, -r * 0.2, r * 1.1, r * 1.1, r * 0.25).fill(main);
351+ for (let i = 0; i < 4; i++) g.roundRect(-r * 0.55 + i * r * 0.28, -r, r * 0.22, r * 0.9, r * 0.1).fill(main);
352+ g.roundRect(-r * 0.9, -r * 0.1, r * 0.35, r * 0.7, r * 0.15).fill(main);
353+ g.roundRect(-r * 0.55, r * 0.6, r * 1.1, r * 0.3, r * 0.08).fill(accent);
354+ break;
355+ }
356+ case "rope": {
357+ g.circle(0, 0, r * 0.75).stroke({ color: main, width: size * 0.09 });
358+ g.circle(0, 0, r * 0.75).stroke({ color: accent, width: size * 0.02, alpha: 0.6 });
359+ for (let i = 0; i < 10; i++) {
360+ const a = (i / 10) * Math.PI * 2;
361+ g.moveTo(Math.cos(a) * r * 0.62, Math.sin(a) * r * 0.62).lineTo(Math.cos(a + 0.3) * r * 0.88, Math.sin(a + 0.3) * r * 0.88).stroke({ color: darken(style.color, 0.3), width: size * 0.02 });
362+ }
363+ break;
364+ }
365+ case "bag": {
366+ g.moveTo(-r * 0.75, r * 0.9).bezierCurveTo(-r * 1.1, -r * 0.3, -r * 0.3, -r * 0.6, 0, -r * 0.55).bezierCurveTo(r * 0.3, -r * 0.6, r * 1.1, -r * 0.3, r * 0.75, r * 0.9).lineTo(-r * 0.75, r * 0.9).fill(main);
367+ g.roundRect(-r * 0.3, -r * 0.95, r * 0.6, r * 0.35, r * 0.1).fill(accent);
368+ g.moveTo(-r * 0.35, r * 0.3).lineTo(r * 0.35, r * 0.3).stroke({ color: darken(style.color, 0.3), width: size * 0.03 });
369+ break;
370+ }
371+ case "lantern": {
372+ g.roundRect(-r * 0.7, -r * 0.6, r * 1.4, r * 1.2, r * 0.45).fill(main);
373+ g.rect(-r * 0.35, -r * 0.9, r * 0.7, r * 0.2).fill(accent);
374+ g.rect(-r * 0.35, r * 0.6, r * 0.7, r * 0.2).fill(accent);
375+ g.moveTo(0, r * 0.8).lineTo(0, r).stroke({ color: accent, width: size * 0.03 });
376+ for (let i = -1; i <= 1; i++) g.moveTo(i * r * 0.3, -r * 0.55).lineTo(i * r * 0.3, r * 0.55).stroke({ color: darken(style.color, 0.3), width: size * 0.015 });
377+ break;
378+ }
379+ case "fan": {
380+ for (let i = -3; i <= 3; i++) {
381+ const a = -Math.PI / 2 + i * 0.28;
382+ g.moveTo(0, r * 0.7).lineTo(Math.cos(a) * r * 1.05, r * 0.7 + Math.sin(a) * r * 1.05).stroke({ color: i % 2 ? main : accent, width: size * 0.07 });
383+ }
384+ g.circle(0, r * 0.7, r * 0.14).fill(0x0b0d14);
385+ break;
386+ }
387+ case "scroll": {
388+ g.roundRect(-r * 0.7, -r * 0.85, r * 1.4, r * 1.7, r * 0.1).fill({ color: accent, alpha: 0.9 });
389+ g.roundRect(-r * 0.85, -r, r * 1.7, r * 0.3, r * 0.15).fill(main);
390+ g.roundRect(-r * 0.85, r * 0.7, r * 1.7, r * 0.3, r * 0.15).fill(main);
391+ for (let i = 0; i < 3; i++) g.rect(-r * 0.45, -r * 0.5 + i * r * 0.32, r * 0.9, r * 0.08).fill({ color: darken(style.color, 0.4), alpha: 0.7 });
392+ break;
393+ }
394+ case "cherry": {
395+ g.moveTo(-r * 0.3, r * 0.3).bezierCurveTo(-r * 0.2, -r * 0.5, r * 0.2, -r * 0.9, r * 0.5, -r).stroke({ color: 0x3ddc97, width: size * 0.035 });
396+ g.moveTo(r * 0.45, r * 0.3).bezierCurveTo(r * 0.5, -r * 0.4, r * 0.5, -r * 0.8, r * 0.5, -r).stroke({ color: 0x3ddc97, width: size * 0.035 });
397+ g.circle(-r * 0.35, r * 0.45, r * 0.42).fill(main);
398+ g.circle(r * 0.45, r * 0.45, r * 0.42).fill(main);
399+ g.circle(-r * 0.5, r * 0.3, r * 0.12).fill({ color: 0xffffff, alpha: 0.6 });
400+ g.circle(r * 0.3, r * 0.3, r * 0.12).fill({ color: 0xffffff, alpha: 0.6 });
401+ break;
402+ }
403+ case "seven": {
404+ g.poly([-r * 0.8, -r, r * 0.8, -r, r * 0.8, -r * 0.55, -r * 0.05, r, -r * 0.55, r, r * 0.2, -r * 0.55, -r * 0.8, -r * 0.55]).fill(main);
405+ g.poly([-r * 0.8, -r, r * 0.8, -r, r * 0.8, -r * 0.55, -r * 0.05, r, -r * 0.55, r, r * 0.2, -r * 0.55, -r * 0.8, -r * 0.55]).stroke({ color: accent, width: size * 0.03 });
406+ break;
407+ }
408+ case "bell": {
409+ g.moveTo(-r * 0.75, r * 0.5).bezierCurveTo(-r * 0.75, -r * 0.5, -r * 0.3, -r * 0.9, 0, -r * 0.9).bezierCurveTo(r * 0.3, -r * 0.9, r * 0.75, -r * 0.5, r * 0.75, r * 0.5).lineTo(-r * 0.75, r * 0.5).fill(main);
410+ g.roundRect(-r * 0.9, r * 0.45, r * 1.8, r * 0.18, r * 0.09).fill(accent);
411+ g.circle(0, r * 0.8, r * 0.15).fill(accent);
412+ g.circle(0, -r * 0.95, r * 0.1).fill(accent);
413+ break;
414+ }
415+ case "joystick": {
416+ g.roundRect(-r * 0.9, r * 0.3, r * 1.8, r * 0.6, r * 0.15).fill(darken(style.color, 0.4));
417+ g.rect(-r * 0.08, -r * 0.4, r * 0.16, r * 0.8).fill(0x9aa3b5);
418+ g.circle(0, -r * 0.55, r * 0.35).fill(main);
419+ g.circle(-r * 0.12, -r * 0.65, r * 0.1).fill({ color: 0xffffff, alpha: 0.5 });
420+ g.circle(r * 0.5, r * 0.6, r * 0.12).fill(accent);
421+ break;
422+ }
423+ case "torii": {
424+ g.rect(-r, -r * 0.85, r * 2, r * 0.18).fill(main);
425+ g.rect(-r * 0.8, -r * 0.5, r * 1.6, r * 0.12).fill(main);
426+ g.rect(-r * 0.6, -r * 0.5, r * 0.16, r * 1.5).fill(main);
427+ g.rect(r * 0.44, -r * 0.5, r * 0.16, r * 1.5).fill(main);
428+ g.rect(-r * 0.08, -r * 0.5, r * 0.16, r * 0.35).fill(accent);
429+ break;
430+ }
431+ case "katana": {
432+ g.moveTo(-r * 0.9, r * 0.9).lineTo(r * 0.7, -r * 0.7).stroke({ color: 0xdfe6f0, width: size * 0.05 });
433+ g.moveTo(-r * 0.9, r * 0.9).lineTo(r * 0.7, -r * 0.7).stroke({ color: 0xffffff, width: size * 0.015, alpha: 0.7 });
434+ g.moveTo(-r * 0.55, r * 0.35).lineTo(-r * 0.25, r * 0.65).stroke({ color: accent, width: size * 0.05 });
435+ g.moveTo(-r * 0.9, r * 0.9).lineTo(-r * 0.6, r * 0.6).stroke({ color: main, width: size * 0.08 });
436+ break;
437+ }
438+ case "cassette": {
439+ g.roundRect(-r, -r * 0.65, r * 2, r * 1.3, r * 0.12).fill(main);
440+ g.roundRect(-r * 0.75, -r * 0.4, r * 1.5, r * 0.55, r * 0.08).fill(darken(style.color, 0.45));
441+ g.circle(-r * 0.4, -r * 0.12, r * 0.16).fill(0x0b0d14).circle(r * 0.4, -r * 0.12, r * 0.16).fill(0x0b0d14);
442+ g.rect(-r * 0.6, r * 0.3, r * 1.2, r * 0.14).fill(accent);
443+ break;
444+ }
445+ case "ankh": {
446+ g.ellipse(0, -r * 0.55, r * 0.35, r * 0.42).stroke({ color: main, width: size * 0.06 });
447+ g.rect(-r * 0.08, -r * 0.1, r * 0.16, r * 1.1).fill(main);
448+ g.rect(-r * 0.7, -r * 0.1, r * 1.4, r * 0.16).fill(main);
449+ g.circle(0, -r * 0.55, r * 0.08).fill(accent);
450+ break;
451+ }
452+ case "scarab": {
453+ g.ellipse(0, r * 0.15, r * 0.6, r * 0.7).fill(main);
454+ g.circle(0, -r * 0.6, r * 0.28).fill(darken(style.color, 0.25));
455+ g.moveTo(0, -r * 0.4).lineTo(0, r * 0.8).stroke({ color: accent, width: size * 0.02 });
456+ for (const sgn of [-1, 1]) {
457+ g.moveTo(sgn * r * 0.55, -r * 0.1).lineTo(sgn * r * 0.95, -r * 0.5).stroke({ color: accent, width: size * 0.035 });
458+ g.moveTo(sgn * r * 0.6, r * 0.3).lineTo(sgn * r * 1.0, r * 0.4).stroke({ color: accent, width: size * 0.035 });
459+ }
460+ break;
461+ }
462+ case "pyramid": {
463+ g.poly([0, -r, r, r * 0.75, -r, r * 0.75]).fill(main);
464+ g.poly([0, -r, r, r * 0.75, 0, r * 0.75]).fill(darken(style.color, 0.25));
465+ g.poly([0, -r, r * 0.25, -r * 0.55, -r * 0.25, -r * 0.55]).fill(accent);
466+ break;
467+ }
468+ case "warning": {
469+ g.poly([0, -r, r * 0.95, r * 0.75, -r * 0.95, r * 0.75]).fill(main);
470+ g.poly([0, -r * 0.6, r * 0.55, r * 0.55, -r * 0.55, r * 0.55]).fill(0x0b0d14);
471+ g.rect(-r * 0.08, -r * 0.3, r * 0.16, r * 0.5).fill(main);
472+ g.circle(0, r * 0.38, r * 0.09).fill(main);
473+ break;
474+ }
475+ case "valve": {
476+ g.circle(0, 0, r * 0.85).stroke({ color: main, width: size * 0.07 });
477+ for (let i = 0; i < 4; i++) {
478+ const a = (i / 4) * Math.PI * 2;
479+ g.moveTo(0, 0).lineTo(Math.cos(a) * r * 0.85, Math.sin(a) * r * 0.85).stroke({ color: main, width: size * 0.05 });
480+ }
481+ g.circle(0, 0, r * 0.25).fill(accent);
482+ break;
483+ }
484+ case "gauge": {
485+ g.arc(0, r * 0.2, r * 0.9, Math.PI, 0).stroke({ color: darken(style.color, 0.3), width: size * 0.09 });
486+ g.arc(0, r * 0.2, r * 0.9, Math.PI, Math.PI * 1.6).stroke({ color: main, width: size * 0.09 });
487+ g.moveTo(0, r * 0.2).lineTo(r * 0.45, -r * 0.5).stroke({ color: accent, width: size * 0.04 });
488+ g.circle(0, r * 0.2, r * 0.12).fill(accent);
489+ break;
490+ }
491+ case "barrel": {
492+ g.roundRect(-r * 0.65, -r * 0.9, r * 1.3, r * 1.8, r * 0.2).fill(main);
493+ g.rect(-r * 0.65, -r * 0.45, r * 1.3, r * 0.12).fill(darken(style.color, 0.4));
494+ g.rect(-r * 0.65, r * 0.33, r * 1.3, r * 0.12).fill(darken(style.color, 0.4));
495+ polygon(g, 0, 0, r * 0.28, 3, -Math.PI / 2).fill(accent);
496+ break;
497+ }
498+ case "wheel": {
499+ g.circle(0, 0, r * 0.95).fill(0x0b0d14);
500+ g.circle(0, 0, r * 0.95).stroke({ color: main, width: size * 0.05 });
501+ g.circle(0, 0, r * 0.6).stroke({ color: accent, width: size * 0.03 });
502+ for (let i = 0; i < 5; i++) {
503+ const a = (i / 5) * Math.PI * 2;
504+ g.moveTo(0, 0).lineTo(Math.cos(a) * r * 0.6, Math.sin(a) * r * 0.6).stroke({ color: accent, width: size * 0.035 });
505+ }
506+ g.circle(0, 0, r * 0.14).fill(main);
507+ break;
508+ }
509+ case "helmet": {
510+ g.arc(0, r * 0.1, r * 0.85, Math.PI, 0).fill(main);
511+ g.rect(-r * 0.85, r * 0.1, r * 1.7, r * 0.45).fill(main);
512+ g.roundRect(-r * 0.6, -r * 0.05, r * 1.4, r * 0.4, r * 0.1).fill(0x0b0d14);
513+ g.moveTo(-r * 0.3, -r * 0.7).lineTo(r * 0.3, -r * 0.7).stroke({ color: accent, width: size * 0.04 });
514+ break;
515+ }
516+ case "flag": {
517+ g.rect(-r * 0.85, -r, r * 0.1, r * 2).fill(0x9aa3b5);
518+ for (let i = 0; i < 4; i++) for (let j = 0; j < 3; j++) g.rect(-r * 0.75 + i * r * 0.4, -r + j * r * 0.33, r * 0.4, r * 0.33).fill((i + j) % 2 ? main : accent);
519+ break;
520+ }
521+ case "trophy": {
522+ g.moveTo(-r * 0.7, -r).lineTo(r * 0.7, -r).lineTo(r * 0.45, r * 0.15).lineTo(-r * 0.45, r * 0.15).fill(main);
523+ g.arc(-r * 0.75, -r * 0.55, r * 0.3, Math.PI * 0.5, Math.PI * 1.5).stroke({ color: main, width: size * 0.04 });
524+ g.arc(r * 0.75, -r * 0.55, r * 0.3, -Math.PI * 0.5, Math.PI * 0.5).stroke({ color: main, width: size * 0.04 });
525+ g.rect(-r * 0.1, r * 0.15, r * 0.2, r * 0.4).fill(main);
526+ g.roundRect(-r * 0.5, r * 0.55, r, r * 0.3, r * 0.06).fill(accent);
527+ break;
528+ }
529+ case "idol": {
530+ g.roundRect(-r * 0.55, -r * 0.9, r * 1.1, r * 1.8, r * 0.15).fill(main);
531+ g.rect(-r * 0.4, -r * 0.55, r * 0.8, r * 0.14).fill(0x0b0d14);
532+ g.rect(-r * 0.4, -r * 0.1, r * 0.8, r * 0.14).fill(0x0b0d14);
533+ g.poly([-r * 0.3, r * 0.35, r * 0.3, r * 0.35, 0, r * 0.7]).fill(accent);
534+ break;
535+ }
536+ case "totem": {
537+ for (let i = 0; i < 3; i++) {
538+ g.roundRect(-r * 0.55, -r + i * r * 0.68, r * 1.1, r * 0.6, r * 0.08).fill(i % 2 ? darken(style.color, 0.25) : main);
539+ g.circle(-r * 0.22, -r * 0.7 + i * r * 0.68, r * 0.08).fill(accent);
540+ g.circle(r * 0.22, -r * 0.7 + i * r * 0.68, r * 0.08).fill(accent);
541+ }
542+ break;
543+ }
544+ case "snake": {
545+ g.moveTo(-r * 0.8, r * 0.8).bezierCurveTo(-r * 0.9, -r * 0.2, r * 0.9, r * 0.6, r * 0.6, -r * 0.5).bezierCurveTo(r * 0.4, -r * 1.0, -r * 0.2, -r * 0.6, 0, -r * 0.3).stroke({ color: main, width: size * 0.08 });
546+ g.circle(0, -r * 0.3, r * 0.2).fill(main);
547+ g.circle(r * 0.06, -r * 0.36, r * 0.06).fill(accent);
548+ break;
549+ }
550+ case "rocket": {
551+ g.moveTo(0, -r).bezierCurveTo(r * 0.6, -r * 0.4, r * 0.5, r * 0.4, r * 0.35, r * 0.6).lineTo(-r * 0.35, r * 0.6).bezierCurveTo(-r * 0.5, r * 0.4, -r * 0.6, -r * 0.4, 0, -r).fill(main);
552+ g.poly([-r * 0.35, r * 0.2, -r * 0.75, r * 0.8, -r * 0.35, r * 0.6]).fill(accent);
553+ g.poly([r * 0.35, r * 0.2, r * 0.75, r * 0.8, r * 0.35, r * 0.6]).fill(accent);
554+ g.circle(0, -r * 0.25, r * 0.18).fill(0x0b0d14);
555+ g.poly([-r * 0.2, r * 0.6, r * 0.2, r * 0.6, 0, r]).fill(0xffb347);
556+ break;
557+ }
558+ case "anchor": {
559+ g.circle(0, -r * 0.75, r * 0.2).stroke({ color: main, width: size * 0.05 });
560+ g.rect(-r * 0.07, -r * 0.55, r * 0.14, r * 1.4).fill(main);
561+ g.rect(-r * 0.55, -r * 0.35, r * 1.1, r * 0.12).fill(main);
562+ g.arc(0, r * 0.15, r * 0.75, Math.PI * 0.15, Math.PI * 0.85).stroke({ color: main, width: size * 0.07 });
563+ g.circle(-r * 0.7, r * 0.4, r * 0.08).fill(accent).circle(r * 0.7, r * 0.4, r * 0.08).fill(accent);
564+ break;
565+ }
566+ case "chest": {
567+ g.roundRect(-r * 0.9, -r * 0.2, r * 1.8, r * 1.0, r * 0.1).fill(main);
568+ g.roundRect(-r * 0.9, -r * 0.75, r * 1.8, r * 0.6, r * 0.3).fill(darken(style.color, 0.2));
569+ g.rect(-r * 0.9, -r * 0.2, r * 1.8, r * 0.12).fill(accent);
570+ g.roundRect(-r * 0.15, -r * 0.2, r * 0.3, r * 0.35, r * 0.05).fill(accent);
571+ break;
572+ }
573+ case "trident": {
574+ g.rect(-r * 0.07, -r * 0.4, r * 0.14, r * 1.4).fill(main);
575+ for (const dx of [-0.5, 0, 0.5]) {
576+ g.moveTo(dx * r, -r * 0.3).lineTo(dx * r, -r).stroke({ color: main, width: size * 0.05 });
577+ g.poly([dx * r - r * 0.12, -r * 0.85, dx * r + r * 0.12, -r * 0.85, dx * r, -r * 1.1]).fill(accent);
578+ }
579+ g.rect(-r * 0.5, -r * 0.4, r, r * 0.12).fill(main);
580+ break;
581+ }
582+ case "shell": {
583+ g.arc(0, r * 0.4, r * 0.95, Math.PI, 0).fill(main);
584+ for (let i = -3; i <= 3; i++) g.moveTo(0, r * 0.4).lineTo(Math.cos(-Math.PI / 2 + i * 0.38) * r * 0.95, r * 0.4 + Math.sin(-Math.PI / 2 + i * 0.38) * r * 0.95).stroke({ color: darken(style.color, 0.25), width: size * 0.02 });
585+ g.rect(-r * 0.95, r * 0.4, r * 1.9, r * 0.18).fill(accent);
586+ break;
587+ }
588+ case "planet": {
589+ g.circle(0, 0, r * 0.7).fill(main);
590+ g.ellipse(0, r * 0.05, r * 1.05, r * 0.28).stroke({ color: accent, width: size * 0.04 });
591+ g.circle(-r * 0.25, -r * 0.25, r * 0.18).fill({ color: 0xffffff, alpha: 0.3 });
592+ g.ellipse(r * 0.15, r * 0.15, r * 0.3, r * 0.12).fill({ color: darken(style.color, 0.25), alpha: 0.8 });
593+ break;
594+ }
595+ case "comet": {
596+ g.moveTo(r * 0.5, -r * 0.5).lineTo(-r * 1.0, r * 0.15).lineTo(-r * 0.6, r * 0.6).lineTo(r * 0.5, -r * 0.5).fill({ color: accent, alpha: 0.5 });
597+ g.moveTo(r * 0.5, -r * 0.5).lineTo(-r * 0.7, r * 0.7).stroke({ color: accent, alpha: 0.4, width: size * 0.05 });
598+ g.circle(r * 0.5, -r * 0.5, r * 0.4).fill(main);
599+ g.circle(r * 0.4, -r * 0.6, r * 0.12).fill({ color: 0xffffff, alpha: 0.6 });
600+ break;
601+ }
602+ case "satellite": {
603+ g.roundRect(-r * 0.3, -r * 0.3, r * 0.6, r * 0.6, r * 0.1).fill(main);
604+ g.rect(-r * 1.0, -r * 0.18, r * 0.6, r * 0.36).fill(accent);
605+ g.rect(r * 0.4, -r * 0.18, r * 0.6, r * 0.36).fill(accent);
606+ for (const x of [-0.8, -0.6, 0.6, 0.8]) g.moveTo(x * r, -r * 0.18).lineTo(x * r, r * 0.18).stroke({ color: darken(style.color, 0.4), width: size * 0.012 });
607+ g.arc(0, -r * 0.55, r * 0.3, Math.PI, 0).stroke({ color: main, width: size * 0.03 });
608+ break;
609+ }
610+ case "pickaxe": {
611+ g.moveTo(-r * 0.7, r * 0.9).lineTo(r * 0.5, -r * 0.4).stroke({ color: 0x8a6d2e, width: size * 0.06 });
612+ g.moveTo(r * 0.0, -r * 0.9).bezierCurveTo(r * 0.7, -r * 0.9, r * 1.0, -r * 0.4, r * 0.95, r * 0.15).lineTo(r * 0.55, -r * 0.35).fill(main);
613+ g.moveTo(r * 0.0, -r * 0.9).lineTo(r * 0.55, -r * 0.35).stroke({ color: accent, width: size * 0.03 });
614+ break;
615+ }
616+ case "ore": {
617+ polygon(g, 0, r * 0.1, r * 0.9, 7).fill(0x3a3f4a);
618+ g.poly([-r * 0.3, -r * 0.1, 0, -r * 0.5, r * 0.35, -r * 0.05, r * 0.1, r * 0.35, -r * 0.25, r * 0.3]).fill(main);
619+ g.poly([-r * 0.3, -r * 0.1, 0, -r * 0.5, 0, 0]).fill(accent);
620+ break;
621+ }
622+ case "drill": {
623+ g.rect(-r * 0.4, -r * 0.9, r * 0.8, r * 0.7).fill(main);
624+ g.poly([-r * 0.4, -r * 0.2, r * 0.4, -r * 0.2, 0, r]).fill(0xc8d0dc);
625+ for (let i = 0; i < 4; i++) g.moveTo(-r * 0.32 + i * r * 0.1, -r * 0.2 + i * r * 0.28).lineTo(r * 0.32 - i * r * 0.1, -r * 0.2 + i * r * 0.28).stroke({ color: 0x5c6270, width: size * 0.015 });
626+ g.rect(-r * 0.15, -r * 0.6, r * 0.3, r * 0.15).fill(accent);
627+ break;
628+ }
629+ case "ingot": {
630+ g.poly([-r * 0.7, -r * 0.3, r * 0.7, -r * 0.3, r * 0.95, r * 0.5, -r * 0.95, r * 0.5]).fill(main);
631+ g.poly([-r * 0.7, -r * 0.3, r * 0.7, -r * 0.3, r * 0.5, 0, -r * 0.5, 0]).fill(accent);
632+ g.moveTo(-r * 0.5, r * 0.15).lineTo(r * 0.5, r * 0.15).stroke({ color: darken(style.color, 0.3), width: size * 0.02 });
633+ break;
634+ }
635+ case "hourglass": {
636+ g.poly([-r * 0.7, -r, r * 0.7, -r, r * 0.1, 0, r * 0.7, r, -r * 0.7, r, -r * 0.1, 0]).stroke({ color: main, width: size * 0.04 });
637+ g.poly([-r * 0.45, -r * 0.8, r * 0.45, -r * 0.8, 0, -r * 0.1]).fill({ color: accent, alpha: 0.8 });
638+ g.poly([-r * 0.55, r * 0.9, r * 0.55, r * 0.9, 0, r * 0.35]).fill({ color: accent, alpha: 0.8 });
639+ break;
640+ }
641+ case "igloo": {
642+ g.arc(0, r * 0.4, r * 0.95, Math.PI, 0).fill(main);
643+ g.rect(-r * 0.95, r * 0.4, r * 1.9, r * 0.2).fill(main);
644+ g.arc(-r * 0.3, r * 0.5, r * 0.35, Math.PI, 0).fill(0x0b0d14);
645+ for (let i = 0; i < 3; i++) g.arc(0, r * 0.4, r * (0.35 + i * 0.25), Math.PI, 0).stroke({ color: accent, width: size * 0.015, alpha: 0.6 });
646+ break;
647+ }
648+ case "paw": {
649+ g.ellipse(0, r * 0.35, r * 0.55, r * 0.45).fill(main);
650+ for (const [dx, dy] of [[-0.6, -0.25], [-0.22, -0.6], [0.22, -0.6], [0.6, -0.25]]) g.circle(dx * r, dy * r, r * 0.2).fill(main);
651+ g.ellipse(0, r * 0.4, r * 0.3, r * 0.22).fill({ color: accent, alpha: 0.6 });
652+ break;
653+ }
654+ case "fish": {
655+ g.ellipse(-r * 0.1, 0, r * 0.7, r * 0.42).fill(main);
656+ g.poly([r * 0.5, 0, r, -r * 0.45, r, r * 0.45]).fill(accent);
657+ g.circle(-r * 0.5, -r * 0.1, r * 0.08).fill(0x0b0d14);
658+ g.moveTo(-r * 0.2, -r * 0.4).bezierCurveTo(0, -r * 0.7, r * 0.2, -r * 0.5, r * 0.25, -r * 0.3).fill({ color: accent, alpha: 0.8 });
659+ break;
660+ }
661+ case "meteor": {
662+ polygon(g, r * 0.2, r * 0.2, r * 0.6, 7, 0.3).fill(main);
663+ g.circle(r * 0.35, 0, r * 0.12).fill({ color: darken(style.color, 0.3) });
664+ g.circle(0, r * 0.4, r * 0.1).fill({ color: darken(style.color, 0.3) });
665+ g.moveTo(-r * 0.2, -r * 0.3).lineTo(-r * 1.0, -r * 1.0).stroke({ color: accent, alpha: 0.6, width: size * 0.06 });
666+ g.moveTo(r * 0.1, -r * 0.55).lineTo(-r * 0.5, -r * 1.05).stroke({ color: accent, alpha: 0.4, width: size * 0.04 });
667+ break;
668+ }
313 } 669 }
314 c.addChild(g); 670 c.addChild(g);
315 return c; 671 return c;
modified apps/web/src/components/lobby/game-art.tsx +1167 −533
Large diff (1,892 lines) is not loaded by default to keep the page fast.Load diff
modified games/src/arctic-fortune/index.ts +3 −3
Large diff (17 lines) is not loaded by default to keep the page fast.Load diff
modified games/src/cosmic-collapse/index.ts +3 −3
Large diff (12 lines) is not loaded by default to keep the page fast.Load diff
modified games/src/deep-treasure/index.ts +4 −4
Large diff (18 lines) is not loaded by default to keep the page fast.Load diff
modified games/src/diamond-heist/index.ts +10 −10
Large diff (28 lines) is not loaded by default to keep the page fast.Load diff
modified games/src/dragon-core/index.ts +1 −1
Large diff (8 lines) is not loaded by default to keep the page fast.Load diff
modified games/src/golden-emperor/index.ts +7 −7
Large diff (20 lines) is not loaded by default to keep the page fast.Load diff
modified games/src/inferno-reels/index.ts +3 −3
Large diff (16 lines) is not loaded by default to keep the page fast.Load diff
modified games/src/lucky-circuit/index.ts +4 −4
Large diff (16 lines) is not loaded by default to keep the page fast.Load diff
modified games/src/midnight-tokyo/index.ts +8 −8
Large diff (23 lines) is not loaded by default to keep the page fast.Load diff
modified games/src/moonbase-77/index.ts +6 −6
Large diff (18 lines) is not loaded by default to keep the page fast.Load diff
modified games/src/neon-vault/index.ts +7 −7
Large diff (24 lines) is not loaded by default to keep the page fast.Load diff
modified games/src/pharaoh-protocol/index.ts +8 −8
Large diff (25 lines) is not loaded by default to keep the page fast.Load diff
modified games/src/quantum-jackpot/index.ts +4 −4
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
modified games/src/reel-reactor/index.ts +5 −5
Large diff (17 lines) is not loaded by default to keep the page fast.Load diff
modified games/src/royal-circuit/index.ts +10 −10
Large diff (27 lines) is not loaded by default to keep the page fast.Load diff
modified games/src/void-miner/index.ts +4 −4
Large diff (20 lines) is not loaded by default to keep the page fast.Load diff
modified games/src/wild-temple/index.ts +8 −8
Large diff (24 lines) is not loaded by default to keep the page fast.Load diff
modified games/src/zero-gravity/index.ts +3 −3
Large diff (17 lines) is not loaded by default to keep the page fast.Load diff
modified packages/game-core/src/types.ts +51 −1
Large diff (59 lines) is not loaded by default to keep the page fast.Load diff
Menu

Theme