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

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.
package/README.md ADDED
@@ -0,0 +1,716 @@
1
+ # SpinWheel SDK + ScratchCard SDK
2
+
3
+ A dynamic, configurable **Spin & Win** wheel and **Scratch Card** SDK for **Vanilla JS** and **React** (with full **TypeScript** support).
4
+
5
+ Package name: `@zencemarketing/spin-scratch-sdk`
6
+
7
+ ---
8
+
9
+ ## Who is this for?
10
+
11
+ If you are a client developer, you generally only need to:
12
+
13
+ 1. Install the package
14
+ 2. Render a container (`<div id="..." />`)
15
+ 3. Call `SpinWheel.init(...)` or `ScratchCard.init(...)` (Vanilla) **or** use the React wrappers
16
+
17
+ Everything else (HTML structure, styles, DOM events, animations) is handled internally by the SDK.
18
+
19
+ ## Requirements
20
+
21
+ - Browser environment (needs `window` and `document`)
22
+ - Node.js >= 16 for building this SDK
23
+
24
+ If you use SSR frameworks (Next.js, Remix), initialize the widgets only on the client (e.g. inside `useEffect`).
25
+
26
+ ## Features
27
+
28
+ | Feature | Details |
29
+ |---|---|
30
+ | **Dynamic segments** | 2–N segments – just pass an array |
31
+ | **Fully configurable** | 40+ options for SpinWheel, 50+ for ScratchCard |
32
+ | **Spin physics** | Configurable duration, spin count, easing |
33
+ | **Spin limit** | Restrict number of spins per session |
34
+ | **Win card overlay** | Auto-generated coupon code, copy button, configurable redeem CTA |
35
+ | **Confetti** | Toggleable confetti with custom colors and count |
36
+ | **Appearance** | Customize ring, pointer, button, background, shadows, animations |
37
+ | **Callbacks** | `onSpinStart`, `onSpinEnd`, `onRedeem`, `onSpinLimitReached` |
38
+ | **Runtime updates** | `updateOptions()`, `updateSegments()`, `updatePrize()` |
39
+ | **React support** | Thin wrapper with `ref` for imperative control |
40
+ | **Zero dependencies** | CSS is auto-injected; fonts loaded from Google Fonts |
41
+
42
+ ---
43
+
44
+ ## Installation
45
+
46
+ ### Install from npm (when published)
47
+
48
+ ```bash
49
+ npm i @zencemarketing/spin-scratch-sdk
50
+ ```
51
+
52
+ ### Use locally (not published yet)
53
+
54
+ In your client app `package.json`:
55
+
56
+ ```json
57
+ {
58
+ "dependencies": {
59
+ "@zencemarketing/spin-scratch-sdk": "file:../path-to/ZenceGamification/sdk"
60
+ }
61
+ }
62
+ ```
63
+
64
+ Then:
65
+
66
+ ```bash
67
+ # In the SDK folder
68
+ npm i
69
+ npm run build
70
+
71
+ # In the client folder
72
+ npm i
73
+ ```
74
+
75
+ Note: Always point `file:` to the SDK **root folder** (the one that contains `package.json`), not `dist/`.
76
+
77
+ ## TypeScript support
78
+
79
+ This SDK ships TypeScript declarations out of the box — no `@types/` package needed.
80
+
81
+ ### Two entry points
82
+
83
+ | Entry | Import path | Use case |
84
+ |---|---|---|
85
+ | **Main** | `@zencemarketing/spin-scratch-sdk` | Vanilla JS/TS, factory React |
86
+ | **React** | `@zencemarketing/spin-scratch-sdk/react` | Pre-wired React components |
87
+
88
+ ### Recommended import for React + TypeScript
89
+
90
+ ```tsx
91
+ import {
92
+ SpinWheelReact,
93
+ ScratchCardReact,
94
+ type SpinWheelReactProps,
95
+ type ScratchCardReactProps,
96
+ type SpinWheelReactHandle,
97
+ type ScratchCardReactHandle,
98
+ type SegmentConfig,
99
+ type ScratchPrize,
100
+ } from '@zencemarketing/spin-scratch-sdk/react';
101
+ ```
102
+
103
+ ### Recommended import for Vanilla TypeScript
104
+
105
+ ```ts
106
+ import {
107
+ SpinWheel,
108
+ ScratchCard,
109
+ type SpinWheelOptions,
110
+ type ScratchCardOptions,
111
+ type SegmentConfig,
112
+ type ScratchPrize,
113
+ } from '@zencemarketing/spin-scratch-sdk';
114
+ ```
115
+
116
+ ### Key type tips
117
+
118
+ - **Segment shape**: Required fields are `label` and `color` (NOT `name`)
119
+ - **React props**: Use `SpinWheelReactProps` / `ScratchCardReactProps` — these already omit `container` (the SDK handles it). **Do NOT use** `Omit<SpinWheelOptions, 'container'>` manually.
120
+ - **Ref handles**: Use `SpinWheelReactHandle` / `ScratchCardReactHandle` for `useRef` generic.
121
+
122
+ Tip: If you hover on `SpinWheel.init` and you only see `options: SpinWheelOptions`, that’s normal. To see all available fields:
123
+
124
+ - Use IntelliSense inside `SpinWheel.init({ ... })` (Ctrl+Space)
125
+ - Or Ctrl/Cmd-click `SpinWheelOptions` to open the full type
126
+
127
+ ---
128
+
129
+ ## Quick Start (Vanilla JS)
130
+
131
+ ### Option A: Use the UMD bundle in plain HTML
132
+
133
+ When the package is published, you can use a CDN like `unpkg`:
134
+
135
+ ```html
136
+ <div id="my-wheel"></div>
137
+
138
+ <!-- Uses the "unpkg" entry from package.json -->
139
+ <script src="https://unpkg.com/@zencemarketing/spin-scratch-sdk"></script>
140
+
141
+ <script>
142
+ var SpinWheel = window.SpinWheelSDK.SpinWheel;
143
+
144
+ var wheel = SpinWheel.init({
145
+ container: '#my-wheel',
146
+ segments: [
147
+ { label: 'Free Gift', color: '#55efc4', icon: '🎁', title: 'Free Gift', worth: '₹299' },
148
+ { label: 'Surprise Reward', color: '#81ecec', icon: '✨', title: 'Surprise Reward', worth: '₹199' },
149
+ { label: 'Better Luck', color: '#dfe6e9', icon: '🌟', title: 'Better Luck', worth: '' },
150
+ ],
151
+ headerTitle: 'Spin & Win',
152
+ headerSubtitle: 'Try your luck!',
153
+ onSpinEnd: function (prize, index) {
154
+ console.log('Won:', prize, 'at index', index);
155
+ },
156
+ });
157
+
158
+ // Optional: wheel.spin();
159
+ </script>
160
+ ```
161
+
162
+ ### Option B: Import in a bundler app (Vite/Webpack/etc.)
163
+
164
+ ```js
165
+ import { SpinWheel } from '@zencemarketing/spin-scratch-sdk';
166
+
167
+ const wheel = SpinWheel.init({
168
+ container: '#my-wheel',
169
+ segments: [
170
+ { label: 'Prize 1', color: '#55efc4' },
171
+ { label: 'Prize 2', color: '#81ecec' },
172
+ ],
173
+ });
174
+ ```
175
+
176
+ ## Quick Start (React)
177
+
178
+ There are two ways to use this SDK in React.
179
+
180
+ ### Option A (recommended): Import from `/react` subpath
181
+
182
+ The simplest way — components are pre-wired, fully typed, and just work:
183
+
184
+ ```tsx
185
+ import React, { useRef } from 'react';
186
+ import {
187
+ SpinWheelReact,
188
+ type SpinWheelReactHandle,
189
+ type SegmentConfig,
190
+ } from '@zencemarketing/spin-scratch-sdk/react';
191
+
192
+ export default function App() {
193
+ const ref = useRef<SpinWheelReactHandle>(null);
194
+
195
+ const segments: SegmentConfig[] = [
196
+ { label: 'Free Gift', color: '#55efc4', icon: '🎁', title: 'Free Gift', worth: '₹299' },
197
+ { label: 'Surprise', color: '#81ecec', icon: '✨', title: 'Surprise', worth: '₹199' },
198
+ { label: 'Better Luck', color: '#dfe6e9', icon: '🌟' },
199
+ ];
200
+
201
+ return (
202
+ <SpinWheelReact
203
+ ref={ref}
204
+ headerTitle="Spin & Win"
205
+ segments={segments}
206
+ onSpinEnd={(prize: SegmentConfig, idx: number) => console.log('Won:', prize, idx)}
207
+ />
208
+ );
209
+ }
210
+ ```
211
+
212
+ ### Option B: Factory function (advanced)
213
+
214
+ If you need more control or want to avoid the `/react` subpath:
215
+
216
+ ```tsx
217
+ import React, { useRef } from 'react';
218
+ import { createSpinWheelReact, type SpinWheelReactHandle } from '@zencemarketing/spin-scratch-sdk';
219
+
220
+ const SpinWheelReact = createSpinWheelReact(React);
221
+
222
+ export default function App() {
223
+ const ref = useRef<SpinWheelReactHandle>(null);
224
+
225
+ return (
226
+ <SpinWheelReact
227
+ ref={ref}
228
+ headerTitle="Spin & Win"
229
+ segments={[
230
+ { label: 'Free Gift', color: '#55efc4', icon: '🎁', title: 'Free Gift', worth: '₹299' },
231
+ { label: 'Surprise', color: '#81ecec', icon: '✨', title: 'Surprise', worth: '₹199' },
232
+ ]}
233
+ onSpinEnd={(prize, idx) => console.log('Won:', prize, idx)}
234
+ />
235
+ );
236
+ }
237
+ ```
238
+
239
+ ### Option B: Use Vanilla API inside `useEffect`
240
+
241
+ This gives you full control, but you must clean up on unmount.
242
+
243
+ ```jsx
244
+ import React, { useEffect } from 'react';
245
+ import { SpinWheel } from '@zencemarketing/spin-scratch-sdk';
246
+
247
+ export default function App() {
248
+ useEffect(() => {
249
+ const wheel = SpinWheel.init({
250
+ container: '#spin-wheel-container',
251
+ segments: [
252
+ { label: 'Prize 1', color: '#55efc4' },
253
+ { label: 'Prize 2', color: '#81ecec' },
254
+ ],
255
+ });
256
+
257
+ return () => {
258
+ wheel.destroy();
259
+ };
260
+ }, []);
261
+
262
+ return <div id="spin-wheel-container" />;
263
+ }
264
+ ```
265
+
266
+ Tip (React StrictMode): React may mount/unmount twice in development. Always keep the cleanup (`destroy()`) to avoid duplicate widgets.
267
+
268
+ ---
269
+
270
+ ## ScratchCard quick start (Vanilla)
271
+
272
+ ```js
273
+ import { ScratchCard } from '@zencemarketing/spin-scratch-sdk';
274
+
275
+ const card = ScratchCard.init({
276
+ container: '#scratch-card-container',
277
+ prize: { name: 'Gift Voucher', icon: '🎁', label: 'Congratulations!' },
278
+ onReveal: (prize) => console.log('Revealed:', prize),
279
+ });
280
+ ```
281
+
282
+ ## ScratchCard quick start (React)
283
+
284
+ ```tsx
285
+ import React, { useRef } from 'react';
286
+ import {
287
+ ScratchCardReact,
288
+ type ScratchCardReactHandle,
289
+ type ScratchPrize,
290
+ } from '@zencemarketing/spin-scratch-sdk/react';
291
+
292
+ export default function App() {
293
+ const ref = useRef<ScratchCardReactHandle>(null);
294
+
295
+ return (
296
+ <ScratchCardReact
297
+ ref={ref}
298
+ prize={{ name: 'Gift Voucher', icon: '🎁', label: 'Congratulations!' }}
299
+ onReveal={(prize: ScratchPrize) => console.log('Revealed:', prize)}
300
+ />
301
+ );
302
+ }
303
+ ```
304
+
305
+ Or using the factory pattern:
306
+
307
+ ```tsx
308
+ import React, { useRef } from 'react';
309
+ import { createScratchCardReact, type ScratchCardReactHandle } from '@zencemarketing/spin-scratch-sdk';
310
+
311
+ const ScratchCardReact = createScratchCardReact(React);
312
+
313
+ export default function App() {
314
+ const ref = useRef<ScratchCardReactHandle>(null);
315
+
316
+ return (
317
+ <ScratchCardReact
318
+ ref={ref}
319
+ prize={{ name: 'Gift Voucher', icon: '🎁', label: 'Congratulations!' }}
320
+ onReveal={(prize) => console.log('Revealed:', prize)}
321
+ />
322
+ );
323
+ }
324
+ ```
325
+
326
+ ## SpinWheel API Reference
327
+
328
+ ### `SpinWheel.init(options)` → `SpinWheelInstance`
329
+
330
+ #### Core Options
331
+
332
+ | Option | Type | Default | Description |
333
+ |---|---|---|---|
334
+ | `container` | `string \| HTMLElement` | *required* | CSS selector or DOM element to mount into |
335
+ | `segments` | `Array<Segment>` | *required* | Array of prize segments (min 2) |
336
+ | `winningIndex` | `number \| null` | `null` | Pre-determined winning index. Overridden by `forceIndex` in `.spin()` |
337
+
338
+ #### Spin Behavior
339
+
340
+ | Option | Type | Default | Description |
341
+ |---|---|---|---|
342
+ | `spinDuration` | `number` | `5000` | Total spin animation duration (ms) |
343
+ | `minSpins` | `number` | `5` | Minimum full rotations |
344
+ | `maxSpins` | `number` | `8` | Maximum full rotations |
345
+ | `spinLimit` | `number \| null` | `null` | Max spins allowed (`null` = unlimited) |
346
+
347
+ #### Header UI
348
+
349
+ | Option | Type | Default | Description |
350
+ |---|---|---|---|
351
+ | `headerTitle` | `string \| null` | `null` | Title above the wheel |
352
+ | `headerSubtitle` | `string \| null` | `null` | Subtitle below the title |
353
+ | `titleColor` | `string \| null` | `null` | Custom title color (uses `theme.goldLight` if null) |
354
+ | `subtitleColor` | `string \| null` | `null` | Custom subtitle color (uses `theme.textMuted` if null) |
355
+
356
+ #### Hub / Center
357
+
358
+ | Option | Type | Default | Description |
359
+ |---|---|---|---|
360
+ | `hubLabel` | `string` | `'Spin & win'` | Text inside the center hub |
361
+ | `hubIcon` | `string` | `'▲'` | Icon inside the center hub |
362
+
363
+ #### Spin Button
364
+
365
+ | Option | Type | Default | Description |
366
+ |---|---|---|---|
367
+ | `showButton` | `boolean` | `true` | Show the external SPIN! button |
368
+ | `buttonText` | `string` | `'SPIN!'` | Button label text |
369
+
370
+ #### Wheel Appearance
371
+
372
+ | Option | Type | Default | Description |
373
+ |---|---|---|---|
374
+ | `backgroundColor` | `string \| null` | `null` | Container background color |
375
+ | `backgroundImage` | `string \| null` | `null` | Background image URL |
376
+ | `ringColor` | `string \| null` | `null` | Wheel ring color |
377
+ | `ringShadow` | `boolean` | `true` | Enable ring shadow/glow |
378
+ | `ringAnimation` | `boolean` | `true` | Enable pulsing ring animation |
379
+ | `pointerColor` | `string \| null` | `null` | Pointer/arrow color |
380
+ | `buttonColor` | `string \| null` | `null` | Spin button gradient color |
381
+ | `buttonShadow` | `boolean` | `true` | Enable button shadow |
382
+ | `buttonAnimation` | `boolean` | `true` | Enable button hover/active animations |
383
+
384
+ #### Win Card
385
+
386
+ | Option | Type | Default | Description |
387
+ |---|---|---|---|
388
+ | `showWinCard` | `boolean` | `true` | Show win card overlay after spin |
389
+ | `generateCode` | `boolean` | `true` | Auto-generate a random coupon code |
390
+ | `codeLength` | `number` | `9` | Length of auto-generated codes |
391
+ | `redeemUrl` | `string \| null` | `null` | URL for redeem button |
392
+ | `winCardBrandLabel` | `string \| null` | `null` | Brand label on win card (uses `hubLabel` if null) |
393
+ | `winCardWorthLabel` | `string` | `'WORTH'` | Label before worth value |
394
+ | `winCardRedeemButtonText` | `string` | `'Redeem Now'` | Redeem button text |
395
+ | `winCardRedeemButtonColorTop` | `string` | `'#15803d'` | Redeem button gradient top color |
396
+ | `winCardRedeemButtonColorBottom` | `string` | `'#166534'` | Redeem button gradient bottom color |
397
+ | `winCardRedeemButtonTextColor` | `string` | `'#ffffff'` | Redeem button text color |
398
+
399
+ #### Confetti
400
+
401
+ | Option | Type | Default | Description |
402
+ |---|---|---|---|
403
+ | `confettiOnWin` | `boolean` | `true` | Show confetti on win |
404
+ | `confettiColors` | `string[] \| null` | `null` | Array of confetti colors |
405
+ | `confettiCount` | `number` | `40` | Number of confetti pieces |
406
+
407
+ #### Theme
408
+
409
+ | Option | Type | Default | Description |
410
+ |---|---|---|---|
411
+ | `theme` | `object` | See below | Color theme overrides |
412
+
413
+ #### Callbacks
414
+
415
+ | Option | Type | Default | Description |
416
+ |---|---|---|---|
417
+ | `onSpinStart` | `function` | `null` | `(winIndex) => void` |
418
+ | `onSpinEnd` | `function` | `null` | `(prize, winIndex) => void` |
419
+ | `onRedeem` | `function` | `null` | `(prize) => void` |
420
+ | `onSpinLimitReached` | `function` | `null` | `(count) => void` |
421
+
422
+ ### Segment Object
423
+
424
+ ```js
425
+ {
426
+ label: 'Free Gift', // text on the wheel (required)
427
+ color: '#55efc4', // segment background color (required)
428
+ icon: '🎁', // emoji/icon (optional)
429
+ title: 'Free Gift Voucher', // title on win card (optional, falls back to label)
430
+ worth: '₹299', // "WORTH ₹299" on win card (optional)
431
+ code: 'FIXEDCODE', // fixed code – overrides auto-generate (optional)
432
+ }
433
+ ```
434
+
435
+ ### SpinWheel Instance Methods
436
+
437
+ | Method | Description |
438
+ |---|---|
439
+ | `.spin(forceIndex?)` | Trigger a spin; optionally force which segment wins |
440
+ | `.updateSegments(segments[])` | Replace all segments at runtime and re-render |
441
+ | `.hideWinCard()` | Programmatically close the win card overlay |
442
+ | `.updateOptions(opts)` | Update configuration options at runtime |
443
+ | `.resetSpinCount(count?)` | Reset the spin count (default: 0) |
444
+ | `.destroy()` | Remove all SDK-created DOM and clean up |
445
+
446
+ ### SpinWheel Instance Getters
447
+
448
+ | Getter | Type | Description |
449
+ |---|---|---|
450
+ | `.lastWonPrize` | `Segment \| null` | The last prize that was won |
451
+ | `.lastWonIndex` | `number` | The last won segment index (-1 if none) |
452
+ | `.spinCount` | `number` | Total spins performed |
453
+ | `.remainingSpins` | `number \| null` | Remaining spins (`null` if unlimited) |
454
+
455
+ ### SpinWheel Theme Defaults
456
+
457
+ ```js
458
+ {
459
+ gold: '#e8c547',
460
+ goldLight: '#f5d76e',
461
+ goldDark: '#c9a227',
462
+ bgDark: '#0d0d12',
463
+ textMuted: '#9ca3af',
464
+ }
465
+ ```
466
+
467
+ ---
468
+
469
+ ## ScratchCard API Reference
470
+
471
+ ### `ScratchCard.init(options)` → `ScratchCardInstance`
472
+
473
+ #### Core Options
474
+
475
+ | Option | Type | Default | Description |
476
+ |---|---|---|---|
477
+ | `container` | `string \| HTMLElement` | *required* | CSS selector or DOM element |
478
+ | `prize` | `Prize` | *required* | Prize object: `{ name, icon, label }` |
479
+
480
+ #### Header UI
481
+
482
+ | Option | Type | Default | Description |
483
+ |---|---|---|---|
484
+ | `headerTitle` | `string` | `'Scratch the Card...'` | Header text |
485
+ | `headerTitleColor` | `string \| null` | `null` | Header title color |
486
+
487
+ #### Instruction
488
+
489
+ | Option | Type | Default | Description |
490
+ |---|---|---|---|
491
+ | `instruction` | `string` | `'Scratch the card...'` | Instruction text |
492
+ | `instructionColor` | `string \| null` | `null` | Instruction text color |
493
+
494
+ #### Scratch Behavior
495
+
496
+ | Option | Type | Default | Description |
497
+ |---|---|---|---|
498
+ | `revealThreshold` | `number` | `55` | % scratched to auto-reveal |
499
+ | `brushSize` | `number` | `28` | Scratch brush size in px |
500
+
501
+ #### Coin / Eraser Tool
502
+
503
+ | Option | Type | Default | Description |
504
+ |---|---|---|---|
505
+ | `coinSize` | `number` | `56` | Coin cursor size in px |
506
+ | `showCoin` | `boolean` | `true` | Show coin cursor |
507
+ | `coinIcon` | `string` | `'$'` | Icon on coin |
508
+ | `coinGradientStart` | `string \| null` | `null` | Coin gradient start color |
509
+ | `coinGradientEnd` | `string \| null` | `null` | Coin gradient end color |
510
+
511
+ #### Card Appearance
512
+
513
+ | Option | Type | Default | Description |
514
+ |---|---|---|---|
515
+ | `cardBackground` | `string \| null` | `null` | Card background CSS |
516
+ | `cardShadow` | `boolean` | `true` | Enable card shadow |
517
+ | `cardBorderRadius` | `number` | `24` | Card border radius in px |
518
+
519
+ #### Scratch Zone
520
+
521
+ | Option | Type | Default | Description |
522
+ |---|---|---|---|
523
+ | `scratchZoneBackground` | `string \| null` | `null` | Scratch zone background |
524
+ | `scratchZoneShadow` | `boolean` | `true` | Enable zone shadow |
525
+ | `scratchZoneBorderRadius` | `number` | `20` | Zone border radius in px |
526
+
527
+ #### Scratch Layer
528
+
529
+ | Option | Type | Default | Description |
530
+ |---|---|---|---|
531
+ | `scratchLayerColor` | `string` | `'rgb(150,130,180)'` | Scratch layer color |
532
+ | `scratchLayerSparkles` | `boolean` | `true` | Show sparkles on layer |
533
+ | `scratchLayerSparkleCount` | `number` | `40` | Number of sparkles |
534
+
535
+ #### Prize Display
536
+
537
+ | Option | Type | Default | Description |
538
+ |---|---|---|---|
539
+ | `prizeTextColor` | `string \| null` | `null` | Prize label color |
540
+ | `prizeNameColor` | `string \| null` | `null` | Prize name color |
541
+ | `prizeIconBackground` | `string \| null` | `null` | Prize icon background |
542
+
543
+ #### Gift Icon Hint
544
+
545
+ | Option | Type | Default | Description |
546
+ |---|---|---|---|
547
+ | `showGiftIcon` | `boolean` | `true` | Show gift icon hint |
548
+ | `giftIcon` | `string` | `'🎁'` | Gift icon emoji |
549
+ | `giftIconBackground` | `string \| null` | `null` | Gift icon background |
550
+
551
+ #### Modal
552
+
553
+ | Option | Type | Default | Description |
554
+ |---|---|---|---|
555
+ | `showModal` | `boolean` | `true` | Show modal after reveal |
556
+ | `modalTitle` | `string` | `'Congratulations!'` | Modal title |
557
+ | `modalTitleColor` | `string \| null` | `null` | Modal title color |
558
+ | `modalButtonText` | `string` | `'Claim your'` | Modal button text prefix |
559
+ | `modalButtonColor` | `string \| null` | `null` | Modal button background |
560
+ | `modalButtonTextColor` | `string \| null` | `null` | Modal button text color |
561
+ | `modalBackdropBlur` | `boolean` | `true` | Enable backdrop blur |
562
+
563
+ #### Confetti
564
+
565
+ | Option | Type | Default | Description |
566
+ |---|---|---|---|
567
+ | `confettiEnabled` | `boolean` | `true` | Enable confetti |
568
+ | `confettiColors` | `string[] \| null` | `null` | Confetti colors array |
569
+ | `confettiCount` | `number` | `100` | Number of confetti pieces |
570
+ | `confettiDuration` | `number` | `5500` | Confetti duration in ms |
571
+
572
+ #### Animation
573
+
574
+ | Option | Type | Default | Description |
575
+ |---|---|---|---|
576
+ | `animationType` | `string` | `'default'` | `'default'` \| `'bounce'` \| `'none'` |
577
+ | `animationDuration` | `number` | `600` | Animation duration in ms |
578
+
579
+ #### Theme
580
+
581
+ | Option | Type | Default | Description |
582
+ |---|---|---|---|
583
+ | `theme` | `object` | See below | Color theme overrides |
584
+
585
+ #### Callbacks
586
+
587
+ | Option | Type | Default | Description |
588
+ |---|---|---|---|
589
+ | `onScratchStart` | `function` | `null` | `() => void` |
590
+ | `onScratchProgress` | `function` | `null` | `(percent) => void` |
591
+ | `onReveal` | `function` | `null` | `(prize) => void` |
592
+ | `onClaim` | `function` | `null` | `(prize) => void` |
593
+
594
+ ### Prize Object
595
+
596
+ ```js
597
+ {
598
+ name: 'iPhone 16', // Prize name (required)
599
+ icon: '📱', // Emoji/icon (optional)
600
+ label: 'Congratulations!', // Label above prize name (optional)
601
+ }
602
+ ```
603
+
604
+ ### ScratchCard Instance Methods
605
+
606
+ | Method | Description |
607
+ |---|---|
608
+ | `.reveal()` | Programmatically reveal the prize |
609
+ | `.reset()` | Reset the card for replay |
610
+ | `.hideModal()` | Hide the win modal |
611
+ | `.showModal()` | Show the win modal |
612
+ | `.updatePrize(prize)` | Update prize at runtime and reset card |
613
+ | `.updateOptions(opts)` | Update configuration at runtime |
614
+ | `.destroy()` | Remove all SDK-created DOM and clean up |
615
+
616
+ ### ScratchCard Instance Getters
617
+
618
+ | Getter | Type | Description |
619
+ |---|---|---|
620
+ | `.scratchPercent` | `number` | Current scratch progress (0-100) |
621
+ | `.hasRevealed` | `boolean` | Whether the prize has been revealed |
622
+ | `.prize` | `Prize \| null` | Current prize object |
623
+
624
+ ### ScratchCard Theme Defaults
625
+
626
+ ```js
627
+ {
628
+ purpleDark: '#4a2c6a',
629
+ purpleMid: '#6b4a8a',
630
+ purpleLight: '#8b6baa',
631
+ gold: '#d4a84b',
632
+ goldLight: '#e8c547',
633
+ goldDark: '#b8923a',
634
+ white: '#ffffff',
635
+ textDark: '#2d2d2d',
636
+ textMuted: '#6b6b6b',
637
+ }
638
+ ```
639
+
640
+ ---
641
+
642
+ ## React Ref Methods
643
+
644
+ ### SpinWheelReact
645
+
646
+ | Method / Getter | Description |
647
+ |---|---|
648
+ | `ref.current.spin(forceIndex?)` | Trigger a spin |
649
+ | `ref.current.updateSegments(segs)` | Replace segments |
650
+ | `ref.current.hideWinCard()` | Close win card |
651
+ | `ref.current.updateOptions(opts)` | Update config |
652
+ | `ref.current.resetSpinCount(count?)` | Reset spin count |
653
+ | `ref.current.spinCount` | Current spin count |
654
+ | `ref.current.remainingSpins` | Remaining spins |
655
+ | `ref.current.lastWonPrize` | Last won prize |
656
+ | `ref.current.lastWonIndex` | Last won index |
657
+ | `ref.current.instance` | Raw SpinWheelInstance |
658
+
659
+ ### ScratchCardReact
660
+
661
+ | Method / Getter | Description |
662
+ |---|---|
663
+ | `ref.current.reveal()` | Reveal prize |
664
+ | `ref.current.reset()` | Reset card |
665
+ | `ref.current.hideModal()` | Hide modal |
666
+ | `ref.current.showModal()` | Show modal |
667
+ | `ref.current.updatePrize(prize)` | Update prize |
668
+ | `ref.current.updateOptions(opts)` | Update config |
669
+ | `ref.current.scratchPercent` | Scratch progress |
670
+ | `ref.current.hasRevealed` | Is revealed? |
671
+ | `ref.current.prize` | Current prize |
672
+ | `ref.current.instance` | Raw ScratchCardInstance |
673
+
674
+ ---
675
+
676
+ ## File Structure
677
+
678
+ ```
679
+ sdk/
680
+ ├── src/
681
+ │ ├── SpinWheel.js ← SpinWheel Core SDK (vanilla JS)
682
+ │ ├── SpinWheelReact.js ← SpinWheel React wrapper (factory)
683
+ │ ├── ScratchCard.js ← ScratchCard Core SDK (vanilla JS)
684
+ │ ├── ScratchCardReact.js ← ScratchCard React wrapper (factory)
685
+ │ ├── styles.js ← SpinWheel styles
686
+ │ ├── scratchStyles.js ← ScratchCard styles
687
+ │ ├── index.js ← Main barrel export
688
+ │ ├── react.js ← React subpath entry (pre-wires factories)
689
+ │ └── utils.js ← Shared utilities
690
+ ├── dist/ ← Built SDK files
691
+ │ ├── spin-wheel-sdk.umd.js ← UMD (browser <script>)
692
+ │ ├── spin-wheel-sdk.umd.min.js ← UMD minified
693
+ │ ├── spin-wheel-sdk.esm.mjs ← ESM (import/export)
694
+ │ ├── spin-wheel-sdk.cjs.js ← CommonJS (require)
695
+ │ ├── spin-wheel-sdk.d.ts ← TypeScript declarations (main)
696
+ │ ├── react.esm.mjs ← React subpath ESM
697
+ │ ├── react.cjs.js ← React subpath CJS
698
+ │ └── react.d.ts ← TypeScript declarations (React)
699
+ ├── types/ ← Source TypeScript declarations (copied into dist/)
700
+ │ ├── spin-wheel-sdk.d.ts
701
+ │ └── react.d.ts
702
+ ├── scripts/ ← Build helpers
703
+ │ └── copy-types.mjs
704
+ ├── demo-vanilla-combined.html ← Combined Vanilla JS demo
705
+ ├── demo-react-combined.html ← Combined React demo
706
+ ├── package.json
707
+ ├── CHANGELOG.md
708
+ ├── LICENSE
709
+ └── README.md
710
+ ```
711
+
712
+ ---
713
+
714
+ ## License
715
+
716
+ MIT