@pie-element/mc-populated-blank 0.2.10 → 0.2.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.
Files changed (55) hide show
  1. package/README.md +0 -2
  2. package/dist/author/index.js +186 -152
  3. package/dist/author/index.js.map +1 -1
  4. package/dist/controller/defaults.d.ts +41 -73
  5. package/dist/controller/defaults.d.ts.map +1 -1
  6. package/dist/controller/index.d.ts +154 -10
  7. package/dist/controller/index.d.ts.map +1 -1
  8. package/dist/controller/index.js +29 -30
  9. package/dist/controller/index.js.map +1 -1
  10. package/dist/delivery/computeAudioMode.d.ts +7 -0
  11. package/dist/delivery/computeAudioMode.d.ts.map +1 -0
  12. package/dist/delivery/computeChoiceCorrectness.d.ts +18 -0
  13. package/dist/delivery/computeChoiceCorrectness.d.ts.map +1 -0
  14. package/dist/delivery/computeDisplayState.d.ts +47 -0
  15. package/dist/delivery/computeDisplayState.d.ts.map +1 -0
  16. package/dist/delivery/computeLayoutProfile.d.ts +18 -0
  17. package/dist/delivery/computeLayoutProfile.d.ts.map +1 -0
  18. package/dist/delivery/computeLayoutStyle.d.ts +27 -0
  19. package/dist/delivery/computeLayoutStyle.d.ts.map +1 -0
  20. package/dist/delivery/index.d.ts.map +1 -1
  21. package/dist/delivery/index.js +1591 -927
  22. package/dist/delivery/index.js.map +1 -1
  23. package/dist/delivery/variant-css-map.d.ts.map +1 -1
  24. package/dist/index.iife.d.ts +151 -10
  25. package/dist/index.iife.d.ts.map +1 -1
  26. package/dist/index.iife.js +233 -114
  27. package/dist/index.js +1591 -927
  28. package/dist/index.js.map +1 -1
  29. package/dist/print/index.js +194 -166
  30. package/dist/print/index.js.map +1 -1
  31. package/dist/shared/layoutLimits.d.ts +40 -0
  32. package/dist/shared/layoutLimits.d.ts.map +1 -0
  33. package/dist/shared/types.d.ts +82 -0
  34. package/dist/shared/types.d.ts.map +1 -0
  35. package/package.json +5 -14
  36. package/src/author/Author.svelte +0 -41
  37. package/src/author/index.ts +0 -28
  38. package/src/controller/defaults.ts +0 -91
  39. package/src/controller/index.ts +0 -359
  40. package/src/delivery/McPopulatedBlank.svelte +0 -1246
  41. package/src/delivery/cqt-css/sel-r1-g-stem.css +0 -29
  42. package/src/delivery/cqt-css/sel-r1-gg-plus.css +0 -29
  43. package/src/delivery/cqt-css/sel-r1-ggplus.css +0 -34
  44. package/src/delivery/cqt-css/sel-r1-gplusggg.css +0 -20
  45. package/src/delivery/cqt-css/sel-r1-plusggg.css +0 -20
  46. package/src/delivery/cqt-css/sel-r1-s3.css +0 -91
  47. package/src/delivery/cqt-css/sel-vic.css +0 -38
  48. package/src/delivery/cqt-css/sr-vic.css +0 -14
  49. package/src/delivery/index.ts +0 -103
  50. package/src/delivery/variant-css-map.ts +0 -110
  51. package/src/index.iife.ts +0 -13
  52. package/src/index.ts +0 -1
  53. package/src/print/Print.svelte +0 -95
  54. package/src/print/index.ts +0 -18
  55. package/src/types/css-raw.d.ts +0 -4
@@ -1,1246 +0,0 @@
1
- <svelte:options
2
- customElement={{
3
- tag: 'mc-populated-blank',
4
- shadow: 'none',
5
- props: {
6
- model: { type: 'Object' },
7
- session: { type: 'Object' },
8
- options: { type: 'Object' },
9
- },
10
- }}
11
- />
12
-
13
- <script lang="ts">
14
- import { color } from '@pie-lib/styling-svelte';
15
- import {
16
- ensureVariantCssInjected,
17
- getVariantCssConfig,
18
- getVariantRootClass,
19
- } from './variant-css-map';
20
- /** Must match controller `BLANK_TOKEN` (kept local to avoid pulling controller into delivery). */
21
- const BLANK_TOKEN = '{{blank}}';
22
- const DEFAULT_LAYOUT_LIMITS = {
23
- blankStandaloneWidthRem: 7,
24
- blankWideWidthRem: 10,
25
- blankUnderlineWidthPx: 2,
26
- blankUnderlineWideWidthPx: 4,
27
- horizontalChoiceWidthPx: 170,
28
- horizontalChoiceWidthVw: 30,
29
- horizontalChoiceTileMinHeightRem: 11,
30
- horizontalChoiceContentMinHeightRem: 7.5,
31
- selectedImageMaxHeightRem: 4,
32
- choiceImageMaxHeightRem: 5,
33
- listenButtonSizePx: 128,
34
- stimulusMinColumnPx: 210,
35
- textMinColumnPx: 260,
36
- legendMaxChars: 120,
37
- choiceGroupGapRem: 0.5,
38
- choiceRowGapRem: 0.5,
39
- toggleButtonGapRem: 0.5,
40
- horizontalChoiceRadioTopMarginRem: 0.5,
41
- audioBlankTemplateMarginTopRem: 0.8,
42
- audioBlankTemplateMarginBottomRem: 1.8,
43
- audioInstructionsMaxWidthPx: 875,
44
- narrowHorizontalChoiceMaxWidthPx: 230,
45
- stimulusGridColumnGapRem: 2,
46
- stimulusGridRowGapRem: 0.7,
47
- stimulusSentenceMarginTopRem: 0.2,
48
- stimulusChoicesMarginTopRem: 0.9,
49
- tokenGridColumnGapRem: 1.5,
50
- tokenGridRowGapRem: 0.8,
51
- tokenTemplateMarginTopRem: 0.6,
52
- tokenInlineTokenGapRem: 0.35,
53
- tokenChoicesMarginTopRem: 0.2,
54
- inlineGridColumnGapRem: 1.5,
55
- inlineGridRowGapRem: 0.65,
56
- inlineTemplateMarginTopRem: 0.45,
57
- inlineChoicesMarginTopRem: 0.25,
58
- } as const;
59
- const DEFAULT_LAYOUT_PROFILE_PRESETS: Record<string, Record<string, number>> = {
60
- audio_blank_only: {
61
- blankWideWidthRem: 10,
62
- blankUnderlineWideWidthPx: 4,
63
- },
64
- stimulus_image_blank: {
65
- blankWideWidthRem: 10,
66
- blankUnderlineWideWidthPx: 4,
67
- },
68
- token_sequence: {
69
- blankStandaloneWidthRem: 7,
70
- blankUnderlineWideWidthPx: 4,
71
- },
72
- };
73
- const DEFAULT_AUDIO_BUTTON_SKINS = {
74
- default: {
75
- silentUrl:
76
- 'https://assets.learnosity.com/organisations/844/0c9f2aa3-3cd5-4de7-93ef-541c24ca35da.svg',
77
- playingUrl:
78
- 'https://assets.learnosity.com/organisations/844/231dfdc2-c113-4be5-91fb-e75a0ca5994b.svg',
79
- },
80
- es: {
81
- silentUrl:
82
- 'https://assets.learnosity.com/organisations/844/27a9d5b5-d873-4bd5-b9ba-22748782d8ba.svg',
83
- playingUrl:
84
- 'https://assets.learnosity.com/organisations/844/120f216d-96b7-4560-94b8-1d90710216b7.svg',
85
- },
86
- } as const;
87
- const DEFAULT_UI_TEXT = {
88
- answerChoices: 'Answer choices',
89
- selectedAnswerInSentence: 'Selected answer in sentence',
90
- showCorrectAnswer: 'Show correct answer',
91
- hideCorrectAnswer: 'Hide correct answer',
92
- clickToEnableAutoplay: 'Click to enable audio autoplay',
93
- audioResourceUnavailable: 'Audio is enabled but no playable audio URL is configured.',
94
- transcriptLabel: 'Transcript',
95
- listenLabelEn: 'Listen',
96
- listenLabelEs: 'Escuchar',
97
- } as const;
98
-
99
- let { model, session } = $props<{ model?: any; session?: any; options?: any }>();
100
- let audioEl = $state<HTMLAudioElement | null>(null);
101
- let isMediaPlaying = $state(false);
102
- let localChoiceId = $state('');
103
- let autoPlayPromptOpen = $state(false);
104
- let autoplayAttempted = $state(false);
105
- let featureAudioButtonEl = $state<HTMLButtonElement | null>(null);
106
- let autoplayEnableButtonEl = $state<HTMLButtonElement | null>(null);
107
- let toggleCorrectAnswerButtonEl = $state<HTMLButtonElement | null>(null);
108
- let choicesGroupEl = $state<HTMLDivElement | null>(null);
109
- const instanceId = `mc-populated-blank-${Math.random().toString(36).slice(2, 10)}`;
110
-
111
- const deliveryMode = $derived((model?.mode || model?.env?.mode || '').toString());
112
- const isEvaluateMode = $derived(deliveryMode === 'evaluate');
113
- const correctness = $derived(model?.correctness);
114
- const isResponseCorrect = $derived(
115
- typeof model?.responseCorrect === 'boolean' ? model.responseCorrect : correctness === 'correct'
116
- );
117
- const isCorrect = $derived(correctness === 'correct');
118
- const isIncorrect = $derived(correctness === 'incorrect');
119
- const isAudioOnlyMode = $derived(model?.interactionMode === 'audio_mc_only');
120
- const isBlankOnlyTemplate = $derived.by(() => {
121
- const t = model?.template || '';
122
- if (!t) return false;
123
- const plain = t
124
- .replace(/<[^>]+>/g, '')
125
- .replace(/&nbsp;/g, ' ')
126
- .trim();
127
- return plain === BLANK_TOKEN;
128
- });
129
- const layoutProfile = $derived(model?.layoutProfile || '');
130
- const variantCssConfig = $derived(getVariantCssConfig(model?.customType));
131
- const variantRootClass = $derived(getVariantRootClass(model?.customType));
132
- const choiceLayout = $derived(
133
- model?.choiceLayout || (isAudioOnlyMode || isBlankOnlyTemplate ? 'horizontal' : 'vertical')
134
- );
135
- const isHorizontalChoices = $derived(choiceLayout === 'horizontal');
136
- const showVisibleTranscript = $derived(!!model?.showVisibleTranscript);
137
- const hasPlayableAudio = $derived(!!model?.hasAudio && !!model?.audioUrl);
138
- const hasAudioButMissingResource = $derived(!!model?.hasAudio && !model?.audioUrl);
139
- const hasInlineSentenceAudioLayout = $derived(
140
- layoutProfile === 'inline_sentence' && !!model?.hasAudio
141
- );
142
- const uiText = $derived.by(() => ({
143
- ...DEFAULT_UI_TEXT,
144
- ...(model?.uiText || {}),
145
- }));
146
- const profilePresetLimits = $derived.by(() => {
147
- const profile = String(layoutProfile || '');
148
- const defaults = DEFAULT_LAYOUT_PROFILE_PRESETS[profile] || {};
149
- const customPresets =
150
- model?.layoutProfilePresets && typeof model.layoutProfilePresets === 'object'
151
- ? model.layoutProfilePresets
152
- : {};
153
- const custom =
154
- customPresets[profile] && typeof customPresets[profile] === 'object'
155
- ? customPresets[profile]
156
- : {};
157
- return {
158
- ...defaults,
159
- ...custom,
160
- };
161
- });
162
- const layoutLimits = $derived.by(() => {
163
- const configured =
164
- model?.layoutLimits && typeof model.layoutLimits === 'object' ? model.layoutLimits : {};
165
- return {
166
- ...DEFAULT_LAYOUT_LIMITS,
167
- ...profilePresetLimits,
168
- ...configured,
169
- };
170
- });
171
- const correctAnswerStyleVars = $derived.by(() =>
172
- [
173
- `--pie-correct-answer-toggle-label-color:${color.text()}`,
174
- `--pie-correct-answer-toggle-icon-open-bg:${color.tertiaryLight()}`,
175
- `--pie-correct-answer-toggle-icon-closed-bg:${color.backgroundDark()}`,
176
- `--pie-correct-answer-toggle-icon-glyph-color:${color.tertiary()}`,
177
- `--pie-correct-answer-choice-hover-bg:${color.backgroundDark()}`,
178
- `--pie-correct-answer-choice-selected-bg:${color.secondaryBackground()}`,
179
- `--pie-correct-answer-choice-correct-bg:${color.correctSecondary()}`,
180
- `--pie-correct-answer-choice-incorrect-bg:${color.incorrectSecondary()}`,
181
- `--pie-correct-answer-choice-correct-border:${color.correctTertiary()}`,
182
- `--pie-correct-answer-choice-incorrect-border:${color.incorrectWithIcon()}`,
183
- `--pie-correct-answer-feedback-correct-bg:${color.correctWithIcon()}`,
184
- `--pie-correct-answer-feedback-incorrect-bg:${color.incorrectWithIcon()}`,
185
- `--pie-correct-answer-feedback-glyph-color:${color.white()}`,
186
- ].join(';')
187
- );
188
- const rootStyle = $derived.by(() =>
189
- [
190
- `--mpb-listen-button-size:${layoutLimits.listenButtonSizePx}px`,
191
- `--mpb-blank-standalone-width:${layoutLimits.blankStandaloneWidthRem}rem`,
192
- `--mpb-blank-wide-width:${layoutLimits.blankWideWidthRem}rem`,
193
- `--mpb-blank-underline-width:${layoutLimits.blankUnderlineWidthPx}px`,
194
- `--mpb-blank-underline-wide-width:${layoutLimits.blankUnderlineWideWidthPx}px`,
195
- `--mpb-selected-image-max-height:${layoutLimits.selectedImageMaxHeightRem}rem`,
196
- `--mpb-choice-image-max-height:${layoutLimits.choiceImageMaxHeightRem}rem`,
197
- `--mpb-choice-width-px:${layoutLimits.horizontalChoiceWidthPx}px`,
198
- `--mpb-choice-width-vw:${layoutLimits.horizontalChoiceWidthVw}vw`,
199
- `--mpb-choice-tile-min-height:${layoutLimits.horizontalChoiceTileMinHeightRem}rem`,
200
- `--mpb-choice-content-min-height:${layoutLimits.horizontalChoiceContentMinHeightRem}rem`,
201
- `--mpb-stimulus-min-column:${layoutLimits.stimulusMinColumnPx}px`,
202
- `--mpb-text-min-column:${layoutLimits.textMinColumnPx}px`,
203
- `--mpb-choice-group-gap:${layoutLimits.choiceGroupGapRem}rem`,
204
- `--mpb-choice-row-gap:${layoutLimits.choiceRowGapRem}rem`,
205
- `--mpb-toggle-button-gap:${layoutLimits.toggleButtonGapRem}rem`,
206
- `--mpb-horizontal-choice-radio-top-margin:${layoutLimits.horizontalChoiceRadioTopMarginRem}rem`,
207
- `--mpb-audio-blank-template-margin-top:${layoutLimits.audioBlankTemplateMarginTopRem}rem`,
208
- `--mpb-audio-blank-template-margin-bottom:${layoutLimits.audioBlankTemplateMarginBottomRem}rem`,
209
- `--mpb-audio-instructions-max-width:${layoutLimits.audioInstructionsMaxWidthPx}px`,
210
- `--mpb-narrow-choice-max-width:${layoutLimits.narrowHorizontalChoiceMaxWidthPx}px`,
211
- `--mpb-stimulus-grid-column-gap:${layoutLimits.stimulusGridColumnGapRem}rem`,
212
- `--mpb-stimulus-grid-row-gap:${layoutLimits.stimulusGridRowGapRem}rem`,
213
- `--mpb-stimulus-sentence-margin-top:${layoutLimits.stimulusSentenceMarginTopRem}rem`,
214
- `--mpb-stimulus-choices-margin-top:${layoutLimits.stimulusChoicesMarginTopRem}rem`,
215
- `--mpb-token-grid-column-gap:${layoutLimits.tokenGridColumnGapRem}rem`,
216
- `--mpb-token-grid-row-gap:${layoutLimits.tokenGridRowGapRem}rem`,
217
- `--mpb-token-template-margin-top:${layoutLimits.tokenTemplateMarginTopRem}rem`,
218
- `--mpb-token-inline-token-gap:${layoutLimits.tokenInlineTokenGapRem}rem`,
219
- `--mpb-token-choices-margin-top:${layoutLimits.tokenChoicesMarginTopRem}rem`,
220
- `--mpb-inline-grid-column-gap:${layoutLimits.inlineGridColumnGapRem}rem`,
221
- `--mpb-inline-grid-row-gap:${layoutLimits.inlineGridRowGapRem}rem`,
222
- `--mpb-inline-template-margin-top:${layoutLimits.inlineTemplateMarginTopRem}rem`,
223
- `--mpb-inline-choices-margin-top:${layoutLimits.inlineChoicesMarginTopRem}rem`,
224
- correctAnswerStyleVars,
225
- ].join(';')
226
- );
227
- const useFeatureButtonAudio = $derived.by(() => {
228
- if (typeof model?.useFeatureButtonAudio === 'boolean') {
229
- return !!model.useFeatureButtonAudio;
230
- }
231
- const profile = layoutProfile || '';
232
- return (
233
- !!model?.hasAudio &&
234
- (profile === 'audio_blank_only' ||
235
- profile === 'stimulus_image_blank' ||
236
- profile === 'token_sequence')
237
- );
238
- });
239
-
240
- const templateParts = $derived.by(() => {
241
- const t = model?.template || '';
242
- const idx = t.indexOf(BLANK_TOKEN);
243
- if (idx < 0) {
244
- return { before: t, after: '' };
245
- }
246
- return {
247
- before: t.slice(0, idx),
248
- after: t.slice(idx + BLANK_TOKEN.length),
249
- };
250
- });
251
- const blankWidth = $derived.by(() => {
252
- if (layoutProfile === 'audio_blank_only' || layoutProfile === 'stimulus_image_blank') {
253
- return `${layoutLimits.blankWideWidthRem}rem`;
254
- }
255
- if (isBlankOnlyTemplate) {
256
- return `${layoutLimits.blankStandaloneWidthRem}rem`;
257
- }
258
- return 'auto';
259
- });
260
- const blankBorderWidth = $derived.by(() => {
261
- if (layoutProfile === 'audio_blank_only' || layoutProfile === 'stimulus_image_blank') {
262
- return `${layoutLimits.blankUnderlineWideWidthPx}px`;
263
- }
264
- return `${layoutLimits.blankUnderlineWidthPx}px`;
265
- });
266
-
267
- const choices = $derived(Array.isArray(model?.choices) ? model.choices : []);
268
- const choiceMode = $derived(model?.choiceMode || 'text');
269
- const selectedId = $derived(session?.choiceId || localChoiceId || '');
270
- const radioGroupName = $derived(`${instanceId}-choice-group-${model?.id || '1'}`);
271
-
272
- let showCorrectAnswer = $state(false);
273
- const shouldShowCorrectAnswerToggle = $derived(
274
- isEvaluateMode && !isResponseCorrect && !!model?.correctChoiceId
275
- );
276
-
277
- const displayChoiceId = $derived.by(() => {
278
- if (isEvaluateMode && showCorrectAnswer && model?.correctChoiceId) {
279
- return model.correctChoiceId;
280
- }
281
- return selectedId;
282
- });
283
-
284
- const displayChoice = $derived.by(() => choices.find((c: any) => c.id === displayChoiceId));
285
- const displayChoiceLabelHtml = $derived.by(() => String(displayChoice?.labelHtml || ''));
286
-
287
- const legendText = $derived.by(() => {
288
- const plain = (model?.prompt || '')
289
- .replace(/<[^>]+>/g, ' ')
290
- .replace(/\s+/g, ' ')
291
- .trim();
292
- const maxChars = Math.max(
293
- 8,
294
- Number(layoutLimits.legendMaxChars) || DEFAULT_LAYOUT_LIMITS.legendMaxChars
295
- );
296
- return plain.length > maxChars
297
- ? `${plain.slice(0, Math.max(1, maxChars - 1))}…`
298
- : plain || uiText.answerChoices;
299
- });
300
-
301
- const promptId = $derived(`${instanceId}-prompt`);
302
- const transcriptId = $derived(`${instanceId}-transcript`);
303
- const legendId = $derived(`${instanceId}-choices-legend`);
304
- const resultId = $derived(`${instanceId}-result`);
305
-
306
- const choicesGroupLabelledBy = $derived(model?.prompt ? promptId : undefined);
307
- const choicesGroupAriaLabel = $derived.by(() => {
308
- if (model?.prompt) return undefined;
309
- const explicit = String(model?.choiceGroupLabel || '').trim();
310
- if (explicit) return explicit;
311
- return legendText;
312
- });
313
-
314
- const templateDescribedBy = $derived.by(() => {
315
- const ids: string[] = [];
316
- if (model?.prompt) ids.push(promptId);
317
- if (model?.hasAudio && model?.audioTranscript) {
318
- ids.push(transcriptId);
319
- }
320
- return ids.length ? ids.join(' ') : undefined;
321
- });
322
-
323
- const lang = $derived.by(() => {
324
- const locale = model?.locale || '';
325
- return locale ? locale.slice(0, 2) : 'en';
326
- });
327
- const audioErrorMessage = $derived.by(() =>
328
- hasAudioButMissingResource ? uiText.audioResourceUnavailable : ''
329
- );
330
-
331
- const resultText = $derived.by(() => {
332
- if (!isEvaluateMode || showCorrectAnswer) return '';
333
- if (isCorrect) return 'Correct answer selected';
334
- if (isIncorrect && selectedId) return 'Incorrect answer selected';
335
- return '';
336
- });
337
- const choiceCorrectnessById = $derived.by(() => {
338
- const map = new Map<string, 'correct' | 'incorrect'>();
339
- const correctChoiceId = String(model?.correctChoiceId || '');
340
- const activeSelectedId = String(selectedId || '');
341
-
342
- if (!isEvaluateMode || !correctChoiceId) {
343
- return map;
344
- }
345
-
346
- if (showCorrectAnswer) {
347
- // Reveal mode should show only the canonical correct answer state.
348
- map.set(correctChoiceId, 'correct');
349
- return map;
350
- }
351
-
352
- if (!activeSelectedId) {
353
- map.set(correctChoiceId, 'incorrect');
354
- return map;
355
- }
356
-
357
- if (activeSelectedId === correctChoiceId) {
358
- map.set(correctChoiceId, 'correct');
359
- return map;
360
- }
361
-
362
- map.set(activeSelectedId, 'incorrect');
363
- map.set(correctChoiceId, 'incorrect');
364
- return map;
365
- });
366
-
367
- function emitSession(updatedSession: any, sourceEl?: HTMLElement | null) {
368
- const host = getHostElement(sourceEl);
369
- if (host?.onSessionChange) {
370
- host.onSessionChange(updatedSession);
371
- return;
372
- }
373
- host?.dispatchEvent(
374
- new CustomEvent('session-changed', {
375
- bubbles: true,
376
- composed: true,
377
- detail: { complete: !!updatedSession?.choiceId, component: 'mc-populated-blank' },
378
- })
379
- );
380
- }
381
-
382
- function onRadioChange(e: Event) {
383
- const input = e.target as HTMLInputElement;
384
- if (!input.checked) return;
385
- const choiceId = input.value;
386
- localChoiceId = choiceId;
387
- const updatedSession = {
388
- ...session,
389
- id: session?.id || model?.id || '1',
390
- element: 'mc-populated-blank',
391
- choiceId,
392
- };
393
- emitSession(updatedSession, input);
394
- }
395
-
396
- function toggleCorrectAnswer() {
397
- if (!shouldShowCorrectAnswerToggle) return;
398
- showCorrectAnswer = !showCorrectAnswer;
399
- }
400
-
401
- $effect(() => {
402
- if (!isEvaluateMode || isResponseCorrect) {
403
- showCorrectAnswer = false;
404
- }
405
- });
406
-
407
- function onRadioGroupKeydown(e: KeyboardEvent) {
408
- if (model?.disabled) return;
409
- const key = e.key;
410
- if (!['ArrowDown', 'ArrowRight', 'ArrowUp', 'ArrowLeft'].includes(key)) return;
411
- e.preventDefault();
412
- const container = e.currentTarget as HTMLElement;
413
- const inputs = Array.from(
414
- container.querySelectorAll('input[type="radio"]:not([disabled])')
415
- ) as HTMLInputElement[];
416
- if (inputs.length === 0) return;
417
- let idx = inputs.findIndex((i) => i.checked);
418
- if (key === 'ArrowDown' || key === 'ArrowRight') {
419
- idx = idx < 0 ? 0 : (idx + 1) % inputs.length;
420
- } else {
421
- idx = idx < 0 ? inputs.length - 1 : (idx - 1 + inputs.length) % inputs.length;
422
- }
423
- const next = inputs[idx];
424
- next.checked = true;
425
- next.dispatchEvent(new Event('change', { bubbles: true }));
426
- next.focus();
427
- }
428
-
429
- function handleEnableAutoplayClick() {
430
- if (hasPlayableAudio && audioEl) {
431
- audioEl.play().finally(() => {
432
- autoPlayPromptOpen = false;
433
- });
434
- }
435
- }
436
-
437
- function onAudioPlaying(e: Event) {
438
- const host = getHostElement(e.currentTarget as HTMLElement);
439
- isMediaPlaying = true;
440
- host?.onAudioStarted?.();
441
- autoPlayPromptOpen = false;
442
- }
443
-
444
- function onAudioEnded(e: Event) {
445
- const host = getHostElement(e.currentTarget as HTMLElement);
446
- isMediaPlaying = false;
447
- host?.onAudioEnded?.();
448
- }
449
-
450
- function getHostElement(sourceEl?: HTMLElement | null) {
451
- let cursor: HTMLElement | null | undefined = sourceEl;
452
- while (cursor) {
453
- const maybeHost = cursor as any;
454
- if (
455
- typeof maybeHost?.onSessionChange === 'function' ||
456
- typeof maybeHost?.onAudioStarted === 'function' ||
457
- typeof maybeHost?.onAudioEnded === 'function'
458
- ) {
459
- return maybeHost;
460
- }
461
- cursor = cursor.parentElement;
462
- }
463
- return (document.querySelector('mc-populated-blank') as any) || null;
464
- }
465
-
466
- function speechButtonLabel(locale = '') {
467
- return locale.toLowerCase().startsWith('es') ? uiText.listenLabelEs : uiText.listenLabelEn;
468
- }
469
-
470
- const featureAudioSkin = $derived.by(() => {
471
- const locale = String(model?.locale || '').toLowerCase();
472
- const lang = locale.slice(0, 2);
473
- const byLocale =
474
- model?.audioButtonSkinsByLocale && typeof model.audioButtonSkinsByLocale === 'object'
475
- ? model.audioButtonSkinsByLocale
476
- : {};
477
- const customSingle =
478
- model?.audioButtonSkin && typeof model.audioButtonSkin === 'object'
479
- ? model.audioButtonSkin
480
- : null;
481
- const defaultSkin = locale.startsWith('es')
482
- ? DEFAULT_AUDIO_BUTTON_SKINS.es
483
- : DEFAULT_AUDIO_BUTTON_SKINS.default;
484
- return byLocale[locale] || byLocale[lang] || byLocale.default || customSingle || defaultSkin;
485
- });
486
-
487
- function playFeatureAudio() {
488
- if (hasPlayableAudio && audioEl) {
489
- audioEl.play().catch(() => {
490
- // Keep behavior resilient: if playback is blocked, user can retry.
491
- });
492
- }
493
- }
494
-
495
- $effect(() => {
496
- if (session?.choiceId) {
497
- localChoiceId = session.choiceId;
498
- }
499
- });
500
-
501
- $effect(() => {
502
- const el = audioEl;
503
- if (!el) return;
504
- const handlePlaying = (e: Event) => onAudioPlaying(e);
505
- const handleEnded = (e: Event) => onAudioEnded(e);
506
- el.addEventListener('playing', handlePlaying);
507
- el.addEventListener('ended', handleEnded);
508
- return () => {
509
- el.removeEventListener('playing', handlePlaying);
510
- el.removeEventListener('ended', handleEnded);
511
- };
512
- });
513
-
514
- $effect(() => {
515
- const btn = featureAudioButtonEl;
516
- if (!btn) return;
517
- const handleClick = () => playFeatureAudio();
518
- btn.addEventListener('click', handleClick);
519
- return () => {
520
- btn.removeEventListener('click', handleClick);
521
- };
522
- });
523
-
524
- $effect(() => {
525
- const btn = autoplayEnableButtonEl;
526
- if (!btn) return;
527
- const handleClick = () => handleEnableAutoplayClick();
528
- btn.addEventListener('click', handleClick);
529
- return () => {
530
- btn.removeEventListener('click', handleClick);
531
- };
532
- });
533
-
534
- $effect(() => {
535
- const btn = toggleCorrectAnswerButtonEl;
536
- if (!btn) return;
537
- const handleClick = () => toggleCorrectAnswer();
538
- btn.addEventListener('click', handleClick);
539
- return () => {
540
- btn.removeEventListener('click', handleClick);
541
- };
542
- });
543
-
544
- $effect(() => {
545
- const group = choicesGroupEl;
546
- if (!group) return;
547
- const handleChange = (e: Event) => {
548
- const target = e.target as HTMLInputElement | null;
549
- if (!target || target.type !== 'radio') return;
550
- onRadioChange(e);
551
- };
552
- const handleKeydown = (e: KeyboardEvent) => onRadioGroupKeydown(e);
553
- group.addEventListener('change', handleChange);
554
- group.addEventListener('keydown', handleKeydown);
555
- return () => {
556
- group.removeEventListener('change', handleChange);
557
- group.removeEventListener('keydown', handleKeydown);
558
- };
559
- });
560
-
561
- $effect(() => {
562
- if (!model?.hasAudio || !model?.autoplayAudioEnabled) return;
563
- if (autoplayAttempted) return;
564
-
565
- autoplayAttempted = true;
566
- if (audioEl && model?.audioUrl) {
567
- // Browser autoplay restrictions vary. Try immediate play; if blocked show click-to-enable affordance.
568
- audioEl.play().catch(() => {
569
- autoPlayPromptOpen = true;
570
- });
571
- }
572
- });
573
-
574
- $effect(() => {
575
- ensureVariantCssInjected(variantCssConfig);
576
- });
577
- </script>
578
-
579
- <div
580
- class={`p-4 mc-populated-blank-root pie-element pie-element-mc-populated-blank pie-delivery-root layout-${layoutProfile} ${variantRootClass} ${hasInlineSentenceAudioLayout ? 'has-inline-audio' : ''}`}
581
- lang={lang}
582
- style={rootStyle}
583
- >
584
- {#if model?.prompt}
585
- <div class="mb-4 prose pie-prompt" id={promptId}>{@html model.prompt}</div>
586
- {/if}
587
-
588
- {#if shouldShowCorrectAnswerToggle}
589
- <div class="pie-correct-answer-toggle-row">
590
- <button
591
- bind:this={toggleCorrectAnswerButtonEl}
592
- type="button"
593
- class="mb-3 pie-toggle-correct-answer"
594
- style="gap:var(--mpb-toggle-button-gap, 0.5rem);"
595
- aria-pressed={showCorrectAnswer}
596
- data-testid="show-correct-answer"
597
- >
598
- <span class="pie-correct-answer-toggle-content">
599
- <span class="pie-correct-answer-toggle-icon-holder" aria-hidden="true">
600
- {#if showCorrectAnswer}
601
- <svg
602
- class="pie-correct-answer-toggle-svg"
603
- preserveAspectRatio="xMinYMin meet"
604
- version="1.1"
605
- viewBox="-283 359 34 35"
606
- >
607
- <circle cx="-266" cy="375.9" r="14" fill="var(--pie-correct-answer-toggle-icon-open-bg, #bce2ff)" />
608
- <path
609
- d="M-280.5,375.9c0-8,6.5-14.5,14.5-14.5s14.5,6.5,14.5,14.5s-6.5,14.5-14.5,14.5S-280.5,383.9-280.5,375.9z M-279.5,375.9c0,7.4,6.1,13.5,13.5,13.5c7.4,0,13.5-6.1,13.5-13.5s-6.1-13.5-13.5-13.5C-273.4,362.4-279.5,368.5-279.5,375.9z"
610
- fill="var(--pie-correct-answer-toggle-icon-open-bg, #bce2ff)"
611
- />
612
- <polygon
613
- points="-265.4,383.1 -258.6,377.2 -261.2,374.2 -264.3,376.9 -268.9,368.7 -272.4,370.6"
614
- fill="var(--pie-correct-answer-toggle-icon-glyph-color, #1a9cff)"
615
- />
616
- </svg>
617
- {:else}
618
- <svg
619
- class="pie-correct-answer-toggle-svg"
620
- preserveAspectRatio="xMinYMin meet"
621
- version="1.1"
622
- viewBox="-129.5 127 34 35"
623
- >
624
- <path
625
- d="M-112.9,160.4c-8.5,0-15.5-6.9-15.5-15.5c0-8.5,6.9-15.5,15.5-15.5s15.5,6.9,15.5,15.5 C-97.4,153.5-104.3,160.4-112.9,160.4z"
626
- fill="#D0CAC5"
627
- stroke="#E6E3E0"
628
- stroke-width="0.75"
629
- />
630
- <path
631
- d="M-113.2,159c-8,0-14.5-6.5-14.5-14.5s6.5-14.5,14.5-14.5s14.5,6.5,14.5,14.5S-105.2,159-113.2,159z"
632
- fill="#B3ABA4"
633
- stroke="#CDC7C2"
634
- stroke-width="0.5"
635
- />
636
- <circle cx="-114.2" cy="143.5" r="14" fill="white" />
637
- <path
638
- d="M-114.2,158c-8,0-14.5-6.5-14.5-14.5s6.5-14.5,14.5-14.5s14.5,6.5,14.5,14.5S-106.2,158-114.2,158z M-114.2,130c-7.4,0-13.5,6.1-13.5,13.5s6.1,13.5,13.5,13.5s13.5-6.1,13.5-13.5S-106.8,130-114.2,130z"
639
- fill="var(--pie-correct-answer-toggle-icon-closed-bg, #bce2ff)"
640
- />
641
- <polygon
642
- points="-114.8,150.7 -121.6,144.8 -119,141.8 -115.9,144.5 -111.3,136.3 -107.8,138.2"
643
- fill="var(--pie-correct-answer-toggle-icon-glyph-color, #1a9cff)"
644
- />
645
- </svg>
646
- {/if}
647
- </span>
648
- <span class="pie-correct-answer-toggle-label">
649
- {showCorrectAnswer ? uiText.hideCorrectAnswer : uiText.showCorrectAnswer}
650
- </span>
651
- </span>
652
- </button>
653
- </div>
654
- {/if}
655
-
656
- {#if model?.hasAudio}
657
- <div class="mb-4 audio-container pie-audio-container">
658
- {#if hasPlayableAudio && useFeatureButtonAudio}
659
- <audio
660
- bind:this={audioEl}
661
- class="sr-only pie-audio-player"
662
- preload="metadata"
663
- src={model.audioUrl}
664
- aria-hidden="true"
665
- tabindex="-1"
666
- ></audio>
667
- <button
668
- bind:this={featureAudioButtonEl}
669
- class="listen-button pie-listen-button rli-feature-audio"
670
- type="button"
671
- aria-label={speechButtonLabel(model?.locale)}
672
- >
673
- <img
674
- class={`listen-feature-icon pie-listen-icon rli-feature-listen ${isMediaPlaying ? '' : 'listen-active'}`}
675
- src={featureAudioSkin.silentUrl}
676
- alt=""
677
- aria-hidden="true"
678
- />
679
- <img
680
- class={`listen-feature-icon pie-listen-icon rli-feature-listen ${isMediaPlaying ? 'listen-active' : ''}`}
681
- src={featureAudioSkin.playingUrl}
682
- alt=""
683
- aria-hidden="true"
684
- />
685
- </button>
686
- {:else if hasPlayableAudio}
687
- <audio
688
- bind:this={audioEl}
689
- controls
690
- class="w-full max-w-md pie-audio-player"
691
- preload="metadata"
692
- src={model.audioUrl}
693
- aria-describedby={model?.audioTranscript ? transcriptId : undefined}
694
- >
695
- <track kind="captions" />
696
- </audio>
697
- {#if autoPlayPromptOpen}
698
- <button
699
- bind:this={autoplayEnableButtonEl}
700
- class="mt-2 text-sm underline pie-audio-autoplay-enable"
701
- type="button"
702
- >
703
- {uiText.clickToEnableAutoplay}
704
- </button>
705
- {/if}
706
- {:else if hasAudioButMissingResource}
707
- <p class="text-sm text-red-700 pie-audio-error" role="alert">{audioErrorMessage}</p>
708
- {/if}
709
- {#if model?.audioTranscript}
710
- <p
711
- class={`text-sm mt-2 text-gray-700 pie-audio-transcript ${showVisibleTranscript ? '' : 'sr-only'}`}
712
- id={transcriptId}
713
- >
714
- <strong>{uiText.transcriptLabel}:</strong> {model.audioTranscript}
715
- </p>
716
- {/if}
717
- </div>
718
- {/if}
719
-
720
- {#if model?.sentenceHtml}
721
- <div class="mb-3 prose prose-p:my-1 sentence-line pie-sentence-line" aria-describedby={templateDescribedBy}>
722
- {@html model.sentenceHtml}
723
- </div>
724
- {/if}
725
-
726
- {#if !isAudioOnlyMode}
727
- <div class="mb-4 template-line pie-template-line" aria-describedby={templateDescribedBy}>
728
- {@html templateParts.before}
729
- <span
730
- class={`inline-flex items-center min-h-[1.5em] px-2 mx-1 border-b-2 border-gray-500 align-baseline blank-slot pie-blank-slot ${isBlankOnlyTemplate ? 'blank-slot-standalone pie-blank-slot-standalone' : ''}`}
731
- style={`width:${blankWidth};border-bottom-width:${blankBorderWidth};`}
732
- role="status"
733
- aria-live="polite"
734
- aria-atomic="true"
735
- aria-label={uiText.selectedAnswerInSentence}
736
- >
737
- {#if choiceMode === 'image' && displayChoice?.imageUrl}
738
- <img
739
- src={displayChoice.imageUrl}
740
- alt={displayChoice.imageAlt || 'Selected answer image'}
741
- class="w-auto object-contain pie-blank-image"
742
- style="max-height:var(--mpb-selected-image-max-height, 4rem);"
743
- />
744
- {:else if displayChoiceLabelHtml}
745
- <span class="choice-html pie-blank-value">{@html displayChoiceLabelHtml}</span>
746
- {:else}
747
- <span class="blank-inner-empty" aria-hidden="true">&nbsp;</span>
748
- {/if}
749
- </span>
750
- {@html templateParts.after}
751
- </div>
752
- {/if}
753
-
754
- {#if resultText}
755
- <p id={resultId} class="sr-only pie-result-feedback" role="status" aria-live="polite">{resultText}</p>
756
- {/if}
757
-
758
- <fieldset class="border-0 p-0 m-0 pie-choices-fieldset" disabled={model?.disabled}>
759
- <legend class="sr-only pie-choices-legend" id={legendId}>{legendText}</legend>
760
- <div
761
- bind:this={choicesGroupEl}
762
- class={`pie-choices ${isHorizontalChoices ? 'flex flex-row flex-wrap items-start justify-center' : 'flex flex-col'}`}
763
- style="gap:var(--mpb-choice-group-gap, 0.5rem);"
764
- role="radiogroup"
765
- tabindex="-1"
766
- aria-labelledby={choicesGroupLabelledBy}
767
- aria-label={choicesGroupAriaLabel}
768
- aria-describedby={resultText ? resultId : undefined}
769
- >
770
- {#each choices as c (c.id)}
771
- {@const choiceCorrectness = choiceCorrectnessById.get(c.id)}
772
- <div
773
- class={`flex items-start choice-row pie-choice ${isHorizontalChoices ? 'choice-row-horizontal pie-choice-horizontal' : ''} ${((showCorrectAnswer ? model?.correctChoiceId : selectedId) === c.id) ? 'is-selected pie-choice-selected' : ''} ${choiceCorrectness ? `choice-${choiceCorrectness} pie-choice-${choiceCorrectness}` : ''}`}
774
- style="gap:var(--mpb-choice-row-gap, 0.5rem);"
775
- >
776
- {#if isHorizontalChoices}
777
- <label
778
- for={`${instanceId}-opt-${c.id}`}
779
- class="cursor-pointer choice-tile text-center pie-choice-tile"
780
- >
781
- <span class="choice-tile-content pie-choice-tile-content">
782
- {#if choiceMode === 'image' && c.imageUrl}
783
- <img
784
- src={c.imageUrl}
785
- alt={c.imageAlt || `Choice ${c.id}`}
786
- class="object-contain mx-auto pie-choice-image"
787
- style="max-height:var(--mpb-choice-image-max-height, 5rem);"
788
- />
789
- {:else}
790
- <span class="choice-html pie-choice-label">{@html c.labelHtml || ''}</span>
791
- {/if}
792
- </span>
793
- <input
794
- type="radio"
795
- name={radioGroupName}
796
- id={`${instanceId}-opt-${c.id}`}
797
- value={c.id}
798
- checked={
799
- (showCorrectAnswer ? model?.correctChoiceId : selectedId) === c.id
800
- }
801
- disabled={model?.disabled}
802
- class="choice-radio-bottom pie-choice-radio pie-choice-radio-bottom"
803
- />
804
- </label>
805
- {:else}
806
- <input
807
- type="radio"
808
- name={radioGroupName}
809
- id={`${instanceId}-opt-${c.id}`}
810
- value={c.id}
811
- checked={
812
- (showCorrectAnswer ? model?.correctChoiceId : selectedId) === c.id
813
- }
814
- disabled={model?.disabled}
815
- class="choice-radio-inline pie-choice-radio pie-choice-radio-inline"
816
- />
817
- <label for={`${instanceId}-opt-${c.id}`} class="cursor-pointer flex-1 pie-choice-label-wrap">
818
- {#if choiceMode === 'image' && c.imageUrl}
819
- <img
820
- src={c.imageUrl}
821
- alt={c.imageAlt || `Choice ${c.id}`}
822
- class="object-contain pie-choice-image"
823
- style="max-height:var(--mpb-choice-image-max-height, 5rem);"
824
- />
825
- {:else}
826
- <span class="choice-html pie-choice-label">{@html c.labelHtml || ''}</span>
827
- {/if}
828
- </label>
829
- {/if}
830
- {#if isEvaluateMode && choiceCorrectness}
831
- <span
832
- class={`pie-choice-feedback-badge ${choiceCorrectness === 'correct' ? 'pie-choice-feedback-correct' : 'pie-choice-feedback-incorrect'}`}
833
- aria-hidden="true"
834
- >
835
- {choiceCorrectness === 'correct' ? '✓' : '✕'}
836
- </span>
837
- {/if}
838
- </div>
839
- {/each}
840
- </div>
841
- </fieldset>
842
- </div>
843
-
844
- <style>
845
- .sr-only {
846
- position: absolute;
847
- width: 1px;
848
- height: 1px;
849
- padding: 0;
850
- margin: -1px;
851
- overflow: hidden;
852
- clip: rect(0, 0, 0, 0);
853
- white-space: nowrap;
854
- border: 0;
855
- }
856
-
857
- .mc-populated-blank-root :global(.prose) {
858
- max-width: none;
859
- }
860
-
861
- .blank-slot:focus-within {
862
- outline: 2px solid var(--pie-focus, #2563eb);
863
- outline-offset: 2px;
864
- }
865
-
866
- .blank-slot {
867
- display: inline-flex;
868
- align-items: center;
869
- justify-content: center;
870
- text-align: center;
871
- vertical-align: baseline;
872
- }
873
-
874
- .pie-toggle-correct-answer {
875
- width: 100%;
876
- cursor: pointer;
877
- border: 0;
878
- background: transparent;
879
- padding: 0;
880
- display: flex;
881
- justify-content: center;
882
- text-align: center;
883
- color: var(--pie-correct-answer-toggle-label-color, var(--pie-text, black));
884
- }
885
-
886
- .pie-correct-answer-toggle-row {
887
- width: 100%;
888
- display: flex;
889
- justify-content: flex-end;
890
- }
891
-
892
- .pie-correct-answer-toggle-content {
893
- display: flex;
894
- margin: 0 auto;
895
- align-items: center;
896
- }
897
-
898
- .pie-correct-answer-toggle-icon-holder {
899
- width: 25px;
900
- margin-right: 5px;
901
- display: inline-flex;
902
- align-items: center;
903
- justify-content: center;
904
- }
905
-
906
- .pie-correct-answer-toggle-svg {
907
- width: 25px;
908
- height: 25px;
909
- }
910
-
911
- .pie-correct-answer-toggle-label {
912
- width: fit-content;
913
- min-width: 140px;
914
- align-self: center;
915
- vertical-align: middle;
916
- font-weight: 400;
917
- user-select: none;
918
- }
919
-
920
- .pie-toggle-correct-answer:hover .pie-correct-answer-toggle-label,
921
- .pie-toggle-correct-answer:focus-visible .pie-correct-answer-toggle-label {
922
- text-decoration: underline;
923
- }
924
-
925
- .template-line {
926
- white-space: pre-wrap;
927
- }
928
-
929
- .template-line :global(p) {
930
- margin: 0;
931
- }
932
-
933
- .blank-inner-empty {
934
- display: inline-block;
935
- min-width: 4ch;
936
- }
937
-
938
- .blank-slot-standalone {
939
- width: var(--mpb-blank-standalone-width, 7rem);
940
- }
941
-
942
- .listen-button {
943
- width: var(--mpb-listen-button-size, 128px);
944
- height: var(--mpb-listen-button-size, 128px);
945
- border: 0;
946
- padding: 0;
947
- background-color: transparent;
948
- cursor: pointer;
949
- z-index: 1;
950
- }
951
-
952
- .listen-button:hover {
953
- background-color: #e2f1fe;
954
- }
955
-
956
- .listen-feature-icon {
957
- width: var(--mpb-listen-button-size, 128px);
958
- height: var(--mpb-listen-button-size, 128px);
959
- object-fit: contain;
960
- display: none;
961
- }
962
-
963
- .listen-feature-icon.listen-active {
964
- display: block;
965
- }
966
-
967
- .choice-row-horizontal {
968
- flex-direction: column;
969
- align-items: center;
970
- width: min(var(--mpb-choice-width-px, 170px), var(--mpb-choice-width-vw, 30vw));
971
- gap: 0;
972
- }
973
-
974
- .choice-tile {
975
- display: flex;
976
- flex-direction: column;
977
- justify-content: space-between;
978
- align-items: center;
979
- width: 100%;
980
- min-height: var(--mpb-choice-tile-min-height, 11rem);
981
- padding: 0.8rem 0.65rem 0.5rem;
982
- border-radius: 8px;
983
- background: transparent;
984
- transition: background-color 120ms ease-in-out;
985
- }
986
-
987
- .choice-tile-content {
988
- width: 100%;
989
- display: flex;
990
- align-items: center;
991
- justify-content: center;
992
- min-height: var(--mpb-choice-content-min-height, 7.5rem);
993
- }
994
-
995
- .choice-row-horizontal:hover .choice-tile {
996
- background: var(--pie-correct-answer-choice-hover-bg, #ececec);
997
- }
998
-
999
- .pie-choice-horizontal:not(.is-selected):not(:hover) .pie-choice-tile {
1000
- background: transparent;
1001
- }
1002
-
1003
- .choice-row-horizontal.is-selected .choice-tile {
1004
- background: var(--pie-correct-answer-choice-selected-bg, #f1f1f1);
1005
- }
1006
-
1007
- .choice-row-horizontal.is-selected:hover .choice-tile {
1008
- background: var(--pie-correct-answer-choice-selected-bg, #f1f1f1);
1009
- }
1010
-
1011
- .pie-choice:not(.pie-choice-horizontal):hover .pie-choice-label-wrap {
1012
- background: var(--pie-correct-answer-choice-hover-bg, #ececec);
1013
- }
1014
-
1015
- .pie-choice:not(.pie-choice-horizontal):not(.is-selected):not(:hover) .pie-choice-label-wrap {
1016
- background: transparent;
1017
- }
1018
-
1019
- .pie-choice:not(.pie-choice-horizontal).is-selected .pie-choice-label-wrap {
1020
- background: var(--pie-correct-answer-choice-selected-bg, #f1f1f1);
1021
- border-radius: 6px;
1022
- }
1023
-
1024
- .pie-choice:not(.pie-choice-horizontal).is-selected:hover .pie-choice-label-wrap {
1025
- background: var(--pie-correct-answer-choice-selected-bg, #f1f1f1);
1026
- }
1027
-
1028
- .pie-choice.choice-correct {
1029
- border-left: 3px solid var(--pie-correct-answer-choice-correct-border, #0ea449);
1030
- }
1031
-
1032
- .pie-choice.choice-incorrect {
1033
- border-left: 3px solid var(--pie-correct-answer-choice-incorrect-border, #bf0d00);
1034
- }
1035
-
1036
- .pie-choice-horizontal.choice-correct .choice-tile {
1037
- background: var(--pie-correct-answer-choice-correct-bg, #e8f5e9);
1038
- }
1039
-
1040
- .pie-choice-horizontal.choice-incorrect .choice-tile {
1041
- background: var(--pie-correct-answer-choice-incorrect-bg, #ffebee);
1042
- }
1043
-
1044
- .pie-choice-feedback-badge {
1045
- margin-left: auto;
1046
- display: inline-flex;
1047
- align-items: center;
1048
- justify-content: center;
1049
- width: 1.1rem;
1050
- height: 1.1rem;
1051
- border-radius: 9999px;
1052
- font-size: 0.72rem;
1053
- line-height: 1;
1054
- font-weight: 700;
1055
- color: var(--pie-correct-answer-feedback-glyph-color, #fff);
1056
- }
1057
-
1058
- .pie-choice-feedback-correct {
1059
- background: var(--pie-correct-answer-feedback-correct-bg, #087d38);
1060
- }
1061
-
1062
- .pie-choice-feedback-incorrect {
1063
- background: var(--pie-correct-answer-feedback-incorrect-bg, #bf0d00);
1064
- }
1065
-
1066
- .choice-row-horizontal :global(p) {
1067
- margin: 0;
1068
- text-align: center;
1069
- }
1070
-
1071
- .choice-html :global(p) {
1072
- margin: 0;
1073
- }
1074
-
1075
- .choice-html {
1076
- width: 100%;
1077
- display: inline-flex;
1078
- align-items: center;
1079
- justify-content: center;
1080
- text-align: center;
1081
- }
1082
-
1083
- .choice-radio-bottom {
1084
- margin-top: var(--mpb-horizontal-choice-radio-top-margin, 0.5rem);
1085
- }
1086
-
1087
- .choice-radio-inline {
1088
- margin-top: var(--mpb-horizontal-choice-radio-top-margin, 0.5rem);
1089
- }
1090
-
1091
- .layout-audio_blank_only .audio-container,
1092
- .layout-stimulus_image_blank .audio-container {
1093
- display: flex;
1094
- width: min(100%, var(--mpb-audio-instructions-max-width, 875px));
1095
- margin-left: auto;
1096
- margin-right: auto;
1097
- justify-content: flex-end;
1098
- }
1099
-
1100
- .layout-audio_blank_only .template-line,
1101
- .layout-stimulus_image_blank .template-line {
1102
- text-align: center;
1103
- margin: var(--mpb-audio-blank-template-margin-top, 0.8rem) 0
1104
- var(--mpb-audio-blank-template-margin-bottom, 1.8rem);
1105
- }
1106
-
1107
- .layout-audio_blank_only .blank-slot,
1108
- .layout-stimulus_image_blank .blank-slot {
1109
- width: var(--mpb-blank-wide-width, 10rem);
1110
- border-bottom-width: var(--mpb-blank-underline-wide-width, 4px);
1111
- }
1112
-
1113
- .layout-stimulus_image_blank {
1114
- display: grid;
1115
- grid-template-columns: minmax(var(--mpb-stimulus-min-column, 210px), 1fr) auto;
1116
- grid-template-areas:
1117
- 'sentence audio'
1118
- '. template'
1119
- 'choices choices';
1120
- column-gap: var(--mpb-stimulus-grid-column-gap, 2rem);
1121
- row-gap: var(--mpb-stimulus-grid-row-gap, 0.7rem);
1122
- align-items: start;
1123
- }
1124
-
1125
- .layout-stimulus_image_blank .audio-container {
1126
- grid-area: audio;
1127
- margin: 0;
1128
- }
1129
-
1130
- .layout-stimulus_image_blank .sentence-line {
1131
- grid-area: sentence;
1132
- margin: var(--mpb-stimulus-sentence-margin-top, 0.2rem) 0 0;
1133
- }
1134
-
1135
- .layout-stimulus_image_blank .template-line {
1136
- grid-area: template;
1137
- margin: 0;
1138
- text-align: center;
1139
- justify-self: center;
1140
- }
1141
-
1142
- .layout-stimulus_image_blank fieldset {
1143
- grid-area: choices;
1144
- margin-top: var(--mpb-stimulus-choices-margin-top, 0.9rem);
1145
- }
1146
-
1147
- .layout-token_sequence {
1148
- display: grid;
1149
- grid-template-columns: minmax(var(--mpb-text-min-column, 260px), 1fr) auto;
1150
- grid-template-areas:
1151
- 'template audio'
1152
- 'choices choices';
1153
- column-gap: var(--mpb-token-grid-column-gap, 1.5rem);
1154
- row-gap: var(--mpb-token-grid-row-gap, 0.8rem);
1155
- align-items: start;
1156
- }
1157
-
1158
- .layout-token_sequence .audio-container {
1159
- grid-area: audio;
1160
- margin: 0;
1161
- justify-self: end;
1162
- }
1163
-
1164
- .layout-token_sequence .template-line {
1165
- grid-area: template;
1166
- margin: var(--mpb-token-template-margin-top, 0.6rem) 0 0;
1167
- justify-self: center;
1168
- text-align: center;
1169
- }
1170
-
1171
- .layout-token_sequence .template-line :global(span + span) {
1172
- margin-left: var(--mpb-token-inline-token-gap, 0.35rem);
1173
- }
1174
-
1175
- .layout-token_sequence .blank-slot {
1176
- width: var(--mpb-blank-standalone-width, 7rem);
1177
- border-bottom-width: var(--mpb-blank-underline-wide-width, 4px);
1178
- margin-left: var(--mpb-token-inline-token-gap, 0.35rem);
1179
- }
1180
-
1181
- .layout-token_sequence fieldset {
1182
- grid-area: choices;
1183
- margin-top: var(--mpb-token-choices-margin-top, 0.2rem);
1184
- }
1185
-
1186
- .layout-inline_sentence.has-inline-audio {
1187
- display: grid;
1188
- grid-template-columns: minmax(var(--mpb-text-min-column, 260px), 1fr) auto;
1189
- grid-template-areas:
1190
- 'template audio'
1191
- 'choices choices';
1192
- column-gap: var(--mpb-inline-grid-column-gap, 1.5rem);
1193
- row-gap: var(--mpb-inline-grid-row-gap, 0.65rem);
1194
- align-items: start;
1195
- }
1196
-
1197
- .layout-inline_sentence.has-inline-audio .audio-container {
1198
- grid-area: audio;
1199
- margin: 0;
1200
- justify-self: end;
1201
- }
1202
-
1203
- .layout-inline_sentence.has-inline-audio .template-line {
1204
- grid-area: template;
1205
- margin: var(--mpb-inline-template-margin-top, 0.45rem) 0 0;
1206
- text-align: center;
1207
- justify-self: center;
1208
- }
1209
-
1210
- .layout-inline_sentence.has-inline-audio fieldset {
1211
- grid-area: choices;
1212
- margin-top: var(--mpb-inline-choices-margin-top, 0.25rem);
1213
- }
1214
-
1215
- /* Match Learnosity responsive behavior for CQT audio-blank layouts:
1216
- at smaller widths, audio control shifts left and answer tiles stack. */
1217
- @media (max-width: 760px) {
1218
- .layout-audio_blank_only .audio-container,
1219
- .layout-stimulus_image_blank .audio-container,
1220
- .layout-token_sequence .audio-container,
1221
- .layout-inline_sentence.has-inline-audio .audio-container {
1222
- width: 100%;
1223
- margin-left: 0;
1224
- margin-right: 0;
1225
- justify-content: flex-start;
1226
- justify-self: start;
1227
- }
1228
-
1229
- .layout-audio_blank_only .pie-choices,
1230
- .layout-token_sequence .pie-choices,
1231
- .layout-stimulus_image_blank .pie-choices,
1232
- .layout-inline_sentence.has-inline-audio .pie-choices {
1233
- flex-direction: column;
1234
- align-items: flex-start;
1235
- justify-content: flex-start;
1236
- }
1237
-
1238
- .layout-audio_blank_only .choice-row-horizontal,
1239
- .layout-token_sequence .choice-row-horizontal,
1240
- .layout-stimulus_image_blank .choice-row-horizontal,
1241
- .layout-inline_sentence.has-inline-audio .choice-row-horizontal {
1242
- width: min(100%, var(--mpb-narrow-choice-max-width, 230px));
1243
- align-items: flex-start;
1244
- }
1245
- }
1246
- </style>