@energy8platform/shell 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { BlurFilter, Color, Container, Graphics, Rectangle, Text, Ticker } from 'pixi.js';
1
+ import { BlurFilter, Color, Container, Graphics, GraphicsContext, Rectangle, Text, Ticker } from 'pixi.js';
2
2
  import type { ShellTokens } from '@/core/theme';
3
3
  import type { IconName } from '../icons';
4
4
  import { makeIcon, IconView } from '../pixi-icon';
@@ -324,6 +324,35 @@ function drawGlow(glow: Graphics, size: number, accent: string, on: boolean): vo
324
324
  glow.circle(r, r, r + 6).fill({ color: accent, alpha: 0.7 }); // blurred by the glow's filter → halo
325
325
  }
326
326
 
327
+ // Each buy-bonus coin variant (default/social/disabled) is parsed once from its shared SVG art
328
+ // (same vectors as the DOM shell), keyed by the art string.
329
+ const _coinCache = new Map<string, { ctx: GraphicsContext; vb: { x: number; y: number; w: number; h: number } }>();
330
+ function coinData(art: string): { ctx: GraphicsContext; vb: { x: number; y: number; w: number; h: number } } {
331
+ let d = _coinCache.get(art);
332
+ if (!d) {
333
+ const m = art.match(/viewBox="([-\d.]+) ([-\d.]+) ([-\d.]+) ([-\d.]+)"/);
334
+ d = {
335
+ ctx: new GraphicsContext().svg(art),
336
+ vb: m ? { x: +m[1], y: +m[2], w: +m[3], h: +m[4] } : { x: 0, y: 0, w: 24, h: 24 },
337
+ };
338
+ _coinCache.set(art, d);
339
+ }
340
+ return d;
341
+ }
342
+ /** A Container holding the coin `art` scaled to fit `size`, pivoted on its centre so it can pulse. */
343
+ function buildCoin(size: number, art: string): Container {
344
+ const { ctx, vb } = coinData(art);
345
+ const wrap = new Container();
346
+ const g = new Graphics(ctx);
347
+ const s = size / Math.max(vb.w, vb.h);
348
+ g.scale.set(s);
349
+ g.position.set(-vb.x * s + (size - vb.w * s) / 2, -vb.y * s + (size - vb.h * s) / 2);
350
+ wrap.addChild(g);
351
+ wrap.pivot.set(size / 2, size / 2);
352
+ wrap.position.set(size / 2, size / 2);
353
+ return wrap;
354
+ }
355
+
327
356
  // ── SpinDisc — .ge-shell-spin ────────────────────────────────────────────────
328
357
  export interface SpinDiscOpts {
329
358
  size?: number; // 86 desktop / 84 mobile
@@ -390,7 +419,8 @@ export class SpinDisc extends Container implements Sizable {
390
419
  drawDisc(this.disc, this.size, this.tokens.btn, 4);
391
420
  const glyphColor = hot ? this.tokens.accent : this.tokens.btnInk;
392
421
  this.glyph.setColor(glyphColor);
393
- if (this.countText) this.countText.style.fill = hot ? this.tokens.accent : this.tokens.btnInk;
422
+ // the count sits ON the solid (btnInk) STOP square, so it must be light to read
423
+ if (this.countText) this.countText.style.fill = '#ffffff';
394
424
  // Disabled (mid-spin / can't spin): darken OPAQUELY (≈ filter:grayscale(.4) brightness(.62))
395
425
  // with a dark veil over the disc — not alpha, which would let the bright board show through and
396
426
  // read as a translucent/missing button (what looked "transparent" while spinning).
@@ -409,7 +439,7 @@ export class SpinDisc extends Container implements Sizable {
409
439
  this.glyph.visible = false;
410
440
  this.stopGlyph();
411
441
  if (!this.countText) {
412
- this.countText = makeText('', { size: 22, weight: '800', color: this.tokens.btnInk, align: 'center', family: NUM_FONT_FAMILY });
442
+ this.countText = makeText('', { size: 22, weight: '800', color: '#ffffff', align: 'center', family: NUM_FONT_FAMILY });
413
443
  this.addChild(this.countText); // added after the STOP glyph → renders on top of it
414
444
  }
415
445
  const label = Number.isFinite(remaining) ? String(remaining) : '∞';
@@ -495,8 +525,10 @@ export interface BuyBonusOpts {
495
525
  border?: number; // 3 / 2
496
526
  bg: string; // accent
497
527
  fg?: string; // text colour (#fff, or contrast in feature mode)
498
- label: string; // "DISABLE" (feature mode) — ignored when `icon` is set
499
- /** Show this icon (the ticket) instead of the text label — the BUY state. */
528
+ label: string; // "DISABLE" (feature mode) — ignored when `icon`/`coinArt` is set
529
+ /** Render this full-colour buy-bonus coin SVG (default/social/disabled) replaces the accent disc. */
530
+ coinArt?: string;
531
+ /** Show this icon instead of the text label. */
500
532
  icon?: IconName;
501
533
  iconSize?: number; // glyph px (≈ size * 0.62)
502
534
  iconColor?: string; // ticket ink (black)
@@ -510,7 +542,9 @@ export class BuyBonusBadge extends Container implements Sizable {
510
542
  private disc: Graphics;
511
543
  private labelText?: Text;
512
544
  private iconView?: IconView;
513
- private content: Container; // labelText or iconView — the pulsed node
545
+ private content: Container; // labelText / iconView / coin — the pulsed node
546
+ private isCoin = false;
547
+ private dim?: Graphics; // disabled veil over the coin (no filter → crisp, mirrors the spin disc)
514
548
  private bg: string;
515
549
  private fg: string;
516
550
  private border: number;
@@ -528,7 +562,13 @@ export class BuyBonusBadge extends Container implements Sizable {
528
562
  this.ticker = opts.ticker;
529
563
  this.disc = new Graphics();
530
564
  this.addChild(this.glow, this.disc);
531
- if (opts.icon) {
565
+ if (opts.coinArt) {
566
+ this.isCoin = true;
567
+ const coin = buildCoin(this.size, opts.coinArt);
568
+ this.content = coin;
569
+ this.dim = new Graphics();
570
+ this.addChild(coin, this.dim); // veil on top for the disabled state
571
+ } else if (opts.icon) {
532
572
  const gs = opts.iconSize ?? this.size * 0.62;
533
573
  this.iconView = makeIcon(opts.icon, gs, opts.iconColor ?? '#0b0e16');
534
574
  this.iconView.pivot.set(gs / 2, gs / 2); // pulse/scale around the glyph centre
@@ -560,11 +600,20 @@ export class BuyBonusBadge extends Container implements Sizable {
560
600
  }
561
601
 
562
602
  private paint(hovering: boolean): void {
563
- // disabled → grayscale(.5) brightness(.72) baked into the fill/label (no filter → crisp)
564
- drawDisc(this.disc, this.size, this._disabled ? dimColor(this.bg) : this.bg, this.border);
565
- if (this.labelText) this.labelText.style.fill = this._disabled ? dimColor(this.fg) : this.fg;
603
+ if (this.isCoin) {
604
+ // no accent disc the coin IS the button. disabled → dark veil (crisp, no filter).
605
+ if (this.dim) {
606
+ this.dim.clear();
607
+ if (this._disabled) this.dim.circle(this.size / 2, this.size / 2, this.size / 2).fill({ color: 0x000000, alpha: 0.5 });
608
+ }
609
+ } else {
610
+ // disabled → grayscale(.5) brightness(.72) baked into the fill/label (no filter → crisp)
611
+ drawDisc(this.disc, this.size, this._disabled ? dimColor(this.bg) : this.bg, this.border);
612
+ if (this.labelText) this.labelText.style.fill = this._disabled ? dimColor(this.fg) : this.fg;
613
+ }
566
614
  const on = hovering && !this._disabled;
567
- drawGlow(this.glow, this.size, this.bg, on); // soft accent glow only (no ring) `.ge-shell-buybonus:hover`
615
+ // the coin carries its own gold look no accent halo (mirrors `.ge-bb-coin:hover`)
616
+ drawGlow(this.glow, this.size, this.bg, this.isCoin ? false : on);
568
617
  if (on) this.startPulse();
569
618
  else this.stopPulse();
570
619
  }