@schoolio/player 1.2.1 → 1.3.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/dist/index.js CHANGED
@@ -1,9 +1,37 @@
1
- 'use strict';
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
2
19
 
3
- var react = require('react');
4
- var jsxRuntime = require('react/jsx-runtime');
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AttemptViewer: () => AttemptViewer,
24
+ QuizApiClient: () => QuizApiClient,
25
+ QuizPlayer: () => QuizPlayer,
26
+ calculateScore: () => calculateScore,
27
+ checkAnswer: () => checkAnswer,
28
+ createAnswerDetail: () => createAnswerDetail,
29
+ formatTime: () => formatTime
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
5
32
 
6
33
  // src/QuizPlayer.tsx
34
+ var import_react = require("react");
7
35
 
8
36
  // src/api.ts
9
37
  var QuizApiClient = class {
@@ -143,14 +171,17 @@ function formatTime(seconds) {
143
171
  const secs = seconds % 60;
144
172
  return `${mins}:${secs.toString().padStart(2, "0")}`;
145
173
  }
174
+
175
+ // src/QuizPlayer.tsx
176
+ var import_jsx_runtime = require("react/jsx-runtime");
146
177
  var defaultStyles = {
147
178
  container: {
148
179
  fontFamily: "system-ui, -apple-system, sans-serif",
149
- maxWidth: "800px",
150
- margin: "0 auto",
180
+ width: "100%",
151
181
  padding: "20px",
152
182
  backgroundColor: "#ffffff",
153
- borderRadius: "12px"
183
+ borderRadius: "12px",
184
+ boxSizing: "border-box"
154
185
  },
155
186
  header: {
156
187
  marginBottom: "20px",
@@ -180,7 +211,8 @@ var defaultStyles = {
180
211
  transition: "width 0.3s ease"
181
212
  },
182
213
  question: {
183
- marginBottom: "24px"
214
+ marginBottom: "24px",
215
+ minHeight: "280px"
184
216
  },
185
217
  questionText: {
186
218
  fontSize: "18px",
@@ -197,18 +229,31 @@ var defaultStyles = {
197
229
  border: "2px solid #e5e7eb",
198
230
  borderRadius: "8px",
199
231
  cursor: "pointer",
200
- transition: "all 0.2s ease"
232
+ transition: "all 0.2s ease",
233
+ outline: "none",
234
+ backgroundColor: "#ffffff"
201
235
  },
202
236
  optionSelected: {
203
237
  borderColor: "#6721b0",
204
238
  backgroundColor: "#f3e8ff"
205
239
  },
240
+ optionCorrect: {
241
+ borderColor: "#22c55e",
242
+ backgroundColor: "#f0fdf4"
243
+ },
244
+ optionIncorrect: {
245
+ borderColor: "#ef4444",
246
+ backgroundColor: "#fef2f2"
247
+ },
206
248
  input: {
207
249
  width: "100%",
208
250
  padding: "12px 16px",
209
251
  border: "2px solid #e5e7eb",
210
252
  borderRadius: "8px",
211
- fontSize: "16px"
253
+ fontSize: "16px",
254
+ outline: "none",
255
+ boxSizing: "border-box",
256
+ backgroundColor: "#ffffff"
212
257
  },
213
258
  buttons: {
214
259
  display: "flex",
@@ -237,24 +282,90 @@ var defaultStyles = {
237
282
  color: "#9ca3af",
238
283
  cursor: "not-allowed"
239
284
  },
285
+ buttonAddMore: {
286
+ padding: "12px 24px",
287
+ borderRadius: "8px",
288
+ fontSize: "16px",
289
+ fontWeight: "500",
290
+ cursor: "pointer",
291
+ border: "2px solid #6721b0",
292
+ backgroundColor: "transparent",
293
+ color: "#6721b0",
294
+ transition: "all 0.2s ease",
295
+ display: "flex",
296
+ alignItems: "center",
297
+ justifyContent: "center",
298
+ gap: "8px"
299
+ },
300
+ buttonAddMoreDisabled: {
301
+ border: "2px solid #e5e7eb",
302
+ color: "#9ca3af",
303
+ cursor: "not-allowed"
304
+ },
305
+ buttonsColumn: {
306
+ display: "flex",
307
+ flexDirection: "column",
308
+ gap: "12px",
309
+ marginTop: "24px"
310
+ },
240
311
  timer: {
241
312
  fontSize: "14px",
242
313
  color: "#6b7280",
243
314
  marginLeft: "16px"
244
315
  },
245
316
  results: {
317
+ display: "flex",
318
+ flexDirection: "column",
319
+ alignItems: "center",
320
+ justifyContent: "center",
321
+ padding: "48px 24px",
246
322
  textAlign: "center",
247
- padding: "40px 20px"
323
+ minHeight: "400px",
324
+ position: "relative",
325
+ overflow: "hidden"
248
326
  },
249
- resultScore: {
250
- fontSize: "48px",
251
- fontWeight: "700",
252
- color: "#6721b0",
253
- marginBottom: "8px"
327
+ resultsBackground: {
328
+ position: "absolute",
329
+ inset: "0",
330
+ borderRadius: "16px",
331
+ zIndex: 0
332
+ },
333
+ resultsContent: {
334
+ position: "relative",
335
+ zIndex: 1,
336
+ display: "flex",
337
+ flexDirection: "column",
338
+ alignItems: "center"
254
339
  },
255
- resultLabel: {
340
+ resultDetails: {
256
341
  fontSize: "18px",
257
- color: "#6b7280"
342
+ color: "#6b7280",
343
+ marginBottom: "4px"
344
+ },
345
+ resultStars: {
346
+ display: "flex",
347
+ justifyContent: "center",
348
+ gap: "8px",
349
+ marginBottom: "20px"
350
+ },
351
+ resultStar: {
352
+ fontSize: "32px",
353
+ animation: "starPop 0.5s ease-out forwards",
354
+ opacity: 0
355
+ },
356
+ confettiContainer: {
357
+ position: "absolute",
358
+ inset: "0",
359
+ pointerEvents: "none",
360
+ overflow: "hidden",
361
+ zIndex: 0
362
+ },
363
+ confettiPiece: {
364
+ position: "absolute",
365
+ width: "10px",
366
+ height: "10px",
367
+ top: "-10px",
368
+ animation: "confettiFall 3s ease-out forwards"
258
369
  },
259
370
  loading: {
260
371
  textAlign: "center",
@@ -265,10 +376,84 @@ var defaultStyles = {
265
376
  textAlign: "center",
266
377
  padding: "40px",
267
378
  color: "#ef4444"
379
+ },
380
+ intro: {
381
+ display: "flex",
382
+ flexDirection: "column",
383
+ alignItems: "center",
384
+ justifyContent: "center",
385
+ padding: "48px 24px",
386
+ textAlign: "center",
387
+ background: "linear-gradient(135deg, #f3e8ff 0%, #e0e7ff 50%, #fce7f3 100%)",
388
+ borderRadius: "16px",
389
+ minHeight: "320px"
390
+ },
391
+ introTitle: {
392
+ fontSize: "28px",
393
+ fontWeight: "700",
394
+ color: "#4c1d95",
395
+ marginBottom: "12px"
396
+ },
397
+ introSubtitle: {
398
+ fontSize: "16px",
399
+ color: "#6b7280",
400
+ marginBottom: "8px"
401
+ },
402
+ introQuestionCount: {
403
+ fontSize: "14px",
404
+ color: "#8b5cf6",
405
+ marginBottom: "32px",
406
+ fontWeight: "500"
407
+ },
408
+ startButton: {
409
+ padding: "16px 48px",
410
+ fontSize: "18px",
411
+ fontWeight: "600",
412
+ backgroundColor: "#7c3aed",
413
+ color: "#ffffff",
414
+ border: "none",
415
+ borderRadius: "12px",
416
+ cursor: "pointer",
417
+ transition: "all 0.2s ease",
418
+ boxShadow: "0 4px 14px rgba(124, 58, 237, 0.4)"
419
+ },
420
+ feedback: {
421
+ marginTop: "16px",
422
+ padding: "16px",
423
+ borderRadius: "8px",
424
+ backgroundColor: "#f9fafb",
425
+ border: "1px solid #e5e7eb"
426
+ },
427
+ feedbackCorrect: {
428
+ backgroundColor: "#f0fdf4",
429
+ borderColor: "#22c55e"
430
+ },
431
+ feedbackIncorrect: {
432
+ backgroundColor: "#fef2f2",
433
+ borderColor: "#ef4444"
434
+ },
435
+ feedbackTitle: {
436
+ fontSize: "16px",
437
+ fontWeight: "600",
438
+ marginBottom: "8px",
439
+ display: "flex",
440
+ alignItems: "center",
441
+ gap: "8px"
442
+ },
443
+ feedbackTitleCorrect: {
444
+ color: "#16a34a"
445
+ },
446
+ feedbackTitleIncorrect: {
447
+ color: "#dc2626"
448
+ },
449
+ feedbackExplanation: {
450
+ fontSize: "14px",
451
+ color: "#4b5563",
452
+ lineHeight: "1.5"
268
453
  }
269
454
  };
270
455
  function Spinner({ size = 16, color = "#ffffff" }) {
271
- return /* @__PURE__ */ jsxRuntime.jsx(
456
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
272
457
  "span",
273
458
  {
274
459
  style: {
@@ -280,7 +465,7 @@ function Spinner({ size = 16, color = "#ffffff" }) {
280
465
  borderRadius: "50%",
281
466
  animation: "spin 0.8s linear infinite"
282
467
  },
283
- children: /* @__PURE__ */ jsxRuntime.jsx("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
468
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
284
469
  }
285
470
  );
286
471
  }
@@ -296,27 +481,34 @@ function QuizPlayer({
296
481
  onComplete,
297
482
  onError,
298
483
  onProgress,
484
+ onGenerateMoreQuestions,
299
485
  className
300
486
  }) {
301
- const [quiz, setQuiz] = react.useState(null);
302
- const [attempt, setAttempt] = react.useState(null);
303
- const [currentQuestionIndex, setCurrentQuestionIndex] = react.useState(0);
304
- const [answers, setAnswers] = react.useState(/* @__PURE__ */ new Map());
305
- const [answersDetail, setAnswersDetail] = react.useState([]);
306
- const [isSubmitting, setIsSubmitting] = react.useState(false);
307
- const [isNavigating, setIsNavigating] = react.useState(false);
308
- const [isCompleted, setIsCompleted] = react.useState(false);
309
- const [result, setResult] = react.useState(null);
310
- const [error, setError] = react.useState(null);
311
- const [isLoading, setIsLoading] = react.useState(true);
312
- const [elapsedSeconds, setElapsedSeconds] = react.useState(0);
313
- const apiClient = react.useRef(null);
314
- const timerRef = react.useRef(null);
315
- const startTimeRef = react.useRef(Date.now());
316
- react.useEffect(() => {
487
+ const [quiz, setQuiz] = (0, import_react.useState)(null);
488
+ const [attempt, setAttempt] = (0, import_react.useState)(null);
489
+ const [currentQuestionIndex, setCurrentQuestionIndex] = (0, import_react.useState)(0);
490
+ const [answers, setAnswers] = (0, import_react.useState)(/* @__PURE__ */ new Map());
491
+ const [answersDetail, setAnswersDetail] = (0, import_react.useState)([]);
492
+ const [isSubmitting, setIsSubmitting] = (0, import_react.useState)(false);
493
+ const [isNavigating, setIsNavigating] = (0, import_react.useState)(false);
494
+ const [isCompleted, setIsCompleted] = (0, import_react.useState)(false);
495
+ const [result, setResult] = (0, import_react.useState)(null);
496
+ const [error, setError] = (0, import_react.useState)(null);
497
+ const [isLoading, setIsLoading] = (0, import_react.useState)(true);
498
+ const [elapsedSeconds, setElapsedSeconds] = (0, import_react.useState)(0);
499
+ const [showIntro, setShowIntro] = (0, import_react.useState)(true);
500
+ const [timerStarted, setTimerStarted] = (0, import_react.useState)(false);
501
+ const [showFeedback, setShowFeedback] = (0, import_react.useState)(false);
502
+ const [currentAnswerDetail, setCurrentAnswerDetail] = (0, import_react.useState)(null);
503
+ const [extraQuestions, setExtraQuestions] = (0, import_react.useState)([]);
504
+ const [isGeneratingExtra, setIsGeneratingExtra] = (0, import_react.useState)(false);
505
+ const apiClient = (0, import_react.useRef)(null);
506
+ const timerRef = (0, import_react.useRef)(null);
507
+ const startTimeRef = (0, import_react.useRef)(0);
508
+ (0, import_react.useEffect)(() => {
317
509
  apiClient.current = new QuizApiClient({ baseUrl: apiBaseUrl, authToken });
318
510
  }, [apiBaseUrl, authToken]);
319
- react.useEffect(() => {
511
+ (0, import_react.useEffect)(() => {
320
512
  async function initialize() {
321
513
  if (!apiClient.current) return;
322
514
  try {
@@ -363,8 +555,8 @@ function QuizPlayer({
363
555
  }
364
556
  initialize();
365
557
  }, [quizId, lessonId, assignLessonId, courseId, childId, parentId, onError]);
366
- react.useEffect(() => {
367
- if (!isLoading && !isCompleted && !error) {
558
+ (0, import_react.useEffect)(() => {
559
+ if (timerStarted && !isCompleted && !error) {
368
560
  startTimeRef.current = Date.now();
369
561
  timerRef.current = setInterval(() => {
370
562
  setElapsedSeconds(Math.floor((Date.now() - startTimeRef.current) / 1e3));
@@ -375,27 +567,39 @@ function QuizPlayer({
375
567
  clearInterval(timerRef.current);
376
568
  }
377
569
  };
378
- }, [isLoading, isCompleted, error]);
379
- react.useEffect(() => {
570
+ }, [timerStarted, isCompleted, error]);
571
+ const handleStart = (0, import_react.useCallback)(() => {
572
+ setShowIntro(false);
573
+ setTimerStarted(true);
574
+ }, []);
575
+ (0, import_react.useEffect)(() => {
576
+ setShowFeedback(false);
577
+ setCurrentAnswerDetail(null);
578
+ }, [currentQuestionIndex]);
579
+ const allQuestions = quiz ? [...quiz.questions, ...extraQuestions] : [];
580
+ const totalQuestions = allQuestions.length;
581
+ const maxQuestions = 50;
582
+ (0, import_react.useEffect)(() => {
380
583
  if (quiz && onProgress) {
381
584
  onProgress({
382
585
  currentQuestion: currentQuestionIndex + 1,
383
- totalQuestions: quiz.questions.length,
586
+ totalQuestions,
384
587
  answeredQuestions: answers.size
385
588
  });
386
589
  }
387
- }, [currentQuestionIndex, answers.size, quiz, onProgress]);
388
- const currentQuestion = quiz?.questions[currentQuestionIndex];
389
- const handleAnswerChange = react.useCallback((value) => {
590
+ }, [currentQuestionIndex, answers.size, quiz, onProgress, totalQuestions]);
591
+ const currentQuestion = allQuestions[currentQuestionIndex];
592
+ const handleAnswerChange = (0, import_react.useCallback)((value) => {
390
593
  if (!currentQuestion) return;
391
594
  setAnswers((prev) => new Map(prev).set(currentQuestion.id, value));
392
595
  }, [currentQuestion]);
393
- const handleNext = react.useCallback(async () => {
596
+ const handleCheckAnswer = (0, import_react.useCallback)(async () => {
394
597
  if (!quiz || !attempt || !currentQuestion || !apiClient.current) return;
395
598
  const selectedAnswer2 = answers.get(currentQuestion.id);
396
599
  if (selectedAnswer2 === void 0) return;
397
600
  setIsNavigating(true);
398
601
  const answerDetail = createAnswerDetail(currentQuestion, selectedAnswer2);
602
+ setCurrentAnswerDetail(answerDetail);
399
603
  const newAnswersDetail = [...answersDetail];
400
604
  const existingIdx = newAnswersDetail.findIndex((a) => a.questionId === currentQuestion.id);
401
605
  if (existingIdx >= 0) {
@@ -413,16 +617,40 @@ function QuizPlayer({
413
617
  } finally {
414
618
  setIsNavigating(false);
415
619
  }
416
- if (currentQuestionIndex < quiz.questions.length - 1) {
620
+ setShowFeedback(true);
621
+ }, [quiz, attempt, currentQuestion, answers, answersDetail]);
622
+ const handleContinue = (0, import_react.useCallback)(() => {
623
+ if (!quiz) return;
624
+ setShowFeedback(false);
625
+ setCurrentAnswerDetail(null);
626
+ if (currentQuestionIndex < totalQuestions - 1) {
417
627
  setCurrentQuestionIndex((prev) => prev + 1);
418
628
  }
419
- }, [quiz, attempt, currentQuestion, answers, answersDetail, currentQuestionIndex]);
420
- const handlePrevious = react.useCallback(() => {
421
- if (currentQuestionIndex > 0) {
422
- setCurrentQuestionIndex((prev) => prev - 1);
629
+ }, [quiz, currentQuestionIndex, totalQuestions]);
630
+ const handleAddMoreQuestions = (0, import_react.useCallback)(async () => {
631
+ if (!attempt || !onGenerateMoreQuestions || isGeneratingExtra) return;
632
+ if (totalQuestions >= maxQuestions) return;
633
+ setIsGeneratingExtra(true);
634
+ try {
635
+ const result2 = await onGenerateMoreQuestions(attempt.id, totalQuestions);
636
+ if (result2.extraQuestions && result2.extraQuestions.length > 0) {
637
+ const slotsAvailable = maxQuestions - totalQuestions;
638
+ const questionsToAppend = result2.extraQuestions.slice(0, slotsAvailable);
639
+ if (questionsToAppend.length > 0) {
640
+ setExtraQuestions((prev) => [...prev, ...questionsToAppend]);
641
+ setCurrentQuestionIndex(totalQuestions);
642
+ setShowFeedback(false);
643
+ setCurrentAnswerDetail(null);
644
+ }
645
+ }
646
+ } catch (err) {
647
+ console.error("Failed to generate extra questions:", err);
648
+ onError?.(err instanceof Error ? err : new Error("Failed to generate extra questions"));
649
+ } finally {
650
+ setIsGeneratingExtra(false);
423
651
  }
424
- }, [currentQuestionIndex]);
425
- const handleSubmit = react.useCallback(async () => {
652
+ }, [attempt, onGenerateMoreQuestions, isGeneratingExtra, totalQuestions, maxQuestions, onError]);
653
+ const handleSubmit = (0, import_react.useCallback)(async () => {
426
654
  if (!quiz || !attempt || !apiClient.current) return;
427
655
  setIsSubmitting(true);
428
656
  try {
@@ -438,7 +666,7 @@ function QuizPlayer({
438
666
  }
439
667
  }
440
668
  const scoreData = calculateScore(finalAnswersDetail);
441
- const timeSpent = Math.floor((Date.now() - startTimeRef.current) / 1e3);
669
+ const timeSpent = timerStarted && startTimeRef.current > 0 ? Math.floor((Date.now() - startTimeRef.current) / 1e3) : elapsedSeconds;
442
670
  const updatedAttempt = await apiClient.current.updateAttempt(attempt.id, {
443
671
  answers: finalAnswersDetail,
444
672
  status: "completed",
@@ -451,7 +679,7 @@ function QuizPlayer({
451
679
  attemptId: updatedAttempt.id,
452
680
  score: scoreData.score,
453
681
  correctAnswers: scoreData.correctAnswers,
454
- totalQuestions: quiz.questions.length,
682
+ totalQuestions,
455
683
  answers: finalAnswersDetail,
456
684
  timeSpentSeconds: timeSpent
457
685
  };
@@ -467,78 +695,414 @@ function QuizPlayer({
467
695
  } finally {
468
696
  setIsSubmitting(false);
469
697
  }
470
- }, [quiz, attempt, currentQuestion, answers, answersDetail, onComplete, onError]);
698
+ }, [quiz, attempt, currentQuestion, answers, answersDetail, onComplete, onError, totalQuestions, timerStarted, elapsedSeconds]);
471
699
  if (isLoading) {
472
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.loading, children: "Loading quiz..." }) });
700
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.loading, children: "Loading quiz..." }) });
473
701
  }
474
702
  if (error) {
475
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.error, children: /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
703
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.error, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { children: [
476
704
  "Error: ",
477
705
  error
478
706
  ] }) }) });
479
707
  }
480
708
  if (isCompleted && result) {
481
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles.results, children: [
482
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles.resultScore, children: [
483
- result.score,
484
- "%"
485
- ] }),
486
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles.resultLabel, children: [
487
- result.correctAnswers,
488
- " of ",
489
- result.totalQuestions,
490
- " correct"
491
- ] }),
492
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...defaultStyles.resultLabel, marginTop: "8px" }, children: [
493
- "Time: ",
494
- formatTime(result.timeSpentSeconds)
709
+ const percentage = Math.round(result.correctAnswers / result.totalQuestions * 100);
710
+ const getScoreTheme = (pct) => {
711
+ if (pct >= 80) {
712
+ return {
713
+ color: "#22c55e",
714
+ bgGradient: "linear-gradient(135deg, #dcfce7 0%, #bbf7d0 50%, #86efac 100%)",
715
+ badge: "Quiz Champion!",
716
+ badgeColor: "#fbbf24",
717
+ badgeBg: "linear-gradient(135deg, #fef3c7 0%, #fde68a 100%)",
718
+ mascotMood: "celebrating",
719
+ stars: 3
720
+ };
721
+ } else if (pct >= 60) {
722
+ return {
723
+ color: "#f59e0b",
724
+ bgGradient: "linear-gradient(135deg, #fef3c7 0%, #fde68a 50%, #fcd34d 100%)",
725
+ badge: "Rising Star!",
726
+ badgeColor: "#f59e0b",
727
+ badgeBg: "linear-gradient(135deg, #fed7aa 0%, #fdba74 100%)",
728
+ mascotMood: "happy",
729
+ stars: 2
730
+ };
731
+ } else if (pct >= 40) {
732
+ return {
733
+ color: "#3b82f6",
734
+ bgGradient: "linear-gradient(135deg, #dbeafe 0%, #bfdbfe 50%, #93c5fd 100%)",
735
+ badge: "Great Learner!",
736
+ badgeColor: "#3b82f6",
737
+ badgeBg: "linear-gradient(135deg, #bfdbfe 0%, #93c5fd 100%)",
738
+ mascotMood: "encouraging",
739
+ stars: 1
740
+ };
741
+ } else {
742
+ return {
743
+ color: "#8b5cf6",
744
+ bgGradient: "linear-gradient(135deg, #f3e8ff 0%, #e9d5ff 50%, #d8b4fe 100%)",
745
+ badge: "Keep Growing!",
746
+ badgeColor: "#8b5cf6",
747
+ badgeBg: "linear-gradient(135deg, #e9d5ff 0%, #d8b4fe 100%)",
748
+ mascotMood: "supportive",
749
+ stars: 0
750
+ };
751
+ }
752
+ };
753
+ const theme = getScoreTheme(percentage);
754
+ const confettiColors = ["#f43f5e", "#ec4899", "#8b5cf6", "#3b82f6", "#22c55e", "#f59e0b", "#ef4444"];
755
+ const confettiPieces = Array.from({ length: 50 }, (_, i) => ({
756
+ id: i,
757
+ left: `${Math.random() * 100}%`,
758
+ delay: `${Math.random() * 2}s`,
759
+ duration: `${2 + Math.random() * 2}s`,
760
+ color: confettiColors[Math.floor(Math.random() * confettiColors.length)],
761
+ rotation: Math.random() * 360,
762
+ size: 6 + Math.random() * 8
763
+ }));
764
+ const StarIcon = ({ filled, delay }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
765
+ "svg",
766
+ {
767
+ width: "36",
768
+ height: "36",
769
+ viewBox: "0 0 24 24",
770
+ style: {
771
+ animation: "starPop 0.5s ease-out forwards",
772
+ animationDelay: `${delay}s`,
773
+ opacity: 0
774
+ },
775
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
776
+ "path",
777
+ {
778
+ d: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z",
779
+ fill: filled ? "#fbbf24" : "#e5e7eb",
780
+ stroke: filled ? "#f59e0b" : "#d1d5db",
781
+ strokeWidth: "1"
782
+ }
783
+ )
784
+ }
785
+ );
786
+ const MascotOwl = ({ mood }) => {
787
+ const getEyeExpression = () => {
788
+ switch (mood) {
789
+ case "celebrating":
790
+ return { leftEye: ">", rightEye: "<", pupilY: 42 };
791
+ // Squinting happy
792
+ case "happy":
793
+ return { leftEye: null, rightEye: null, pupilY: 42 };
794
+ // Normal happy
795
+ case "encouraging":
796
+ return { leftEye: null, rightEye: null, pupilY: 44 };
797
+ // Looking down warmly
798
+ default:
799
+ return { leftEye: null, rightEye: null, pupilY: 42 };
800
+ }
801
+ };
802
+ const eyeExpr = getEyeExpression();
803
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
804
+ "svg",
805
+ {
806
+ width: "120",
807
+ height: "120",
808
+ viewBox: "0 0 100 100",
809
+ style: {
810
+ animation: mood === "celebrating" ? "bounce 0.6s ease-in-out infinite" : "gentleBob 2s ease-in-out infinite"
811
+ },
812
+ children: [
813
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "50", cy: "60", rx: "35", ry: "30", fill: "#8b5cf6" }),
814
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "50", cy: "65", rx: "25", ry: "20", fill: "#c4b5fd" }),
815
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "50", cy: "35", r: "28", fill: "#a78bfa" }),
816
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polygon", { points: "28,15 35,30 22,28", fill: "#7c3aed" }),
817
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polygon", { points: "72,15 65,30 78,28", fill: "#7c3aed" }),
818
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "38", cy: "38", rx: "10", ry: "12", fill: "white" }),
819
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "62", cy: "38", rx: "10", ry: "12", fill: "white" }),
820
+ eyeExpr.leftEye ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: "38", y: "44", textAnchor: "middle", fontSize: "16", fill: "#1f2937", children: eyeExpr.leftEye }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "38", cy: eyeExpr.pupilY, r: "5", fill: "#1f2937" }),
821
+ eyeExpr.rightEye ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: "62", y: "44", textAnchor: "middle", fontSize: "16", fill: "#1f2937", children: eyeExpr.rightEye }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "62", cy: eyeExpr.pupilY, r: "5", fill: "#1f2937" }),
822
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polygon", { points: "50,45 45,52 55,52", fill: "#fbbf24" }),
823
+ (mood === "celebrating" || mood === "happy") && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
824
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "28", cy: "45", rx: "5", ry: "3", fill: "#fda4af", opacity: "0.6" }),
825
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "72", cy: "45", rx: "5", ry: "3", fill: "#fda4af", opacity: "0.6" })
826
+ ] }),
827
+ mood === "celebrating" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
828
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "18", cy: "55", rx: "8", ry: "15", fill: "#7c3aed", transform: "rotate(-30 18 55)" }),
829
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "82", cy: "55", rx: "8", ry: "15", fill: "#7c3aed", transform: "rotate(30 82 55)" })
830
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
831
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "20", cy: "60", rx: "8", ry: "15", fill: "#7c3aed" }),
832
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "80", cy: "60", rx: "8", ry: "15", fill: "#7c3aed" })
833
+ ] }),
834
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "40", cy: "88", rx: "8", ry: "4", fill: "#fbbf24" }),
835
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "60", cy: "88", rx: "8", ry: "4", fill: "#fbbf24" })
836
+ ]
837
+ }
838
+ );
839
+ };
840
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, style: defaultStyles.container, children: [
841
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `
842
+ @keyframes confettiFall {
843
+ 0% {
844
+ transform: translateY(-10px) rotate(0deg);
845
+ opacity: 1;
846
+ }
847
+ 100% {
848
+ transform: translateY(500px) rotate(720deg);
849
+ opacity: 0;
850
+ }
851
+ }
852
+ @keyframes starPop {
853
+ 0% {
854
+ transform: scale(0);
855
+ opacity: 0;
856
+ }
857
+ 50% {
858
+ transform: scale(1.3);
859
+ }
860
+ 100% {
861
+ transform: scale(1);
862
+ opacity: 1;
863
+ }
864
+ }
865
+ @keyframes bounce {
866
+ 0%, 100% {
867
+ transform: translateY(0);
868
+ }
869
+ 50% {
870
+ transform: translateY(-10px);
871
+ }
872
+ }
873
+ @keyframes gentleBob {
874
+ 0%, 100% {
875
+ transform: translateY(0);
876
+ }
877
+ 50% {
878
+ transform: translateY(-5px);
879
+ }
880
+ }
881
+ @keyframes badgePop {
882
+ 0% {
883
+ transform: scale(0) rotate(-10deg);
884
+ opacity: 0;
885
+ }
886
+ 60% {
887
+ transform: scale(1.1) rotate(5deg);
888
+ }
889
+ 100% {
890
+ transform: scale(1) rotate(0deg);
891
+ opacity: 1;
892
+ }
893
+ }
894
+ @keyframes scoreSlideIn {
895
+ 0% {
896
+ transform: translateY(20px);
897
+ opacity: 0;
898
+ }
899
+ 100% {
900
+ transform: translateY(0);
901
+ opacity: 1;
902
+ }
903
+ }
904
+ ` }),
905
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.results, children: [
906
+ percentage >= 60 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.confettiContainer, children: confettiPieces.map((piece) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
907
+ "div",
908
+ {
909
+ style: {
910
+ ...defaultStyles.confettiPiece,
911
+ left: piece.left,
912
+ width: `${piece.size}px`,
913
+ height: `${piece.size}px`,
914
+ backgroundColor: piece.color,
915
+ borderRadius: Math.random() > 0.5 ? "50%" : "2px",
916
+ animationDelay: piece.delay,
917
+ animationDuration: piece.duration,
918
+ transform: `rotate(${piece.rotation}deg)`
919
+ }
920
+ },
921
+ piece.id
922
+ )) }),
923
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...defaultStyles.resultsBackground, background: theme.bgGradient } }),
924
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.resultsContent, children: [
925
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.resultStars, children: [
926
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StarIcon, { filled: theme.stars >= 1, delay: 0.3 }),
927
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StarIcon, { filled: theme.stars >= 2, delay: 0.5 }),
928
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StarIcon, { filled: theme.stars >= 3, delay: 0.7 })
929
+ ] }),
930
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginBottom: "16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MascotOwl, { mood: theme.mascotMood }) }),
931
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
932
+ "div",
933
+ {
934
+ style: {
935
+ background: theme.badgeBg,
936
+ padding: "12px 28px",
937
+ borderRadius: "50px",
938
+ boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
939
+ marginBottom: "20px",
940
+ animation: "badgePop 0.6s ease-out 0.2s forwards",
941
+ opacity: 0,
942
+ border: `3px solid ${theme.badgeColor}`
943
+ },
944
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
945
+ "span",
946
+ {
947
+ style: {
948
+ fontSize: "22px",
949
+ fontWeight: "700",
950
+ color: "#1f2937",
951
+ textShadow: "0 1px 2px rgba(255,255,255,0.5)"
952
+ },
953
+ children: theme.badge
954
+ }
955
+ )
956
+ }
957
+ ),
958
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
959
+ "div",
960
+ {
961
+ style: {
962
+ animation: "scoreSlideIn 0.5s ease-out 0.4s forwards",
963
+ opacity: 0
964
+ },
965
+ children: [
966
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
967
+ "div",
968
+ {
969
+ style: {
970
+ fontSize: "48px",
971
+ fontWeight: "800",
972
+ color: theme.color,
973
+ lineHeight: "1",
974
+ marginBottom: "4px"
975
+ },
976
+ children: [
977
+ result.correctAnswers,
978
+ " of ",
979
+ result.totalQuestions
980
+ ]
981
+ }
982
+ ),
983
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
984
+ "div",
985
+ {
986
+ style: {
987
+ fontSize: "20px",
988
+ fontWeight: "600",
989
+ color: "#6b7280",
990
+ marginBottom: "12px"
991
+ },
992
+ children: "correct answers"
993
+ }
994
+ )
995
+ ]
996
+ }
997
+ ),
998
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { ...defaultStyles.resultDetails, marginTop: "8px" }, children: [
999
+ "Time: ",
1000
+ formatTime(result.timeSpentSeconds)
1001
+ ] })
1002
+ ] })
495
1003
  ] })
1004
+ ] });
1005
+ }
1006
+ if (quiz && showIntro) {
1007
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.intro, children: [
1008
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.introTitle, children: quiz.title }),
1009
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.introSubtitle, children: "Ready to test your knowledge?" }),
1010
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.introQuestionCount, children: [
1011
+ quiz.questions.length,
1012
+ " question",
1013
+ quiz.questions.length !== 1 ? "s" : "",
1014
+ " to answer"
1015
+ ] }),
1016
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1017
+ "button",
1018
+ {
1019
+ style: defaultStyles.startButton,
1020
+ onClick: handleStart,
1021
+ onMouseOver: (e) => {
1022
+ e.currentTarget.style.transform = "translateY(-2px)";
1023
+ e.currentTarget.style.boxShadow = "0 6px 20px rgba(124, 58, 237, 0.5)";
1024
+ },
1025
+ onMouseOut: (e) => {
1026
+ e.currentTarget.style.transform = "translateY(0)";
1027
+ e.currentTarget.style.boxShadow = "0 4px 14px rgba(124, 58, 237, 0.4)";
1028
+ },
1029
+ "data-testid": "button-start-quiz",
1030
+ children: "Let's Start!"
1031
+ }
1032
+ )
496
1033
  ] }) });
497
1034
  }
498
1035
  if (!quiz || !currentQuestion) {
499
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.error, children: "No quiz data available" }) });
1036
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.error, children: "No quiz data available" }) });
500
1037
  }
501
1038
  const selectedAnswer = answers.get(currentQuestion.id);
502
- const isLastQuestion = currentQuestionIndex === quiz.questions.length - 1;
503
- const progressPercent = (currentQuestionIndex + 1) / quiz.questions.length * 100;
504
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: defaultStyles.container, children: [
505
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles.header, children: [
506
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
507
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.title, children: quiz.title }),
508
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.timer, children: formatTime(elapsedSeconds) })
509
- ] }),
510
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles.progress, children: [
1039
+ const isLastQuestion = currentQuestionIndex === totalQuestions - 1;
1040
+ const progressPercent = (currentQuestionIndex + 1) / totalQuestions * 100;
1041
+ const remainingSlots = maxQuestions - totalQuestions;
1042
+ const questionsToAdd = Math.min(5, remainingSlots);
1043
+ const canAddMore = onGenerateMoreQuestions && remainingSlots > 0;
1044
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, style: defaultStyles.container, children: [
1045
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.header, children: [
1046
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.title, children: quiz.title }),
1047
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.progress, children: [
511
1048
  "Question ",
512
1049
  currentQuestionIndex + 1,
513
1050
  " of ",
514
- quiz.questions.length
1051
+ totalQuestions
515
1052
  ] }),
516
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.progressBar, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { ...defaultStyles.progressFill, width: `${progressPercent}%` } }) })
1053
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.progressBar, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...defaultStyles.progressFill, width: `${progressPercent}%` } }) })
517
1054
  ] }),
518
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles.question, children: [
519
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.questionText, children: currentQuestion.question }),
520
- (currentQuestion.type === "single" || currentQuestion.type === "true-false") && /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => /* @__PURE__ */ jsxRuntime.jsx(
521
- "div",
522
- {
523
- style: {
524
- ...defaultStyles.option,
525
- ...selectedAnswer === option ? defaultStyles.optionSelected : {}
1055
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.question, children: [
1056
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.questionText, children: currentQuestion.question }),
1057
+ (currentQuestion.type === "single" || currentQuestion.type === "true-false") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
1058
+ const isSelected = selectedAnswer === option;
1059
+ const isCorrectOption = currentQuestion.correctAnswer === option;
1060
+ let optionStyle = { ...defaultStyles.option };
1061
+ if (showFeedback) {
1062
+ if (isCorrectOption) {
1063
+ optionStyle = { ...optionStyle, ...defaultStyles.optionCorrect };
1064
+ } else if (isSelected && !isCorrectOption) {
1065
+ optionStyle = { ...optionStyle, ...defaultStyles.optionIncorrect };
1066
+ }
1067
+ } else if (isSelected) {
1068
+ optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
1069
+ }
1070
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1071
+ "div",
1072
+ {
1073
+ style: {
1074
+ ...optionStyle,
1075
+ cursor: showFeedback ? "default" : "pointer"
1076
+ },
1077
+ onClick: () => !showFeedback && handleAnswerChange(option),
1078
+ children: option
526
1079
  },
527
- onClick: () => handleAnswerChange(option),
528
- children: option
529
- },
530
- idx
531
- )) }),
532
- currentQuestion.type === "multiple" && /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
1080
+ idx
1081
+ );
1082
+ }) }),
1083
+ currentQuestion.type === "multiple" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
533
1084
  const selected = Array.isArray(selectedAnswer) && selectedAnswer.includes(option);
534
- return /* @__PURE__ */ jsxRuntime.jsx(
1085
+ const correctAnswers = Array.isArray(currentQuestion.correctAnswer) ? currentQuestion.correctAnswer : currentQuestion.correctAnswer ? [currentQuestion.correctAnswer] : [];
1086
+ const isCorrectOption = correctAnswers.includes(option);
1087
+ let optionStyle = { ...defaultStyles.option };
1088
+ if (showFeedback) {
1089
+ if (isCorrectOption) {
1090
+ optionStyle = { ...optionStyle, ...defaultStyles.optionCorrect };
1091
+ } else if (selected && !isCorrectOption) {
1092
+ optionStyle = { ...optionStyle, ...defaultStyles.optionIncorrect };
1093
+ }
1094
+ } else if (selected) {
1095
+ optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
1096
+ }
1097
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
535
1098
  "div",
536
1099
  {
537
1100
  style: {
538
- ...defaultStyles.option,
539
- ...selected ? defaultStyles.optionSelected : {}
1101
+ ...optionStyle,
1102
+ cursor: showFeedback ? "default" : "pointer"
540
1103
  },
541
1104
  onClick: () => {
1105
+ if (showFeedback) return;
542
1106
  const current = Array.isArray(selectedAnswer) ? selectedAnswer : [];
543
1107
  if (selected) {
544
1108
  handleAnswerChange(current.filter((o) => o !== option));
@@ -551,16 +1115,17 @@ function QuizPlayer({
551
1115
  idx
552
1116
  );
553
1117
  }) }),
554
- (currentQuestion.type === "free" || currentQuestion.type === "essay") && /* @__PURE__ */ jsxRuntime.jsx(
1118
+ (currentQuestion.type === "free" || currentQuestion.type === "essay") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
555
1119
  "textarea",
556
1120
  {
557
1121
  style: { ...defaultStyles.input, minHeight: currentQuestion.type === "essay" ? "150px" : "60px" },
558
1122
  value: selectedAnswer || "",
559
1123
  onChange: (e) => handleAnswerChange(e.target.value),
560
- placeholder: "Type your answer here..."
1124
+ placeholder: "Type your answer here...",
1125
+ disabled: showFeedback
561
1126
  }
562
1127
  ),
563
- currentQuestion.type === "fill" && /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.options, children: currentQuestion.blanks?.map((_, idx) => /* @__PURE__ */ jsxRuntime.jsx(
1128
+ currentQuestion.type === "fill" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.options, children: currentQuestion.blanks?.map((_, idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
564
1129
  "input",
565
1130
  {
566
1131
  style: defaultStyles.input,
@@ -570,50 +1135,93 @@ function QuizPlayer({
570
1135
  current[idx] = e.target.value;
571
1136
  handleAnswerChange(current);
572
1137
  },
573
- placeholder: `Blank ${idx + 1}`
1138
+ placeholder: `Blank ${idx + 1}`,
1139
+ disabled: showFeedback
574
1140
  },
575
1141
  idx
576
- )) })
1142
+ )) }),
1143
+ showFeedback && currentAnswerDetail && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
1144
+ ...defaultStyles.feedback,
1145
+ ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackCorrect : defaultStyles.feedbackIncorrect
1146
+ }, children: [
1147
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
1148
+ ...defaultStyles.feedbackTitle,
1149
+ ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackTitleCorrect : defaultStyles.feedbackTitleIncorrect
1150
+ }, children: currentAnswerDetail.isCorrect ? "\u2713 Correct!" : "\u2717 Incorrect" }),
1151
+ currentQuestion.explanation && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.feedbackExplanation, children: currentQuestion.explanation })
1152
+ ] })
577
1153
  ] }),
578
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles.buttons, children: [
579
- /* @__PURE__ */ jsxRuntime.jsx(
1154
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.buttonsColumn, children: [
1155
+ showFeedback && isLastQuestion && canAddMore && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
580
1156
  "button",
581
1157
  {
582
1158
  style: {
583
- ...defaultStyles.button,
584
- ...currentQuestionIndex > 0 ? defaultStyles.buttonSecondary : defaultStyles.buttonDisabled
1159
+ ...defaultStyles.buttonAddMore,
1160
+ ...isGeneratingExtra ? defaultStyles.buttonAddMoreDisabled : {}
585
1161
  },
586
- onClick: handlePrevious,
587
- disabled: currentQuestionIndex === 0,
588
- children: "Previous"
1162
+ onClick: handleAddMoreQuestions,
1163
+ disabled: isGeneratingExtra,
1164
+ "data-testid": "button-add-more-questions",
1165
+ children: isGeneratingExtra ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1166
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: 16, color: "#9ca3af" }),
1167
+ "Generating Questions..."
1168
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1169
+ "+ Add ",
1170
+ questionsToAdd,
1171
+ " More Question",
1172
+ questionsToAdd !== 1 ? "s" : ""
1173
+ ] })
589
1174
  }
590
1175
  ),
591
- isLastQuestion ? /* @__PURE__ */ jsxRuntime.jsx(
592
- "button",
593
- {
594
- style: {
595
- ...defaultStyles.button,
596
- ...isSubmitting ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
597
- },
598
- onClick: handleSubmit,
599
- disabled: isSubmitting || selectedAnswer === void 0,
600
- children: isSubmitting ? /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 16, color: "#9ca3af" }) : "Submit Quiz"
601
- }
602
- ) : /* @__PURE__ */ jsxRuntime.jsx(
603
- "button",
604
- {
605
- style: {
606
- ...defaultStyles.button,
607
- ...isNavigating || selectedAnswer === void 0 ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
608
- },
609
- onClick: handleNext,
610
- disabled: isNavigating || selectedAnswer === void 0,
611
- children: isNavigating ? /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 16, color: "#9ca3af" }) : "Next"
612
- }
613
- )
1176
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...defaultStyles.buttons, justifyContent: "flex-end" }, children: showFeedback ? (
1177
+ // After viewing feedback
1178
+ isLastQuestion ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1179
+ "button",
1180
+ {
1181
+ style: {
1182
+ ...defaultStyles.button,
1183
+ ...isSubmitting || isGeneratingExtra ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
1184
+ },
1185
+ onClick: handleSubmit,
1186
+ disabled: isSubmitting || isGeneratingExtra,
1187
+ "data-testid": "button-submit-quiz",
1188
+ children: isSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: 16, color: "#9ca3af" }) : "Submit Quiz"
1189
+ }
1190
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1191
+ "button",
1192
+ {
1193
+ style: {
1194
+ ...defaultStyles.button,
1195
+ ...defaultStyles.buttonPrimary
1196
+ },
1197
+ onClick: handleContinue,
1198
+ "data-testid": "button-continue",
1199
+ children: "Continue"
1200
+ }
1201
+ )
1202
+ ) : (
1203
+ // Before checking answer
1204
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1205
+ "button",
1206
+ {
1207
+ style: {
1208
+ ...defaultStyles.button,
1209
+ ...isNavigating || selectedAnswer === void 0 ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
1210
+ },
1211
+ onClick: handleCheckAnswer,
1212
+ disabled: isNavigating || selectedAnswer === void 0,
1213
+ "data-testid": "button-check-answer",
1214
+ children: isNavigating ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: 16, color: "#9ca3af" }) : "Check Answer"
1215
+ }
1216
+ )
1217
+ ) })
614
1218
  ] })
615
1219
  ] });
616
1220
  }
1221
+
1222
+ // src/AttemptViewer.tsx
1223
+ var import_react2 = require("react");
1224
+ var import_jsx_runtime2 = require("react/jsx-runtime");
617
1225
  var defaultStyles2 = {
618
1226
  container: {
619
1227
  fontFamily: "system-ui, -apple-system, sans-serif",
@@ -628,6 +1236,12 @@ var defaultStyles2 = {
628
1236
  borderBottom: "1px solid #e5e7eb",
629
1237
  paddingBottom: "20px"
630
1238
  },
1239
+ title: {
1240
+ fontSize: "24px",
1241
+ fontWeight: "600",
1242
+ marginBottom: "16px",
1243
+ color: "#111827"
1244
+ },
631
1245
  summaryGrid: {
632
1246
  display: "grid",
633
1247
  gridTemplateColumns: "repeat(auto-fit, minmax(120px, 1fr))",
@@ -787,11 +1401,11 @@ function AttemptViewer({
787
1401
  showExplanations = true,
788
1402
  title
789
1403
  }) {
790
- const [attempt, setAttempt] = react.useState(null);
791
- const [loading, setLoading] = react.useState(true);
792
- const [error, setError] = react.useState(null);
793
- react.useEffect(() => {
794
- new QuizApiClient({
1404
+ const [attempt, setAttempt] = (0, import_react2.useState)(null);
1405
+ const [loading, setLoading] = (0, import_react2.useState)(true);
1406
+ const [error, setError] = (0, import_react2.useState)(null);
1407
+ (0, import_react2.useEffect)(() => {
1408
+ const apiClient = new QuizApiClient({
795
1409
  baseUrl: apiBaseUrl,
796
1410
  authToken
797
1411
  });
@@ -823,49 +1437,49 @@ function AttemptViewer({
823
1437
  window.location.reload();
824
1438
  };
825
1439
  if (loading) {
826
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.container, className, children: [
827
- /* @__PURE__ */ jsxRuntime.jsx("style", { children: spinnerKeyframes }),
828
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.loading, children: [
829
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles2.spinner }),
830
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { marginTop: "16px", color: "#6b7280" }, children: "Loading attempt..." })
1440
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.container, className, children: [
1441
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: spinnerKeyframes }),
1442
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.loading, children: [
1443
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.spinner }),
1444
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { marginTop: "16px", color: "#6b7280" }, children: "Loading attempt..." })
831
1445
  ] })
832
1446
  ] });
833
1447
  }
834
1448
  if (error || !attempt) {
835
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles2.container, className, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.error, children: [
836
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "18px", fontWeight: "500" }, children: "Failed to load attempt" }),
837
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { marginTop: "8px", color: "#6b7280" }, children: error }),
838
- /* @__PURE__ */ jsxRuntime.jsx("button", { style: defaultStyles2.retryButton, onClick: handleRetry, children: "Try Again" })
1449
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.container, className, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.error, children: [
1450
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontSize: "18px", fontWeight: "500" }, children: "Failed to load attempt" }),
1451
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { marginTop: "8px", color: "#6b7280" }, children: error }),
1452
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: defaultStyles2.retryButton, onClick: handleRetry, children: "Try Again" })
839
1453
  ] }) });
840
1454
  }
841
1455
  const scorePercentage = attempt.score ?? 0;
842
1456
  const correctCount = attempt.correctAnswers ?? 0;
843
1457
  const totalQuestions = attempt.totalQuestions;
844
1458
  const timeSpent = attempt.timeSpentSeconds ?? 0;
845
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.container, className, children: [
846
- /* @__PURE__ */ jsxRuntime.jsx("style", { children: spinnerKeyframes }),
847
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles2.header, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.summaryGrid, children: [
848
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.summaryCard, children: [
849
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.summaryValue, children: [
1459
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.container, className, children: [
1460
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: spinnerKeyframes }),
1461
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.header, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryGrid, children: [
1462
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
1463
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryValue, children: [
850
1464
  scorePercentage,
851
1465
  "%"
852
1466
  ] }),
853
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles2.summaryLabel, children: "Score" })
1467
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Score" })
854
1468
  ] }),
855
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.summaryCard, children: [
856
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.summaryValue, children: [
1469
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
1470
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryValue, children: [
857
1471
  correctCount,
858
1472
  "/",
859
1473
  totalQuestions
860
1474
  ] }),
861
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles2.summaryLabel, children: "Correct" })
1475
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Correct" })
862
1476
  ] }),
863
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.summaryCard, children: [
864
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles2.summaryValue, children: formatTime(timeSpent) }),
865
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles2.summaryLabel, children: "Time" })
1477
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
1478
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.summaryValue, children: formatTime(timeSpent) }),
1479
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Time" })
866
1480
  ] })
867
1481
  ] }) }),
868
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles2.questionsList, children: attempt.answers.map((answer, index) => /* @__PURE__ */ jsxRuntime.jsxs(
1482
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.questionsList, children: attempt.answers.map((answer, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
869
1483
  "div",
870
1484
  {
871
1485
  style: {
@@ -873,12 +1487,12 @@ function AttemptViewer({
873
1487
  ...answer.isCorrect ? defaultStyles2.questionCardCorrect : defaultStyles2.questionCardIncorrect
874
1488
  },
875
1489
  children: [
876
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.questionHeader, children: [
877
- /* @__PURE__ */ jsxRuntime.jsxs("span", { style: defaultStyles2.questionNumber, children: [
1490
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.questionHeader, children: [
1491
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: defaultStyles2.questionNumber, children: [
878
1492
  "Question ",
879
1493
  index + 1
880
1494
  ] }),
881
- /* @__PURE__ */ jsxRuntime.jsx(
1495
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
882
1496
  "span",
883
1497
  {
884
1498
  style: {
@@ -889,23 +1503,23 @@ function AttemptViewer({
889
1503
  }
890
1504
  )
891
1505
  ] }),
892
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles2.questionText, children: answer.questionText }),
893
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.answerSection, children: [
894
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: defaultStyles2.answerLabel, children: "Your answer:" }),
895
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: defaultStyles2.studentAnswer, children: formatAnswer(answer.selectedAnswer) })
1506
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.questionText, children: answer.questionText }),
1507
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.answerSection, children: [
1508
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: defaultStyles2.answerLabel, children: "Your answer:" }),
1509
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: defaultStyles2.studentAnswer, children: formatAnswer(answer.selectedAnswer) })
896
1510
  ] }),
897
- !answer.isCorrect && answer.correctAnswer && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.answerSection, children: [
898
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: defaultStyles2.answerLabel, children: "Correct answer:" }),
899
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: defaultStyles2.correctAnswer, children: formatAnswer(answer.correctAnswer) })
1511
+ !answer.isCorrect && answer.correctAnswer && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.answerSection, children: [
1512
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: defaultStyles2.answerLabel, children: "Correct answer:" }),
1513
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: defaultStyles2.correctAnswer, children: formatAnswer(answer.correctAnswer) })
900
1514
  ] }),
901
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.points, children: [
1515
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.points, children: [
902
1516
  answer.pointsEarned,
903
1517
  " / ",
904
1518
  answer.points,
905
1519
  " points"
906
1520
  ] }),
907
- showExplanations && answer.explanation && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.explanation, children: [
908
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Explanation:" }),
1521
+ showExplanations && answer.explanation && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.explanation, children: [
1522
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: "Explanation:" }),
909
1523
  " ",
910
1524
  answer.explanation
911
1525
  ] })
@@ -915,13 +1529,14 @@ function AttemptViewer({
915
1529
  )) })
916
1530
  ] });
917
1531
  }
918
-
919
- exports.AttemptViewer = AttemptViewer;
920
- exports.QuizApiClient = QuizApiClient;
921
- exports.QuizPlayer = QuizPlayer;
922
- exports.calculateScore = calculateScore;
923
- exports.checkAnswer = checkAnswer;
924
- exports.createAnswerDetail = createAnswerDetail;
925
- exports.formatTime = formatTime;
926
- //# sourceMappingURL=index.js.map
1532
+ // Annotate the CommonJS export names for ESM import in node:
1533
+ 0 && (module.exports = {
1534
+ AttemptViewer,
1535
+ QuizApiClient,
1536
+ QuizPlayer,
1537
+ calculateScore,
1538
+ checkAnswer,
1539
+ createAnswerDetail,
1540
+ formatTime
1541
+ });
927
1542
  //# sourceMappingURL=index.js.map