@pcampus/reward-widget 0.1.0 → 0.2.0

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 CHANGED
@@ -18,12 +18,13 @@ secrets, coupon codes, or authoritative server logic.
18
18
 
19
19
  ## Status
20
20
 
21
- Initial public scaffold. Component exports and registry publishing will be added
22
- in the next implementation phase.
21
+ Public UI catalog for `spin`, `scratch`, `pick-card`, `mystery-box`, and `quiz`.
22
+ Presentation matches the Merchant Portal preview package. This package is still
23
+ UI-only: no draw, claims, or campaign APIs.
23
24
 
24
25
  ## Public support matrix
25
26
 
26
- The UI catalog targets `spin`, `scratch`, `pick-card`, `mystery-box`, `slot`,
27
+ The UI catalog targets `spin`, `scratch`, `pick-card`, `mystery-box`,
27
28
  and `quiz`. This package exports presentation primitives, theme configuration,
28
29
  and public types only. Reward service APIs, server-side authority, claims,
29
30
  webhooks, secrets, and coupon data remain outside the package.
@@ -49,7 +50,29 @@ const html = renderRewardWidgetMarkup({
49
50
  });
50
51
  ```
51
52
 
52
- ### Client DOM Mount
53
+ ### Interactive widgets (player chrome)
54
+
55
+ ```typescript
56
+ import { mountInteractiveWidget } from '@pcampus/reward-widget';
57
+
58
+ const root = document.getElementById('widget-root');
59
+ mountInteractiveWidget(root, {
60
+ widgetType: 'pick-card',
61
+ rewards: [
62
+ { name: 'ส่วนลด 100 บาท', weight: 40 },
63
+ { name: 'Lucky Bonus', weight: 30 },
64
+ { name: '100 Points', weight: 20 },
65
+ ],
66
+ theme: { brandPrimaryColor: '#4b7fd4' },
67
+ });
68
+ ```
69
+
70
+ SSR equivalent: `renderInteractiveWidgetMarkup({ widgetType, rewards, theme })`.
71
+ Per-type helpers: `renderSpinWheelMarkup` / `mountSpinWheel`, and the same pattern for scratch, pick-card, mystery-box, and quiz.
72
+
73
+ Open `examples/interactive-catalog.html` via a local static server to view all five types.
74
+
75
+ ### Client DOM Mount (chrome shell)
53
76
 
54
77
  ```typescript
55
78
  import { mountRewardWidget } from '@pcampus/reward-widget';
package/dist/index.d.ts CHANGED
@@ -8,9 +8,13 @@ export type RewardWidgetTheme = {
8
8
  pageBackgroundColor?: string;
9
9
  wheelBorderColor?: string;
10
10
  sliceColors?: readonly string[];
11
+ hubBackgroundColor?: string;
12
+ textColor?: string;
13
+ promptColor?: string;
14
+ buttonTextColor?: string;
11
15
  };
12
16
  export { mountRewardWidget, renderRewardWidgetMarkup } from "./components.js";
13
17
  export type { RewardWidgetProps, RewardWidgetState } from "./components.js";
14
- export { createRewardWidgetTheme, defaultRewardWidgetTheme, rewardWidgetThemeCss, } from "./theme.js";
18
+ export { createRewardWidgetTheme, defaultRewardWidgetTheme, pcampusStudioDarkTheme, rewardWidgetThemeCss, pcampusStudioThemeCss, } from "./theme.js";
15
19
  export type { RewardWidgetThemeTokens } from "./theme.js";
16
20
  export * from "./widgets/index.js";
package/dist/index.js CHANGED
@@ -6,5 +6,5 @@ export const WIDGET_TYPES = [
6
6
  "quiz",
7
7
  ];
8
8
  export { mountRewardWidget, renderRewardWidgetMarkup } from "./components.js";
9
- export { createRewardWidgetTheme, defaultRewardWidgetTheme, rewardWidgetThemeCss, } from "./theme.js";
9
+ export { createRewardWidgetTheme, defaultRewardWidgetTheme, pcampusStudioDarkTheme, rewardWidgetThemeCss, pcampusStudioThemeCss, } from "./theme.js";
10
10
  export * from "./widgets/index.js";
package/dist/theme.d.ts CHANGED
@@ -13,6 +13,11 @@ export type RewardWidgetThemeTokens = {
13
13
  spacing: string;
14
14
  motion: string;
15
15
  };
16
+ /** Default Pcampus Studio Light Theme Tokens */
16
17
  export declare const defaultRewardWidgetTheme: RewardWidgetThemeTokens;
18
+ /** Official @pcampus-studio/ui Dark Theme Tokens */
19
+ export declare const pcampusStudioDarkTheme: RewardWidgetThemeTokens;
17
20
  export declare function createRewardWidgetTheme(overrides?: Partial<RewardWidgetThemeTokens>): RewardWidgetThemeTokens;
18
21
  export declare function rewardWidgetThemeCss(overrides?: Partial<RewardWidgetThemeTokens>): string;
22
+ /** Generates CSS custom properties matching @pcampus-studio/ui design system */
23
+ export declare function pcampusStudioThemeCss(mode?: "light" | "dark"): string;
package/dist/theme.js CHANGED
@@ -1,3 +1,4 @@
1
+ /** Default Pcampus Studio Light Theme Tokens */
1
2
  export const defaultRewardWidgetTheme = {
2
3
  primary: "#2952a3",
3
4
  primaryHover: "#1e3a8a",
@@ -13,6 +14,22 @@ export const defaultRewardWidgetTheme = {
13
14
  spacing: "1rem",
14
15
  motion: "160ms",
15
16
  };
17
+ /** Official @pcampus-studio/ui Dark Theme Tokens */
18
+ export const pcampusStudioDarkTheme = {
19
+ primary: "#4b7fd4",
20
+ primaryHover: "#6b9fe4",
21
+ surface: "#1c1c1b",
22
+ background: "#1a1a19",
23
+ border: "rgba(240, 239, 237, 0.14)",
24
+ focus: "#e8a96a",
25
+ disabled: "#6a6a67",
26
+ warning: "#d4884d",
27
+ blocked: "#e74c3c",
28
+ success: "#3dab6a",
29
+ radius: "1rem",
30
+ spacing: "1rem",
31
+ motion: "160ms",
32
+ };
16
33
  export function createRewardWidgetTheme(overrides = {}) {
17
34
  return { ...defaultRewardWidgetTheme, ...overrides };
18
35
  }
@@ -20,3 +37,10 @@ export function rewardWidgetThemeCss(overrides = {}) {
20
37
  const theme = createRewardWidgetTheme(overrides);
21
38
  return `:root{--pcampus-widget-primary:${theme.primary};--pcampus-widget-primary-hover:${theme.primaryHover};--pcampus-widget-surface:${theme.surface};--pcampus-widget-background:${theme.background};--pcampus-widget-border:${theme.border};--pcampus-widget-focus:${theme.focus};--pcampus-widget-disabled:${theme.disabled};--pcampus-widget-warning:${theme.warning};--pcampus-widget-blocked:${theme.blocked};--pcampus-widget-success:${theme.success};--pcampus-widget-radius:${theme.radius};--pcampus-widget-spacing:${theme.spacing};--pcampus-widget-motion:${theme.motion}}`;
22
39
  }
40
+ /** Generates CSS custom properties matching @pcampus-studio/ui design system */
41
+ export function pcampusStudioThemeCss(mode = "dark") {
42
+ if (mode === "dark") {
43
+ return `:root{--pcampus-color-primary:#4b7fd4;--pcampus-color-primary-hover:#6b9fe4;--pcampus-color-accent:#d4884d;--pcampus-color-accent-light:#e8a96a;--pcampus-color-success:#3dab6a;--pcampus-color-error:#e74c3c;--pcampus-color-neutral:#6b7a9e;--pcampus-color-surface:#0f0f0e;--pcampus-color-surface-subtle:#1a1a19;--pcampus-color-surface-card:#1c1c1b;--pcampus-color-text:#f0efed;--pcampus-color-text-secondary:#a8a8a5;--pcampus-color-border:rgba(240,239,237,0.08);--pcampus-color-border-strong:rgba(240,239,237,0.16);--pcampus-font-sans:-apple-system,BlinkMacSystemFont,"Inter",sans-serif;}`;
44
+ }
45
+ return `:root{--pcampus-color-primary:#2952a3;--pcampus-color-primary-hover:#4b7fd4;--pcampus-color-accent:#c87533;--pcampus-color-accent-light:#e8a96a;--pcampus-color-success:#2a7a4b;--pcampus-color-error:#c0392b;--pcampus-color-neutral:#8f9bb3;--pcampus-color-surface:#f9f8f6;--pcampus-color-surface-subtle:#f3f2ef;--pcampus-color-surface-card:#ffffff;--pcampus-color-text:#0d0d0c;--pcampus-color-text-secondary:#4a4a48;--pcampus-color-border:rgba(13,13,12,0.08);--pcampus-color-border-strong:rgba(13,13,12,0.16);--pcampus-font-sans:-apple-system,BlinkMacSystemFont,"Inter",sans-serif;}`;
46
+ }
@@ -0,0 +1,13 @@
1
+ import type { RewardWidgetTheme } from "../index.js";
2
+ export declare const DEFAULT_WIDGET_PRIMARY = "#4b7fd4";
3
+ export declare const DEFAULT_WIDGET_SURFACE = "#1c1c1b";
4
+ export declare const DEFAULT_WIDGET_BORDER = "rgba(240, 239, 237, 0.14)";
5
+ /** Shared merchant chrome: Primary / page background / widget border. */
6
+ export declare function resolveWidgetChrome(theme?: RewardWidgetTheme): {
7
+ primary: string;
8
+ surface: string;
9
+ border: string;
10
+ };
11
+ export declare function mixPrimary(primary: string, percent: number): string;
12
+ export declare function parseHexRgb(hex: string): [number, number, number] | null;
13
+ export declare function scaleRgb(rgb: [number, number, number], factor: number): string;
@@ -0,0 +1,25 @@
1
+ export const DEFAULT_WIDGET_PRIMARY = "#4b7fd4";
2
+ export const DEFAULT_WIDGET_SURFACE = "#1c1c1b";
3
+ export const DEFAULT_WIDGET_BORDER = "rgba(240, 239, 237, 0.14)";
4
+ /** Shared merchant chrome: Primary / page background / widget border. */
5
+ export function resolveWidgetChrome(theme) {
6
+ const primary = theme?.brandPrimaryColor?.trim() || DEFAULT_WIDGET_PRIMARY;
7
+ const surface = theme?.pageBackgroundColor?.trim() || DEFAULT_WIDGET_SURFACE;
8
+ const explicitBorder = theme?.wheelBorderColor?.trim();
9
+ const border = explicitBorder ||
10
+ (theme?.brandPrimaryColor?.trim() ? primary : DEFAULT_WIDGET_BORDER);
11
+ return { primary, surface, border };
12
+ }
13
+ export function mixPrimary(primary, percent) {
14
+ return `color-mix(in srgb, ${primary} ${percent}%, transparent)`;
15
+ }
16
+ export function parseHexRgb(hex) {
17
+ const match = hex.trim().replace("#", "").match(/^([0-9a-f]{6})$/i);
18
+ if (!match)
19
+ return null;
20
+ const value = Number.parseInt(match[1], 16);
21
+ return [(value >> 16) & 255, (value >> 8) & 255, value & 255];
22
+ }
23
+ export function scaleRgb(rgb, factor) {
24
+ return `rgb(${rgb.map((channel) => Math.max(0, Math.min(255, Math.round(channel * factor)))).join(",")})`;
25
+ }
@@ -0,0 +1,13 @@
1
+ export type CouponSuccessPayload = {
2
+ rewardName: string;
3
+ couponCode?: string | null;
4
+ };
5
+ /**
6
+ * Locked G-042 style: D (left stub + big prize) + A (transparent tear cutouts via mask).
7
+ */
8
+ export declare function couponSuccessCss(): string;
9
+ export declare function couponSuccessCardHtml(payload: CouponSuccessPayload): string;
10
+ /**
11
+ * Show shared coupon-shaped success overlay with puff. Dismiss via OK/close only.
12
+ */
13
+ export declare function showCouponSuccess(payload: CouponSuccessPayload): void;
@@ -0,0 +1,256 @@
1
+ import { textEscape } from "./types.js";
2
+ /** SVG mask: ticket rect with true transparent scallop / bite cutouts (evenodd). */
3
+ const COUPON_TEAR_MASK = encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 160" preserveAspectRatio="none">` +
4
+ `<path fill="white" fill-rule="evenodd" d="` +
5
+ `M0 0H400V160H0Z` +
6
+ /* center bites — full circles on left/right edges */
7
+ `M0 80m-13 0a13 13 0 1 0 26 0a13 13 0 1 0-26 0` +
8
+ `M400 80m-13 0a13 13 0 1 0 26 0a13 13 0 1 0-26 0` +
9
+ /* left scallops */
10
+ `M0 18m-7.5 0a7.5 7.5 0 1 0 15 0a7.5 7.5 0 1 0-15 0` +
11
+ `M0 42m-7.5 0a7.5 7.5 0 1 0 15 0a7.5 7.5 0 1 0-15 0` +
12
+ `M0 118m-7.5 0a7.5 7.5 0 1 0 15 0a7.5 7.5 0 1 0-15 0` +
13
+ `M0 142m-7.5 0a7.5 7.5 0 1 0 15 0a7.5 7.5 0 1 0-15 0` +
14
+ /* right scallops */
15
+ `M400 18m-7.5 0a7.5 7.5 0 1 0 15 0a7.5 7.5 0 1 0-15 0` +
16
+ `M400 42m-7.5 0a7.5 7.5 0 1 0 15 0a7.5 7.5 0 1 0-15 0` +
17
+ `M400 118m-7.5 0a7.5 7.5 0 1 0 15 0a7.5 7.5 0 1 0-15 0` +
18
+ `M400 142m-7.5 0a7.5 7.5 0 1 0 15 0a7.5 7.5 0 1 0-15 0` +
19
+ `"/></svg>`);
20
+ /**
21
+ * Locked G-042 style: D (left stub + big prize) + A (transparent tear cutouts via mask).
22
+ */
23
+ export function couponSuccessCss() {
24
+ return `
25
+ .pcampus-coupon-overlay {
26
+ position: fixed;
27
+ inset: 0;
28
+ z-index: 100000;
29
+ display: flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+ padding: 1.25rem;
33
+ background: rgba(13, 13, 12, 0.62);
34
+ box-sizing: border-box;
35
+ }
36
+ .pcampus-coupon-shell {
37
+ display: flex;
38
+ flex-direction: column;
39
+ align-items: center;
40
+ gap: 1rem;
41
+ width: min(100%, 26rem);
42
+ animation: pcampus-coupon-puff 0.55s cubic-bezier(0.175, 0.885, 0.32, 1.275) both;
43
+ }
44
+ @keyframes pcampus-coupon-puff {
45
+ 0% { transform: scale(0.72) rotate(-1.5deg); opacity: 0; filter: blur(2px); }
46
+ 55% { transform: scale(1.05) rotate(0.4deg); opacity: 1; filter: blur(0); }
47
+ 100% { transform: scale(1) rotate(0deg); opacity: 1; filter: blur(0); }
48
+ }
49
+ @media (prefers-reduced-motion: reduce) {
50
+ .pcampus-coupon-shell {
51
+ animation: pcampus-coupon-fade 0.2s ease both;
52
+ }
53
+ @keyframes pcampus-coupon-fade {
54
+ from { opacity: 0; }
55
+ to { opacity: 1; }
56
+ }
57
+ }
58
+ .pcampus-coupon-ticket {
59
+ position: relative;
60
+ display: flex;
61
+ width: 100%;
62
+ min-height: 9.75rem;
63
+ background: #fff;
64
+ filter: drop-shadow(0 18px 28px rgba(15, 23, 42, 0.4));
65
+ -webkit-mask-image: url("data:image/svg+xml,${COUPON_TEAR_MASK}");
66
+ mask-image: url("data:image/svg+xml,${COUPON_TEAR_MASK}");
67
+ -webkit-mask-size: 100% 100%;
68
+ mask-size: 100% 100%;
69
+ -webkit-mask-repeat: no-repeat;
70
+ mask-repeat: no-repeat;
71
+ -webkit-mask-position: center;
72
+ mask-position: center;
73
+ }
74
+ .pcampus-coupon-stub {
75
+ flex: 0 0 3.35rem;
76
+ display: flex;
77
+ align-items: center;
78
+ justify-content: center;
79
+ background: linear-gradient(180deg, #1e40af 0%, #1e3a8a 100%);
80
+ color: #eff6ff;
81
+ }
82
+ .pcampus-coupon-stub-label {
83
+ writing-mode: vertical-rl;
84
+ transform: rotate(180deg);
85
+ font-size: 0.7rem;
86
+ font-weight: 800;
87
+ letter-spacing: 0.18em;
88
+ text-transform: uppercase;
89
+ }
90
+ .pcampus-coupon-perforation {
91
+ flex: 0 0 12px;
92
+ background:
93
+ repeating-linear-gradient(
94
+ 180deg,
95
+ transparent 0 6px,
96
+ #93c5fd 6px 8px
97
+ ),
98
+ #fff;
99
+ border-left: 1px dashed #60a5fa;
100
+ border-right: 1px dashed #60a5fa;
101
+ }
102
+ .pcampus-coupon-body {
103
+ position: relative;
104
+ flex: 1 1 auto;
105
+ display: flex;
106
+ flex-direction: column;
107
+ justify-content: center;
108
+ gap: 0.3rem;
109
+ padding: 1rem 1.15rem 0.9rem 1rem;
110
+ background: #fff;
111
+ border: 2px dotted #60a5fa;
112
+ border-left: none;
113
+ margin: 0.45rem 0.55rem 0.45rem 0;
114
+ border-radius: 0 10px 10px 0;
115
+ box-sizing: border-box;
116
+ }
117
+ .pcampus-coupon-pill {
118
+ display: inline-flex;
119
+ align-self: flex-start;
120
+ background: #1e3a8a;
121
+ color: #fff;
122
+ font-size: 0.62rem;
123
+ font-weight: 800;
124
+ letter-spacing: 0.1em;
125
+ text-transform: uppercase;
126
+ padding: 0.22rem 0.55rem;
127
+ border-radius: 9999px;
128
+ }
129
+ .pcampus-coupon-prize {
130
+ font-size: 1.55rem;
131
+ font-weight: 900;
132
+ color: #1d4ed8;
133
+ line-height: 1.15;
134
+ word-break: break-word;
135
+ }
136
+ .pcampus-coupon-sub {
137
+ font-size: 0.82rem;
138
+ font-weight: 700;
139
+ color: #1e3a8a;
140
+ }
141
+ .pcampus-coupon-code-wrap {
142
+ margin-top: 0.2rem;
143
+ }
144
+ .pcampus-coupon-code-label {
145
+ font-size: 0.62rem;
146
+ font-weight: 700;
147
+ color: #64748b;
148
+ letter-spacing: 0.08em;
149
+ text-transform: uppercase;
150
+ }
151
+ .pcampus-coupon-code {
152
+ margin-top: 0.15rem;
153
+ display: inline-block;
154
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
155
+ font-size: 0.98rem;
156
+ font-weight: 800;
157
+ letter-spacing: 0.12em;
158
+ color: #1e3a8a;
159
+ background: #eff6ff;
160
+ border: 1px dashed #60a5fa;
161
+ padding: 0.25rem 0.5rem;
162
+ border-radius: 6px;
163
+ word-break: break-all;
164
+ }
165
+ .pcampus-coupon-footer {
166
+ font-size: 0.68rem;
167
+ color: #64748b;
168
+ font-weight: 500;
169
+ margin-top: 0.15rem;
170
+ }
171
+ .pcampus-coupon-close {
172
+ display: inline-flex;
173
+ align-items: center;
174
+ justify-content: center;
175
+ min-width: 7.5rem;
176
+ padding: 0.6rem 1.35rem;
177
+ font-size: 0.92rem;
178
+ font-weight: 700;
179
+ color: #fff;
180
+ background: #1e3a8a;
181
+ border: none;
182
+ border-radius: 9999px;
183
+ cursor: pointer;
184
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.28);
185
+ }
186
+ .pcampus-coupon-close:hover {
187
+ background: #1e40af;
188
+ }
189
+ .pcampus-coupon-close:focus-visible {
190
+ outline: 2px solid #93c5fd;
191
+ outline-offset: 2px;
192
+ }
193
+ `.trim();
194
+ }
195
+ export function couponSuccessCardHtml(payload) {
196
+ const name = textEscape(payload.rewardName);
197
+ const code = payload.couponCode?.trim();
198
+ const codeBlock = code
199
+ ? `<div class="pcampus-coupon-code-wrap">
200
+ <div class="pcampus-coupon-code-label">รหัสคูปอง</div>
201
+ <div class="pcampus-coupon-code">${textEscape(code)}</div>
202
+ </div>`
203
+ : "";
204
+ return `
205
+ <div class="pcampus-coupon-shell">
206
+ <div class="pcampus-coupon-ticket" role="dialog" aria-modal="true" aria-label="คูปองรางวัล">
207
+ <div class="pcampus-coupon-stub">
208
+ <span class="pcampus-coupon-stub-label">COUPON</span>
209
+ </div>
210
+ <div class="pcampus-coupon-perforation" aria-hidden="true"></div>
211
+ <div class="pcampus-coupon-body">
212
+ <span class="pcampus-coupon-pill">Reward Coupon</span>
213
+ <div class="pcampus-coupon-prize">${name}</div>
214
+ <div class="pcampus-coupon-sub">คุณได้รับรางวัลนี้</div>
215
+ ${codeBlock}
216
+ <div class="pcampus-coupon-footer">เก็บไว้ใช้ตามเงื่อนไขของร้านค้า</div>
217
+ </div>
218
+ </div>
219
+ <button type="button" class="pcampus-coupon-close">ปิด</button>
220
+ </div>
221
+ `.trim();
222
+ }
223
+ function ensureCouponStyles() {
224
+ if (typeof document === "undefined")
225
+ return;
226
+ const id = "pcampus-coupon-success-css-v4";
227
+ for (const old of [
228
+ "pcampus-coupon-success-css",
229
+ "pcampus-coupon-success-css-v2",
230
+ "pcampus-coupon-success-css-v3",
231
+ ]) {
232
+ document.getElementById(old)?.remove();
233
+ }
234
+ let style = document.getElementById(id);
235
+ if (!style) {
236
+ style = document.createElement("style");
237
+ style.id = id;
238
+ document.head.appendChild(style);
239
+ }
240
+ style.textContent = couponSuccessCss();
241
+ }
242
+ /**
243
+ * Show shared coupon-shaped success overlay with puff. Dismiss via OK/close only.
244
+ */
245
+ export function showCouponSuccess(payload) {
246
+ if (typeof document === "undefined" || !payload.rewardName.trim())
247
+ return;
248
+ ensureCouponStyles();
249
+ document.querySelector(".pcampus-coupon-overlay")?.remove();
250
+ const overlay = document.createElement("div");
251
+ overlay.className = "pcampus-coupon-overlay";
252
+ overlay.innerHTML = couponSuccessCardHtml(payload);
253
+ const close = () => overlay.remove();
254
+ overlay.querySelector(".pcampus-coupon-close")?.addEventListener("click", close);
255
+ document.body.appendChild(overlay);
256
+ }
@@ -1,4 +1,5 @@
1
1
  export * from "./types.js";
2
+ export * from "./coupon-success.js";
2
3
  export * from "./spin.js";
3
4
  export * from "./scratch.js";
4
5
  export * from "./pick-card.js";
@@ -1,4 +1,5 @@
1
1
  export * from "./types.js";
2
+ export * from "./coupon-success.js";
2
3
  export * from "./spin.js";
3
4
  export * from "./scratch.js";
4
5
  export * from "./pick-card.js";
@@ -10,7 +11,7 @@ import { renderPickCardMarkup, mountPickCard } from "./pick-card.js";
10
11
  import { renderMysteryBoxMarkup, mountMysteryBox } from "./mystery-box.js";
11
12
  import { renderQuizMarkup, mountQuiz } from "./quiz.js";
12
13
  export function renderInteractiveWidgetMarkup(options) {
13
- const { widgetType, rewards = [], theme } = options;
14
+ const { widgetType, rewards = [], theme, quiz } = options;
14
15
  switch (widgetType) {
15
16
  case "spin":
16
17
  return renderSpinWheelMarkup(rewards, theme);
@@ -21,13 +22,13 @@ export function renderInteractiveWidgetMarkup(options) {
21
22
  case "mystery-box":
22
23
  return renderMysteryBoxMarkup(rewards, theme);
23
24
  case "quiz":
24
- return renderQuizMarkup(rewards, theme);
25
+ return renderQuizMarkup(rewards, theme, quiz);
25
26
  default:
26
27
  return `<div class="pcampus-unknown-widget">Unsupported widget type: ${widgetType}</div>`;
27
28
  }
28
29
  }
29
30
  export function mountInteractiveWidget(target, options) {
30
- const { widgetType, rewards = [], theme, onAction } = options;
31
+ const { widgetType, rewards = [], theme, quiz, onAction, onQuizComplete } = options;
31
32
  switch (widgetType) {
32
33
  case "spin":
33
34
  mountSpinWheel(target, rewards, theme, onAction);
@@ -42,7 +43,7 @@ export function mountInteractiveWidget(target, options) {
42
43
  mountMysteryBox(target, rewards, theme, onAction);
43
44
  break;
44
45
  case "quiz":
45
- mountQuiz(target, rewards, theme, onAction);
46
+ mountQuiz(target, rewards, theme, onAction, quiz, onQuizComplete);
46
47
  break;
47
48
  default:
48
49
  target.innerHTML = `<div class="pcampus-unknown-widget">Unsupported widget type: ${widgetType}</div>`;