@superbuilders/incept-renderer 0.1.8 → 0.1.12

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
@@ -12,6 +12,7 @@ A secure, server-driven QTI 3.0 assessment renderer for React/Next.js applicatio
12
12
  - [Server Functions](#server-functions)
13
13
  - [Client Components](#client-components)
14
14
  - [Types](#types)
15
+ - [Rendering Stimuli](#rendering-stimuli)
15
16
  - [Theming](#theming)
16
17
  - [Complete Examples](#complete-examples)
17
18
  - [Supported Interactions](#supported-interactions)
@@ -317,6 +318,29 @@ const result = await validateResponsesSecure(qtiXmlString, {
317
318
  }
318
319
  ```
319
320
 
321
+ #### `parseAssessmentStimulusXml(xml: string)`
322
+
323
+ Parses QTI Assessment Stimulus XML (reading materials, passages).
324
+
325
+ ```tsx
326
+ import { parseAssessmentStimulusXml } from "@superbuilders/incept-renderer"
327
+
328
+ const stimulus = parseAssessmentStimulusXml(stimulusXmlString)
329
+ ```
330
+
331
+ **Parameters:**
332
+ - `xml` (string) - Raw QTI 3.0 Assessment Stimulus XML string
333
+
334
+ **Returns:**
335
+ ```tsx
336
+ {
337
+ identifier: string // Unique identifier from the XML
338
+ title: string // Human-readable title
339
+ xmlLang: string // Language code (e.g., "en")
340
+ bodyHtml: string // Sanitized HTML content ready for rendering
341
+ }
342
+ ```
343
+
320
344
  ---
321
345
 
322
346
  ### Client Components
@@ -357,6 +381,26 @@ import { QTIRenderer } from "@superbuilders/incept-renderer"
357
381
  | `responseFeedback` | `Record<string, { isCorrect: boolean; messageHtml?: string }>` | ❌ | Per-response feedback messages |
358
382
  | `theme` | `"duolingo" \| "neobrutalist" \| string` | ❌ | Visual theme (default: `"duolingo"`) |
359
383
 
384
+ #### `<QTIStimulusRenderer />`
385
+
386
+ Renders QTI Assessment Stimuli (reading materials, passages).
387
+
388
+ ```tsx
389
+ import { QTIStimulusRenderer } from "@superbuilders/incept-renderer"
390
+
391
+ <QTIStimulusRenderer
392
+ stimulus={parsedStimulus}
393
+ className="my-8"
394
+ />
395
+ ```
396
+
397
+ **Props:**
398
+
399
+ | Prop | Type | Required | Description |
400
+ |------|------|----------|-------------|
401
+ | `stimulus` | `AssessmentStimulus` | ✅ | Parsed stimulus from `parseAssessmentStimulusXml` |
402
+ | `className` | `string` | ❌ | Additional CSS classes for the container |
403
+
360
404
  ---
361
405
 
362
406
  ### Types
@@ -365,12 +409,15 @@ Import from `@superbuilders/incept-renderer`:
365
409
 
366
410
  ```tsx
367
411
  import type {
412
+ // Assessment Items (questions)
368
413
  DisplayItem,
369
414
  DisplayBlock,
370
415
  DisplayChoice,
371
416
  DisplayChoiceInteraction,
372
417
  FormShape,
373
- ValidateResult
418
+ ValidateResult,
419
+ // Assessment Stimuli (reading materials)
420
+ AssessmentStimulus
374
421
  } from "@superbuilders/incept-renderer"
375
422
  ```
376
423
 
@@ -435,17 +482,191 @@ interface ValidateResult {
435
482
  }
436
483
  ```
437
484
 
485
+ #### `AssessmentStimulus`
486
+
487
+ ```tsx
488
+ interface AssessmentStimulus {
489
+ identifier: string // Unique identifier from the XML
490
+ title: string // Human-readable title
491
+ xmlLang: string // Language code (e.g., "en", "es")
492
+ bodyHtml: string // Sanitized HTML content
493
+ }
494
+ ```
495
+
496
+ ---
497
+
498
+ ## Rendering Stimuli
499
+
500
+ In addition to assessment items (questions), the package supports rendering **Assessment Stimuli** — reading materials, passages, or articles that provide context for questions.
501
+
502
+ ### What are Stimuli?
503
+
504
+ QTI Assessment Stimuli (`qti-assessment-stimulus`) are standalone content blocks that contain:
505
+ - Reading passages
506
+ - Articles with images
507
+ - Reference materials
508
+ - Any HTML content that accompanies questions
509
+
510
+ Unlike assessment items, stimuli have no interactions or correct answers — they're purely display content.
511
+
512
+ ### Parsing Stimulus XML
513
+
514
+ Use `parseAssessmentStimulusXml` to parse stimulus XML:
515
+
516
+ ```tsx
517
+ import { parseAssessmentStimulusXml } from "@superbuilders/incept-renderer"
518
+
519
+ const stimulusXml = `<?xml version="1.0" encoding="UTF-8"?>
520
+ <qti-assessment-stimulus
521
+ xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0"
522
+ identifier="stimulus-1"
523
+ xml:lang="en"
524
+ title="Biodiversity and Ecosystem Health">
525
+ <qti-stimulus-body>
526
+ <h2>Biodiversity and ecosystem health</h2>
527
+ <p><strong>Biodiversity</strong> is the variety of species in an ecosystem.</p>
528
+ <figure>
529
+ <img src="coral-reef.jpg" alt="A coral reef" />
530
+ <figcaption>Coral reef ecosystems have high biodiversity.</figcaption>
531
+ </figure>
532
+ </qti-stimulus-body>
533
+ </qti-assessment-stimulus>`
534
+
535
+ const stimulus = parseAssessmentStimulusXml(stimulusXml)
536
+ // Returns: { identifier, title, xmlLang, bodyHtml }
537
+ ```
538
+
539
+ ### Rendering Stimuli
540
+
541
+ Use the `QTIStimulusRenderer` component to render parsed stimuli:
542
+
543
+ ```tsx
544
+ import { QTIStimulusRenderer, parseAssessmentStimulusXml } from "@superbuilders/incept-renderer"
545
+
546
+ function ReadingPassage({ xml }: { xml: string }) {
547
+ const stimulus = parseAssessmentStimulusXml(xml)
548
+
549
+ return (
550
+ <QTIStimulusRenderer
551
+ stimulus={stimulus}
552
+ className="my-8"
553
+ />
554
+ )
555
+ }
556
+ ```
557
+
558
+ ### Server Action Pattern
559
+
560
+ For Next.js apps, create a server action to parse stimuli:
561
+
562
+ ```tsx
563
+ // lib/qti-actions.ts
564
+ "use server"
565
+
566
+ import { parseAssessmentStimulusXml } from "@superbuilders/incept-renderer"
567
+
568
+ export async function parseStimulus(xml: string) {
569
+ return parseAssessmentStimulusXml(xml)
570
+ }
571
+ ```
572
+
573
+ ```tsx
574
+ // app/reading/[id]/page.tsx
575
+ import { parseStimulus } from "@/lib/qti-actions"
576
+ import { QTIStimulusRenderer } from "@superbuilders/incept-renderer"
577
+
578
+ export default async function ReadingPage({ params }: { params: Promise<{ id: string }> }) {
579
+ const { id } = await params
580
+ const xml = await fetchStimulusXml(id) // Your data fetching
581
+ const stimulus = await parseStimulus(xml)
582
+
583
+ return <QTIStimulusRenderer stimulus={stimulus} />
584
+ }
585
+ ```
586
+
587
+ ### Stimulus with Questions
588
+
589
+ A common pattern is showing a stimulus alongside related questions:
590
+
591
+ ```tsx
592
+ import { QTIStimulusRenderer, QTIRenderer } from "@superbuilders/incept-renderer"
593
+
594
+ function QuestionWithPassage({ stimulus, item, ...props }) {
595
+ return (
596
+ <div className="grid grid-cols-2 gap-8">
597
+ {/* Reading passage on the left */}
598
+ <div className="overflow-y-auto max-h-[80vh]">
599
+ <QTIStimulusRenderer stimulus={stimulus} />
600
+ </div>
601
+
602
+ {/* Question on the right */}
603
+ <div>
604
+ <QTIRenderer item={item} {...props} />
605
+ </div>
606
+ </div>
607
+ )
608
+ }
609
+ ```
610
+
611
+ ### AssessmentStimulus Type
612
+
613
+ ```tsx
614
+ interface AssessmentStimulus {
615
+ identifier: string // Unique ID from the XML
616
+ title: string // Human-readable title
617
+ xmlLang: string // Language code (e.g., "en")
618
+ bodyHtml: string // Sanitized HTML content
619
+ }
620
+ ```
621
+
622
+ ### Supported Content
623
+
624
+ The stimulus renderer supports:
625
+ - **Headings** (`h1`–`h6`)
626
+ - **Text formatting** (`p`, `strong`, `em`, `b`, `i`, `u`, `sub`, `sup`)
627
+ - **Lists** (`ul`, `ol`, `li`)
628
+ - **Images** (`img`, `figure`, `figcaption`)
629
+ - **Tables** (`table`, `thead`, `tbody`, `tr`, `th`, `td`)
630
+ - **Links** (`a`)
631
+ - **MathML** (mathematical expressions)
632
+ - **Collapsible sections** (`details`, `summary`)
633
+ - **Semantic elements** (`article`, `section`, `blockquote`, `cite`)
634
+
635
+ All content is sanitized to prevent XSS attacks while preserving safe HTML structure.
636
+
438
637
  ---
439
638
 
440
639
  ## Theming
441
640
 
442
- The package includes two built-in themes and supports custom themes via CSS variables.
641
+ The package includes two built-in themes and supports custom themes via CSS variables. **You are not required to use the built-in styles** — you can fully customize the appearance to match your application's design system.
642
+
643
+ ### Required CSS Imports
644
+
645
+ Import the base theme CSS in your app's root layout or global styles:
646
+
647
+ ```tsx
648
+ // In your layout.tsx or globals.css
649
+ import "@superbuilders/incept-renderer/styles/themes.css"
650
+ ```
443
651
 
444
652
  ### Built-in Themes
445
653
 
446
654
  #### Duolingo (Default)
447
655
 
448
- Clean, friendly design inspired by Duolingo's learning interface.
656
+ Clean, friendly design inspired by Duolingo's learning interface with a vibrant color palette.
657
+
658
+ **For the complete Duolingo experience**, import the Duolingo theme CSS which includes:
659
+ - Full Duolingo color palette (owl green, macaw blue, cardinal red, etc.)
660
+ - Rounded square choice indicators (A, B, C, D)
661
+ - Filled checkboxes with inset gap effect
662
+ - HTML content styling (lists, typography)
663
+ - Dark mode support
664
+
665
+ ```tsx
666
+ // In your layout.tsx or globals.css
667
+ import "@superbuilders/incept-renderer/styles/themes.css"
668
+ import "@superbuilders/incept-renderer/styles/duolingo.css" // Full Duolingo styling
669
+ ```
449
670
 
450
671
  ```tsx
451
672
  <QTIRenderer theme="duolingo" ... />
@@ -461,19 +682,200 @@ Bold, high-contrast design with thick borders and sharp shadows.
461
682
 
462
683
  ### Custom Themes
463
684
 
464
- Create your own theme by overriding CSS variables:
685
+ Create your own theme by defining CSS variables for `[data-qti-theme="your-theme-name"]` in your stylesheet, then pass that theme name to the `QTIRenderer` component.
686
+
687
+ ```tsx
688
+ <QTIRenderer theme="my-custom-theme" ... />
689
+ ```
690
+
691
+ ### CSS Variables Reference
692
+
693
+ Below is the **complete list of CSS variables** you can override when creating a custom theme. All variables are optional — any variable you don't define will fall back to the default value.
694
+
695
+ #### Container Variables
696
+
697
+ Controls the main question container/card appearance.
698
+
699
+ | Variable | Description | Default |
700
+ |----------|-------------|---------|
701
+ | `--qti-container-bg` | Background color of the container | `var(--background, #ffffff)` |
702
+ | `--qti-container-border` | Border color of the container | `var(--border, #e5e5e5)` |
703
+ | `--qti-container-border-width` | Border thickness | `2px` |
704
+ | `--qti-container-radius` | Border radius (rounded corners) | `var(--radius, 0.5rem)` |
705
+ | `--qti-container-shadow` | Box shadow | `none` |
706
+
707
+ #### Typography Variables
708
+
709
+ Controls fonts and text styling throughout the renderer.
710
+
711
+ | Variable | Description | Default |
712
+ |----------|-------------|---------|
713
+ | `--qti-font-family` | Font family for all QTI text | `inherit` |
714
+ | `--qti-font-weight` | Base font weight | `500` |
715
+ | `--qti-tracking` | Letter spacing | `normal` |
716
+
717
+ #### Choice Variables
718
+
719
+ Controls the appearance of answer choices (radio buttons, checkboxes).
720
+
721
+ | Variable | Description | Default |
722
+ |----------|-------------|---------|
723
+ | `--qti-choice-bg` | Default background of choices | `var(--background, #ffffff)` |
724
+ | `--qti-choice-border` | Default border color of choices | `var(--accent, #f4f4f5)` |
725
+ | `--qti-choice-hover-bg` | Background on hover | `var(--muted, #f4f4f5)` |
726
+ | `--qti-choice-selected-bg` | Background when selected | `oklch(0.9 0.1 220 / 0.3)` |
727
+ | `--qti-choice-selected-border` | Border when selected | `oklch(0.6 0.2 250)` |
728
+ | `--qti-choice-correct-bg` | Background for correct answers | `oklch(0.9 0.15 140 / 0.3)` |
729
+ | `--qti-choice-correct-border` | Border for correct answers | `#22c55e` |
730
+ | `--qti-choice-incorrect-bg` | Background for incorrect answers | `oklch(0.9 0.1 25 / 0.3)` |
731
+ | `--qti-choice-incorrect-border` | Border for incorrect answers | `#ef4444` |
732
+
733
+ #### Button Variables
734
+
735
+ Controls the appearance of buttons (if using the QTI button classes).
736
+
737
+ | Variable | Description | Default |
738
+ |----------|-------------|---------|
739
+ | `--qti-button-bg` | Button background color | `var(--primary, #18181b)` |
740
+ | `--qti-button-text` | Button text color | `var(--primary-foreground, #ffffff)` |
741
+ | `--qti-button-border` | Button border color | `var(--primary, #18181b)` |
742
+ | `--qti-button-shadow` | Button box shadow | `none` |
743
+ | `--qti-button-radius` | Button border radius | `var(--radius, 0.5rem)` |
744
+ | `--qti-button-font-weight` | Button font weight | `700` |
745
+
746
+ #### Feedback Variables
747
+
748
+ Controls the appearance of feedback messages shown after validation.
749
+
750
+ | Variable | Description | Default |
751
+ |----------|-------------|---------|
752
+ | `--qti-feedback-bg` | Feedback container background | `var(--background, #ffffff)` |
753
+ | `--qti-feedback-border` | Feedback container border | `var(--border, #e5e5e5)` |
754
+ | `--qti-feedback-correct-text` | Text color for correct feedback | `#22c55e` |
755
+ | `--qti-feedback-correct-icon-bg` | Icon background for correct | `#22c55e` |
756
+ | `--qti-feedback-incorrect-text` | Text color for incorrect feedback | `#ef4444` |
757
+ | `--qti-feedback-incorrect-icon-bg` | Icon background for incorrect | `#ef4444` |
758
+
759
+ #### Input Variables (Text Entry, Textarea, Dropdowns)
760
+
761
+ Controls the appearance of text input fields, textareas, and select/dropdown elements.
762
+
763
+ | Variable | Description | Default |
764
+ |----------|-------------|---------|
765
+ | `--qti-input-bg` | Input background color | `var(--background, #ffffff)` |
766
+ | `--qti-input-border` | Input border color | `var(--border, #e5e5e5)` |
767
+ | `--qti-input-border-width` | Input border thickness | `2px` |
768
+ | `--qti-input-radius` | Input border radius | `var(--radius, 0.5rem)` |
769
+ | `--qti-input-shadow` | Input box shadow | `none` |
770
+ | `--qti-input-focus-border` | Border color when focused | `var(--ring, #2563eb)` |
771
+ | `--qti-input-focus-shadow` | Box shadow when focused | `0 0 0 2px rgba(37, 99, 235, 0.2)` |
772
+ | `--qti-input-correct-border` | Border for correct text answers | `#22c55e` |
773
+ | `--qti-input-incorrect-border` | Border for incorrect text answers | `#ef4444` |
774
+
775
+ #### Choice Indicator Variables (Radio/Checkbox Circles)
776
+
777
+ Controls the appearance of the radio button and checkbox indicators.
778
+
779
+ | Variable | Description | Default |
780
+ |----------|-------------|---------|
781
+ | `--qti-indicator-size` | Size of the indicator | `1.5rem` |
782
+ | `--qti-indicator-bg` | Default background | `var(--background, #ffffff)` |
783
+ | `--qti-indicator-border` | Default border color | `var(--border, #d4d4d8)` |
784
+ | `--qti-indicator-border-width` | Border thickness | `2px` |
785
+ | `--qti-indicator-radius` | Border radius (use `9999px` for circles, `0` for squares) | `9999px` |
786
+ | `--qti-indicator-checked-bg` | Background when checked | `var(--primary, #18181b)` |
787
+ | `--qti-indicator-checked-border` | Border when checked | `var(--primary, #18181b)` |
788
+ | `--qti-indicator-checked-text` | Checkmark/dot color | `var(--primary-foreground, #ffffff)` |
789
+
790
+ #### Progress Bar Variables
791
+
792
+ Controls the appearance of progress indicators.
793
+
794
+ | Variable | Description | Default |
795
+ |----------|-------------|---------|
796
+ | `--qti-progress-bg` | Progress bar background (empty part) | `var(--muted, #f4f4f5)` |
797
+ | `--qti-progress-fill` | Progress bar fill color | `var(--primary, #18181b)` |
798
+ | `--qti-progress-height` | Height of the progress bar | `0.5rem` |
799
+ | `--qti-progress-radius` | Border radius | `var(--radius, 0.5rem)` |
800
+ | `--qti-progress-border` | Border color | `transparent` |
801
+ | `--qti-progress-border-width` | Border thickness | `0px` |
802
+
803
+ #### Content Variables (Question Text, Prompts)
804
+
805
+ Controls the appearance of HTML content (question text, prompts, instructions).
806
+
807
+ | Variable | Description | Default |
808
+ |----------|-------------|---------|
809
+ | `--qti-content-font-size` | Font size for content | `1.125rem` |
810
+ | `--qti-content-line-height` | Line height for content | `1.625` |
811
+ | `--qti-content-font-weight` | Font weight for content | `500` |
812
+
813
+ #### Image Grid Variables
814
+
815
+ Controls the layout of image-based choice interactions.
816
+
817
+ | Variable | Description | Default |
818
+ |----------|-------------|---------|
819
+ | `--qti-grid-gap` | Gap between grid items | `1rem` |
820
+ | `--qti-image-card-min-height` | Minimum height of image cards | `180px` |
821
+ | `--qti-image-card-padding` | Padding inside image cards | `1rem` |
822
+ | `--qti-image-max-height` | Maximum height of images | `160px` |
823
+
824
+ #### Order Interaction Variables (Drag-and-Drop Sorting)
825
+
826
+ Controls the appearance of the order interaction container that holds draggable items.
827
+
828
+ | Variable | Description | Default |
829
+ |----------|-------------|---------|
830
+ | `--qti-order-container-bg` | Container background | `var(--muted, #f4f4f5)` |
831
+ | `--qti-order-container-border` | Container border color | `transparent` |
832
+ | `--qti-order-container-border-width` | Container border thickness | `2px` |
833
+ | `--qti-order-container-radius` | Container border radius | `0.75rem` |
834
+ | `--qti-order-container-padding` | Container padding | `1rem` |
835
+ | `--qti-order-container-gap` | Gap between drag items | `0.5rem` |
836
+ | `--qti-order-container-correct-bg` | Background when correct | `oklch(0.9 0.15 140 / 0.2)` |
837
+ | `--qti-order-container-correct-border` | Border when correct | `#22c55e` |
838
+ | `--qti-order-container-incorrect-bg` | Background when incorrect | `oklch(0.9 0.1 25 / 0.2)` |
839
+ | `--qti-order-container-incorrect-border` | Border when incorrect | `#ef4444` |
840
+
841
+ #### Order Item Variables (Drag Tokens)
842
+
843
+ Controls the appearance of individual draggable items/tokens.
844
+
845
+ | Variable | Description | Default |
846
+ |----------|-------------|---------|
847
+ | `--qti-order-item-bg` | Item background | `var(--background, #ffffff)` |
848
+ | `--qti-order-item-border` | Item border color | `var(--border, #e5e5e5)` |
849
+ | `--qti-order-item-border-width` | Item border thickness | `1px` |
850
+ | `--qti-order-item-radius` | Item border radius | `0.5rem` |
851
+ | `--qti-order-item-padding` | Item padding | `1rem` |
852
+ | `--qti-order-item-shadow` | Item box shadow | `0 1px 2px rgba(0, 0, 0, 0.05)` |
853
+ | `--qti-order-item-hover-border` | Border on hover | `oklch(0.6 0.2 250)` |
854
+ | `--qti-order-item-hover-shadow` | Shadow on hover | `0 4px 6px rgba(0, 0, 0, 0.1)` |
855
+ | `--qti-order-item-dragging-border` | Border while dragging | `oklch(0.6 0.2 250)` |
856
+ | `--qti-order-item-dragging-shadow` | Shadow while dragging | `0 8px 16px rgba(0, 0, 0, 0.15)` |
857
+ | `--qti-order-item-handle-color` | Drag handle icon color | `var(--muted-foreground, #71717a)` |
858
+
859
+ ### Complete Custom Theme Example
860
+
861
+ Here's a complete example defining all variables for a custom theme:
465
862
 
466
863
  ```css
467
- /* In your globals.css */
864
+ /* In your globals.css or styles file */
468
865
  [data-qti-theme="my-custom-theme"] {
469
- /* Container styling */
866
+ /* ========== Container ========== */
470
867
  --qti-container-bg: #ffffff;
471
868
  --qti-container-border: #e0e0e0;
472
869
  --qti-container-border-width: 1px;
473
870
  --qti-container-radius: 12px;
474
871
  --qti-container-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
475
872
 
476
- /* Choice styling */
873
+ /* ========== Typography ========== */
874
+ --qti-font-family: "Inter", system-ui, sans-serif;
875
+ --qti-font-weight: 400;
876
+ --qti-tracking: normal;
877
+
878
+ /* ========== Choices (Radio/Checkbox Cards) ========== */
477
879
  --qti-choice-bg: #f8f8f8;
478
880
  --qti-choice-border: #d0d0d0;
479
881
  --qti-choice-hover-bg: #f0f0f0;
@@ -484,30 +886,151 @@ Create your own theme by overriding CSS variables:
484
886
  --qti-choice-incorrect-bg: #ffebee;
485
887
  --qti-choice-incorrect-border: #f44336;
486
888
 
487
- /* Button styling */
889
+ /* ========== Buttons ========== */
488
890
  --qti-button-bg: #2196f3;
489
891
  --qti-button-text: #ffffff;
490
892
  --qti-button-border: transparent;
491
893
  --qti-button-shadow: none;
894
+ --qti-button-radius: 8px;
895
+ --qti-button-font-weight: 600;
492
896
 
493
- /* Typography */
494
- --qti-font-family: system-ui, sans-serif;
495
- --qti-font-weight: 400;
496
-
497
- /* Feedback styling */
897
+ /* ========== Feedback Messages ========== */
498
898
  --qti-feedback-bg: #ffffff;
499
899
  --qti-feedback-border: #e0e0e0;
500
900
  --qti-feedback-correct-text: #2e7d32;
901
+ --qti-feedback-correct-icon-bg: #4caf50;
501
902
  --qti-feedback-incorrect-text: #c62828;
903
+ --qti-feedback-incorrect-icon-bg: #f44336;
904
+
905
+ /* ========== Text Inputs / Textareas / Dropdowns ========== */
906
+ --qti-input-bg: #ffffff;
907
+ --qti-input-border: #d0d0d0;
908
+ --qti-input-border-width: 1px;
909
+ --qti-input-radius: 8px;
910
+ --qti-input-shadow: none;
911
+ --qti-input-focus-border: #2196f3;
912
+ --qti-input-focus-shadow: 0 0 0 3px rgba(33, 150, 243, 0.2);
913
+ --qti-input-correct-border: #4caf50;
914
+ --qti-input-incorrect-border: #f44336;
915
+
916
+ /* ========== Choice Indicators (Radio/Checkbox Circles) ========== */
917
+ --qti-indicator-size: 1.5rem;
918
+ --qti-indicator-bg: #ffffff;
919
+ --qti-indicator-border: #d0d0d0;
920
+ --qti-indicator-border-width: 2px;
921
+ --qti-indicator-radius: 9999px; /* Use 0 for square checkboxes */
922
+ --qti-indicator-checked-bg: #2196f3;
923
+ --qti-indicator-checked-border: #2196f3;
924
+ --qti-indicator-checked-text: #ffffff;
925
+
926
+ /* ========== Progress Bar ========== */
927
+ --qti-progress-bg: #e0e0e0;
928
+ --qti-progress-fill: #2196f3;
929
+ --qti-progress-height: 8px;
930
+ --qti-progress-radius: 4px;
931
+ --qti-progress-border: transparent;
932
+ --qti-progress-border-width: 0;
933
+
934
+ /* ========== Content (Question Text) ========== */
935
+ --qti-content-font-size: 1.125rem;
936
+ --qti-content-line-height: 1.6;
937
+ --qti-content-font-weight: 400;
938
+
939
+ /* ========== Image Grids ========== */
940
+ --qti-grid-gap: 1rem;
941
+ --qti-image-card-min-height: 180px;
942
+ --qti-image-card-padding: 1rem;
943
+ --qti-image-max-height: 160px;
944
+
945
+ /* ========== Order Interaction (Drag-and-Drop Container) ========== */
946
+ --qti-order-container-bg: #f5f5f5;
947
+ --qti-order-container-border: #e0e0e0;
948
+ --qti-order-container-border-width: 1px;
949
+ --qti-order-container-radius: 12px;
950
+ --qti-order-container-padding: 1rem;
951
+ --qti-order-container-gap: 0.75rem;
952
+ --qti-order-container-correct-bg: rgba(76, 175, 80, 0.15);
953
+ --qti-order-container-correct-border: #4caf50;
954
+ --qti-order-container-incorrect-bg: rgba(244, 67, 54, 0.15);
955
+ --qti-order-container-incorrect-border: #f44336;
956
+
957
+ /* ========== Order Items (Drag Tokens) ========== */
958
+ --qti-order-item-bg: #ffffff;
959
+ --qti-order-item-border: #e0e0e0;
960
+ --qti-order-item-border-width: 1px;
961
+ --qti-order-item-radius: 8px;
962
+ --qti-order-item-padding: 1rem;
963
+ --qti-order-item-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
964
+ --qti-order-item-hover-border: #2196f3;
965
+ --qti-order-item-hover-shadow: 0 4px 8px rgba(0, 0, 0, 0.12);
966
+ --qti-order-item-dragging-border: #2196f3;
967
+ --qti-order-item-dragging-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
968
+ --qti-order-item-handle-color: #9e9e9e;
969
+ }
970
+
971
+ /* Optional: Dark mode support for your custom theme */
972
+ .dark[data-qti-theme="my-custom-theme"],
973
+ [data-qti-theme="my-custom-theme"] .dark {
974
+ /* Container */
975
+ --qti-container-bg: #1a1a1a;
976
+ --qti-container-border: #333333;
977
+
978
+ /* Choices */
979
+ --qti-choice-bg: #252525;
980
+ --qti-choice-border: #444444;
981
+ --qti-choice-hover-bg: #333333;
982
+ --qti-choice-selected-bg: rgba(33, 150, 243, 0.2);
983
+ --qti-choice-selected-border: #2196f3;
984
+ --qti-choice-correct-bg: rgba(76, 175, 80, 0.2);
985
+ --qti-choice-incorrect-bg: rgba(244, 67, 54, 0.2);
986
+
987
+ /* Feedback */
988
+ --qti-feedback-bg: #1a1a1a;
989
+ --qti-feedback-border: #333333;
990
+
991
+ /* Inputs */
992
+ --qti-input-bg: #252525;
993
+ --qti-input-border: #444444;
994
+
995
+ /* Indicators */
996
+ --qti-indicator-bg: #252525;
997
+ --qti-indicator-border: #444444;
998
+
999
+ /* Progress */
1000
+ --qti-progress-bg: #333333;
1001
+
1002
+ /* Order Interaction */
1003
+ --qti-order-container-bg: #252525;
1004
+ --qti-order-container-border: #444444;
1005
+ --qti-order-container-correct-bg: rgba(76, 175, 80, 0.2);
1006
+ --qti-order-container-incorrect-bg: rgba(244, 67, 54, 0.2);
1007
+
1008
+ /* Order Items */
1009
+ --qti-order-item-bg: #1a1a1a;
1010
+ --qti-order-item-border: #444444;
1011
+ --qti-order-item-handle-color: #888888;
502
1012
  }
503
1013
  ```
504
1014
 
505
- Then use it:
1015
+ Then use your custom theme:
506
1016
 
507
1017
  ```tsx
508
1018
  <QTIRenderer theme="my-custom-theme" ... />
509
1019
  ```
510
1020
 
1021
+ ### Partial Overrides
1022
+
1023
+ You don't need to define all variables. You can create a theme that only overrides specific values:
1024
+
1025
+ ```css
1026
+ /* Minimal custom theme - only changes colors */
1027
+ [data-qti-theme="brand-colors"] {
1028
+ --qti-choice-selected-bg: #your-brand-color-light;
1029
+ --qti-choice-selected-border: #your-brand-color;
1030
+ --qti-button-bg: #your-brand-color;
1031
+ }
1032
+ ```
1033
+
511
1034
  ---
512
1035
 
513
1036
  ## Complete Examples
@@ -1,4 +1,7 @@
1
- import { g as DisplayItem, F as FormShape, V as ValidateResult } from '../types-B7YRTQKt.js';
1
+ export { a as parseAssessmentStimulusXml } from '../parser-B8n3iHSM.js';
2
+ import { k as DisplayItem, m as FormShape, V as ValidateResult } from '../schema-DKduufCs.js';
3
+ export { a as AssessmentStimulus } from '../schema-DKduufCs.js';
4
+ import 'zod';
2
5
 
3
6
  declare function buildDisplayModelFromXml(qtiXml: string): {
4
7
  itemKey: string;