@zencemarketing/spin-scratch-sdk 0.1.0-alpha.1 → 0.1.0-alpha.2

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,5 +1,5 @@
1
1
  /**
2
- * SpinWheel SDK v0.1.0-alpha.1
2
+ * SpinWheel SDK v0.1.0-alpha.2
3
3
  * (c) 2026 – MIT License
4
4
  * A dynamic, configurable Spin & Win wheel and Scratch Card SDK for Vanilla JS & React.
5
5
  */
@@ -28,6 +28,63 @@
28
28
  */
29
29
  function buildCSS(config, hexToRGBA) {
30
30
  const t = config.theme || {};
31
+ const isHexColor = value => typeof value === 'string' && /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(value.trim());
32
+ const parseRgbLike = value => {
33
+ if (typeof value !== 'string') return null;
34
+ const m = value.trim().match(/^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*(\d*\.?\d+)\s*)?\)$/i);
35
+ if (!m) return null;
36
+ const r = Math.min(255, Math.max(0, Number(m[1])));
37
+ const g = Math.min(255, Math.max(0, Number(m[2])));
38
+ const b = Math.min(255, Math.max(0, Number(m[3])));
39
+ if ([r, g, b].some(n => Number.isNaN(n))) return null;
40
+ return {
41
+ r,
42
+ g,
43
+ b
44
+ };
45
+ };
46
+ const parseHex = value => {
47
+ if (!isHexColor(value)) return null;
48
+ const hex = value.trim().slice(1);
49
+ if (hex.length === 3) {
50
+ const r = parseInt(hex[0] + hex[0], 16);
51
+ const g = parseInt(hex[1] + hex[1], 16);
52
+ const b = parseInt(hex[2] + hex[2], 16);
53
+ return {
54
+ r,
55
+ g,
56
+ b
57
+ };
58
+ }
59
+ const r = parseInt(hex.slice(0, 2), 16);
60
+ const g = parseInt(hex.slice(2, 4), 16);
61
+ const b = parseInt(hex.slice(4, 6), 16);
62
+ return {
63
+ r,
64
+ g,
65
+ b
66
+ };
67
+ };
68
+ const toRGBA = (value, alpha) => {
69
+ const rgb = parseHex(value) || parseRgbLike(value);
70
+ if (!rgb) return `rgba(0,0,0,${alpha})`;
71
+ return `rgba(${rgb.r},${rgb.g},${rgb.b},${alpha})`;
72
+ };
73
+ const darkenColor = (value, amount = 0.22) => {
74
+ const rgb = parseHex(value) || parseRgbLike(value);
75
+ if (!rgb) return null;
76
+ const factor = 1 - Math.min(0.9, Math.max(0, amount));
77
+ const r = Math.round(rgb.r * factor);
78
+ const g = Math.round(rgb.g * factor);
79
+ const b = Math.round(rgb.b * factor);
80
+ return `rgb(${r},${g},${b})`;
81
+ };
82
+ const readableTextOn = bg => {
83
+ const rgb = parseHex(bg) || parseRgbLike(bg);
84
+ if (!rgb) return '#1a1a1f';
85
+ const luma = (0.2126 * rgb.r + 0.7152 * rgb.g + 0.0722 * rgb.b) / 255;
86
+ return luma < 0.55 ? '#ffffff' : '#1a1a1f';
87
+ };
31
88
 
32
89
  // Extract theme colors with fallbacks
33
90
  const gold = t.gold || '#e8c547';
@@ -37,11 +94,16 @@ function buildCSS(config, hexToRGBA) {
37
94
  const textMuted = t.textMuted || '#9ca3af';
38
95
 
39
96
  // Configurable colors
40
- const titleColor = config.titleColor || goldLight;
97
+ const hasCustomTitleColor = config.titleColor !== null && config.titleColor !== undefined;
98
+ const titleSpinColor = hasCustomTitleColor ? config.titleColor : goldLight;
99
+ const titleWinColor = hasCustomTitleColor ? config.titleColor : goldDark;
41
100
  const subtitleColor = config.subtitleColor || textMuted;
42
101
  const ringColor = config.ringColor || gold;
43
102
  const pointerColor = config.pointerColor || gold;
44
- const buttonBaseColor = config.buttonColor || gold;
103
+ const hasCustomButtonColor = config.buttonColor !== null && config.buttonColor !== undefined;
104
+ const buttonBaseColor = hasCustomButtonColor ? config.buttonColor : gold;
105
+ const buttonTextColor = hasCustomButtonColor ? readableTextOn(buttonBaseColor) : '#1a1a1f';
106
+ const buttonEdgeColor = hasCustomButtonColor ? darkenColor(buttonBaseColor) || 'rgba(0,0,0,0.25)' : goldDark;
45
107
  const redeemBtnTop = config.winCardRedeemButtonColorTop || '#15803d';
46
108
  const redeemBtnBottom = config.winCardRedeemButtonColorBottom || '#166534';
47
109
  const redeemBtnText = config.winCardRedeemButtonTextColor || '#ffffff';
@@ -55,7 +117,7 @@ function buildCSS(config, hexToRGBA) {
55
117
  // Computed values
56
118
  hexToRGBA(gold, 0.4);
57
119
  const ringGlow = hexToRGBA(ringColor, 0.4);
58
- const buttonGlow = hexToRGBA(buttonBaseColor, 0.4);
120
+ const buttonGlow = toRGBA(buttonBaseColor, 0.4);
59
121
 
60
122
  // Background styles
61
123
  const bgStyle = config.backgroundColor ? `background-color:${config.backgroundColor};` : '';
@@ -66,9 +128,10 @@ function buildCSS(config, hexToRGBA) {
66
128
  const ringAnimationCSS = ringAnimation ? `animation:sw-ring-pulse 3s ease-in-out infinite;` : '';
67
129
 
68
130
  // Button shadow and animation
69
- const buttonBoxShadow = buttonShadow ? `box-shadow:0 4px 0 ${goldDark}, 0 6px 20px ${buttonGlow}, inset 0 1px 0 rgba(255,255,255,.3);` : `box-shadow:0 4px 0 ${goldDark}, inset 0 1px 0 rgba(255,255,255,.3);`;
70
- const buttonHoverStyle = buttonAnimation ? `.sw-spin-btn:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 6px 0 ${goldDark},0 8px 28px ${buttonGlow},inset 0 1px 0 rgba(255,255,255,.3)}` : `.sw-spin-btn:hover:not(:disabled){filter:brightness(1.05)}`;
71
- const buttonActiveStyle = buttonAnimation ? `.sw-spin-btn:active:not(:disabled){transform:translateY(2px);box-shadow:0 2px 0 ${goldDark},0 4px 15px ${buttonGlow},inset 0 1px 0 rgba(255,255,255,.2)}` : `.sw-spin-btn:active:not(:disabled){transform:translateY(1px)}`;
131
+ const buttonBoxShadow = buttonShadow ? `box-shadow:0 4px 0 ${buttonEdgeColor}, 0 6px 20px ${buttonGlow}, inset 0 1px 0 rgba(255,255,255,.3);` : `box-shadow:inset 0 1px 0 rgba(255,255,255,.25);`;
132
+ const buttonHoverStyle = buttonAnimation ? buttonShadow ? `.sw-spin-btn:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 6px 0 ${buttonEdgeColor},0 8px 28px ${buttonGlow},inset 0 1px 0 rgba(255,255,255,.3)}` : `.sw-spin-btn:hover:not(:disabled){transform:translateY(-2px);filter:brightness(1.05)}` : `.sw-spin-btn:hover:not(:disabled){filter:brightness(1.05)}`;
133
+ const buttonActiveStyle = buttonAnimation ? buttonShadow ? `.sw-spin-btn:active:not(:disabled){transform:translateY(2px);box-shadow:0 2px 0 ${buttonEdgeColor},0 4px 15px ${buttonGlow},inset 0 1px 0 rgba(255,255,255,.2)}` : `.sw-spin-btn:active:not(:disabled){transform:translateY(1px);filter:brightness(0.98)}` : `.sw-spin-btn:active:not(:disabled){transform:translateY(1px)}`;
134
+ const buttonBackground = hasCustomButtonColor ? `background:${buttonBaseColor};` : `background:linear-gradient(180deg,${goldLight},${goldDark});`;
72
135
  return `
73
136
  /* === SpinWheel SDK v2.1 === */
74
137
  @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700;800&family=Outfit:wght@300;500;600;700&display=swap');
@@ -89,10 +152,10 @@ function buildCSS(config, hexToRGBA) {
89
152
  font-size:clamp(2rem,6vw,3.5rem); font-weight:800; letter-spacing:.02em;
90
153
  }
91
154
  .sw-title .sw-spin-text {
92
- color:${titleColor};
93
- text-shadow:0 0 30px ${hexToRGBA(titleColor, 0.4)}, 0 0 60px ${hexToRGBA(titleColor, 0.2)};
155
+ color:${titleSpinColor};
156
+ text-shadow:0 0 30px ${toRGBA(titleSpinColor, 0.4)}, 0 0 60px ${toRGBA(titleSpinColor, 0.2)};
94
157
  }
95
- .sw-title .sw-win-text { color:${goldDark}; }
158
+ .sw-title .sw-win-text { color:${titleWinColor}; }
96
159
  .sw-subtitle { margin-top:.5rem; color:${subtitleColor}; font-size:1rem; font-weight:300; }
97
160
 
98
161
  /* ── wheel ── */
@@ -174,7 +237,7 @@ ${ringAnimation ? `
174
237
  .sw-spin-btn {
175
238
  font-family:'Outfit',sans-serif; font-size:1.25rem; font-weight:700;
176
239
  letter-spacing:.15em; padding:1rem 3rem; border:none; border-radius:12px;
177
- background:linear-gradient(180deg,${goldLight},${goldDark}); color:#1a1a1f;
240
+ ${buttonBackground} color:${buttonTextColor};
178
241
  cursor:pointer;
179
242
  ${buttonBoxShadow}
180
243
  transition:transform .15s ease, box-shadow .2s ease, filter .2s ease;
@@ -1426,7 +1489,7 @@ class SpinWheel {
1426
1489
  static Instance = SpinWheelInstance;
1427
1490
 
1428
1491
  /** Current SDK version – kept in sync with package.json via build banner */
1429
- static VERSION = '0.1.0-alpha.1';
1492
+ static VERSION = '0.1.0-alpha.2';
1430
1493
 
1431
1494
  /** Export defaults for reference */
1432
1495
  static DEFAULTS = DEFAULTS$1;
@@ -1661,7 +1724,8 @@ function buildScratchCSS(config, hexToRGBA) {
1661
1724
 
1662
1725
  // Background configs
1663
1726
  const cardBg = config.cardBackground || `linear-gradient(180deg, #faf8fc 0%, #f0ebf5 100%)`;
1664
- const scratchZoneBg = config.scratchZoneBackground || `linear-gradient(145deg, ${purpleDark} 0%, ${purpleMid} 50%, #5a3a7a 100%)`;
1727
+ const hasCustomScratchZoneBg = config.scratchZoneBackground !== null && config.scratchZoneBackground !== undefined;
1728
+ const scratchZoneBg = hasCustomScratchZoneBg ? config.scratchZoneBackground : `linear-gradient(145deg, ${purpleDark} 0%, ${purpleMid} 50%, #5a3a7a 100%)`;
1665
1729
  const modalBtnBg = config.modalButtonColor || `linear-gradient(145deg, ${purpleMid} 0%, ${purpleDark} 100%)`;
1666
1730
 
1667
1731
  // Computed values
@@ -1771,9 +1835,7 @@ function buildScratchCSS(config, hexToRGBA) {
1771
1835
  align-items:center;
1772
1836
  justify-content:center;
1773
1837
  padding:20px;
1774
- background:
1775
- radial-gradient(circle at 30% 30%, rgba(255,255,255,0.08) 0%, transparent 45%),
1776
- linear-gradient(145deg, ${purpleDark} 0%, ${purpleMid} 100%);
1838
+ background:${hasCustomScratchZoneBg ? scratchZoneBg : `radial-gradient(circle at 30% 30%, rgba(255,255,255,0.08) 0%, transparent 45%), linear-gradient(145deg, ${purpleDark} 0%, ${purpleMid} 100%)`};
1777
1839
  }
1778
1840
 
1779
1841
  .sc-prize-content::before {
@@ -3398,7 +3460,7 @@ const ScratchCard = {
3398
3460
  /** The underlying class – useful for `instanceof` checks or subclassing. */
3399
3461
  Instance: ScratchCardInstance,
3400
3462
  /** Current SDK version */
3401
- VERSION: '0.1.0-alpha.1',
3463
+ VERSION: '0.1.0-alpha.2',
3402
3464
  /** Export defaults for reference */
3403
3465
  DEFAULTS: DEFAULTS
3404
3466
  };