@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
  */
@@ -32,6 +32,51 @@
32
32
  */
33
33
  function buildCSS(config, hexToRGBA) {
34
34
  const t = config.theme || {};
35
+ const isHexColor = (value) => typeof value === 'string' && /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(value.trim());
36
+ const parseRgbLike = (value) => {
37
+ if (typeof value !== 'string') return null;
38
+ 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);
39
+ if (!m) return null;
40
+ const r = Math.min(255, Math.max(0, Number(m[1])));
41
+ const g = Math.min(255, Math.max(0, Number(m[2])));
42
+ const b = Math.min(255, Math.max(0, Number(m[3])));
43
+ if ([r, g, b].some((n) => Number.isNaN(n))) return null;
44
+ return { r, g, b };
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 { r, g, b };
54
+ }
55
+ const r = parseInt(hex.slice(0, 2), 16);
56
+ const g = parseInt(hex.slice(2, 4), 16);
57
+ const b = parseInt(hex.slice(4, 6), 16);
58
+ return { r, g, b };
59
+ };
60
+ const toRGBA = (value, alpha) => {
61
+ const rgb = parseHex(value) || parseRgbLike(value);
62
+ if (!rgb) return `rgba(0,0,0,${alpha})`;
63
+ return `rgba(${rgb.r},${rgb.g},${rgb.b},${alpha})`;
64
+ };
65
+ const darkenColor = (value, amount = 0.22) => {
66
+ const rgb = parseHex(value) || parseRgbLike(value);
67
+ if (!rgb) return null;
68
+ const factor = 1 - Math.min(0.9, Math.max(0, amount));
69
+ const r = Math.round(rgb.r * factor);
70
+ const g = Math.round(rgb.g * factor);
71
+ const b = Math.round(rgb.b * factor);
72
+ return `rgb(${r},${g},${b})`;
73
+ };
74
+ const readableTextOn = (bg) => {
75
+ const rgb = parseHex(bg) || parseRgbLike(bg);
76
+ if (!rgb) return '#1a1a1f';
77
+ const luma = (0.2126 * rgb.r + 0.7152 * rgb.g + 0.0722 * rgb.b) / 255;
78
+ return luma < 0.55 ? '#ffffff' : '#1a1a1f';
79
+ };
35
80
 
36
81
  // Extract theme colors with fallbacks
37
82
  const gold = t.gold || '#e8c547';
@@ -41,11 +86,16 @@
41
86
  const textMuted = t.textMuted || '#9ca3af';
42
87
 
43
88
  // Configurable colors
44
- const titleColor = config.titleColor || goldLight;
89
+ const hasCustomTitleColor = config.titleColor !== null && config.titleColor !== undefined;
90
+ const titleSpinColor = hasCustomTitleColor ? config.titleColor : goldLight;
91
+ const titleWinColor = hasCustomTitleColor ? config.titleColor : goldDark;
45
92
  const subtitleColor = config.subtitleColor || textMuted;
46
93
  const ringColor = config.ringColor || gold;
47
94
  const pointerColor = config.pointerColor || gold;
48
- const buttonBaseColor = config.buttonColor || gold;
95
+ const hasCustomButtonColor = config.buttonColor !== null && config.buttonColor !== undefined;
96
+ const buttonBaseColor = hasCustomButtonColor ? config.buttonColor : gold;
97
+ const buttonTextColor = hasCustomButtonColor ? readableTextOn(buttonBaseColor) : '#1a1a1f';
98
+ const buttonEdgeColor = hasCustomButtonColor ? (darkenColor(buttonBaseColor) || 'rgba(0,0,0,0.25)') : goldDark;
49
99
  const redeemBtnTop = config.winCardRedeemButtonColorTop || '#15803d';
50
100
  const redeemBtnBottom = config.winCardRedeemButtonColorBottom || '#166534';
51
101
  const redeemBtnText = config.winCardRedeemButtonTextColor || '#ffffff';
@@ -59,7 +109,7 @@
59
109
  // Computed values
60
110
  hexToRGBA(gold, 0.4);
61
111
  const ringGlow = hexToRGBA(ringColor, 0.4);
62
- const buttonGlow = hexToRGBA(buttonBaseColor, 0.4);
112
+ const buttonGlow = toRGBA(buttonBaseColor, 0.4);
63
113
 
64
114
  // Background styles
65
115
  const bgStyle = config.backgroundColor
@@ -80,17 +130,25 @@
80
130
 
81
131
  // Button shadow and animation
82
132
  const buttonBoxShadow = buttonShadow
83
- ? `box-shadow:0 4px 0 ${goldDark}, 0 6px 20px ${buttonGlow}, inset 0 1px 0 rgba(255,255,255,.3);`
84
- : `box-shadow:0 4px 0 ${goldDark}, inset 0 1px 0 rgba(255,255,255,.3);`;
133
+ ? `box-shadow:0 4px 0 ${buttonEdgeColor}, 0 6px 20px ${buttonGlow}, inset 0 1px 0 rgba(255,255,255,.3);`
134
+ : `box-shadow:inset 0 1px 0 rgba(255,255,255,.25);`;
85
135
 
86
136
  const buttonHoverStyle = buttonAnimation
87
- ? `.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)}`
137
+ ? (buttonShadow
138
+ ? `.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)}`
139
+ : `.sw-spin-btn:hover:not(:disabled){transform:translateY(-2px);filter:brightness(1.05)}`)
88
140
  : `.sw-spin-btn:hover:not(:disabled){filter:brightness(1.05)}`;
89
141
 
90
142
  const buttonActiveStyle = buttonAnimation
91
- ? `.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)}`
143
+ ? (buttonShadow
144
+ ? `.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)}`
145
+ : `.sw-spin-btn:active:not(:disabled){transform:translateY(1px);filter:brightness(0.98)}`)
92
146
  : `.sw-spin-btn:active:not(:disabled){transform:translateY(1px)}`;
93
147
 
148
+ const buttonBackground = hasCustomButtonColor
149
+ ? `background:${buttonBaseColor};`
150
+ : `background:linear-gradient(180deg,${goldLight},${goldDark});`;
151
+
94
152
  return `
95
153
  /* === SpinWheel SDK v2.1 === */
96
154
  @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700;800&family=Outfit:wght@300;500;600;700&display=swap');
@@ -111,10 +169,10 @@
111
169
  font-size:clamp(2rem,6vw,3.5rem); font-weight:800; letter-spacing:.02em;
112
170
  }
113
171
  .sw-title .sw-spin-text {
114
- color:${titleColor};
115
- text-shadow:0 0 30px ${hexToRGBA(titleColor, 0.4)}, 0 0 60px ${hexToRGBA(titleColor, 0.2)};
172
+ color:${titleSpinColor};
173
+ text-shadow:0 0 30px ${toRGBA(titleSpinColor, 0.4)}, 0 0 60px ${toRGBA(titleSpinColor, 0.2)};
116
174
  }
117
- .sw-title .sw-win-text { color:${goldDark}; }
175
+ .sw-title .sw-win-text { color:${titleWinColor}; }
118
176
  .sw-subtitle { margin-top:.5rem; color:${subtitleColor}; font-size:1rem; font-weight:300; }
119
177
 
120
178
  /* ── wheel ── */
@@ -196,7 +254,7 @@ ${ringAnimation ? `
196
254
  .sw-spin-btn {
197
255
  font-family:'Outfit',sans-serif; font-size:1.25rem; font-weight:700;
198
256
  letter-spacing:.15em; padding:1rem 3rem; border:none; border-radius:12px;
199
- background:linear-gradient(180deg,${goldLight},${goldDark}); color:#1a1a1f;
257
+ ${buttonBackground} color:${buttonTextColor};
200
258
  cursor:pointer;
201
259
  ${buttonBoxShadow}
202
260
  transition:transform .15s ease, box-shadow .2s ease, filter .2s ease;
@@ -1457,7 +1515,7 @@ ${buttonActiveStyle}
1457
1515
  static Instance = SpinWheelInstance;
1458
1516
 
1459
1517
  /** Current SDK version – kept in sync with package.json via build banner */
1460
- static VERSION = '0.1.0-alpha.1';
1518
+ static VERSION = '0.1.0-alpha.2';
1461
1519
 
1462
1520
  /** Export defaults for reference */
1463
1521
  static DEFAULTS = DEFAULTS$1;
@@ -1721,7 +1779,10 @@ ${buttonActiveStyle}
1721
1779
 
1722
1780
  // Background configs
1723
1781
  const cardBg = config.cardBackground || `linear-gradient(180deg, #faf8fc 0%, #f0ebf5 100%)`;
1724
- const scratchZoneBg = config.scratchZoneBackground || `linear-gradient(145deg, ${purpleDark} 0%, ${purpleMid} 50%, #5a3a7a 100%)`;
1782
+ const hasCustomScratchZoneBg = config.scratchZoneBackground !== null && config.scratchZoneBackground !== undefined;
1783
+ const scratchZoneBg = hasCustomScratchZoneBg
1784
+ ? config.scratchZoneBackground
1785
+ : `linear-gradient(145deg, ${purpleDark} 0%, ${purpleMid} 50%, #5a3a7a 100%)`;
1725
1786
  const modalBtnBg = config.modalButtonColor || `linear-gradient(145deg, ${purpleMid} 0%, ${purpleDark} 100%)`;
1726
1787
 
1727
1788
  // Computed values
@@ -1841,9 +1902,7 @@ ${buttonActiveStyle}
1841
1902
  align-items:center;
1842
1903
  justify-content:center;
1843
1904
  padding:20px;
1844
- background:
1845
- radial-gradient(circle at 30% 30%, rgba(255,255,255,0.08) 0%, transparent 45%),
1846
- linear-gradient(145deg, ${purpleDark} 0%, ${purpleMid} 100%);
1905
+ 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%)`};
1847
1906
  }
1848
1907
 
1849
1908
  .sc-prize-content::before {
@@ -3462,7 +3521,7 @@ ${buttonActiveStyle}
3462
3521
  Instance: ScratchCardInstance,
3463
3522
 
3464
3523
  /** Current SDK version */
3465
- VERSION: '0.1.0-alpha.1',
3524
+ VERSION: '0.1.0-alpha.2',
3466
3525
 
3467
3526
  /** Export defaults for reference */
3468
3527
  DEFAULTS: DEFAULTS,