@pie-element/mc-populated-blank 0.3.0-next.12 → 0.3.0-next.13

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.
@@ -1,2 +1,2 @@
1
- import { a as e, c as t, i as n, l as r, n as i, o as a, r as o, s, t as c, u as l } from "../controller-D_ITRCYG.js";
1
+ import { a as e, c as t, i as n, l as r, n as i, o as a, r as o, s, t as c, u as l } from "../controller-C9qqF83g.js";
2
2
  export { c as countBlankTokens, i as createCorrectResponseSession, o as createDefaultModel, n as getCorrectness, e as getPartialScore, a as model, s as normalize, t as normalizeSession, r as outcome, l as validate };
@@ -1,4 +1,4 @@
1
- import { t as e } from "./layoutLimits-Bp17YP2V.js";
1
+ import { t as e } from "./layoutLimits-C2D60jW0.js";
2
2
  //#region src/controller/defaults.ts
3
3
  var t = "{{blank}}", n = { model: {
4
4
  prompt: "",
@@ -193,7 +193,7 @@ var h = ({ id: e, labelHtml: t, imageUrl: n, imageAlt: r }) => ({
193
193
  let m = e.correctChoiceId;
194
194
  m ? l.some((e) => e?.id === m) || (r.correctResponse = "The correct response must be one of the choices.") : r.correctResponse = "No correct response defined.", e.hasAudio && (e.audioUrl?.trim() || (r.audioUrl = "A playable audio URL is required when audio is enabled"));
195
195
  let h = e?.layoutLimits;
196
- if (h && typeof h == "object") for (let e of /* @__PURE__ */ "blankStandaloneWidthRem.blankWideWidthRem.blankUnderlineWidthPx.blankUnderlineWideWidthPx.horizontalChoiceWidthPx.horizontalChoiceWidthVw.horizontalChoiceTileMinHeightRem.horizontalChoiceContentMinHeightRem.choiceImageMaxHeightRem.choiceImageMaxWidthRem.listenButtonSizePx.stimulusMinColumnPx.textMinColumnPx.legendMaxChars.choiceGroupGapRem.choiceRowGapRem.toggleButtonGapRem.horizontalChoiceRadioTopMarginRem.audioBlankTemplateMarginTopRem.audioBlankTemplateMarginBottomRem.audioInstructionsMaxWidthPx.narrowHorizontalChoiceMaxWidthPx.stimulusGridColumnGapRem.stimulusGridRowGapRem.stimulusSentenceMarginTopRem.stimulusChoicesMarginTopRem.tokenGridColumnGapRem.tokenGridRowGapRem.tokenTemplateMarginTopRem.tokenInlineTokenGapRem.tokenChoicesMarginTopRem.inlineGridColumnGapRem.inlineGridRowGapRem.inlineTemplateMarginTopRem.inlineChoicesMarginTopRem".split(".")) {
196
+ if (h && typeof h == "object") for (let e of /* @__PURE__ */ "blankStandaloneWidthRem.blankWideWidthRem.blankUnderlineWidthPx.blankUnderlineWideWidthPx.horizontalChoiceWidthPx.horizontalChoiceWidthVw.horizontalChoiceTileMinHeightRem.horizontalChoiceContentMinHeightRem.choiceImageMaxHeightRem.choiceImageMaxWidthRem.listenButtonSizePx.stimulusMinColumnPx.textMinColumnPx.legendMaxChars.choiceGroupGapRem.choiceRowGapRem.toggleButtonGapRem.horizontalChoiceRadioTopMarginRem.audioBlankTemplateMarginTopRem.audioBlankTemplateMarginBottomRem.audioInstructionsMaxWidthPx.stimulusGridColumnGapRem.stimulusGridRowGapRem.stimulusSentenceMarginTopRem.stimulusChoicesMarginTopRem.tokenGridColumnGapRem.tokenGridRowGapRem.tokenTemplateMarginTopRem.tokenInlineTokenGapRem.tokenChoicesMarginTopRem.inlineGridColumnGapRem.inlineGridRowGapRem.inlineTemplateMarginTopRem.inlineChoicesMarginTopRem".split(".")) {
197
197
  if (h[e] === void 0 || h[e] === null) continue;
198
198
  let t = Number(h[e]);
199
199
  if (!Number.isFinite(t) || t <= 0) {
@@ -206,4 +206,4 @@ var h = ({ id: e, labelHtml: t, imageUrl: n, imageAlt: r }) => ({
206
206
  //#endregion
207
207
  export { s as a, f as c, o as i, c as l, b as n, y as o, u as r, d as s, a as t, S as u };
208
208
 
209
- //# sourceMappingURL=controller-D_ITRCYG.js.map
209
+ //# sourceMappingURL=controller-C9qqF83g.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"controller-D_ITRCYG.js","names":[],"sources":["../../src/controller/defaults.ts","../../src/author/defaults.ts","../../src/controller/index.ts"],"sourcesContent":["import { DEFAULT_LAYOUT_LIMITS } from '../shared/layoutLimits';\n\nexport { DEFAULT_LAYOUT_LIMITS };\n\n/** Token authors must include exactly once in `template` (HTML). */\nexport const BLANK_TOKEN = '{{blank}}';\n\n/**\n * The values `normalize` fills in for fields an item leaves out. They carry no\n * content and no `id` or `element`: those come from the item config, and a\n * default here would stand in for a field the author never set.\n */\nexport default {\n model: {\n prompt: '',\n promptEnabled: true,\n interactionMode: 'populate_blank' as const,\n layoutProfile: '',\n choiceLayout: '',\n layoutProfilePresets: {},\n layoutLimits: { ...DEFAULT_LAYOUT_LIMITS },\n audioButtonSkin: null,\n audioButtonSkinsByLocale: {},\n sentenceHtml: '',\n template: '',\n choiceMode: 'text' as const,\n choices: [],\n choiceGroupLabel: '',\n correctChoiceId: '',\n hasAudio: false,\n autoplayAudioEnabled: false,\n completeAudioEnabled: false,\n audioUrl: '',\n audioTranscript: '',\n locale: '',\n teacherInstructions: '',\n teacherInstructionsEnabled: true,\n },\n};\n","import controllerDefaults, { BLANK_TOKEN } from '../controller/defaults';\n\n/** The model a new item starts from: a valid question an author edits in place. */\nexport default {\n model: {\n ...controllerDefaults.model,\n prompt: '<p><strong>Demo prompt</strong></p>',\n template: `<p>The answer is ${BLANK_TOKEN}.</p>`,\n choices: [\n { id: 'c1', labelHtml: '<p>Option A</p>' },\n { id: 'c2', labelHtml: '<p>Option B</p>' },\n { id: 'c3', labelHtml: '<p>Option C</p>' },\n ],\n correctChoiceId: 'c2',\n },\n configuration: {\n prompt: {\n label: 'Prompt',\n settings: true,\n },\n },\n};\n","import authorDefaults from '../author/defaults';\nimport defaults, { BLANK_TOKEN, DEFAULT_LAYOUT_LIMITS } from './defaults';\nimport type {\n McpbChoice,\n McpbQuestion,\n McpbSession,\n McpbEnv,\n McpbCorrectness,\n} from '../shared/types';\n\nconst isEmptyObject = (value: unknown): boolean =>\n !!value &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.keys(value as Record<string, unknown>).length === 0;\n\nexport function countBlankTokens(template: string): number {\n if (!template) return 0;\n const re = /\\{\\{blank\\}\\}/g;\n return (template.match(re) || []).length;\n}\n\nexport const getCorrectness = (question: McpbQuestion, session: McpbSession): McpbCorrectness => {\n if (!session?.choiceId) {\n return 'unanswered';\n }\n const correct = question?.correctChoiceId || '';\n if (session.choiceId === correct) {\n return 'correct';\n }\n return 'incorrect';\n};\n\n/** One blank, one key: an answered session scores 1 only when it picked the key. */\nexport const getPartialScore = (question: McpbQuestion, session: McpbSession) =>\n getCorrectness(question, session) === 'correct' ? 1 : 0;\n\nexport const outcome = (question: McpbQuestion, session: McpbSession, env: McpbEnv) =>\n new Promise((resolve) => {\n if (!session || isEmptyObject(session)) {\n resolve({\n score: 0,\n empty: true,\n traceLog: ['Student did not select any answers. Score is 0.'],\n });\n return;\n }\n\n session = normalizeSession(session);\n const normalizedQuestion = normalize(question);\n const correctness = getCorrectness(normalizedQuestion, session);\n\n if (correctness === 'unanswered') {\n resolve({\n score: 0,\n empty: true,\n traceLog: ['Student did not select any answers. Score is 0.'],\n });\n return;\n }\n\n const score = getPartialScore(normalizedQuestion, session);\n const traceLog = [\n `Mode: ${env?.mode || 'unknown'}.`,\n `Student selected choice: ${session.choiceId}.`,\n `Correct choice: ${normalizedQuestion.correctChoiceId || 'none'}.`,\n `Final score: ${score}.`,\n ];\n resolve({ score, empty: false, traceLog });\n });\n\nconst withDefaults = (base: McpbQuestion, model: McpbQuestion = {}) => ({\n ...base,\n ...model,\n layoutLimits: {\n ...DEFAULT_LAYOUT_LIMITS,\n ...(model?.layoutLimits || {}),\n },\n});\n\n/** The authoring starting point: a question that passes `validate()` as it stands. */\nexport const createDefaultModel = (model: McpbQuestion = {}) =>\n withDefaults(authorDefaults.model, model);\n\n/**\n * What `model()` and `outcome()` read: the item's own fields, with neutral\n * values for the rest. Starter content here would reach a learner as the\n * item's prompt or choices.\n */\nexport const normalize = (question: McpbQuestion = {}) => {\n const { shuffle, ...rest } = question || {};\n return {\n ...withDefaults(defaults.model, rest),\n // `lockChoiceOrder` alone decides the order, as in multiple-choice. Older items\n // spell an unlocked order `shuffle: true`, which counts only when it is unset.\n lockChoiceOrder:\n typeof rest.lockChoiceOrder === 'boolean' ? rest.lockChoiceOrder : shuffle !== true,\n };\n};\n\nexport const normalizeSession = (s: McpbSession): McpbSession => ({ ...s });\n\nconst shouldLockChoices = (question: McpbQuestion, env: McpbEnv) => {\n if (question?.lockChoiceOrder) return true;\n if (env?.['@pie-element']?.lockChoiceOrder) return true;\n return env?.role === 'instructor';\n};\n\nfunction shuffleArray<T>(items: T[]): T[] {\n const out = [...items];\n for (let i = out.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [out[i], out[j]] = [out[j], out[i]];\n }\n return out;\n}\n\n/**\n * The fields delivery renders. An imported choice can carry more, such as a\n * correctness flag or feedback, which must not reach a learner's browser.\n */\nconst toDeliveryChoice = ({ id, labelHtml, imageUrl, imageAlt }: McpbChoice): McpbChoice => ({\n id,\n ...(labelHtml !== undefined ? { labelHtml } : {}),\n ...(imageUrl !== undefined ? { imageUrl } : {}),\n ...(imageAlt !== undefined ? { imageAlt } : {}),\n});\n\nconst getStoredShuffle = (session: McpbSession): string[] =>\n Array.isArray(session?.data?.shuffledValues)\n ? session.data.shuffledValues\n : Array.isArray(session?.shuffledValues)\n ? session.shuffledValues\n : [];\n\nconst applyShuffledValues = (\n choices: McpbChoice[],\n shuffledValues: string[],\n choiceKey: keyof McpbChoice\n) => {\n const orderedChoices = shuffledValues\n .map((value) => choices.find((choice) => choice?.[choiceKey] === value))\n .filter((c): c is McpbChoice => !!c);\n\n if (orderedChoices.length === choices.length) {\n return orderedChoices;\n }\n\n const orderedValues = new Set(orderedChoices.map((choice) => choice[choiceKey]));\n const leftovers = choices.filter((choice) => !orderedValues.has(choice?.[choiceKey]));\n return [...orderedChoices, ...leftovers];\n};\n\ntype UpdateSessionFn = (\n id: string,\n element: string,\n data: { shuffledValues: string[] }\n) => Promise<void>;\n\nconst getOrderedChoices = async (\n question: McpbQuestion,\n session: McpbSession,\n env: McpbEnv,\n updateSession?: UpdateSessionFn\n) => {\n const choices = Array.isArray(question?.choices) ? [...question.choices] : [];\n if (!choices.length || shouldLockChoices(question, env || {})) {\n return choices;\n }\n\n const shuffledValues = getStoredShuffle(session);\n if (shuffledValues.length) {\n return applyShuffledValues(choices, shuffledValues, 'id');\n }\n\n const shuffledChoices = shuffleArray(choices);\n\n if (updateSession && typeof updateSession === 'function' && session?.id && session?.element) {\n const shuffledIds = shuffledChoices.map((choice) => choice?.id).filter(Boolean);\n if (shuffledIds.length) {\n await updateSession(session.id, session.element, { shuffledValues: shuffledIds });\n }\n }\n\n return shuffledChoices;\n};\n\nexport const model = async (\n question: McpbQuestion,\n session: McpbSession | null,\n env: McpbEnv | null,\n updateSession?: UpdateSessionFn\n) => {\n const safeSession: McpbSession = session || {};\n const safeEnv: McpbEnv = env || {};\n const normalizedQuestion = normalize(question);\n const choices = (await getOrderedChoices(normalizedQuestion, safeSession, safeEnv, updateSession))\n .filter((c): c is McpbChoice => !!c && typeof c === 'object')\n .map(toDeliveryChoice);\n\n const out: Record<string, unknown> = {\n prompt: normalizedQuestion.promptEnabled ? normalizedQuestion.prompt : null,\n interactionMode: normalizedQuestion.interactionMode || 'populate_blank',\n layoutProfile: normalizedQuestion.layoutProfile || '',\n choiceLayout: normalizedQuestion.choiceLayout || '',\n layoutProfilePresets:\n normalizedQuestion.layoutProfilePresets &&\n typeof normalizedQuestion.layoutProfilePresets === 'object'\n ? normalizedQuestion.layoutProfilePresets\n : {},\n layoutLimits: normalizedQuestion.layoutLimits || { ...DEFAULT_LAYOUT_LIMITS },\n audioButtonSkin:\n normalizedQuestion.audioButtonSkin && typeof normalizedQuestion.audioButtonSkin === 'object'\n ? normalizedQuestion.audioButtonSkin\n : null,\n audioButtonSkinsByLocale:\n normalizedQuestion.audioButtonSkinsByLocale &&\n typeof normalizedQuestion.audioButtonSkinsByLocale === 'object'\n ? normalizedQuestion.audioButtonSkinsByLocale\n : {},\n sentenceHtml: normalizedQuestion.sentenceHtml || null,\n template: normalizedQuestion.template,\n choiceMode: normalizedQuestion.choiceMode,\n choices,\n choiceGroupLabel: normalizedQuestion.choiceGroupLabel || '',\n hasAudio: normalizedQuestion.hasAudio,\n autoplayAudioEnabled: !!normalizedQuestion.autoplayAudioEnabled,\n completeAudioEnabled: !!normalizedQuestion.completeAudioEnabled,\n audioUrl: normalizedQuestion.hasAudio ? normalizedQuestion.audioUrl : null,\n audioTranscript: normalizedQuestion.hasAudio ? normalizedQuestion.audioTranscript : null,\n customType: normalizedQuestion.customType || '',\n useFeatureButtonAudio:\n typeof normalizedQuestion.useFeatureButtonAudio === 'boolean'\n ? normalizedQuestion.useFeatureButtonAudio\n : undefined,\n locale: normalizedQuestion.locale || '',\n language: normalizedQuestion.language || normalizedQuestion.locale || undefined,\n disabled: safeEnv.mode !== 'gather',\n mode: safeEnv.mode,\n };\n\n if (safeEnv.mode === 'evaluate') {\n const correctness = getCorrectness(normalizedQuestion, safeSession);\n out.correctness = correctness;\n out.responseCorrect = correctness === 'correct';\n out.correctChoiceId = normalizedQuestion.correctChoiceId;\n }\n\n if (safeEnv.role === 'instructor' && (safeEnv.mode === 'view' || safeEnv.mode === 'evaluate')) {\n out.teacherInstructions = normalizedQuestion.teacherInstructionsEnabled\n ? normalizedQuestion.teacherInstructions\n : null;\n } else {\n out.teacherInstructions = null;\n }\n\n return out;\n};\n\nexport const createCorrectResponseSession = (question: McpbQuestion, env: McpbEnv) => {\n return new Promise((resolve) => {\n if (env.mode !== 'evaluate' && env.role === 'instructor') {\n // The player sets `id` and `element` on the entry from the item config.\n resolve({\n id: '1',\n choiceId: question?.correctChoiceId || '',\n });\n } else {\n resolve(null);\n }\n });\n};\n\n/** Markup with its tags removed, keeping media an author can answer from. */\nconst getContent = (html?: string) =>\n (html || '').replace(/(<(?!img|iframe|source)([^>]+)>)/gi, '').trim();\n\ntype ValidateConfig = {\n minAnswerChoices?: number;\n maxAnswerChoices?: number;\n prompt?: { required?: boolean };\n teacherInstructions?: { required?: boolean };\n};\n\n/**\n * Authoring errors in multiple-choice's shape: a message per field, and under\n * `choices` a message per choice keyed by its id.\n */\nexport const validate = (question: McpbQuestion = {}, config: ValidateConfig = {}) => {\n const errors: Record<string, string | Record<string, string>> = {};\n const { minAnswerChoices = 2, maxAnswerChoices } = config;\n\n if ((question.promptEnabled || config.prompt?.required) && !getContent(question.prompt)) {\n errors.prompt = 'This field is required.';\n }\n\n if (config.teacherInstructions?.required && !getContent(question.teacherInstructions)) {\n errors.teacherInstructions = 'This field is required.';\n }\n\n const interactionMode = question.interactionMode || 'populate_blank';\n const template = question.template || '';\n const n = countBlankTokens(template);\n if (interactionMode === 'audio_mc_only') {\n if (n > 0) {\n errors.template = `Template cannot contain ${BLANK_TOKEN} in audio-only mode`;\n }\n } else if (interactionMode === 'populate_blank') {\n if (n === 0) {\n errors.template = `Template must contain exactly one ${BLANK_TOKEN} placeholder`;\n } else if (n > 1) {\n errors.template = `Template must contain only one ${BLANK_TOKEN} placeholder`;\n }\n } else {\n errors.interactionMode = 'Unknown interaction mode';\n }\n\n const choices = Array.isArray(question.choices) ? question.choices : [];\n if (choices.length < minAnswerChoices) {\n errors.answerChoices = `There should be at least ${minAnswerChoices} choices defined.`;\n } else if (maxAnswerChoices != null && choices.length > maxAnswerChoices) {\n errors.answerChoices = `No more than ${maxAnswerChoices} choices should be defined.`;\n }\n\n const mode = question.choiceMode || 'text';\n const choicesErrors: Record<string, string> = {};\n const seenIds = new Set<string>();\n const seenLabels = new Set<string>();\n choices.forEach((c, i) => {\n const key = c?.id || String(i);\n if (!c?.id) {\n choicesErrors[key] = 'Choice needs an id.';\n return;\n }\n // The session stores the picked id, so two choices sharing one cannot be told apart.\n if (seenIds.has(c.id)) {\n choicesErrors[key] = 'Choice id should be unique.';\n return;\n }\n seenIds.add(c.id);\n if (mode === 'text') {\n const label = getContent(c.labelHtml);\n if (!label) {\n choicesErrors[key] = 'Content should not be empty.';\n } else if (seenLabels.has(label)) {\n choicesErrors[key] = 'Content should be unique.';\n }\n seenLabels.add(label);\n } else if (!c.imageUrl?.trim()) {\n choicesErrors[key] = 'An image is required.';\n } else if (!c.imageAlt?.trim()) {\n choicesErrors[key] = 'Image alt text is required.';\n }\n });\n if (Object.keys(choicesErrors).length) {\n errors.choices = choicesErrors;\n }\n\n const correct = question.correctChoiceId;\n if (!correct) {\n errors.correctResponse = 'No correct response defined.';\n } else if (!choices.some((c) => c?.id === correct)) {\n errors.correctResponse = 'The correct response must be one of the choices.';\n }\n\n if (question.hasAudio) {\n const hasAudioUrl = !!question.audioUrl?.trim();\n if (!hasAudioUrl) {\n errors.audioUrl = 'A playable audio URL is required when audio is enabled';\n }\n }\n\n const limits = question?.layoutLimits;\n if (limits && typeof limits === 'object') {\n const numericLimitKeys = [\n 'blankStandaloneWidthRem',\n 'blankWideWidthRem',\n 'blankUnderlineWidthPx',\n 'blankUnderlineWideWidthPx',\n 'horizontalChoiceWidthPx',\n 'horizontalChoiceWidthVw',\n 'horizontalChoiceTileMinHeightRem',\n 'horizontalChoiceContentMinHeightRem',\n 'choiceImageMaxHeightRem',\n 'choiceImageMaxWidthRem',\n 'listenButtonSizePx',\n 'stimulusMinColumnPx',\n 'textMinColumnPx',\n 'legendMaxChars',\n 'choiceGroupGapRem',\n 'choiceRowGapRem',\n 'toggleButtonGapRem',\n 'horizontalChoiceRadioTopMarginRem',\n 'audioBlankTemplateMarginTopRem',\n 'audioBlankTemplateMarginBottomRem',\n 'audioInstructionsMaxWidthPx',\n 'narrowHorizontalChoiceMaxWidthPx',\n 'stimulusGridColumnGapRem',\n 'stimulusGridRowGapRem',\n 'stimulusSentenceMarginTopRem',\n 'stimulusChoicesMarginTopRem',\n 'tokenGridColumnGapRem',\n 'tokenGridRowGapRem',\n 'tokenTemplateMarginTopRem',\n 'tokenInlineTokenGapRem',\n 'tokenChoicesMarginTopRem',\n 'inlineGridColumnGapRem',\n 'inlineGridRowGapRem',\n 'inlineTemplateMarginTopRem',\n 'inlineChoicesMarginTopRem',\n ] as const;\n for (const key of numericLimitKeys) {\n if (limits[key] === undefined || limits[key] === null) continue;\n const value = Number(limits[key]);\n if (!Number.isFinite(value) || value <= 0) {\n errors.layoutLimits = `${key} must be a positive number when provided`;\n break;\n }\n }\n }\n\n return errors;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"controller-C9qqF83g.js","names":[],"sources":["../../src/controller/defaults.ts","../../src/author/defaults.ts","../../src/controller/index.ts"],"sourcesContent":["import { DEFAULT_LAYOUT_LIMITS } from '../shared/layoutLimits';\n\nexport { DEFAULT_LAYOUT_LIMITS };\n\n/** Token authors must include exactly once in `template` (HTML). */\nexport const BLANK_TOKEN = '{{blank}}';\n\n/**\n * The values `normalize` fills in for fields an item leaves out. They carry no\n * content and no `id` or `element`: those come from the item config, and a\n * default here would stand in for a field the author never set.\n */\nexport default {\n model: {\n prompt: '',\n promptEnabled: true,\n interactionMode: 'populate_blank' as const,\n layoutProfile: '',\n choiceLayout: '',\n layoutProfilePresets: {},\n layoutLimits: { ...DEFAULT_LAYOUT_LIMITS },\n audioButtonSkin: null,\n audioButtonSkinsByLocale: {},\n sentenceHtml: '',\n template: '',\n choiceMode: 'text' as const,\n choices: [],\n choiceGroupLabel: '',\n correctChoiceId: '',\n hasAudio: false,\n autoplayAudioEnabled: false,\n completeAudioEnabled: false,\n audioUrl: '',\n audioTranscript: '',\n locale: '',\n teacherInstructions: '',\n teacherInstructionsEnabled: true,\n },\n};\n","import controllerDefaults, { BLANK_TOKEN } from '../controller/defaults';\n\n/** The model a new item starts from: a valid question an author edits in place. */\nexport default {\n model: {\n ...controllerDefaults.model,\n prompt: '<p><strong>Demo prompt</strong></p>',\n template: `<p>The answer is ${BLANK_TOKEN}.</p>`,\n choices: [\n { id: 'c1', labelHtml: '<p>Option A</p>' },\n { id: 'c2', labelHtml: '<p>Option B</p>' },\n { id: 'c3', labelHtml: '<p>Option C</p>' },\n ],\n correctChoiceId: 'c2',\n },\n configuration: {\n prompt: {\n label: 'Prompt',\n settings: true,\n },\n },\n};\n","import authorDefaults from '../author/defaults';\nimport defaults, { BLANK_TOKEN, DEFAULT_LAYOUT_LIMITS } from './defaults';\nimport type {\n McpbChoice,\n McpbQuestion,\n McpbSession,\n McpbEnv,\n McpbCorrectness,\n} from '../shared/types';\n\nconst isEmptyObject = (value: unknown): boolean =>\n !!value &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.keys(value as Record<string, unknown>).length === 0;\n\nexport function countBlankTokens(template: string): number {\n if (!template) return 0;\n const re = /\\{\\{blank\\}\\}/g;\n return (template.match(re) || []).length;\n}\n\nexport const getCorrectness = (question: McpbQuestion, session: McpbSession): McpbCorrectness => {\n if (!session?.choiceId) {\n return 'unanswered';\n }\n const correct = question?.correctChoiceId || '';\n if (session.choiceId === correct) {\n return 'correct';\n }\n return 'incorrect';\n};\n\n/** One blank, one key: an answered session scores 1 only when it picked the key. */\nexport const getPartialScore = (question: McpbQuestion, session: McpbSession) =>\n getCorrectness(question, session) === 'correct' ? 1 : 0;\n\nexport const outcome = (question: McpbQuestion, session: McpbSession, env: McpbEnv) =>\n new Promise((resolve) => {\n if (!session || isEmptyObject(session)) {\n resolve({\n score: 0,\n empty: true,\n traceLog: ['Student did not select any answers. Score is 0.'],\n });\n return;\n }\n\n session = normalizeSession(session);\n const normalizedQuestion = normalize(question);\n const correctness = getCorrectness(normalizedQuestion, session);\n\n if (correctness === 'unanswered') {\n resolve({\n score: 0,\n empty: true,\n traceLog: ['Student did not select any answers. Score is 0.'],\n });\n return;\n }\n\n const score = getPartialScore(normalizedQuestion, session);\n const traceLog = [\n `Mode: ${env?.mode || 'unknown'}.`,\n `Student selected choice: ${session.choiceId}.`,\n `Correct choice: ${normalizedQuestion.correctChoiceId || 'none'}.`,\n `Final score: ${score}.`,\n ];\n resolve({ score, empty: false, traceLog });\n });\n\nconst withDefaults = (base: McpbQuestion, model: McpbQuestion = {}) => ({\n ...base,\n ...model,\n layoutLimits: {\n ...DEFAULT_LAYOUT_LIMITS,\n ...(model?.layoutLimits || {}),\n },\n});\n\n/** The authoring starting point: a question that passes `validate()` as it stands. */\nexport const createDefaultModel = (model: McpbQuestion = {}) =>\n withDefaults(authorDefaults.model, model);\n\n/**\n * What `model()` and `outcome()` read: the item's own fields, with neutral\n * values for the rest. Starter content here would reach a learner as the\n * item's prompt or choices.\n */\nexport const normalize = (question: McpbQuestion = {}) => {\n const { shuffle, ...rest } = question || {};\n return {\n ...withDefaults(defaults.model, rest),\n // `lockChoiceOrder` alone decides the order, as in multiple-choice. Older items\n // spell an unlocked order `shuffle: true`, which counts only when it is unset.\n lockChoiceOrder:\n typeof rest.lockChoiceOrder === 'boolean' ? rest.lockChoiceOrder : shuffle !== true,\n };\n};\n\nexport const normalizeSession = (s: McpbSession): McpbSession => ({ ...s });\n\nconst shouldLockChoices = (question: McpbQuestion, env: McpbEnv) => {\n if (question?.lockChoiceOrder) return true;\n if (env?.['@pie-element']?.lockChoiceOrder) return true;\n return env?.role === 'instructor';\n};\n\nfunction shuffleArray<T>(items: T[]): T[] {\n const out = [...items];\n for (let i = out.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [out[i], out[j]] = [out[j], out[i]];\n }\n return out;\n}\n\n/**\n * The fields delivery renders. An imported choice can carry more, such as a\n * correctness flag or feedback, which must not reach a learner's browser.\n */\nconst toDeliveryChoice = ({ id, labelHtml, imageUrl, imageAlt }: McpbChoice): McpbChoice => ({\n id,\n ...(labelHtml !== undefined ? { labelHtml } : {}),\n ...(imageUrl !== undefined ? { imageUrl } : {}),\n ...(imageAlt !== undefined ? { imageAlt } : {}),\n});\n\nconst getStoredShuffle = (session: McpbSession): string[] =>\n Array.isArray(session?.data?.shuffledValues)\n ? session.data.shuffledValues\n : Array.isArray(session?.shuffledValues)\n ? session.shuffledValues\n : [];\n\nconst applyShuffledValues = (\n choices: McpbChoice[],\n shuffledValues: string[],\n choiceKey: keyof McpbChoice\n) => {\n const orderedChoices = shuffledValues\n .map((value) => choices.find((choice) => choice?.[choiceKey] === value))\n .filter((c): c is McpbChoice => !!c);\n\n if (orderedChoices.length === choices.length) {\n return orderedChoices;\n }\n\n const orderedValues = new Set(orderedChoices.map((choice) => choice[choiceKey]));\n const leftovers = choices.filter((choice) => !orderedValues.has(choice?.[choiceKey]));\n return [...orderedChoices, ...leftovers];\n};\n\ntype UpdateSessionFn = (\n id: string,\n element: string,\n data: { shuffledValues: string[] }\n) => Promise<void>;\n\nconst getOrderedChoices = async (\n question: McpbQuestion,\n session: McpbSession,\n env: McpbEnv,\n updateSession?: UpdateSessionFn\n) => {\n const choices = Array.isArray(question?.choices) ? [...question.choices] : [];\n if (!choices.length || shouldLockChoices(question, env || {})) {\n return choices;\n }\n\n const shuffledValues = getStoredShuffle(session);\n if (shuffledValues.length) {\n return applyShuffledValues(choices, shuffledValues, 'id');\n }\n\n const shuffledChoices = shuffleArray(choices);\n\n if (updateSession && typeof updateSession === 'function' && session?.id && session?.element) {\n const shuffledIds = shuffledChoices.map((choice) => choice?.id).filter(Boolean);\n if (shuffledIds.length) {\n await updateSession(session.id, session.element, { shuffledValues: shuffledIds });\n }\n }\n\n return shuffledChoices;\n};\n\nexport const model = async (\n question: McpbQuestion,\n session: McpbSession | null,\n env: McpbEnv | null,\n updateSession?: UpdateSessionFn\n) => {\n const safeSession: McpbSession = session || {};\n const safeEnv: McpbEnv = env || {};\n const normalizedQuestion = normalize(question);\n const choices = (await getOrderedChoices(normalizedQuestion, safeSession, safeEnv, updateSession))\n .filter((c): c is McpbChoice => !!c && typeof c === 'object')\n .map(toDeliveryChoice);\n\n const out: Record<string, unknown> = {\n prompt: normalizedQuestion.promptEnabled ? normalizedQuestion.prompt : null,\n interactionMode: normalizedQuestion.interactionMode || 'populate_blank',\n layoutProfile: normalizedQuestion.layoutProfile || '',\n choiceLayout: normalizedQuestion.choiceLayout || '',\n layoutProfilePresets:\n normalizedQuestion.layoutProfilePresets &&\n typeof normalizedQuestion.layoutProfilePresets === 'object'\n ? normalizedQuestion.layoutProfilePresets\n : {},\n layoutLimits: normalizedQuestion.layoutLimits || { ...DEFAULT_LAYOUT_LIMITS },\n audioButtonSkin:\n normalizedQuestion.audioButtonSkin && typeof normalizedQuestion.audioButtonSkin === 'object'\n ? normalizedQuestion.audioButtonSkin\n : null,\n audioButtonSkinsByLocale:\n normalizedQuestion.audioButtonSkinsByLocale &&\n typeof normalizedQuestion.audioButtonSkinsByLocale === 'object'\n ? normalizedQuestion.audioButtonSkinsByLocale\n : {},\n sentenceHtml: normalizedQuestion.sentenceHtml || null,\n template: normalizedQuestion.template,\n choiceMode: normalizedQuestion.choiceMode,\n choices,\n choiceGroupLabel: normalizedQuestion.choiceGroupLabel || '',\n hasAudio: normalizedQuestion.hasAudio,\n autoplayAudioEnabled: !!normalizedQuestion.autoplayAudioEnabled,\n completeAudioEnabled: !!normalizedQuestion.completeAudioEnabled,\n audioUrl: normalizedQuestion.hasAudio ? normalizedQuestion.audioUrl : null,\n audioTranscript: normalizedQuestion.hasAudio ? normalizedQuestion.audioTranscript : null,\n customType: normalizedQuestion.customType || '',\n useFeatureButtonAudio:\n typeof normalizedQuestion.useFeatureButtonAudio === 'boolean'\n ? normalizedQuestion.useFeatureButtonAudio\n : undefined,\n locale: normalizedQuestion.locale || '',\n language: normalizedQuestion.language || normalizedQuestion.locale || undefined,\n disabled: safeEnv.mode !== 'gather',\n mode: safeEnv.mode,\n };\n\n if (safeEnv.mode === 'evaluate') {\n const correctness = getCorrectness(normalizedQuestion, safeSession);\n out.correctness = correctness;\n out.responseCorrect = correctness === 'correct';\n out.correctChoiceId = normalizedQuestion.correctChoiceId;\n }\n\n if (safeEnv.role === 'instructor' && (safeEnv.mode === 'view' || safeEnv.mode === 'evaluate')) {\n out.teacherInstructions = normalizedQuestion.teacherInstructionsEnabled\n ? normalizedQuestion.teacherInstructions\n : null;\n } else {\n out.teacherInstructions = null;\n }\n\n return out;\n};\n\nexport const createCorrectResponseSession = (question: McpbQuestion, env: McpbEnv) => {\n return new Promise((resolve) => {\n if (env.mode !== 'evaluate' && env.role === 'instructor') {\n // The player sets `id` and `element` on the entry from the item config.\n resolve({\n id: '1',\n choiceId: question?.correctChoiceId || '',\n });\n } else {\n resolve(null);\n }\n });\n};\n\n/** Markup with its tags removed, keeping media an author can answer from. */\nconst getContent = (html?: string) =>\n (html || '').replace(/(<(?!img|iframe|source)([^>]+)>)/gi, '').trim();\n\ntype ValidateConfig = {\n minAnswerChoices?: number;\n maxAnswerChoices?: number;\n prompt?: { required?: boolean };\n teacherInstructions?: { required?: boolean };\n};\n\n/**\n * Authoring errors in multiple-choice's shape: a message per field, and under\n * `choices` a message per choice keyed by its id.\n */\nexport const validate = (question: McpbQuestion = {}, config: ValidateConfig = {}) => {\n const errors: Record<string, string | Record<string, string>> = {};\n const { minAnswerChoices = 2, maxAnswerChoices } = config;\n\n if ((question.promptEnabled || config.prompt?.required) && !getContent(question.prompt)) {\n errors.prompt = 'This field is required.';\n }\n\n if (config.teacherInstructions?.required && !getContent(question.teacherInstructions)) {\n errors.teacherInstructions = 'This field is required.';\n }\n\n const interactionMode = question.interactionMode || 'populate_blank';\n const template = question.template || '';\n const n = countBlankTokens(template);\n if (interactionMode === 'audio_mc_only') {\n if (n > 0) {\n errors.template = `Template cannot contain ${BLANK_TOKEN} in audio-only mode`;\n }\n } else if (interactionMode === 'populate_blank') {\n if (n === 0) {\n errors.template = `Template must contain exactly one ${BLANK_TOKEN} placeholder`;\n } else if (n > 1) {\n errors.template = `Template must contain only one ${BLANK_TOKEN} placeholder`;\n }\n } else {\n errors.interactionMode = 'Unknown interaction mode';\n }\n\n const choices = Array.isArray(question.choices) ? question.choices : [];\n if (choices.length < minAnswerChoices) {\n errors.answerChoices = `There should be at least ${minAnswerChoices} choices defined.`;\n } else if (maxAnswerChoices != null && choices.length > maxAnswerChoices) {\n errors.answerChoices = `No more than ${maxAnswerChoices} choices should be defined.`;\n }\n\n const mode = question.choiceMode || 'text';\n const choicesErrors: Record<string, string> = {};\n const seenIds = new Set<string>();\n const seenLabels = new Set<string>();\n choices.forEach((c, i) => {\n const key = c?.id || String(i);\n if (!c?.id) {\n choicesErrors[key] = 'Choice needs an id.';\n return;\n }\n // The session stores the picked id, so two choices sharing one cannot be told apart.\n if (seenIds.has(c.id)) {\n choicesErrors[key] = 'Choice id should be unique.';\n return;\n }\n seenIds.add(c.id);\n if (mode === 'text') {\n const label = getContent(c.labelHtml);\n if (!label) {\n choicesErrors[key] = 'Content should not be empty.';\n } else if (seenLabels.has(label)) {\n choicesErrors[key] = 'Content should be unique.';\n }\n seenLabels.add(label);\n } else if (!c.imageUrl?.trim()) {\n choicesErrors[key] = 'An image is required.';\n } else if (!c.imageAlt?.trim()) {\n choicesErrors[key] = 'Image alt text is required.';\n }\n });\n if (Object.keys(choicesErrors).length) {\n errors.choices = choicesErrors;\n }\n\n const correct = question.correctChoiceId;\n if (!correct) {\n errors.correctResponse = 'No correct response defined.';\n } else if (!choices.some((c) => c?.id === correct)) {\n errors.correctResponse = 'The correct response must be one of the choices.';\n }\n\n if (question.hasAudio) {\n const hasAudioUrl = !!question.audioUrl?.trim();\n if (!hasAudioUrl) {\n errors.audioUrl = 'A playable audio URL is required when audio is enabled';\n }\n }\n\n const limits = question?.layoutLimits;\n if (limits && typeof limits === 'object') {\n const numericLimitKeys = [\n 'blankStandaloneWidthRem',\n 'blankWideWidthRem',\n 'blankUnderlineWidthPx',\n 'blankUnderlineWideWidthPx',\n 'horizontalChoiceWidthPx',\n 'horizontalChoiceWidthVw',\n 'horizontalChoiceTileMinHeightRem',\n 'horizontalChoiceContentMinHeightRem',\n 'choiceImageMaxHeightRem',\n 'choiceImageMaxWidthRem',\n 'listenButtonSizePx',\n 'stimulusMinColumnPx',\n 'textMinColumnPx',\n 'legendMaxChars',\n 'choiceGroupGapRem',\n 'choiceRowGapRem',\n 'toggleButtonGapRem',\n 'horizontalChoiceRadioTopMarginRem',\n 'audioBlankTemplateMarginTopRem',\n 'audioBlankTemplateMarginBottomRem',\n 'audioInstructionsMaxWidthPx',\n 'stimulusGridColumnGapRem',\n 'stimulusGridRowGapRem',\n 'stimulusSentenceMarginTopRem',\n 'stimulusChoicesMarginTopRem',\n 'tokenGridColumnGapRem',\n 'tokenGridRowGapRem',\n 'tokenTemplateMarginTopRem',\n 'tokenInlineTokenGapRem',\n 'tokenChoicesMarginTopRem',\n 'inlineGridColumnGapRem',\n 'inlineGridRowGapRem',\n 'inlineTemplateMarginTopRem',\n 'inlineChoicesMarginTopRem',\n ] as const;\n for (const key of numericLimitKeys) {\n if (limits[key] === undefined || limits[key] === null) continue;\n const value = Number(limits[key]);\n if (!Number.isFinite(value) || value <= 0) {\n errors.layoutLimits = `${key} must be a positive number when provided`;\n break;\n }\n }\n }\n\n return errors;\n};\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { t as e } from "../McPopulatedBlank-RbRB0ikv.js";
1
+ import { t as e } from "../McPopulatedBlank-B0WKe5yV.js";
2
2
  //#region ../../lib-svelte/delivery-events/dist/index2.js
3
3
  function t(e) {
4
4
  return !!e && typeof e == "object" && !Array.isArray(e);
@@ -21,7 +21,6 @@ var e = {
21
21
  audioBlankTemplateMarginTopRem: .8,
22
22
  audioBlankTemplateMarginBottomRem: 1.8,
23
23
  audioInstructionsMaxWidthPx: 875,
24
- narrowHorizontalChoiceMaxWidthPx: 230,
25
24
  stimulusGridColumnGapRem: 2,
26
25
  stimulusGridRowGapRem: .7,
27
26
  stimulusSentenceMarginTopRem: .2,
@@ -39,4 +38,4 @@ var e = {
39
38
  //#endregion
40
39
  export { e as t };
41
40
 
42
- //# sourceMappingURL=layoutLimits-Bp17YP2V.js.map
41
+ //# sourceMappingURL=layoutLimits-C2D60jW0.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layoutLimits-C2D60jW0.js","names":[],"sources":["../../src/shared/layoutLimits.ts"],"sourcesContent":["export type LayoutLimits = {\n blankStandaloneWidthRem: number;\n blankWideWidthRem: number;\n blankUnderlineWidthPx: number;\n blankUnderlineWideWidthPx: number;\n horizontalChoiceWidthPx: number;\n horizontalChoiceWidthVw: number;\n horizontalChoiceTileMinHeightRem: number;\n horizontalChoiceContentMinHeightRem: number;\n choiceImageMaxHeightRem: number;\n choiceImageMaxWidthRem: number;\n listenButtonSizePx: number;\n stimulusMinColumnPx: number;\n textMinColumnPx: number;\n legendMaxChars: number;\n choiceGroupGapRem: number;\n choiceRowGapRem: number;\n toggleButtonGapRem: number;\n horizontalChoiceRadioTopMarginRem: number;\n audioBlankTemplateMarginTopRem: number;\n audioBlankTemplateMarginBottomRem: number;\n audioInstructionsMaxWidthPx: number;\n stimulusGridColumnGapRem: number;\n stimulusGridRowGapRem: number;\n stimulusSentenceMarginTopRem: number;\n stimulusChoicesMarginTopRem: number;\n tokenGridColumnGapRem: number;\n tokenGridRowGapRem: number;\n tokenTemplateMarginTopRem: number;\n tokenInlineTokenGapRem: number;\n tokenChoicesMarginTopRem: number;\n inlineGridColumnGapRem: number;\n inlineGridRowGapRem: number;\n inlineTemplateMarginTopRem: number;\n inlineChoicesMarginTopRem: number;\n};\n\n/** Baseline values tuned to match historical CQT visuals; override via model.layoutLimits. */\nexport const DEFAULT_LAYOUT_LIMITS: LayoutLimits = {\n blankStandaloneWidthRem: 7,\n blankWideWidthRem: 10,\n blankUnderlineWidthPx: 2,\n blankUnderlineWideWidthPx: 4,\n horizontalChoiceWidthPx: 170,\n horizontalChoiceWidthVw: 30,\n horizontalChoiceTileMinHeightRem: 11,\n horizontalChoiceContentMinHeightRem: 7.5,\n // 9.375rem = 150px at the standard 16px root font size — Learnosity graphic-choice\n // SVGs are authored at 150x150; images up to that size must render at full size.\n // Shared by the choice tile image and the selected-answer image inside the cloze\n // blank, so the same choice image always renders at the same size in both places.\n choiceImageMaxHeightRem: 9.375,\n choiceImageMaxWidthRem: 9.375,\n listenButtonSizePx: 128,\n stimulusMinColumnPx: 210,\n textMinColumnPx: 260,\n legendMaxChars: 120,\n choiceGroupGapRem: 0.5,\n choiceRowGapRem: 0.5,\n toggleButtonGapRem: 0.5,\n horizontalChoiceRadioTopMarginRem: 0.5,\n audioBlankTemplateMarginTopRem: 0.8,\n audioBlankTemplateMarginBottomRem: 1.8,\n audioInstructionsMaxWidthPx: 875,\n stimulusGridColumnGapRem: 2,\n stimulusGridRowGapRem: 0.7,\n stimulusSentenceMarginTopRem: 0.2,\n stimulusChoicesMarginTopRem: 0.9,\n tokenGridColumnGapRem: 1.5,\n tokenGridRowGapRem: 0.8,\n tokenTemplateMarginTopRem: 0.6,\n tokenInlineTokenGapRem: 0.35,\n tokenChoicesMarginTopRem: 0.2,\n inlineGridColumnGapRem: 1.5,\n inlineGridRowGapRem: 0.65,\n inlineTemplateMarginTopRem: 0.45,\n inlineChoicesMarginTopRem: 0.25,\n};\n"],"mappings":";AAsCA,IAAa,IAAsC;CACjD,yBAAyB;CACzB,mBAAmB;CACnB,uBAAuB;CACvB,2BAA2B;CAC3B,yBAAyB;CACzB,yBAAyB;CACzB,kCAAkC;CAClC,qCAAqC;CAKrC,yBAAyB;CACzB,wBAAwB;CACxB,oBAAoB;CACpB,qBAAqB;CACrB,iBAAiB;CACjB,gBAAgB;CAChB,mBAAmB;CACnB,iBAAiB;CACjB,oBAAoB;CACpB,mCAAmC;CACnC,gCAAgC;CAChC,mCAAmC;CACnC,6BAA6B;CAC7B,0BAA0B;CAC1B,uBAAuB;CACvB,8BAA8B;CAC9B,6BAA6B;CAC7B,uBAAuB;CACvB,oBAAoB;CACpB,2BAA2B;CAC3B,wBAAwB;CACxB,0BAA0B;CAC1B,wBAAwB;CACxB,qBAAqB;CACrB,4BAA4B;CAC5B,2BAA2B;AAC7B"}
@@ -1,6 +1,6 @@
1
1
  import { A as e, C as t, D as n, E as r, F as i, N as a, P as o, S as s, T as c, _ as l, d as u, g as d, h as f, j as p, k as m, m as h, n as g, p as _, t as v, u as y, w as b, x } from "../client-CWh3mUUV.js";
2
- import { n as S, t as C } from "../McPopulatedBlank-RbRB0ikv.js";
3
- import { n as w, o as T } from "../controller-D_ITRCYG.js";
2
+ import { n as S, t as C } from "../McPopulatedBlank-B0WKe5yV.js";
3
+ import { n as w, o as T } from "../controller-C9qqF83g.js";
4
4
  //#region src/print/preparePrint.ts
5
5
  async function E(e, t) {
6
6
  let n = {
@@ -37,7 +37,6 @@ declare const _default: {
37
37
  audioBlankTemplateMarginTopRem: number;
38
38
  audioBlankTemplateMarginBottomRem: number;
39
39
  audioInstructionsMaxWidthPx: number;
40
- narrowHorizontalChoiceMaxWidthPx: number;
41
40
  stimulusGridColumnGapRem: number;
42
41
  stimulusGridRowGapRem: number;
43
42
  stimulusSentenceMarginTopRem: number;
@@ -28,7 +28,6 @@ export declare const createDefaultModel: (model?: McpbQuestion) => {
28
28
  audioBlankTemplateMarginTopRem: number;
29
29
  audioBlankTemplateMarginBottomRem: number;
30
30
  audioInstructionsMaxWidthPx: number;
31
- narrowHorizontalChoiceMaxWidthPx: number;
32
31
  stimulusGridColumnGapRem: number;
33
32
  stimulusGridRowGapRem: number;
34
33
  stimulusSentenceMarginTopRem: number;
@@ -103,7 +102,6 @@ export declare const normalize: (question?: McpbQuestion) => {
103
102
  audioBlankTemplateMarginTopRem: number;
104
103
  audioBlankTemplateMarginBottomRem: number;
105
104
  audioInstructionsMaxWidthPx: number;
106
- narrowHorizontalChoiceMaxWidthPx: number;
107
105
  stimulusGridColumnGapRem: number;
108
106
  stimulusGridRowGapRem: number;
109
107
  stimulusSentenceMarginTopRem: number;
@@ -22,7 +22,6 @@ var DEFAULT_LAYOUT_LIMITS = {
22
22
  audioBlankTemplateMarginTopRem: .8,
23
23
  audioBlankTemplateMarginBottomRem: 1.8,
24
24
  audioInstructionsMaxWidthPx: 875,
25
- narrowHorizontalChoiceMaxWidthPx: 230,
26
25
  stimulusGridColumnGapRem: 2,
27
26
  stimulusGridRowGapRem: .7,
28
27
  stimulusSentenceMarginTopRem: .2,
@@ -337,7 +336,6 @@ var validate = (question = {}, config = {}) => {
337
336
  "audioBlankTemplateMarginTopRem",
338
337
  "audioBlankTemplateMarginBottomRem",
339
338
  "audioInstructionsMaxWidthPx",
340
- "narrowHorizontalChoiceMaxWidthPx",
341
339
  "stimulusGridColumnGapRem",
342
340
  "stimulusGridRowGapRem",
343
341
  "stimulusSentenceMarginTopRem",
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/shared/layoutLimits.ts","../../src/controller/defaults.ts","../../src/author/defaults.ts","../../src/controller/index.ts"],"sourcesContent":["export type LayoutLimits = {\n blankStandaloneWidthRem: number;\n blankWideWidthRem: number;\n blankUnderlineWidthPx: number;\n blankUnderlineWideWidthPx: number;\n horizontalChoiceWidthPx: number;\n horizontalChoiceWidthVw: number;\n horizontalChoiceTileMinHeightRem: number;\n horizontalChoiceContentMinHeightRem: number;\n choiceImageMaxHeightRem: number;\n choiceImageMaxWidthRem: number;\n listenButtonSizePx: number;\n stimulusMinColumnPx: number;\n textMinColumnPx: number;\n legendMaxChars: number;\n choiceGroupGapRem: number;\n choiceRowGapRem: number;\n toggleButtonGapRem: number;\n horizontalChoiceRadioTopMarginRem: number;\n audioBlankTemplateMarginTopRem: number;\n audioBlankTemplateMarginBottomRem: number;\n audioInstructionsMaxWidthPx: number;\n narrowHorizontalChoiceMaxWidthPx: number;\n stimulusGridColumnGapRem: number;\n stimulusGridRowGapRem: number;\n stimulusSentenceMarginTopRem: number;\n stimulusChoicesMarginTopRem: number;\n tokenGridColumnGapRem: number;\n tokenGridRowGapRem: number;\n tokenTemplateMarginTopRem: number;\n tokenInlineTokenGapRem: number;\n tokenChoicesMarginTopRem: number;\n inlineGridColumnGapRem: number;\n inlineGridRowGapRem: number;\n inlineTemplateMarginTopRem: number;\n inlineChoicesMarginTopRem: number;\n};\n\n/** Baseline values tuned to match historical CQT visuals; override via model.layoutLimits. */\nexport const DEFAULT_LAYOUT_LIMITS: LayoutLimits = {\n blankStandaloneWidthRem: 7,\n blankWideWidthRem: 10,\n blankUnderlineWidthPx: 2,\n blankUnderlineWideWidthPx: 4,\n horizontalChoiceWidthPx: 170,\n horizontalChoiceWidthVw: 30,\n horizontalChoiceTileMinHeightRem: 11,\n horizontalChoiceContentMinHeightRem: 7.5,\n // 9.375rem = 150px at the standard 16px root font size — Learnosity graphic-choice\n // SVGs are authored at 150x150; images up to that size must render at full size.\n // Shared by the choice tile image and the selected-answer image inside the cloze\n // blank, so the same choice image always renders at the same size in both places.\n choiceImageMaxHeightRem: 9.375,\n choiceImageMaxWidthRem: 9.375,\n listenButtonSizePx: 128,\n stimulusMinColumnPx: 210,\n textMinColumnPx: 260,\n legendMaxChars: 120,\n choiceGroupGapRem: 0.5,\n choiceRowGapRem: 0.5,\n toggleButtonGapRem: 0.5,\n horizontalChoiceRadioTopMarginRem: 0.5,\n audioBlankTemplateMarginTopRem: 0.8,\n audioBlankTemplateMarginBottomRem: 1.8,\n audioInstructionsMaxWidthPx: 875,\n narrowHorizontalChoiceMaxWidthPx: 230,\n stimulusGridColumnGapRem: 2,\n stimulusGridRowGapRem: 0.7,\n stimulusSentenceMarginTopRem: 0.2,\n stimulusChoicesMarginTopRem: 0.9,\n tokenGridColumnGapRem: 1.5,\n tokenGridRowGapRem: 0.8,\n tokenTemplateMarginTopRem: 0.6,\n tokenInlineTokenGapRem: 0.35,\n tokenChoicesMarginTopRem: 0.2,\n inlineGridColumnGapRem: 1.5,\n inlineGridRowGapRem: 0.65,\n inlineTemplateMarginTopRem: 0.45,\n inlineChoicesMarginTopRem: 0.25,\n};\n","import { DEFAULT_LAYOUT_LIMITS } from '../shared/layoutLimits';\n\nexport { DEFAULT_LAYOUT_LIMITS };\n\n/** Token authors must include exactly once in `template` (HTML). */\nexport const BLANK_TOKEN = '{{blank}}';\n\n/**\n * The values `normalize` fills in for fields an item leaves out. They carry no\n * content and no `id` or `element`: those come from the item config, and a\n * default here would stand in for a field the author never set.\n */\nexport default {\n model: {\n prompt: '',\n promptEnabled: true,\n interactionMode: 'populate_blank' as const,\n layoutProfile: '',\n choiceLayout: '',\n layoutProfilePresets: {},\n layoutLimits: { ...DEFAULT_LAYOUT_LIMITS },\n audioButtonSkin: null,\n audioButtonSkinsByLocale: {},\n sentenceHtml: '',\n template: '',\n choiceMode: 'text' as const,\n choices: [],\n choiceGroupLabel: '',\n correctChoiceId: '',\n hasAudio: false,\n autoplayAudioEnabled: false,\n completeAudioEnabled: false,\n audioUrl: '',\n audioTranscript: '',\n locale: '',\n teacherInstructions: '',\n teacherInstructionsEnabled: true,\n },\n};\n","import controllerDefaults, { BLANK_TOKEN } from '../controller/defaults';\n\n/** The model a new item starts from: a valid question an author edits in place. */\nexport default {\n model: {\n ...controllerDefaults.model,\n prompt: '<p><strong>Demo prompt</strong></p>',\n template: `<p>The answer is ${BLANK_TOKEN}.</p>`,\n choices: [\n { id: 'c1', labelHtml: '<p>Option A</p>' },\n { id: 'c2', labelHtml: '<p>Option B</p>' },\n { id: 'c3', labelHtml: '<p>Option C</p>' },\n ],\n correctChoiceId: 'c2',\n },\n configuration: {\n prompt: {\n label: 'Prompt',\n settings: true,\n },\n },\n};\n","import authorDefaults from '../author/defaults';\nimport defaults, { BLANK_TOKEN, DEFAULT_LAYOUT_LIMITS } from './defaults';\nimport type {\n McpbChoice,\n McpbQuestion,\n McpbSession,\n McpbEnv,\n McpbCorrectness,\n} from '../shared/types';\n\nconst isEmptyObject = (value: unknown): boolean =>\n !!value &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.keys(value as Record<string, unknown>).length === 0;\n\nexport function countBlankTokens(template: string): number {\n if (!template) return 0;\n const re = /\\{\\{blank\\}\\}/g;\n return (template.match(re) || []).length;\n}\n\nexport const getCorrectness = (question: McpbQuestion, session: McpbSession): McpbCorrectness => {\n if (!session?.choiceId) {\n return 'unanswered';\n }\n const correct = question?.correctChoiceId || '';\n if (session.choiceId === correct) {\n return 'correct';\n }\n return 'incorrect';\n};\n\n/** One blank, one key: an answered session scores 1 only when it picked the key. */\nexport const getPartialScore = (question: McpbQuestion, session: McpbSession) =>\n getCorrectness(question, session) === 'correct' ? 1 : 0;\n\nexport const outcome = (question: McpbQuestion, session: McpbSession, env: McpbEnv) =>\n new Promise((resolve) => {\n if (!session || isEmptyObject(session)) {\n resolve({\n score: 0,\n empty: true,\n traceLog: ['Student did not select any answers. Score is 0.'],\n });\n return;\n }\n\n session = normalizeSession(session);\n const normalizedQuestion = normalize(question);\n const correctness = getCorrectness(normalizedQuestion, session);\n\n if (correctness === 'unanswered') {\n resolve({\n score: 0,\n empty: true,\n traceLog: ['Student did not select any answers. Score is 0.'],\n });\n return;\n }\n\n const score = getPartialScore(normalizedQuestion, session);\n const traceLog = [\n `Mode: ${env?.mode || 'unknown'}.`,\n `Student selected choice: ${session.choiceId}.`,\n `Correct choice: ${normalizedQuestion.correctChoiceId || 'none'}.`,\n `Final score: ${score}.`,\n ];\n resolve({ score, empty: false, traceLog });\n });\n\nconst withDefaults = (base: McpbQuestion, model: McpbQuestion = {}) => ({\n ...base,\n ...model,\n layoutLimits: {\n ...DEFAULT_LAYOUT_LIMITS,\n ...(model?.layoutLimits || {}),\n },\n});\n\n/** The authoring starting point: a question that passes `validate()` as it stands. */\nexport const createDefaultModel = (model: McpbQuestion = {}) =>\n withDefaults(authorDefaults.model, model);\n\n/**\n * What `model()` and `outcome()` read: the item's own fields, with neutral\n * values for the rest. Starter content here would reach a learner as the\n * item's prompt or choices.\n */\nexport const normalize = (question: McpbQuestion = {}) => {\n const { shuffle, ...rest } = question || {};\n return {\n ...withDefaults(defaults.model, rest),\n // `lockChoiceOrder` alone decides the order, as in multiple-choice. Older items\n // spell an unlocked order `shuffle: true`, which counts only when it is unset.\n lockChoiceOrder:\n typeof rest.lockChoiceOrder === 'boolean' ? rest.lockChoiceOrder : shuffle !== true,\n };\n};\n\nexport const normalizeSession = (s: McpbSession): McpbSession => ({ ...s });\n\nconst shouldLockChoices = (question: McpbQuestion, env: McpbEnv) => {\n if (question?.lockChoiceOrder) return true;\n if (env?.['@pie-element']?.lockChoiceOrder) return true;\n return env?.role === 'instructor';\n};\n\nfunction shuffleArray<T>(items: T[]): T[] {\n const out = [...items];\n for (let i = out.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [out[i], out[j]] = [out[j], out[i]];\n }\n return out;\n}\n\n/**\n * The fields delivery renders. An imported choice can carry more, such as a\n * correctness flag or feedback, which must not reach a learner's browser.\n */\nconst toDeliveryChoice = ({ id, labelHtml, imageUrl, imageAlt }: McpbChoice): McpbChoice => ({\n id,\n ...(labelHtml !== undefined ? { labelHtml } : {}),\n ...(imageUrl !== undefined ? { imageUrl } : {}),\n ...(imageAlt !== undefined ? { imageAlt } : {}),\n});\n\nconst getStoredShuffle = (session: McpbSession): string[] =>\n Array.isArray(session?.data?.shuffledValues)\n ? session.data.shuffledValues\n : Array.isArray(session?.shuffledValues)\n ? session.shuffledValues\n : [];\n\nconst applyShuffledValues = (\n choices: McpbChoice[],\n shuffledValues: string[],\n choiceKey: keyof McpbChoice\n) => {\n const orderedChoices = shuffledValues\n .map((value) => choices.find((choice) => choice?.[choiceKey] === value))\n .filter((c): c is McpbChoice => !!c);\n\n if (orderedChoices.length === choices.length) {\n return orderedChoices;\n }\n\n const orderedValues = new Set(orderedChoices.map((choice) => choice[choiceKey]));\n const leftovers = choices.filter((choice) => !orderedValues.has(choice?.[choiceKey]));\n return [...orderedChoices, ...leftovers];\n};\n\ntype UpdateSessionFn = (\n id: string,\n element: string,\n data: { shuffledValues: string[] }\n) => Promise<void>;\n\nconst getOrderedChoices = async (\n question: McpbQuestion,\n session: McpbSession,\n env: McpbEnv,\n updateSession?: UpdateSessionFn\n) => {\n const choices = Array.isArray(question?.choices) ? [...question.choices] : [];\n if (!choices.length || shouldLockChoices(question, env || {})) {\n return choices;\n }\n\n const shuffledValues = getStoredShuffle(session);\n if (shuffledValues.length) {\n return applyShuffledValues(choices, shuffledValues, 'id');\n }\n\n const shuffledChoices = shuffleArray(choices);\n\n if (updateSession && typeof updateSession === 'function' && session?.id && session?.element) {\n const shuffledIds = shuffledChoices.map((choice) => choice?.id).filter(Boolean);\n if (shuffledIds.length) {\n await updateSession(session.id, session.element, { shuffledValues: shuffledIds });\n }\n }\n\n return shuffledChoices;\n};\n\nexport const model = async (\n question: McpbQuestion,\n session: McpbSession | null,\n env: McpbEnv | null,\n updateSession?: UpdateSessionFn\n) => {\n const safeSession: McpbSession = session || {};\n const safeEnv: McpbEnv = env || {};\n const normalizedQuestion = normalize(question);\n const choices = (await getOrderedChoices(normalizedQuestion, safeSession, safeEnv, updateSession))\n .filter((c): c is McpbChoice => !!c && typeof c === 'object')\n .map(toDeliveryChoice);\n\n const out: Record<string, unknown> = {\n prompt: normalizedQuestion.promptEnabled ? normalizedQuestion.prompt : null,\n interactionMode: normalizedQuestion.interactionMode || 'populate_blank',\n layoutProfile: normalizedQuestion.layoutProfile || '',\n choiceLayout: normalizedQuestion.choiceLayout || '',\n layoutProfilePresets:\n normalizedQuestion.layoutProfilePresets &&\n typeof normalizedQuestion.layoutProfilePresets === 'object'\n ? normalizedQuestion.layoutProfilePresets\n : {},\n layoutLimits: normalizedQuestion.layoutLimits || { ...DEFAULT_LAYOUT_LIMITS },\n audioButtonSkin:\n normalizedQuestion.audioButtonSkin && typeof normalizedQuestion.audioButtonSkin === 'object'\n ? normalizedQuestion.audioButtonSkin\n : null,\n audioButtonSkinsByLocale:\n normalizedQuestion.audioButtonSkinsByLocale &&\n typeof normalizedQuestion.audioButtonSkinsByLocale === 'object'\n ? normalizedQuestion.audioButtonSkinsByLocale\n : {},\n sentenceHtml: normalizedQuestion.sentenceHtml || null,\n template: normalizedQuestion.template,\n choiceMode: normalizedQuestion.choiceMode,\n choices,\n choiceGroupLabel: normalizedQuestion.choiceGroupLabel || '',\n hasAudio: normalizedQuestion.hasAudio,\n autoplayAudioEnabled: !!normalizedQuestion.autoplayAudioEnabled,\n completeAudioEnabled: !!normalizedQuestion.completeAudioEnabled,\n audioUrl: normalizedQuestion.hasAudio ? normalizedQuestion.audioUrl : null,\n audioTranscript: normalizedQuestion.hasAudio ? normalizedQuestion.audioTranscript : null,\n customType: normalizedQuestion.customType || '',\n useFeatureButtonAudio:\n typeof normalizedQuestion.useFeatureButtonAudio === 'boolean'\n ? normalizedQuestion.useFeatureButtonAudio\n : undefined,\n locale: normalizedQuestion.locale || '',\n language: normalizedQuestion.language || normalizedQuestion.locale || undefined,\n disabled: safeEnv.mode !== 'gather',\n mode: safeEnv.mode,\n };\n\n if (safeEnv.mode === 'evaluate') {\n const correctness = getCorrectness(normalizedQuestion, safeSession);\n out.correctness = correctness;\n out.responseCorrect = correctness === 'correct';\n out.correctChoiceId = normalizedQuestion.correctChoiceId;\n }\n\n if (safeEnv.role === 'instructor' && (safeEnv.mode === 'view' || safeEnv.mode === 'evaluate')) {\n out.teacherInstructions = normalizedQuestion.teacherInstructionsEnabled\n ? normalizedQuestion.teacherInstructions\n : null;\n } else {\n out.teacherInstructions = null;\n }\n\n return out;\n};\n\nexport const createCorrectResponseSession = (question: McpbQuestion, env: McpbEnv) => {\n return new Promise((resolve) => {\n if (env.mode !== 'evaluate' && env.role === 'instructor') {\n // The player sets `id` and `element` on the entry from the item config.\n resolve({\n id: '1',\n choiceId: question?.correctChoiceId || '',\n });\n } else {\n resolve(null);\n }\n });\n};\n\n/** Markup with its tags removed, keeping media an author can answer from. */\nconst getContent = (html?: string) =>\n (html || '').replace(/(<(?!img|iframe|source)([^>]+)>)/gi, '').trim();\n\ntype ValidateConfig = {\n minAnswerChoices?: number;\n maxAnswerChoices?: number;\n prompt?: { required?: boolean };\n teacherInstructions?: { required?: boolean };\n};\n\n/**\n * Authoring errors in multiple-choice's shape: a message per field, and under\n * `choices` a message per choice keyed by its id.\n */\nexport const validate = (question: McpbQuestion = {}, config: ValidateConfig = {}) => {\n const errors: Record<string, string | Record<string, string>> = {};\n const { minAnswerChoices = 2, maxAnswerChoices } = config;\n\n if ((question.promptEnabled || config.prompt?.required) && !getContent(question.prompt)) {\n errors.prompt = 'This field is required.';\n }\n\n if (config.teacherInstructions?.required && !getContent(question.teacherInstructions)) {\n errors.teacherInstructions = 'This field is required.';\n }\n\n const interactionMode = question.interactionMode || 'populate_blank';\n const template = question.template || '';\n const n = countBlankTokens(template);\n if (interactionMode === 'audio_mc_only') {\n if (n > 0) {\n errors.template = `Template cannot contain ${BLANK_TOKEN} in audio-only mode`;\n }\n } else if (interactionMode === 'populate_blank') {\n if (n === 0) {\n errors.template = `Template must contain exactly one ${BLANK_TOKEN} placeholder`;\n } else if (n > 1) {\n errors.template = `Template must contain only one ${BLANK_TOKEN} placeholder`;\n }\n } else {\n errors.interactionMode = 'Unknown interaction mode';\n }\n\n const choices = Array.isArray(question.choices) ? question.choices : [];\n if (choices.length < minAnswerChoices) {\n errors.answerChoices = `There should be at least ${minAnswerChoices} choices defined.`;\n } else if (maxAnswerChoices != null && choices.length > maxAnswerChoices) {\n errors.answerChoices = `No more than ${maxAnswerChoices} choices should be defined.`;\n }\n\n const mode = question.choiceMode || 'text';\n const choicesErrors: Record<string, string> = {};\n const seenIds = new Set<string>();\n const seenLabels = new Set<string>();\n choices.forEach((c, i) => {\n const key = c?.id || String(i);\n if (!c?.id) {\n choicesErrors[key] = 'Choice needs an id.';\n return;\n }\n // The session stores the picked id, so two choices sharing one cannot be told apart.\n if (seenIds.has(c.id)) {\n choicesErrors[key] = 'Choice id should be unique.';\n return;\n }\n seenIds.add(c.id);\n if (mode === 'text') {\n const label = getContent(c.labelHtml);\n if (!label) {\n choicesErrors[key] = 'Content should not be empty.';\n } else if (seenLabels.has(label)) {\n choicesErrors[key] = 'Content should be unique.';\n }\n seenLabels.add(label);\n } else if (!c.imageUrl?.trim()) {\n choicesErrors[key] = 'An image is required.';\n } else if (!c.imageAlt?.trim()) {\n choicesErrors[key] = 'Image alt text is required.';\n }\n });\n if (Object.keys(choicesErrors).length) {\n errors.choices = choicesErrors;\n }\n\n const correct = question.correctChoiceId;\n if (!correct) {\n errors.correctResponse = 'No correct response defined.';\n } else if (!choices.some((c) => c?.id === correct)) {\n errors.correctResponse = 'The correct response must be one of the choices.';\n }\n\n if (question.hasAudio) {\n const hasAudioUrl = !!question.audioUrl?.trim();\n if (!hasAudioUrl) {\n errors.audioUrl = 'A playable audio URL is required when audio is enabled';\n }\n }\n\n const limits = question?.layoutLimits;\n if (limits && typeof limits === 'object') {\n const numericLimitKeys = [\n 'blankStandaloneWidthRem',\n 'blankWideWidthRem',\n 'blankUnderlineWidthPx',\n 'blankUnderlineWideWidthPx',\n 'horizontalChoiceWidthPx',\n 'horizontalChoiceWidthVw',\n 'horizontalChoiceTileMinHeightRem',\n 'horizontalChoiceContentMinHeightRem',\n 'choiceImageMaxHeightRem',\n 'choiceImageMaxWidthRem',\n 'listenButtonSizePx',\n 'stimulusMinColumnPx',\n 'textMinColumnPx',\n 'legendMaxChars',\n 'choiceGroupGapRem',\n 'choiceRowGapRem',\n 'toggleButtonGapRem',\n 'horizontalChoiceRadioTopMarginRem',\n 'audioBlankTemplateMarginTopRem',\n 'audioBlankTemplateMarginBottomRem',\n 'audioInstructionsMaxWidthPx',\n 'narrowHorizontalChoiceMaxWidthPx',\n 'stimulusGridColumnGapRem',\n 'stimulusGridRowGapRem',\n 'stimulusSentenceMarginTopRem',\n 'stimulusChoicesMarginTopRem',\n 'tokenGridColumnGapRem',\n 'tokenGridRowGapRem',\n 'tokenTemplateMarginTopRem',\n 'tokenInlineTokenGapRem',\n 'tokenChoicesMarginTopRem',\n 'inlineGridColumnGapRem',\n 'inlineGridRowGapRem',\n 'inlineTemplateMarginTopRem',\n 'inlineChoicesMarginTopRem',\n ] as const;\n for (const key of numericLimitKeys) {\n if (limits[key] === undefined || limits[key] === null) continue;\n const value = Number(limits[key]);\n if (!Number.isFinite(value) || value <= 0) {\n errors.layoutLimits = `${key} must be a positive number when provided`;\n break;\n }\n }\n }\n\n return errors;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/shared/layoutLimits.ts","../../src/controller/defaults.ts","../../src/author/defaults.ts","../../src/controller/index.ts"],"sourcesContent":["export type LayoutLimits = {\n blankStandaloneWidthRem: number;\n blankWideWidthRem: number;\n blankUnderlineWidthPx: number;\n blankUnderlineWideWidthPx: number;\n horizontalChoiceWidthPx: number;\n horizontalChoiceWidthVw: number;\n horizontalChoiceTileMinHeightRem: number;\n horizontalChoiceContentMinHeightRem: number;\n choiceImageMaxHeightRem: number;\n choiceImageMaxWidthRem: number;\n listenButtonSizePx: number;\n stimulusMinColumnPx: number;\n textMinColumnPx: number;\n legendMaxChars: number;\n choiceGroupGapRem: number;\n choiceRowGapRem: number;\n toggleButtonGapRem: number;\n horizontalChoiceRadioTopMarginRem: number;\n audioBlankTemplateMarginTopRem: number;\n audioBlankTemplateMarginBottomRem: number;\n audioInstructionsMaxWidthPx: number;\n stimulusGridColumnGapRem: number;\n stimulusGridRowGapRem: number;\n stimulusSentenceMarginTopRem: number;\n stimulusChoicesMarginTopRem: number;\n tokenGridColumnGapRem: number;\n tokenGridRowGapRem: number;\n tokenTemplateMarginTopRem: number;\n tokenInlineTokenGapRem: number;\n tokenChoicesMarginTopRem: number;\n inlineGridColumnGapRem: number;\n inlineGridRowGapRem: number;\n inlineTemplateMarginTopRem: number;\n inlineChoicesMarginTopRem: number;\n};\n\n/** Baseline values tuned to match historical CQT visuals; override via model.layoutLimits. */\nexport const DEFAULT_LAYOUT_LIMITS: LayoutLimits = {\n blankStandaloneWidthRem: 7,\n blankWideWidthRem: 10,\n blankUnderlineWidthPx: 2,\n blankUnderlineWideWidthPx: 4,\n horizontalChoiceWidthPx: 170,\n horizontalChoiceWidthVw: 30,\n horizontalChoiceTileMinHeightRem: 11,\n horizontalChoiceContentMinHeightRem: 7.5,\n // 9.375rem = 150px at the standard 16px root font size — Learnosity graphic-choice\n // SVGs are authored at 150x150; images up to that size must render at full size.\n // Shared by the choice tile image and the selected-answer image inside the cloze\n // blank, so the same choice image always renders at the same size in both places.\n choiceImageMaxHeightRem: 9.375,\n choiceImageMaxWidthRem: 9.375,\n listenButtonSizePx: 128,\n stimulusMinColumnPx: 210,\n textMinColumnPx: 260,\n legendMaxChars: 120,\n choiceGroupGapRem: 0.5,\n choiceRowGapRem: 0.5,\n toggleButtonGapRem: 0.5,\n horizontalChoiceRadioTopMarginRem: 0.5,\n audioBlankTemplateMarginTopRem: 0.8,\n audioBlankTemplateMarginBottomRem: 1.8,\n audioInstructionsMaxWidthPx: 875,\n stimulusGridColumnGapRem: 2,\n stimulusGridRowGapRem: 0.7,\n stimulusSentenceMarginTopRem: 0.2,\n stimulusChoicesMarginTopRem: 0.9,\n tokenGridColumnGapRem: 1.5,\n tokenGridRowGapRem: 0.8,\n tokenTemplateMarginTopRem: 0.6,\n tokenInlineTokenGapRem: 0.35,\n tokenChoicesMarginTopRem: 0.2,\n inlineGridColumnGapRem: 1.5,\n inlineGridRowGapRem: 0.65,\n inlineTemplateMarginTopRem: 0.45,\n inlineChoicesMarginTopRem: 0.25,\n};\n","import { DEFAULT_LAYOUT_LIMITS } from '../shared/layoutLimits';\n\nexport { DEFAULT_LAYOUT_LIMITS };\n\n/** Token authors must include exactly once in `template` (HTML). */\nexport const BLANK_TOKEN = '{{blank}}';\n\n/**\n * The values `normalize` fills in for fields an item leaves out. They carry no\n * content and no `id` or `element`: those come from the item config, and a\n * default here would stand in for a field the author never set.\n */\nexport default {\n model: {\n prompt: '',\n promptEnabled: true,\n interactionMode: 'populate_blank' as const,\n layoutProfile: '',\n choiceLayout: '',\n layoutProfilePresets: {},\n layoutLimits: { ...DEFAULT_LAYOUT_LIMITS },\n audioButtonSkin: null,\n audioButtonSkinsByLocale: {},\n sentenceHtml: '',\n template: '',\n choiceMode: 'text' as const,\n choices: [],\n choiceGroupLabel: '',\n correctChoiceId: '',\n hasAudio: false,\n autoplayAudioEnabled: false,\n completeAudioEnabled: false,\n audioUrl: '',\n audioTranscript: '',\n locale: '',\n teacherInstructions: '',\n teacherInstructionsEnabled: true,\n },\n};\n","import controllerDefaults, { BLANK_TOKEN } from '../controller/defaults';\n\n/** The model a new item starts from: a valid question an author edits in place. */\nexport default {\n model: {\n ...controllerDefaults.model,\n prompt: '<p><strong>Demo prompt</strong></p>',\n template: `<p>The answer is ${BLANK_TOKEN}.</p>`,\n choices: [\n { id: 'c1', labelHtml: '<p>Option A</p>' },\n { id: 'c2', labelHtml: '<p>Option B</p>' },\n { id: 'c3', labelHtml: '<p>Option C</p>' },\n ],\n correctChoiceId: 'c2',\n },\n configuration: {\n prompt: {\n label: 'Prompt',\n settings: true,\n },\n },\n};\n","import authorDefaults from '../author/defaults';\nimport defaults, { BLANK_TOKEN, DEFAULT_LAYOUT_LIMITS } from './defaults';\nimport type {\n McpbChoice,\n McpbQuestion,\n McpbSession,\n McpbEnv,\n McpbCorrectness,\n} from '../shared/types';\n\nconst isEmptyObject = (value: unknown): boolean =>\n !!value &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.keys(value as Record<string, unknown>).length === 0;\n\nexport function countBlankTokens(template: string): number {\n if (!template) return 0;\n const re = /\\{\\{blank\\}\\}/g;\n return (template.match(re) || []).length;\n}\n\nexport const getCorrectness = (question: McpbQuestion, session: McpbSession): McpbCorrectness => {\n if (!session?.choiceId) {\n return 'unanswered';\n }\n const correct = question?.correctChoiceId || '';\n if (session.choiceId === correct) {\n return 'correct';\n }\n return 'incorrect';\n};\n\n/** One blank, one key: an answered session scores 1 only when it picked the key. */\nexport const getPartialScore = (question: McpbQuestion, session: McpbSession) =>\n getCorrectness(question, session) === 'correct' ? 1 : 0;\n\nexport const outcome = (question: McpbQuestion, session: McpbSession, env: McpbEnv) =>\n new Promise((resolve) => {\n if (!session || isEmptyObject(session)) {\n resolve({\n score: 0,\n empty: true,\n traceLog: ['Student did not select any answers. Score is 0.'],\n });\n return;\n }\n\n session = normalizeSession(session);\n const normalizedQuestion = normalize(question);\n const correctness = getCorrectness(normalizedQuestion, session);\n\n if (correctness === 'unanswered') {\n resolve({\n score: 0,\n empty: true,\n traceLog: ['Student did not select any answers. Score is 0.'],\n });\n return;\n }\n\n const score = getPartialScore(normalizedQuestion, session);\n const traceLog = [\n `Mode: ${env?.mode || 'unknown'}.`,\n `Student selected choice: ${session.choiceId}.`,\n `Correct choice: ${normalizedQuestion.correctChoiceId || 'none'}.`,\n `Final score: ${score}.`,\n ];\n resolve({ score, empty: false, traceLog });\n });\n\nconst withDefaults = (base: McpbQuestion, model: McpbQuestion = {}) => ({\n ...base,\n ...model,\n layoutLimits: {\n ...DEFAULT_LAYOUT_LIMITS,\n ...(model?.layoutLimits || {}),\n },\n});\n\n/** The authoring starting point: a question that passes `validate()` as it stands. */\nexport const createDefaultModel = (model: McpbQuestion = {}) =>\n withDefaults(authorDefaults.model, model);\n\n/**\n * What `model()` and `outcome()` read: the item's own fields, with neutral\n * values for the rest. Starter content here would reach a learner as the\n * item's prompt or choices.\n */\nexport const normalize = (question: McpbQuestion = {}) => {\n const { shuffle, ...rest } = question || {};\n return {\n ...withDefaults(defaults.model, rest),\n // `lockChoiceOrder` alone decides the order, as in multiple-choice. Older items\n // spell an unlocked order `shuffle: true`, which counts only when it is unset.\n lockChoiceOrder:\n typeof rest.lockChoiceOrder === 'boolean' ? rest.lockChoiceOrder : shuffle !== true,\n };\n};\n\nexport const normalizeSession = (s: McpbSession): McpbSession => ({ ...s });\n\nconst shouldLockChoices = (question: McpbQuestion, env: McpbEnv) => {\n if (question?.lockChoiceOrder) return true;\n if (env?.['@pie-element']?.lockChoiceOrder) return true;\n return env?.role === 'instructor';\n};\n\nfunction shuffleArray<T>(items: T[]): T[] {\n const out = [...items];\n for (let i = out.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [out[i], out[j]] = [out[j], out[i]];\n }\n return out;\n}\n\n/**\n * The fields delivery renders. An imported choice can carry more, such as a\n * correctness flag or feedback, which must not reach a learner's browser.\n */\nconst toDeliveryChoice = ({ id, labelHtml, imageUrl, imageAlt }: McpbChoice): McpbChoice => ({\n id,\n ...(labelHtml !== undefined ? { labelHtml } : {}),\n ...(imageUrl !== undefined ? { imageUrl } : {}),\n ...(imageAlt !== undefined ? { imageAlt } : {}),\n});\n\nconst getStoredShuffle = (session: McpbSession): string[] =>\n Array.isArray(session?.data?.shuffledValues)\n ? session.data.shuffledValues\n : Array.isArray(session?.shuffledValues)\n ? session.shuffledValues\n : [];\n\nconst applyShuffledValues = (\n choices: McpbChoice[],\n shuffledValues: string[],\n choiceKey: keyof McpbChoice\n) => {\n const orderedChoices = shuffledValues\n .map((value) => choices.find((choice) => choice?.[choiceKey] === value))\n .filter((c): c is McpbChoice => !!c);\n\n if (orderedChoices.length === choices.length) {\n return orderedChoices;\n }\n\n const orderedValues = new Set(orderedChoices.map((choice) => choice[choiceKey]));\n const leftovers = choices.filter((choice) => !orderedValues.has(choice?.[choiceKey]));\n return [...orderedChoices, ...leftovers];\n};\n\ntype UpdateSessionFn = (\n id: string,\n element: string,\n data: { shuffledValues: string[] }\n) => Promise<void>;\n\nconst getOrderedChoices = async (\n question: McpbQuestion,\n session: McpbSession,\n env: McpbEnv,\n updateSession?: UpdateSessionFn\n) => {\n const choices = Array.isArray(question?.choices) ? [...question.choices] : [];\n if (!choices.length || shouldLockChoices(question, env || {})) {\n return choices;\n }\n\n const shuffledValues = getStoredShuffle(session);\n if (shuffledValues.length) {\n return applyShuffledValues(choices, shuffledValues, 'id');\n }\n\n const shuffledChoices = shuffleArray(choices);\n\n if (updateSession && typeof updateSession === 'function' && session?.id && session?.element) {\n const shuffledIds = shuffledChoices.map((choice) => choice?.id).filter(Boolean);\n if (shuffledIds.length) {\n await updateSession(session.id, session.element, { shuffledValues: shuffledIds });\n }\n }\n\n return shuffledChoices;\n};\n\nexport const model = async (\n question: McpbQuestion,\n session: McpbSession | null,\n env: McpbEnv | null,\n updateSession?: UpdateSessionFn\n) => {\n const safeSession: McpbSession = session || {};\n const safeEnv: McpbEnv = env || {};\n const normalizedQuestion = normalize(question);\n const choices = (await getOrderedChoices(normalizedQuestion, safeSession, safeEnv, updateSession))\n .filter((c): c is McpbChoice => !!c && typeof c === 'object')\n .map(toDeliveryChoice);\n\n const out: Record<string, unknown> = {\n prompt: normalizedQuestion.promptEnabled ? normalizedQuestion.prompt : null,\n interactionMode: normalizedQuestion.interactionMode || 'populate_blank',\n layoutProfile: normalizedQuestion.layoutProfile || '',\n choiceLayout: normalizedQuestion.choiceLayout || '',\n layoutProfilePresets:\n normalizedQuestion.layoutProfilePresets &&\n typeof normalizedQuestion.layoutProfilePresets === 'object'\n ? normalizedQuestion.layoutProfilePresets\n : {},\n layoutLimits: normalizedQuestion.layoutLimits || { ...DEFAULT_LAYOUT_LIMITS },\n audioButtonSkin:\n normalizedQuestion.audioButtonSkin && typeof normalizedQuestion.audioButtonSkin === 'object'\n ? normalizedQuestion.audioButtonSkin\n : null,\n audioButtonSkinsByLocale:\n normalizedQuestion.audioButtonSkinsByLocale &&\n typeof normalizedQuestion.audioButtonSkinsByLocale === 'object'\n ? normalizedQuestion.audioButtonSkinsByLocale\n : {},\n sentenceHtml: normalizedQuestion.sentenceHtml || null,\n template: normalizedQuestion.template,\n choiceMode: normalizedQuestion.choiceMode,\n choices,\n choiceGroupLabel: normalizedQuestion.choiceGroupLabel || '',\n hasAudio: normalizedQuestion.hasAudio,\n autoplayAudioEnabled: !!normalizedQuestion.autoplayAudioEnabled,\n completeAudioEnabled: !!normalizedQuestion.completeAudioEnabled,\n audioUrl: normalizedQuestion.hasAudio ? normalizedQuestion.audioUrl : null,\n audioTranscript: normalizedQuestion.hasAudio ? normalizedQuestion.audioTranscript : null,\n customType: normalizedQuestion.customType || '',\n useFeatureButtonAudio:\n typeof normalizedQuestion.useFeatureButtonAudio === 'boolean'\n ? normalizedQuestion.useFeatureButtonAudio\n : undefined,\n locale: normalizedQuestion.locale || '',\n language: normalizedQuestion.language || normalizedQuestion.locale || undefined,\n disabled: safeEnv.mode !== 'gather',\n mode: safeEnv.mode,\n };\n\n if (safeEnv.mode === 'evaluate') {\n const correctness = getCorrectness(normalizedQuestion, safeSession);\n out.correctness = correctness;\n out.responseCorrect = correctness === 'correct';\n out.correctChoiceId = normalizedQuestion.correctChoiceId;\n }\n\n if (safeEnv.role === 'instructor' && (safeEnv.mode === 'view' || safeEnv.mode === 'evaluate')) {\n out.teacherInstructions = normalizedQuestion.teacherInstructionsEnabled\n ? normalizedQuestion.teacherInstructions\n : null;\n } else {\n out.teacherInstructions = null;\n }\n\n return out;\n};\n\nexport const createCorrectResponseSession = (question: McpbQuestion, env: McpbEnv) => {\n return new Promise((resolve) => {\n if (env.mode !== 'evaluate' && env.role === 'instructor') {\n // The player sets `id` and `element` on the entry from the item config.\n resolve({\n id: '1',\n choiceId: question?.correctChoiceId || '',\n });\n } else {\n resolve(null);\n }\n });\n};\n\n/** Markup with its tags removed, keeping media an author can answer from. */\nconst getContent = (html?: string) =>\n (html || '').replace(/(<(?!img|iframe|source)([^>]+)>)/gi, '').trim();\n\ntype ValidateConfig = {\n minAnswerChoices?: number;\n maxAnswerChoices?: number;\n prompt?: { required?: boolean };\n teacherInstructions?: { required?: boolean };\n};\n\n/**\n * Authoring errors in multiple-choice's shape: a message per field, and under\n * `choices` a message per choice keyed by its id.\n */\nexport const validate = (question: McpbQuestion = {}, config: ValidateConfig = {}) => {\n const errors: Record<string, string | Record<string, string>> = {};\n const { minAnswerChoices = 2, maxAnswerChoices } = config;\n\n if ((question.promptEnabled || config.prompt?.required) && !getContent(question.prompt)) {\n errors.prompt = 'This field is required.';\n }\n\n if (config.teacherInstructions?.required && !getContent(question.teacherInstructions)) {\n errors.teacherInstructions = 'This field is required.';\n }\n\n const interactionMode = question.interactionMode || 'populate_blank';\n const template = question.template || '';\n const n = countBlankTokens(template);\n if (interactionMode === 'audio_mc_only') {\n if (n > 0) {\n errors.template = `Template cannot contain ${BLANK_TOKEN} in audio-only mode`;\n }\n } else if (interactionMode === 'populate_blank') {\n if (n === 0) {\n errors.template = `Template must contain exactly one ${BLANK_TOKEN} placeholder`;\n } else if (n > 1) {\n errors.template = `Template must contain only one ${BLANK_TOKEN} placeholder`;\n }\n } else {\n errors.interactionMode = 'Unknown interaction mode';\n }\n\n const choices = Array.isArray(question.choices) ? question.choices : [];\n if (choices.length < minAnswerChoices) {\n errors.answerChoices = `There should be at least ${minAnswerChoices} choices defined.`;\n } else if (maxAnswerChoices != null && choices.length > maxAnswerChoices) {\n errors.answerChoices = `No more than ${maxAnswerChoices} choices should be defined.`;\n }\n\n const mode = question.choiceMode || 'text';\n const choicesErrors: Record<string, string> = {};\n const seenIds = new Set<string>();\n const seenLabels = new Set<string>();\n choices.forEach((c, i) => {\n const key = c?.id || String(i);\n if (!c?.id) {\n choicesErrors[key] = 'Choice needs an id.';\n return;\n }\n // The session stores the picked id, so two choices sharing one cannot be told apart.\n if (seenIds.has(c.id)) {\n choicesErrors[key] = 'Choice id should be unique.';\n return;\n }\n seenIds.add(c.id);\n if (mode === 'text') {\n const label = getContent(c.labelHtml);\n if (!label) {\n choicesErrors[key] = 'Content should not be empty.';\n } else if (seenLabels.has(label)) {\n choicesErrors[key] = 'Content should be unique.';\n }\n seenLabels.add(label);\n } else if (!c.imageUrl?.trim()) {\n choicesErrors[key] = 'An image is required.';\n } else if (!c.imageAlt?.trim()) {\n choicesErrors[key] = 'Image alt text is required.';\n }\n });\n if (Object.keys(choicesErrors).length) {\n errors.choices = choicesErrors;\n }\n\n const correct = question.correctChoiceId;\n if (!correct) {\n errors.correctResponse = 'No correct response defined.';\n } else if (!choices.some((c) => c?.id === correct)) {\n errors.correctResponse = 'The correct response must be one of the choices.';\n }\n\n if (question.hasAudio) {\n const hasAudioUrl = !!question.audioUrl?.trim();\n if (!hasAudioUrl) {\n errors.audioUrl = 'A playable audio URL is required when audio is enabled';\n }\n }\n\n const limits = question?.layoutLimits;\n if (limits && typeof limits === 'object') {\n const numericLimitKeys = [\n 'blankStandaloneWidthRem',\n 'blankWideWidthRem',\n 'blankUnderlineWidthPx',\n 'blankUnderlineWideWidthPx',\n 'horizontalChoiceWidthPx',\n 'horizontalChoiceWidthVw',\n 'horizontalChoiceTileMinHeightRem',\n 'horizontalChoiceContentMinHeightRem',\n 'choiceImageMaxHeightRem',\n 'choiceImageMaxWidthRem',\n 'listenButtonSizePx',\n 'stimulusMinColumnPx',\n 'textMinColumnPx',\n 'legendMaxChars',\n 'choiceGroupGapRem',\n 'choiceRowGapRem',\n 'toggleButtonGapRem',\n 'horizontalChoiceRadioTopMarginRem',\n 'audioBlankTemplateMarginTopRem',\n 'audioBlankTemplateMarginBottomRem',\n 'audioInstructionsMaxWidthPx',\n 'stimulusGridColumnGapRem',\n 'stimulusGridRowGapRem',\n 'stimulusSentenceMarginTopRem',\n 'stimulusChoicesMarginTopRem',\n 'tokenGridColumnGapRem',\n 'tokenGridRowGapRem',\n 'tokenTemplateMarginTopRem',\n 'tokenInlineTokenGapRem',\n 'tokenChoicesMarginTopRem',\n 'inlineGridColumnGapRem',\n 'inlineGridRowGapRem',\n 'inlineTemplateMarginTopRem',\n 'inlineChoicesMarginTopRem',\n ] as const;\n for (const key of numericLimitKeys) {\n if (limits[key] === undefined || limits[key] === null) continue;\n const value = Number(limits[key]);\n if (!Number.isFinite(value) || value <= 0) {\n errors.layoutLimits = `${key} must be a positive number when provided`;\n break;\n }\n }\n }\n\n return errors;\n};\n"],"mappings":"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"}
@@ -7984,7 +7984,7 @@ var root_5$1 = /* @__PURE__ */ from_html(`<span aria-hidden="true"> </span>`);
7984
7984
  var root_6$1 = /* @__PURE__ */ from_html(`<div style="gap:var(--mpb-choice-row-gap, 0.5rem);"><!> <!></div>`);
7985
7985
  var $$css$2 = {
7986
7986
  hash: "svelte-8xu1pt",
7987
- code: ".choice-row.svelte-8xu1pt {display:flex;align-items:flex-start;}.choice-row-horizontal.svelte-8xu1pt {flex-direction:column;align-items:center;width:min(var(--mpb-choice-width-px, 170px), var(--mpb-choice-width-vw, 30vw));min-height:var(--mpb-choice-tile-min-height, 11rem);gap:0;}.choice-tile.svelte-8xu1pt {display:flex;flex-direction:column;justify-content:space-between;align-items:center;width:100%;min-height:var(--mpb-choice-tile-min-height, 11rem);padding:0.8rem 0.65rem 0.5rem;border-radius:8px;background:transparent;transition:background-color 120ms ease-in-out;cursor:pointer;text-align:center;}.choice-tile-content.svelte-8xu1pt {width:100%;display:flex;align-items:center;justify-content:center;min-height:var(--mpb-choice-content-min-height, 7.5rem);}.choice-image.svelte-8xu1pt {display:block;object-fit:contain;}.choice-label-wrap.svelte-8xu1pt {flex:1 1 0%;cursor:pointer;}.choice-row-horizontal.svelte-8xu1pt:hover .choice-tile:where(.svelte-8xu1pt) {background:var(--mpb-choice-hover-bg, var(--pie-background-dark, #ecedf1));}.pie-choice-horizontal.svelte-8xu1pt:not(.is-selected):not(:hover) .pie-choice-tile:where(.svelte-8xu1pt) {background:transparent;}.choice-row-horizontal.is-selected.svelte-8xu1pt .choice-tile:where(.svelte-8xu1pt) {background:var(--mpb-choice-selected-bg, var(--pie-secondary-background, rgba(241, 241, 241, 1)));}.choice-row-horizontal.is-selected.svelte-8xu1pt:hover .choice-tile:where(.svelte-8xu1pt) {background:var(--mpb-choice-selected-bg, var(--pie-secondary-background, rgba(241, 241, 241, 1)));}.pie-choice.svelte-8xu1pt:not(.pie-choice-horizontal):hover .pie-choice-label-wrap:where(.svelte-8xu1pt) {background:var(--pie-background-dark, #ecedf1);}.pie-choice.svelte-8xu1pt:not(.pie-choice-horizontal):not(.is-selected):not(:hover) .pie-choice-label-wrap:where(.svelte-8xu1pt) {background:transparent;}.pie-choice:not(.pie-choice-horizontal).is-selected.svelte-8xu1pt .pie-choice-label-wrap:where(.svelte-8xu1pt) {background:var(--pie-secondary-background, rgba(241, 241, 241, 1));border-radius:6px;}.pie-choice:not(.pie-choice-horizontal).is-selected.svelte-8xu1pt:hover .pie-choice-label-wrap:where(.svelte-8xu1pt) {background:var(--pie-secondary-background, rgba(241, 241, 241, 1));}.pie-choice.choice-correct.svelte-8xu1pt {border-left:3px solid var(--pie-correct-tertiary, #0ea449);}.pie-choice.choice-incorrect.svelte-8xu1pt {border-left:3px solid var(--pie-incorrect-icon, #bf0d00);}.pie-choice-horizontal.choice-correct.svelte-8xu1pt .choice-tile:where(.svelte-8xu1pt) {background:var(--pie-correct-secondary, #e8f5e9);}.pie-choice-horizontal.choice-incorrect.svelte-8xu1pt .choice-tile:where(.svelte-8xu1pt) {background:var(--pie-incorrect-secondary, #ffebee);}.pie-choice-feedback-badge.svelte-8xu1pt {margin-left:auto;display:inline-flex;align-items:center;justify-content:center;width:1.1rem;height:1.1rem;border-radius:9999px;font-size:0.72rem;line-height:1;font-weight:700;color:var(--pie-white, #ffffff);}.pie-choice-feedback-correct.svelte-8xu1pt {background:var(--pie-correct-icon, #087d38);}.pie-choice-feedback-incorrect.svelte-8xu1pt {background:var(--pie-incorrect-icon, #bf0d00);}.choice-row-horizontal.svelte-8xu1pt p {margin:0;text-align:center;}.choice-html.svelte-8xu1pt p {margin:0;}.choice-html.svelte-8xu1pt {width:100%;display:inline-flex;align-items:center;justify-content:center;text-align:center;}\n\n /* Choices carrying raw <img> markup in labelHtml (rather than the dedicated\n imageUrl/imageAlt fields) render unconstrained at their authored size\n otherwise — 3rd-party/CQT-sourced content is typically authored well above\n the platform's 150x150 content-element spec. Applies regardless of\n customType, unlike the r1-CQT-specific cqt-css overlays. See CONTOOL-3159. */.choice-html.svelte-8xu1pt img {width:150px;height:150px;}\n\n /* Only the top margin is spaced; the browser's own radio side margins would\n otherwise offset the control from its label (the demo app's preflight zeroed them). */.choice-radio-bottom.svelte-8xu1pt {margin:var(--mpb-horizontal-choice-radio-top-margin, 0.5rem) 0 0;padding:var(--mpb-choice-radio-padding, 0px);}.choice-radio-inline.svelte-8xu1pt {margin:var(--mpb-horizontal-choice-radio-top-margin, 0.5rem) 0 0;}"
7987
+ code: ".choice-row.svelte-8xu1pt {display:flex;align-items:flex-start;}.choice-row-horizontal.svelte-8xu1pt {flex-direction:column;align-items:center;width:min(var(--mpb-choice-width-px, 170px), var(--mpb-choice-width-vw, 30vw));min-height:var(--mpb-choice-tile-min-height, 11rem);gap:0;}.choice-tile.svelte-8xu1pt {display:flex;flex-direction:column;justify-content:space-between;align-items:center;width:100%;min-height:var(--mpb-choice-tile-min-height, 11rem);padding:0.8rem 0.65rem 0.5rem;border-radius:8px;background:transparent;transition:background-color 120ms ease-in-out;cursor:pointer;text-align:center;}.choice-tile-content.svelte-8xu1pt {width:100%;display:flex;align-items:center;justify-content:center;min-height:var(--mpb-choice-content-min-height, 7.5rem);}.choice-image.svelte-8xu1pt {display:block;object-fit:contain;}.choice-label-wrap.svelte-8xu1pt {flex:1 1 0%;cursor:pointer;}\n\n /* Every background painted here carries its own text colour: a variant that\n pins a light surface (the CQT sheets' #fcfcd3) would otherwise sit under\n the scheme's text colour, which is near-white in a dark scheme. With neither\n the hook nor --pie-text defined the declaration is invalid at computed-value\n time and the colour inherits. */.choice-row-horizontal.svelte-8xu1pt:hover .choice-tile:where(.svelte-8xu1pt) {background:var(--mpb-choice-hover-bg, var(--pie-background-dark, #ecedf1));color:var(--mpb-choice-hover-color, var(--pie-text));}.pie-choice-horizontal.svelte-8xu1pt:not(.is-selected):not(:hover) .pie-choice-tile:where(.svelte-8xu1pt) {background:transparent;}.choice-row-horizontal.is-selected.svelte-8xu1pt .choice-tile:where(.svelte-8xu1pt) {background:var(--mpb-choice-selected-bg, var(--pie-secondary-background, rgba(241, 241, 241, 1)));color:var(--mpb-choice-selected-color, var(--pie-text));}.choice-row-horizontal.is-selected.svelte-8xu1pt:hover .choice-tile:where(.svelte-8xu1pt) {background:var(--mpb-choice-selected-bg, var(--pie-secondary-background, rgba(241, 241, 241, 1)));color:var(--mpb-choice-selected-color, var(--pie-text));}.pie-choice.svelte-8xu1pt:not(.pie-choice-horizontal):hover .pie-choice-label-wrap:where(.svelte-8xu1pt) {background:var(--pie-background-dark, #ecedf1);color:var(--pie-text);}.pie-choice.svelte-8xu1pt:not(.pie-choice-horizontal):not(.is-selected):not(:hover) .pie-choice-label-wrap:where(.svelte-8xu1pt) {background:transparent;}.pie-choice:not(.pie-choice-horizontal).is-selected.svelte-8xu1pt .pie-choice-label-wrap:where(.svelte-8xu1pt) {background:var(--pie-secondary-background, rgba(241, 241, 241, 1));color:var(--pie-text);border-radius:6px;}.pie-choice:not(.pie-choice-horizontal).is-selected.svelte-8xu1pt:hover .pie-choice-label-wrap:where(.svelte-8xu1pt) {background:var(--pie-secondary-background, rgba(241, 241, 241, 1));color:var(--pie-text);}.pie-choice.choice-correct.svelte-8xu1pt {border-left:3px solid var(--pie-correct-tertiary, #0ea449);}.pie-choice.choice-incorrect.svelte-8xu1pt {border-left:3px solid var(--pie-incorrect-icon, #bf0d00);}.pie-choice-horizontal.choice-correct.svelte-8xu1pt .choice-tile:where(.svelte-8xu1pt) {background:var(--pie-correct-secondary, #e8f5e9);color:var(--pie-text);}.pie-choice-horizontal.choice-incorrect.svelte-8xu1pt .choice-tile:where(.svelte-8xu1pt) {background:var(--pie-incorrect-secondary, #ffebee);color:var(--pie-text);}.pie-choice-feedback-badge.svelte-8xu1pt {margin-left:auto;display:inline-flex;align-items:center;justify-content:center;width:1.1rem;height:1.1rem;border-radius:9999px;font-size:0.72rem;line-height:1;font-weight:700;color:var(--pie-white, #ffffff);}.pie-choice-feedback-correct.svelte-8xu1pt {background:var(--pie-correct-icon, #087d38);}.pie-choice-feedback-incorrect.svelte-8xu1pt {background:var(--pie-incorrect-icon, #bf0d00);}.choice-row-horizontal.svelte-8xu1pt p {margin:0;text-align:center;}.choice-html.svelte-8xu1pt p {margin:0;}.choice-html.svelte-8xu1pt {width:100%;display:inline-flex;align-items:center;justify-content:center;text-align:center;}\n\n /* Choices carrying raw <img> markup in labelHtml (rather than the dedicated\n imageUrl/imageAlt fields) render unconstrained at their authored size\n otherwise — 3rd-party/CQT-sourced content is typically authored well above\n the platform's 150x150 content-element spec. Applies regardless of\n customType, unlike the r1-CQT-specific cqt-css overlays. See CONTOOL-3159. */.choice-html.svelte-8xu1pt img {width:150px;height:150px;}\n\n /* Only the top margin is spaced; the browser's own radio side margins would\n otherwise offset the control from its label (the demo app's preflight zeroed them). */.choice-radio-bottom.svelte-8xu1pt {margin:var(--mpb-horizontal-choice-radio-top-margin, 0.5rem) 0 0;padding:var(--mpb-choice-radio-padding, 0px);}.choice-radio-inline.svelte-8xu1pt {margin:var(--mpb-horizontal-choice-radio-top-margin, 0.5rem) 0 0;}"
7988
7988
  };
7989
7989
  function ChoiceRow($$anchor, $$props) {
7990
7990
  push($$props, true);
@@ -8303,7 +8303,6 @@ var DEFAULT_LAYOUT_LIMITS = {
8303
8303
  audioBlankTemplateMarginTopRem: .8,
8304
8304
  audioBlankTemplateMarginBottomRem: 1.8,
8305
8305
  audioInstructionsMaxWidthPx: 875,
8306
- narrowHorizontalChoiceMaxWidthPx: 230,
8307
8306
  stimulusGridColumnGapRem: 2,
8308
8307
  stimulusGridRowGapRem: .7,
8309
8308
  stimulusSentenceMarginTopRem: .2,
@@ -8378,10 +8377,6 @@ var CSS_VAR_SPEC = {
8378
8377
  varName: "--mpb-horizontal-choice-radio-top-margin",
8379
8378
  unit: "rem"
8380
8379
  },
8381
- narrowHorizontalChoiceMaxWidthPx: {
8382
- varName: "--mpb-narrow-choice-max-width",
8383
- unit: "px"
8384
- },
8385
8380
  toggleButtonGapRem: {
8386
8381
  varName: "--mpb-toggle-button-gap",
8387
8382
  unit: "rem"
@@ -8577,49 +8572,49 @@ var VARIANT_BY_CUSTOM_TYPE = {
8577
8572
  variantId: "sel-r1-plusggg",
8578
8573
  variantClass: "variant-sel-r1-plusggg",
8579
8574
  sourceUrl: "https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_r1-_plusggg/0.0.1/question.css",
8580
- cssText: "/* Source baseline: https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_r1-_plusggg/0.0.1/question.css */\n/* Shared r1 base styles (font, template-line flex, choice-label) are in sel-r1-base.css */\n.mc-populated-blank-root.variant-sel-r1-plusggg {\n max-width: 875px;\n margin-inline: auto;\n --mpb-listen-button-size: 128px;\n --mpb-choice-width-px: 155px;\n --mpb-choice-tile-min-height: 11.25rem;\n --mpb-choice-content-min-height: 9.375rem;\n --mpb-choice-group-gap: 1rem;\n --mpb-choice-hover-bg: #f2f2f2;\n --mpb-choice-selected-bg: #fcfcd3;\n}\n\n.mc-populated-blank-root.variant-sel-r1-plusggg {\n --mpb-choice-radio-padding: 20px;\n}\n"
8575
+ cssText: "/* Source baseline: https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_r1-_plusggg/0.0.1/question.css */\n/* Shared r1 base styles (font, template-line flex, choice-label) are in sel-r1-base.css */\n.mc-populated-blank-root.variant-sel-r1-plusggg {\n max-width: 875px;\n margin-inline: auto;\n --mpb-listen-button-size: 128px;\n --mpb-choice-width-px: 155px;\n --mpb-choice-tile-min-height: 11.25rem;\n --mpb-choice-content-min-height: 9.375rem;\n --mpb-choice-group-gap: 1rem;\n /* A fixed light surface takes fixed dark text: the scheme text colour is near-white in dark schemes. */\n --mpb-choice-hover-bg: #f2f2f2;\n --mpb-choice-hover-color: #000;\n --mpb-choice-selected-bg: #fcfcd3;\n --mpb-choice-selected-color: #000;\n}\n\n.mc-populated-blank-root.variant-sel-r1-plusggg {\n --mpb-choice-radio-padding: 20px;\n}\n"
8581
8576
  },
8582
8577
  "sr-vic": {
8583
8578
  variantId: "sr-vic",
8584
8579
  variantClass: "variant-sr-vic",
8585
8580
  sourceUrl: "https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sr-vic/0.0.1/question.css",
8586
- cssText: "/* Source baseline: https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sr-vic/0.0.1/question.css */\n/* Reference layout: web-ItemBankViewer/learnosity/templates/Renaissance/vic.scss */\n.mc-populated-blank-root.variant-sr-vic {\n font-family: Starial, Arial, sans-serif;\n --mpb-inline-grid-column-gap: 1rem;\n --mpb-inline-grid-row-gap: 0.5rem;\n --mpb-inline-template-margin-top: 0.2rem;\n --mpb-inline-choices-margin-top: 0.2rem;\n}\n\n.mc-populated-blank-root.variant-sr-vic .pie-template-line,\n.mc-populated-blank-root.variant-sr-vic .pie-blank-value,\n.mc-populated-blank-root.variant-sr-vic .pie-choice-label {\n line-height: 123%;\n}\n\n/* vic.scss: .rli-vic-answer uses display:flex so sentence and cloze stay on\n one line. The template HTML wraps text in <p>, which is block by default and\n breaks to a new line. Making <p> inline keeps the sentence and blank inline. */\n.mc-populated-blank-root.variant-sr-vic .pie-template-line p {\n display: inline;\n margin: 0;\n}\n\n/* LSY .rli-vic-answer has no text-align rule and inherits start; PIE's\n inline_sentence layout sets text-align:center on .template-line via a\n Svelte-scoped selector with specificity (0,4,0), so we need to match or\n exceed that. Pin sr-vic back to start so wrapped lines stay left-aligned.\n Same root cause as sel-vic. See CONTOOL-2573. */\n.mc-populated-blank-root.variant-sr-vic.layout-inline_sentence .pie-template-line.template-line {\n text-align: start;\n}\n\n/* vic.scss: .rli-vic-cloze > .rli-vic-content-element { color: #cc3333 } */\n.mc-populated-blank-root.variant-sr-vic .pie-blank-value {\n color: #cc3333;\n}\n\n/* vic.scss: .rli-vic-selected { background-color: #fcfcd3 } applied to the\n whole distractor row (radio + label), not just the label wrapper. */\n.mc-populated-blank-root.variant-sr-vic .pie-choice.is-selected {\n background-color: #fcfcd3;\n border-radius: 6px;\n}\n\n.mc-populated-blank-root.variant-sr-vic .pie-choice.is-selected .pie-choice-label-wrap {\n background-color: transparent;\n}\n\n/* vic.scss: distractor label text sits to the right of the radio — no centering. */\n.mc-populated-blank-root.variant-sr-vic .pie-choice-label {\n text-align: left;\n justify-content: flex-start;\n}\n\n/* vic.scss: .rli-vic-distractor:hover { background-color: #F2F2F2 } applied to\n the full row element, not only the inner label wrapper. */\n.mc-populated-blank-root.variant-sr-vic .pie-choice:not(.pie-choice-horizontal):hover {\n background-color: #f2f2f2;\n border-radius: 6px;\n}\n\n.mc-populated-blank-root.variant-sr-vic\n .pie-choice:not(.pie-choice-horizontal):hover\n .pie-choice-label-wrap {\n background-color: transparent;\n}\n\n/* Selected state must win over hover — yellow persists when hovering a chosen row. */\n.mc-populated-blank-root.variant-sr-vic .pie-choice:not(.pie-choice-horizontal).is-selected:hover {\n background-color: #fcfcd3;\n}\n"
8581
+ cssText: "/* Source baseline: https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sr-vic/0.0.1/question.css */\n/* Reference layout: web-ItemBankViewer/learnosity/templates/Renaissance/vic.scss */\n.mc-populated-blank-root.variant-sr-vic {\n font-family: Starial, Arial, sans-serif;\n}\n\n.mc-populated-blank-root.variant-sr-vic .pie-template-line,\n.mc-populated-blank-root.variant-sr-vic .pie-blank-value,\n.mc-populated-blank-root.variant-sr-vic .pie-choice-label {\n line-height: 123%;\n}\n\n/* vic.scss: .rli-vic-answer uses display:flex so sentence and cloze stay on\n one line. The template HTML wraps text in <p>, which is block by default and\n breaks to a new line. Making <p> inline keeps the sentence and blank inline. */\n.mc-populated-blank-root.variant-sr-vic .pie-template-line p {\n display: inline;\n margin: 0;\n}\n\n/* LSY .rli-vic-answer has no text-align rule and inherits start; PIE's\n inline_sentence layout sets text-align:center on .template-line via a\n Svelte-scoped selector with specificity (0,4,0), so we need to match or\n exceed that. Pin sr-vic back to start so wrapped lines stay left-aligned.\n Same root cause as sel-vic. See CONTOOL-2573. */\n.mc-populated-blank-root.variant-sr-vic.layout-inline_sentence .pie-template-line.template-line {\n text-align: start;\n}\n\n/* vic.scss: .rli-vic-cloze > .rli-vic-content-element { color: #cc3333 }\n The red holds AA only on LSY's white page (5.1:1), so the answer carries that\n surface: on a dark host it is 3.08:1. light-dark() would miss pie-players'\n colour schemes, which declare no color-scheme. */\n.mc-populated-blank-root.variant-sr-vic .pie-blank-value {\n color: #cc3333;\n background-color: #fff;\n}\n\n/* vic.scss: .rli-vic-selected { background-color: #fcfcd3 } applied to the\n whole distractor row (radio + label), not just the label wrapper. */\n.mc-populated-blank-root.variant-sr-vic .pie-choice.is-selected {\n background-color: #fcfcd3;\n color: #000;\n border-radius: 6px;\n}\n\n.mc-populated-blank-root.variant-sr-vic .pie-choice.is-selected .pie-choice-label-wrap {\n background-color: transparent;\n color: inherit;\n}\n\n/* vic.scss: distractor label text sits to the right of the radio — no centering. */\n.mc-populated-blank-root.variant-sr-vic .pie-choice-label {\n text-align: left;\n justify-content: flex-start;\n}\n\n/* vic.scss: .rli-vic-distractor:hover { background-color: #F2F2F2 } applied to\n the full row element, not only the inner label wrapper. */\n.mc-populated-blank-root.variant-sr-vic .pie-choice:not(.pie-choice-horizontal):hover {\n background-color: #f2f2f2;\n color: #000;\n border-radius: 6px;\n}\n\n.mc-populated-blank-root.variant-sr-vic\n .pie-choice:not(.pie-choice-horizontal):hover\n .pie-choice-label-wrap {\n background-color: transparent;\n color: inherit;\n}\n\n/* Selected state must win over hover — yellow persists when hovering a chosen row. */\n.mc-populated-blank-root.variant-sr-vic .pie-choice:not(.pie-choice-horizontal).is-selected:hover {\n background-color: #fcfcd3;\n color: #000;\n}\n"
8587
8582
  },
8588
8583
  sel_vic: {
8589
8584
  variantId: "sel-vic",
8590
8585
  variantClass: "variant-sel-vic",
8591
8586
  sourceUrl: "https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_vic/0.0.1/question.css",
8592
- cssText: "/* Source baseline: https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_vic/0.0.1/question.css */\n/* Reference layout: web-ItemBankViewer/learnosity/templates/Renaissance/sel_vic/scss/main.scss + vic.scss */\n.mc-populated-blank-root.variant-sel-vic {\n font-family: Starial, Arial, sans-serif;\n /* LSY chain: .lrn_widget (14px host) → .sel-vic { 1.5em } = 21px.\n PIE collapses both LSY layers into this single variant-root selector, so\n the resolved value is set directly. See CONTOOL-2567. */\n font-size: 21px;\n max-width: 800px;\n margin-inline: auto;\n --mpb-inline-grid-column-gap: 1rem;\n --mpb-inline-grid-row-gap: 0.5rem;\n --mpb-inline-template-margin-top: 0.2rem;\n --mpb-inline-choices-margin-top: 0.2rem;\n}\n\n.mc-populated-blank-root.variant-sel-vic .pie-audio-transcript {\n font-family: Roboto, Arial, sans-serif;\n font-size: 16px;\n font-style: normal;\n font-weight: 400;\n}\n\n.mc-populated-blank-root.variant-sel-vic .pie-template-line,\n.mc-populated-blank-root.variant-sel-vic .pie-blank-value,\n.mc-populated-blank-root.variant-sel-vic .pie-choice-label {\n line-height: 123%;\n}\n\n/* sel_vic/main.scss: template left ~75%, audio right ~25% on the same row.\n Svelte scoping prevents the component-internal grid-area rule from reaching\n .audio-container inside AudioPlayer, so we override it here via the global\n .pie-audio-container class which is not subject to scope hashing. */\n.mc-populated-blank-root.variant-sel-vic .pie-audio-container {\n grid-area: audio;\n margin: 0;\n justify-self: end;\n align-self: start;\n min-height: 128px;\n}\n\n/* vic.scss: sentence and cloze sit inline — the <p> wrapper must not break to\n a new line before the blank slot. */\n.mc-populated-blank-root.variant-sel-vic .pie-template-line p {\n display: inline;\n margin: 0;\n}\n\n/* LSY .rli-vic-answer has no text-align rule and inherits start; PIE's\n inline_sentence layout sets text-align:center on .template-line via a\n Svelte-scoped selector with specificity (0,4,0), so we need to match or\n exceed that. Pin sel-vic back to start so wrapped lines stay left-aligned.\n See CONTOOL-2573. */\n.mc-populated-blank-root.variant-sel-vic.layout-inline_sentence .pie-template-line.template-line {\n text-align: start;\n}\n\n/* vic.scss: .rli-vic-cloze { margin-left: 6px; margin-right: 6px } gives the\n cloze breathing room from adjacent text or punctuation. The shared blank slot\n has no inline margin, so set it here so the cloze→trailing-punctuation gap\n matches LSY. See CONTOOL-2572. */\n.mc-populated-blank-root.variant-sel-vic .pie-template-line .pie-blank-slot {\n margin-left: 6px;\n margin-right: 6px;\n}\n\n/* vic.scss: .rli-vic-cloze > .rli-vic-content-element { color: #cc3333 } */\n.mc-populated-blank-root.variant-sel-vic .pie-blank-value {\n color: #cc3333;\n}\n\n/* sel_vic/main.scss: .sel-vic { max-width: 800px } — the fieldset/choices must\n also be constrained so rows don't span beyond 800px. */\n.mc-populated-blank-root.variant-sel-vic .pie-choices-fieldset {\n max-width: 800px;\n width: 100%;\n}\n\n/* vic.scss: distractor label text sits to the right of the radio — no centering. */\n.mc-populated-blank-root.variant-sel-vic .pie-choice-label {\n text-align: left;\n justify-content: flex-start;\n}\n\n/* vic.scss: .rli-vic-selected { background-color: #fcfcd3 } on the full row. */\n.mc-populated-blank-root.variant-sel-vic .pie-choice.is-selected {\n background-color: #fcfcd3;\n border-radius: 6px;\n}\n\n.mc-populated-blank-root.variant-sel-vic .pie-choice.is-selected .pie-choice-label-wrap {\n background-color: transparent;\n}\n\n/* Selected state must win over hover. */\n.mc-populated-blank-root.variant-sel-vic .pie-choice:not(.pie-choice-horizontal).is-selected:hover {\n background-color: #fcfcd3;\n}\n\n/* vic.scss: .rli-vic-distractor:hover { background-color: #F2F2F2 } on the full row. */\n.mc-populated-blank-root.variant-sel-vic .pie-choice:not(.pie-choice-horizontal):hover {\n background-color: #f2f2f2;\n border-radius: 6px;\n}\n\n.mc-populated-blank-root.variant-sel-vic\n .pie-choice:not(.pie-choice-horizontal):hover\n .pie-choice-label-wrap {\n background-color: transparent;\n}\n\n@media (min-width: 620px) {\n .mc-populated-blank-root.variant-sel-vic .pie-audio-transcript {\n min-width: 620px;\n max-width: 800px;\n transform: translateX(-55px);\n }\n}\n"
8587
+ cssText: "/* Source baseline: https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_vic/0.0.1/question.css */\n/* Reference layout: web-ItemBankViewer/learnosity/templates/Renaissance/sel_vic/scss/main.scss + vic.scss */\n.mc-populated-blank-root.variant-sel-vic {\n font-family: Starial, Arial, sans-serif;\n /* LSY chain: .lrn_widget (14px host) → .sel-vic { 1.5em } = 21px.\n PIE collapses both LSY layers into this single variant-root selector, so\n the resolved value is set directly. See CONTOOL-2567. */\n font-size: 21px;\n max-width: 800px;\n margin-inline: auto;\n}\n\n.mc-populated-blank-root.variant-sel-vic .pie-audio-transcript {\n font-family: Roboto, Arial, sans-serif;\n font-size: 16px;\n font-style: normal;\n font-weight: 400;\n}\n\n.mc-populated-blank-root.variant-sel-vic .pie-template-line,\n.mc-populated-blank-root.variant-sel-vic .pie-blank-value,\n.mc-populated-blank-root.variant-sel-vic .pie-choice-label {\n line-height: 123%;\n}\n\n/* sel_vic/main.scss: template left ~75%, audio right ~25% on the same row.\n Svelte scoping prevents the component-internal grid-area rule from reaching\n .audio-container inside AudioPlayer, so we override it here via the global\n .pie-audio-container class which is not subject to scope hashing. */\n.mc-populated-blank-root.variant-sel-vic .pie-audio-container {\n grid-area: audio;\n margin: 0;\n justify-self: end;\n align-self: start;\n min-height: 128px;\n}\n\n/* vic.scss: sentence and cloze sit inline — the <p> wrapper must not break to\n a new line before the blank slot. */\n.mc-populated-blank-root.variant-sel-vic .pie-template-line p {\n display: inline;\n margin: 0;\n}\n\n/* LSY .rli-vic-answer has no text-align rule and inherits start; PIE's\n inline_sentence layout sets text-align:center on .template-line via a\n Svelte-scoped selector with specificity (0,4,0), so we need to match or\n exceed that. Pin sel-vic back to start so wrapped lines stay left-aligned.\n See CONTOOL-2573. */\n.mc-populated-blank-root.variant-sel-vic.layout-inline_sentence .pie-template-line.template-line {\n text-align: start;\n}\n\n/* vic.scss: .rli-vic-cloze { margin-left: 6px; margin-right: 6px } gives the\n cloze breathing room from adjacent text or punctuation. The shared blank slot\n has no inline margin, so set it here so the cloze→trailing-punctuation gap\n matches LSY. See CONTOOL-2572. */\n.mc-populated-blank-root.variant-sel-vic .pie-template-line .pie-blank-slot {\n margin-left: 6px;\n margin-right: 6px;\n}\n\n/* vic.scss: .rli-vic-cloze > .rli-vic-content-element { color: #cc3333 }\n The red holds AA only on LSY's white page (5.1:1), so the answer carries that\n surface: on a dark host it is 3.08:1. light-dark() would miss pie-players'\n colour schemes, which declare no color-scheme. */\n.mc-populated-blank-root.variant-sel-vic .pie-blank-value {\n color: #cc3333;\n background-color: #fff;\n}\n\n/* sel_vic/main.scss: .sel-vic { max-width: 800px } — the fieldset/choices must\n also be constrained so rows don't span beyond 800px. */\n.mc-populated-blank-root.variant-sel-vic .pie-choices-fieldset {\n max-width: 800px;\n width: 100%;\n}\n\n/* vic.scss: distractor label text sits to the right of the radio — no centering. */\n.mc-populated-blank-root.variant-sel-vic .pie-choice-label {\n text-align: left;\n justify-content: flex-start;\n}\n\n/* vic.scss: .rli-vic-selected { background-color: #fcfcd3 } on the full row. */\n.mc-populated-blank-root.variant-sel-vic .pie-choice.is-selected {\n background-color: #fcfcd3;\n color: #000;\n border-radius: 6px;\n}\n\n.mc-populated-blank-root.variant-sel-vic .pie-choice.is-selected .pie-choice-label-wrap {\n background-color: transparent;\n color: inherit;\n}\n\n/* Selected state must win over hover. */\n.mc-populated-blank-root.variant-sel-vic .pie-choice:not(.pie-choice-horizontal).is-selected:hover {\n background-color: #fcfcd3;\n color: #000;\n}\n\n/* vic.scss: .rli-vic-distractor:hover { background-color: #F2F2F2 } on the full row. */\n.mc-populated-blank-root.variant-sel-vic .pie-choice:not(.pie-choice-horizontal):hover {\n background-color: #f2f2f2;\n color: #000;\n border-radius: 6px;\n}\n\n.mc-populated-blank-root.variant-sel-vic\n .pie-choice:not(.pie-choice-horizontal):hover\n .pie-choice-label-wrap {\n background-color: transparent;\n color: inherit;\n}\n\n@media (min-width: 620px) {\n .mc-populated-blank-root.variant-sel-vic .pie-audio-transcript {\n min-width: 620px;\n max-width: 800px;\n transform: translateX(-55px);\n }\n}\n"
8593
8588
  },
8594
8589
  "sel_r1-_gplusggg": {
8595
8590
  variantId: "sel-r1-gplusggg",
8596
8591
  variantClass: "variant-sel-r1-gplusggg",
8597
8592
  sourceUrl: "https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_r1-_gplusggg/0.0.1/question.css",
8598
- cssText: "/* Source baseline: https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_r1-_gplusggg/0.0.1/question.css */\n/* Shared r1 base styles (font, template-line flex, choice-label) are in sel-r1-base.css */\n.mc-populated-blank-root.variant-sel-r1-gplusggg {\n max-width: 875px;\n margin-inline: auto;\n --mpb-listen-button-size: 128px;\n --mpb-choice-width-px: 155px;\n --mpb-choice-tile-min-height: 11.25rem;\n --mpb-choice-content-min-height: 9.375rem;\n --mpb-choice-group-gap: 1rem;\n --mpb-choice-hover-bg: #f2f2f2;\n --mpb-choice-selected-bg: #fcfcd3;\n}\n\n/* r1.scss: .rli-r1-content-element boxes all have fixed line-height:89px, so every\n token in the stem row shares a stable baseline regardless of content. In PIE,\n the blank slot's baseline shifts when the empty placeholder (short &nbsp;) is\n replaced by the filled value span (full text height), dragging adjacent tokens\n down by ~9px. Switching to align-items:center is safe here because gplusggg has\n only one row of content (blank + \"four\"), so vertical centering and baseline\n alignment produce the same visual result. The :is() wrapper raises specificity\n above the base [class*=] rule without needing !important. */\n:is(.mc-populated-blank-root.variant-sel-r1-gplusggg) .pie-template-line {\n align-items: center;\n}\n"
8593
+ cssText: "/* Source baseline: https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_r1-_gplusggg/0.0.1/question.css */\n/* Shared r1 base styles (font, template-line flex, choice-label) are in sel-r1-base.css */\n.mc-populated-blank-root.variant-sel-r1-gplusggg {\n max-width: 875px;\n margin-inline: auto;\n --mpb-listen-button-size: 128px;\n --mpb-choice-width-px: 155px;\n --mpb-choice-tile-min-height: 11.25rem;\n --mpb-choice-content-min-height: 9.375rem;\n --mpb-choice-group-gap: 1rem;\n /* A fixed light surface takes fixed dark text: the scheme text colour is near-white in dark schemes. */\n --mpb-choice-hover-bg: #f2f2f2;\n --mpb-choice-hover-color: #000;\n --mpb-choice-selected-bg: #fcfcd3;\n --mpb-choice-selected-color: #000;\n}\n\n/* r1.scss: .rli-r1-content-element boxes all have fixed line-height:89px, so every\n token in the stem row shares a stable baseline regardless of content. In PIE,\n the blank slot's baseline shifts when the empty placeholder (short &nbsp;) is\n replaced by the filled value span (full text height), dragging adjacent tokens\n down by ~9px. Switching to align-items:center is safe here because gplusggg has\n only one row of content (blank + \"four\"), so vertical centering and baseline\n alignment produce the same visual result. The :is() wrapper raises specificity\n above the base [class*=] rule without needing !important. */\n:is(.mc-populated-blank-root.variant-sel-r1-gplusggg) .pie-template-line {\n align-items: center;\n}\n"
8599
8594
  },
8600
8595
  "sel_r1-g_plusggg": {
8601
8596
  variantId: "sel-r1-g-stem",
8602
8597
  variantClass: "variant-sel-r1-g-stem",
8603
8598
  sourceUrl: "https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_r1-g_plusggg/0.0.1/question.css",
8604
- cssText: "/* Source baseline: https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_r1-g_plusggg/0.0.1/question.css */\n/* Shared r1 base styles (font, template-line flex, choice-label) are in sel-r1-base.css */\n.mc-populated-blank-root.variant-sel-r1-g-stem {\n max-width: 875px;\n margin-inline: auto;\n --mpb-listen-button-size: 128px;\n --mpb-choice-width-px: 155px;\n --mpb-choice-tile-min-height: 11.25rem;\n --mpb-choice-content-min-height: 9.375rem;\n --mpb-choice-hover-bg: #f2f2f2;\n --mpb-choice-selected-bg: #fcfcd3;\n /* r1.scss: .rli-r1-cloze { border-bottom: 6px solid } */\n --mpb-blank-underline-wide-width: 6px;\n}\n\n/* PIE-793: sel-r1-base.css's 156px blank box fits a 150x150 image answer, which image\n choices (variant-sel-r1-g-stem-graphic) keep. Text answers drop it on every classless\n span, so the empty and filled blank match height (CONTOOL-3159). */\n.mc-populated-blank-root.variant-sel-r1-g-stem.layout-token_sequence.choice-mode-text\n .pie-blank-slot\n span:not([class]) {\n min-height: 0;\n}\n\n/* The slot shrinks to its content and anchors to the top of the row, so the answer stays\n level with the stem token and the underline rises to meet it. Any align-self keeps the\n slot out of the row's shared baseline (see sel-r1-base.css), so the stem token does not\n move on selection. */\n.mc-populated-blank-root.variant-sel-r1-g-stem.layout-token_sequence.choice-mode-text\n.pie-blank-slot {\n min-height: 0;\n align-self: flex-start;\n}\n"
8599
+ cssText: "/* Source baseline: https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_r1-g_plusggg/0.0.1/question.css */\n/* Shared r1 base styles (font, template-line flex, choice-label) are in sel-r1-base.css */\n.mc-populated-blank-root.variant-sel-r1-g-stem {\n max-width: 875px;\n margin-inline: auto;\n --mpb-listen-button-size: 128px;\n --mpb-choice-width-px: 155px;\n --mpb-choice-tile-min-height: 11.25rem;\n --mpb-choice-content-min-height: 9.375rem;\n /* A fixed light surface takes fixed dark text: the scheme text colour is near-white in dark schemes. */\n --mpb-choice-hover-bg: #f2f2f2;\n --mpb-choice-hover-color: #000;\n --mpb-choice-selected-bg: #fcfcd3;\n --mpb-choice-selected-color: #000;\n /* r1.scss: .rli-r1-cloze { border-bottom: 6px solid } */\n --mpb-blank-underline-wide-width: 6px;\n}\n\n/* PIE-793: sel-r1-base.css's 156px blank box fits a 150x150 image answer, which image\n choices (variant-sel-r1-g-stem-graphic) keep. Text answers drop it on every classless\n span, so the empty and filled blank match height (CONTOOL-3159). */\n.mc-populated-blank-root.variant-sel-r1-g-stem.layout-token_sequence.choice-mode-text\n .pie-blank-slot\n span:not([class]) {\n min-height: 0;\n}\n\n/* The slot shrinks to its content and anchors to the top of the row, so the answer stays\n level with the stem token and the underline rises to meet it. Any align-self keeps the\n slot out of the row's shared baseline (see sel-r1-base.css), so the stem token does not\n move on selection. */\n.mc-populated-blank-root.variant-sel-r1-g-stem.layout-token_sequence.choice-mode-text\n.pie-blank-slot {\n min-height: 0;\n align-self: flex-start;\n}\n"
8605
8600
  },
8606
8601
  "sel_r1-gg_plusggg": {
8607
8602
  variantId: "sel-r1-gg-plus",
8608
8603
  variantClass: "variant-sel-r1-gg-plus",
8609
8604
  sourceUrl: "https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_r1-gg_plusggg/0.0.1/question.css",
8610
- cssText: "/* Source baseline: https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_r1-gg_plusggg/0.0.1/question.css */\n/* Shared r1 base styles (font, template-line flex, choice-label) are in sel-r1-base.css */\n.mc-populated-blank-root.variant-sel-r1-gg-plus {\n max-width: 875px;\n margin-inline: auto;\n --mpb-listen-button-size: 128px;\n --mpb-choice-width-px: 155px;\n --mpb-choice-tile-min-height: 11.25rem;\n --mpb-choice-content-min-height: 9.375rem;\n --mpb-choice-hover-bg: #f2f2f2;\n --mpb-choice-selected-bg: #fcfcd3;\n}\n"
8605
+ cssText: "/* Source baseline: https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_r1-gg_plusggg/0.0.1/question.css */\n/* Shared r1 base styles (font, template-line flex, choice-label) are in sel-r1-base.css */\n.mc-populated-blank-root.variant-sel-r1-gg-plus {\n max-width: 875px;\n margin-inline: auto;\n --mpb-listen-button-size: 128px;\n --mpb-choice-width-px: 155px;\n --mpb-choice-tile-min-height: 11.25rem;\n --mpb-choice-content-min-height: 9.375rem;\n /* A fixed light surface takes fixed dark text: the scheme text colour is near-white in dark schemes. */\n --mpb-choice-hover-bg: #f2f2f2;\n --mpb-choice-hover-color: #000;\n --mpb-choice-selected-bg: #fcfcd3;\n --mpb-choice-selected-color: #000;\n}\n"
8611
8606
  },
8612
8607
  "sel_r1-_ggplusggg": {
8613
8608
  variantId: "sel-r1-ggplus",
8614
8609
  variantClass: "variant-sel-r1-ggplus",
8615
8610
  sourceUrl: "https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_r1-_ggplusggg/0.0.1/question.css",
8616
- cssText: "/* Source baseline: https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_r1-_ggplusggg/0.0.1/question.css */\n/* Shared r1 base styles (font, template-line flex, choice-label) are in sel-r1-base.css */\n.mc-populated-blank-root.variant-sel-r1-ggplus {\n max-width: 875px;\n margin-inline: auto;\n --mpb-listen-button-size: 128px;\n --mpb-choice-width-px: 155px;\n --mpb-choice-tile-min-height: 11.25rem;\n --mpb-choice-content-min-height: 9.375rem;\n --mpb-choice-hover-bg: #f2f2f2;\n --mpb-choice-selected-bg: #fcfcd3;\n}\n"
8611
+ cssText: "/* Source baseline: https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_r1-_ggplusggg/0.0.1/question.css */\n/* Shared r1 base styles (font, template-line flex, choice-label) are in sel-r1-base.css */\n.mc-populated-blank-root.variant-sel-r1-ggplus {\n max-width: 875px;\n margin-inline: auto;\n --mpb-listen-button-size: 128px;\n --mpb-choice-width-px: 155px;\n --mpb-choice-tile-min-height: 11.25rem;\n --mpb-choice-content-min-height: 9.375rem;\n /* A fixed light surface takes fixed dark text: the scheme text colour is near-white in dark schemes. */\n --mpb-choice-hover-bg: #f2f2f2;\n --mpb-choice-hover-color: #000;\n --mpb-choice-selected-bg: #fcfcd3;\n --mpb-choice-selected-color: #000;\n}\n"
8617
8612
  },
8618
8613
  "sel_r1-s3_plusggg": {
8619
8614
  variantId: "sel-r1-s3",
8620
8615
  variantClass: "variant-sel-r1-s3",
8621
8616
  sourceUrl: "https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_r1-s3_plusggg/0.0.1/question.css",
8622
- cssText: "/* Source baseline: https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_r1-s3_plusggg/0.0.1/question.css */\n/* Shared r1 base styles (font, template-line flex, choice-label) are in sel-r1-base.css */\n.mc-populated-blank-root.variant-sel-r1-s3 {\n max-width: 860px;\n margin-inline: auto;\n --mpb-listen-button-size: 128px;\n --mpb-choice-width-px: 155px;\n --mpb-choice-tile-min-height: 11.25rem;\n --mpb-choice-content-min-height: 9.375rem;\n --mpb-choice-group-gap: 0.35rem;\n --mpb-choice-row-gap: 0.35rem;\n --mpb-horizontal-choice-radio-top-margin: 0.25rem;\n --mpb-stimulus-min-column: 240px;\n --mpb-stimulus-grid-column-gap: 1.1rem;\n --mpb-stimulus-choices-margin-top: 0.85rem;\n --mpb-audio-blank-template-margin-bottom: 0.9rem;\n --mpb-blank-wide-width: 6.8rem;\n}\n\n/* sel_r1-s3_plusggg/question.css does not set font-weight on distractors or\n cloze content-element, so they inherit 400 from the host root. The earlier\n font-weight:500 override caused PIE to render visibly heavier than LSY for\n the same author content. See CONTOOL-2571. */\n.mc-populated-blank-root.variant-sel-r1-s3 .pie-choice-label,\n.mc-populated-blank-root.variant-sel-r1-s3 .pie-choice-label :is(span, p, div) {\n font-size: 1.9em;\n line-height: 89px;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3 .pie-blank-value,\n.mc-populated-blank-root.variant-sel-r1-s3 .pie-blank-value :is(span, p, div) {\n line-height: 1.1;\n white-space: nowrap;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3 .pie-choice-horizontal .pie-choice-tile {\n border-radius: 6px;\n background: #f1f1f1;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3.layout-stimulus_image_blank {\n grid-template-columns: minmax(220px, auto) auto auto;\n grid-template-areas:\n \"transcript transcript transcript\"\n \"sentence template audio\"\n \"choices choices choices\";\n justify-content: center;\n /* question.css: .rli-s3-cloze-container { justify-content:end } bottom-anchors\n the cloze against the shared sentence/audio row instead of top-aligning it —\n matches every item sitting on the same visual baseline. See CONTOOL-3159. */\n align-items: end;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3.layout-stimulus_image_blank .pie-template-line {\n grid-area: template;\n justify-self: center;\n}\n\n/* ClozeMarker.svelte's shared .cloze-marker rule vertically *centers* its content\n (align-items:center). question.css's .rli-s3-cloze-container uses\n justify-content:end instead, bottom-anchoring the answer text against the\n underline. Centering leaves it floating well above both the underline and the\n sentence image's own (bottom-anchored) baseline. The blank's own box height\n (160px, from McPopulatedBlank.svelte's shared audio_blank_only/stimulus_image_blank\n rule) is left as-is — it already matches the Learnosity reference (~161px)\n closely; only the internal alignment was wrong. See CONTOOL-3159. */\n.mc-populated-blank-root.variant-sel-r1-s3.layout-stimulus_image_blank .pie-blank-slot {\n align-items: flex-end;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3.layout-stimulus_image_blank .pie-sentence-line {\n justify-self: end;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3.layout-stimulus_image_blank .pie-audio-container {\n justify-self: start;\n align-self: start;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3.layout-stimulus_image_blank .pie-choices-fieldset {\n justify-self: center;\n width: fit-content;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3 .pie-sentence-line img {\n max-width: 100%;\n height: auto;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3 .pie-choice-horizontal:hover .pie-choice-tile {\n background: #e2e2e2;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3 .pie-choice-horizontal.is-selected .pie-choice-tile,\n.mc-populated-blank-root.variant-sel-r1-s3\n .pie-choice-horizontal.is-selected:hover\n .pie-choice-tile {\n background: #fcfcd3;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3 .pie-choice-horizontal .pie-choice-radio {\n transform: scale(0.85);\n}\n"
8617
+ cssText: "/* Source baseline: https://global-pr-ibvcustomfiles.renaissance-go.com/learnosity/sel_r1-s3_plusggg/0.0.1/question.css */\n/* Shared r1 base styles (font, template-line flex, choice-label) are in sel-r1-base.css */\n.mc-populated-blank-root.variant-sel-r1-s3 {\n max-width: 860px;\n margin-inline: auto;\n --mpb-listen-button-size: 128px;\n --mpb-choice-width-px: 155px;\n --mpb-choice-tile-min-height: 11.25rem;\n --mpb-choice-content-min-height: 9.375rem;\n --mpb-choice-group-gap: 0.35rem;\n --mpb-choice-row-gap: 0.35rem;\n --mpb-horizontal-choice-radio-top-margin: 0.25rem;\n --mpb-stimulus-min-column: 240px;\n --mpb-stimulus-grid-column-gap: 1.1rem;\n --mpb-stimulus-choices-margin-top: 0.85rem;\n --mpb-audio-blank-template-margin-bottom: 0.9rem;\n --mpb-blank-wide-width: 6.8rem;\n}\n\n/* sel_r1-s3_plusggg/question.css does not set font-weight on distractors or\n cloze content-element, so they inherit 400 from the host root. The earlier\n font-weight:500 override caused PIE to render visibly heavier than LSY for\n the same author content. See CONTOOL-2571. */\n.mc-populated-blank-root.variant-sel-r1-s3 .pie-choice-label,\n.mc-populated-blank-root.variant-sel-r1-s3 .pie-choice-label :is(span, p, div) {\n font-size: 1.9em;\n line-height: 89px;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3 .pie-blank-value,\n.mc-populated-blank-root.variant-sel-r1-s3 .pie-blank-value :is(span, p, div) {\n line-height: 1.1;\n white-space: nowrap;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3 .pie-choice-horizontal .pie-choice-tile {\n border-radius: 6px;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3.layout-stimulus_image_blank {\n grid-template-columns: minmax(220px, auto) auto auto;\n grid-template-areas:\n \"transcript transcript transcript\"\n \"sentence template audio\"\n \"choices choices choices\";\n justify-content: center;\n /* question.css: .rli-s3-cloze-container { justify-content:end } bottom-anchors\n the cloze against the shared sentence/audio row instead of top-aligning it —\n matches every item sitting on the same visual baseline. See CONTOOL-3159. */\n align-items: end;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3.layout-stimulus_image_blank .pie-template-line {\n grid-area: template;\n justify-self: center;\n}\n\n/* ClozeMarker.svelte's shared .cloze-marker rule vertically *centers* its content\n (align-items:center). question.css's .rli-s3-cloze-container uses\n justify-content:end instead, bottom-anchoring the answer text against the\n underline. Centering leaves it floating well above both the underline and the\n sentence image's own (bottom-anchored) baseline. The blank's own box height\n (160px, from McPopulatedBlank.svelte's shared audio_blank_only/stimulus_image_blank\n rule) is left as-is — it already matches the Learnosity reference (~161px)\n closely; only the internal alignment was wrong. See CONTOOL-3159. */\n.mc-populated-blank-root.variant-sel-r1-s3.layout-stimulus_image_blank .pie-blank-slot {\n align-items: flex-end;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3.layout-stimulus_image_blank .pie-sentence-line {\n justify-self: end;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3.layout-stimulus_image_blank .pie-audio-container {\n justify-self: start;\n align-self: start;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3.layout-stimulus_image_blank .pie-choices-fieldset {\n justify-self: center;\n width: fit-content;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3 .pie-sentence-line img {\n max-width: 100%;\n height: auto;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3 .pie-choice-horizontal:hover .pie-choice-tile {\n background: #e2e2e2;\n color: #000;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3 .pie-choice-horizontal.is-selected .pie-choice-tile,\n.mc-populated-blank-root.variant-sel-r1-s3\n .pie-choice-horizontal.is-selected:hover\n .pie-choice-tile {\n background: #fcfcd3;\n color: #000;\n}\n\n.mc-populated-blank-root.variant-sel-r1-s3 .pie-choice-horizontal .pie-choice-radio {\n transform: scale(0.85);\n}\n"
8623
8618
  }
8624
8619
  };
8625
8620
  var normalizeCustomType = (value) => {
@@ -8691,7 +8686,7 @@ var root_6 = /* @__PURE__ */ from_html(`<p class="sr-only pie-result-feedback sv
8691
8686
  var root_7 = /* @__PURE__ */ from_html(`<div><!> <!> <!> <!> <!> <!> <!> <p class="sr-only pie-blank-hint svelte-6wlbb4"> </p> <fieldset class="pie-choices-fieldset svelte-6wlbb4"><legend class="sr-only pie-choices-legend svelte-6wlbb4"> </legend> <div style="gap:var(--mpb-choice-group-gap, 0.5rem);" role="radiogroup" tabindex="-1"></div></fieldset></div>`);
8692
8687
  var $$css = {
8693
8688
  hash: "svelte-6wlbb4",
8694
- code: ".sr-only.svelte-6wlbb4 {position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;}\n\n /* Layout is self-contained: PIE players ship no Tailwind, so no utility class or\n preflight reset can be assumed. The blank slot, choice tiles and variant\n max-widths are sized for border-box. */.mc-populated-blank-root.svelte-6wlbb4,\n .mc-populated-blank-root.svelte-6wlbb4 *,\n .mc-populated-blank-root.svelte-6wlbb4 *::before,\n .mc-populated-blank-root.svelte-6wlbb4 *::after {box-sizing:border-box;}\n\n /* An authored image on its own line (the sel_r1-s3 stimulus) would otherwise sit\n on the text baseline and add the descender gap below it. */.mc-populated-blank-root.svelte-6wlbb4 img {vertical-align:middle;}.mc-populated-blank-root.svelte-6wlbb4 {--mpb-focus-ring: var(\n --pie-focus-outline,\n var(--pie-button-focus-outline, var(--pie-focus-checked-border, #1565c0))\n );padding:1rem;}.pie-prompt.svelte-6wlbb4 {margin-bottom:1rem;}\n\n /* Flush paragraphs, as the demo app's preflight reset rendered them. */.pie-prompt.svelte-6wlbb4 p,\n .sentence-line.svelte-6wlbb4 p {margin:0;}.sentence-line.svelte-6wlbb4 {margin-bottom:0.75rem;}.pie-toggle-correct-answer.svelte-6wlbb4 {width:100%;cursor:pointer;border:0;background:transparent;padding:0;margin-bottom:0.75rem;display:flex;justify-content:center;text-align:center;font:inherit;color:var(--pie-text, black);}.pie-correct-answer-toggle-row.svelte-6wlbb4 {width:100%;display:flex;justify-content:flex-end;}.pie-correct-answer-toggle-content.svelte-6wlbb4 {display:flex;margin:0 auto;align-items:center;}.pie-correct-answer-toggle-icon-holder.svelte-6wlbb4 {width:25px;margin-right:5px;display:inline-flex;align-items:center;justify-content:center;}.pie-correct-answer-toggle-svg.svelte-6wlbb4 {width:25px;height:25px;}.pie-correct-answer-toggle-label.svelte-6wlbb4 {width:fit-content;min-width:140px;align-self:center;vertical-align:middle;font-weight:400;user-select:none;}.pie-toggle-correct-answer.svelte-6wlbb4:hover .pie-correct-answer-toggle-label:where(.svelte-6wlbb4),\n .pie-toggle-correct-answer.svelte-6wlbb4:focus-visible .pie-correct-answer-toggle-label:where(.svelte-6wlbb4) {text-decoration:underline;}.template-line.svelte-6wlbb4 {white-space:pre-wrap;margin-bottom:1rem;}.template-line.svelte-6wlbb4 p {margin:0;display:inline;}.pie-choices-fieldset.svelte-6wlbb4 {border:0;padding:0;margin:0;}.pie-choices.svelte-6wlbb4 {display:flex;}.choices-horizontal.svelte-6wlbb4 {flex-direction:row;flex-wrap:wrap;align-items:flex-start;justify-content:center;}.choices-vertical.svelte-6wlbb4 {flex-direction:column;}.layout-audio_blank_only.svelte-6wlbb4 .pie-audio-container,\n .layout-stimulus_image_blank.svelte-6wlbb4 .pie-audio-container {display:flex;width:min(100%, var(--mpb-audio-instructions-max-width, 875px));margin-left:auto;margin-right:auto;justify-content:flex-end;}.layout-audio_blank_only.svelte-6wlbb4 .template-line:where(.svelte-6wlbb4),\n .layout-stimulus_image_blank.svelte-6wlbb4 .template-line:where(.svelte-6wlbb4) {text-align:center;margin:var(--mpb-audio-blank-template-margin-top, 0.8rem) 0\n var(--mpb-audio-blank-template-margin-bottom, 1.8rem);}.layout-audio_blank_only.svelte-6wlbb4 fieldset:where(.svelte-6wlbb4) {display:flex;justify-content:center;}.layout-audio_blank_only.svelte-6wlbb4 .pie-blank-slot,\n .layout-stimulus_image_blank.svelte-6wlbb4 .pie-blank-slot {width:var(--mpb-blank-wide-width, 10rem);border-bottom-width:var(--mpb-blank-underline-wide-width, 6px);min-height:160px;padding-bottom:4px;}.layout-stimulus_image_blank.svelte-6wlbb4 {display:grid;grid-template-columns:minmax(var(--mpb-stimulus-min-column, 210px), 1fr) auto;grid-template-areas:'sentence audio'\n '. template'\n 'choices choices';column-gap:var(--mpb-stimulus-grid-column-gap, 2rem);row-gap:var(--mpb-stimulus-grid-row-gap, 0.7rem);align-items:start;}.layout-stimulus_image_blank.svelte-6wlbb4 .pie-audio-container {grid-area:audio;margin:0;}.layout-stimulus_image_blank.svelte-6wlbb4 .sentence-line:where(.svelte-6wlbb4) {grid-area:sentence;margin:var(--mpb-stimulus-sentence-margin-top, 0.2rem) 0 0;}.layout-stimulus_image_blank.svelte-6wlbb4 .template-line:where(.svelte-6wlbb4) {grid-area:template;margin:0;text-align:center;justify-self:center;}.layout-stimulus_image_blank.svelte-6wlbb4 fieldset:where(.svelte-6wlbb4) {grid-area:choices;margin-top:var(--mpb-stimulus-choices-margin-top, 0.9rem);}.layout-token_sequence.svelte-6wlbb4 {display:grid;grid-template-columns:1fr auto;grid-template-areas:'audio audio'\n 'template template'\n 'choices choices';column-gap:var(--mpb-token-grid-column-gap, 1.5rem);row-gap:var(--mpb-token-grid-row-gap, 0.8rem);align-items:start;}.layout-token_sequence.svelte-6wlbb4 .pie-audio-container {grid-area:audio;margin:0;justify-self:end;}.layout-token_sequence.svelte-6wlbb4 .template-line:where(.svelte-6wlbb4) {grid-area:template;margin:var(--mpb-token-template-margin-top, 0.6rem) 0 0;justify-self:center;text-align:center;}.layout-token_sequence.svelte-6wlbb4 .template-line:where(.svelte-6wlbb4) span + span {margin-left:var(--mpb-token-inline-token-gap, 0.35rem);}.layout-token_sequence.svelte-6wlbb4 .pie-blank-slot {width:var(--mpb-blank-standalone-width, 7rem);border-bottom-width:var(--mpb-blank-underline-wide-width, 4px);margin-left:var(--mpb-token-inline-token-gap, 0.35rem);}.layout-token_sequence.svelte-6wlbb4 fieldset:where(.svelte-6wlbb4) {grid-area:choices;margin-top:var(--mpb-token-choices-margin-top, 0.2rem);}.layout-inline_sentence.has-inline-audio.svelte-6wlbb4 {display:grid;grid-template-columns:minmax(var(--mpb-text-min-column, 260px), 1fr) auto;grid-template-areas:'template audio'\n 'choices choices';column-gap:var(--mpb-inline-grid-column-gap, 1.5rem);row-gap:var(--mpb-inline-grid-row-gap, 0.65rem);align-items:start;}.layout-inline_sentence.has-inline-audio.svelte-6wlbb4 .pie-audio-container {grid-area:audio;margin:0;justify-self:end;}.layout-inline_sentence.has-inline-audio.svelte-6wlbb4 .template-line:where(.svelte-6wlbb4) {grid-area:template;margin:var(--mpb-inline-template-margin-top, 0.45rem) 0 0;text-align:center;justify-self:center;}.layout-inline_sentence.has-inline-audio.svelte-6wlbb4 fieldset:where(.svelte-6wlbb4) {grid-area:choices;margin-top:var(--mpb-inline-choices-margin-top, 0.25rem);}\n\n /* Match Learnosity responsive behavior for CQT audio-blank layouts:\n at smaller widths, audio control shifts left and answer tiles stack. */\n @media (max-width: 760px) {.layout-audio_blank_only.svelte-6wlbb4 .pie-audio-container,\n .layout-stimulus_image_blank.svelte-6wlbb4 .pie-audio-container,\n .layout-token_sequence.svelte-6wlbb4 .pie-audio-container,\n .layout-inline_sentence.has-inline-audio.svelte-6wlbb4 .pie-audio-container {width:100%;margin-left:0;margin-right:0;justify-content:flex-start;justify-self:start;}.layout-audio_blank_only.svelte-6wlbb4 .pie-choices:where(.svelte-6wlbb4),\n .layout-token_sequence.svelte-6wlbb4 .pie-choices:where(.svelte-6wlbb4),\n .layout-stimulus_image_blank.svelte-6wlbb4 .pie-choices:where(.svelte-6wlbb4),\n .layout-inline_sentence.has-inline-audio.svelte-6wlbb4 .pie-choices:where(.svelte-6wlbb4) {flex-direction:column;align-items:flex-start;justify-content:flex-start;}.layout-audio_blank_only.svelte-6wlbb4 .choice-row-horizontal:where(.svelte-6wlbb4),\n .layout-token_sequence.svelte-6wlbb4 .choice-row-horizontal:where(.svelte-6wlbb4),\n .layout-stimulus_image_blank.svelte-6wlbb4 .choice-row-horizontal:where(.svelte-6wlbb4),\n .layout-inline_sentence.has-inline-audio.svelte-6wlbb4 .choice-row-horizontal:where(.svelte-6wlbb4) {width:min(100%, var(--mpb-narrow-choice-max-width, 230px));align-items:flex-start;}\n }"
8689
+ code: ".sr-only.svelte-6wlbb4 {position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;}\n\n /* Layout is self-contained: PIE players ship no Tailwind, so no utility class or\n preflight reset can be assumed. The blank slot, choice tiles and variant\n max-widths are sized for border-box. */.mc-populated-blank-root.svelte-6wlbb4,\n .mc-populated-blank-root.svelte-6wlbb4 *,\n .mc-populated-blank-root.svelte-6wlbb4 *::before,\n .mc-populated-blank-root.svelte-6wlbb4 *::after {box-sizing:border-box;}\n\n /* An authored image on its own line (the sel_r1-s3 stimulus) would otherwise sit\n on the text baseline and add the descender gap below it. */.mc-populated-blank-root.svelte-6wlbb4 img {vertical-align:middle;}.mc-populated-blank-root.svelte-6wlbb4 {--mpb-focus-ring: var(\n --pie-focus-outline,\n var(--pie-button-focus-outline, var(--pie-focus-checked-border, #1565c0))\n );padding:1rem;}.pie-prompt.svelte-6wlbb4 {margin-bottom:1rem;}\n\n /* Flush paragraphs, as the demo app's preflight reset rendered them. */.pie-prompt.svelte-6wlbb4 p,\n .sentence-line.svelte-6wlbb4 p {margin:0;}.sentence-line.svelte-6wlbb4 {margin-bottom:0.75rem;}.pie-toggle-correct-answer.svelte-6wlbb4 {width:100%;cursor:pointer;border:0;background:transparent;padding:0;margin-bottom:0.75rem;display:flex;justify-content:center;text-align:center;font:inherit;color:var(--pie-text, black);}.pie-correct-answer-toggle-row.svelte-6wlbb4 {width:100%;display:flex;justify-content:flex-end;}.pie-correct-answer-toggle-content.svelte-6wlbb4 {display:flex;margin:0 auto;align-items:center;}.pie-correct-answer-toggle-icon-holder.svelte-6wlbb4 {width:25px;margin-right:5px;display:inline-flex;align-items:center;justify-content:center;}.pie-correct-answer-toggle-svg.svelte-6wlbb4 {width:25px;height:25px;}.pie-correct-answer-toggle-label.svelte-6wlbb4 {width:fit-content;min-width:140px;align-self:center;vertical-align:middle;font-weight:400;user-select:none;}.pie-toggle-correct-answer.svelte-6wlbb4:hover .pie-correct-answer-toggle-label:where(.svelte-6wlbb4),\n .pie-toggle-correct-answer.svelte-6wlbb4:focus-visible .pie-correct-answer-toggle-label:where(.svelte-6wlbb4) {text-decoration:underline;}.template-line.svelte-6wlbb4 {white-space:pre-wrap;margin-bottom:1rem;}.template-line.svelte-6wlbb4 p {margin:0;display:inline;}.pie-choices-fieldset.svelte-6wlbb4 {border:0;padding:0;margin:0;}.pie-choices.svelte-6wlbb4 {display:flex;}.choices-horizontal.svelte-6wlbb4 {flex-direction:row;flex-wrap:wrap;align-items:flex-start;justify-content:center;}.choices-vertical.svelte-6wlbb4 {flex-direction:column;}.layout-audio_blank_only.svelte-6wlbb4 .pie-audio-container,\n .layout-stimulus_image_blank.svelte-6wlbb4 .pie-audio-container {display:flex;width:min(100%, var(--mpb-audio-instructions-max-width, 875px));margin-left:auto;margin-right:auto;justify-content:flex-end;}.layout-audio_blank_only.svelte-6wlbb4 .template-line:where(.svelte-6wlbb4),\n .layout-stimulus_image_blank.svelte-6wlbb4 .template-line:where(.svelte-6wlbb4) {text-align:center;margin:var(--mpb-audio-blank-template-margin-top, 0.8rem) 0\n var(--mpb-audio-blank-template-margin-bottom, 1.8rem);}.layout-audio_blank_only.svelte-6wlbb4 fieldset:where(.svelte-6wlbb4) {display:flex;justify-content:center;}.layout-audio_blank_only.svelte-6wlbb4 .pie-blank-slot,\n .layout-stimulus_image_blank.svelte-6wlbb4 .pie-blank-slot {width:var(--mpb-blank-wide-width, 10rem);border-bottom-width:var(--mpb-blank-underline-wide-width, 6px);min-height:160px;padding-bottom:4px;}.layout-stimulus_image_blank.svelte-6wlbb4 {display:grid;grid-template-columns:minmax(var(--mpb-stimulus-min-column, 210px), 1fr) auto;grid-template-areas:'sentence audio'\n '. template'\n 'choices choices';column-gap:var(--mpb-stimulus-grid-column-gap, 2rem);row-gap:var(--mpb-stimulus-grid-row-gap, 0.7rem);align-items:start;}.layout-stimulus_image_blank.svelte-6wlbb4 .pie-audio-container {grid-area:audio;margin:0;}.layout-stimulus_image_blank.svelte-6wlbb4 .sentence-line:where(.svelte-6wlbb4) {grid-area:sentence;margin:var(--mpb-stimulus-sentence-margin-top, 0.2rem) 0 0;}.layout-stimulus_image_blank.svelte-6wlbb4 .template-line:where(.svelte-6wlbb4) {grid-area:template;margin:0;text-align:center;justify-self:center;}.layout-stimulus_image_blank.svelte-6wlbb4 fieldset:where(.svelte-6wlbb4) {grid-area:choices;margin-top:var(--mpb-stimulus-choices-margin-top, 0.9rem);}.layout-token_sequence.svelte-6wlbb4 {display:grid;grid-template-columns:1fr auto;grid-template-areas:'audio audio'\n 'template template'\n 'choices choices';column-gap:var(--mpb-token-grid-column-gap, 1.5rem);row-gap:var(--mpb-token-grid-row-gap, 0.8rem);align-items:start;}.layout-token_sequence.svelte-6wlbb4 .pie-audio-container {grid-area:audio;margin:0;justify-self:end;}.layout-token_sequence.svelte-6wlbb4 .template-line:where(.svelte-6wlbb4) {grid-area:template;margin:var(--mpb-token-template-margin-top, 0.6rem) 0 0;justify-self:center;text-align:center;}.layout-token_sequence.svelte-6wlbb4 .template-line:where(.svelte-6wlbb4) span + span {margin-left:var(--mpb-token-inline-token-gap, 0.35rem);}.layout-token_sequence.svelte-6wlbb4 .pie-blank-slot {width:var(--mpb-blank-standalone-width, 7rem);border-bottom-width:var(--mpb-blank-underline-wide-width, 4px);margin-left:var(--mpb-token-inline-token-gap, 0.35rem);}.layout-token_sequence.svelte-6wlbb4 fieldset:where(.svelte-6wlbb4) {grid-area:choices;margin-top:var(--mpb-token-choices-margin-top, 0.2rem);}.layout-inline_sentence.has-inline-audio.svelte-6wlbb4 {display:grid;grid-template-columns:minmax(var(--mpb-text-min-column, 260px), 1fr) auto;grid-template-areas:'template audio'\n 'choices choices';column-gap:var(--mpb-inline-grid-column-gap, 1.5rem);row-gap:var(--mpb-inline-grid-row-gap, 0.65rem);align-items:start;}.layout-inline_sentence.has-inline-audio.svelte-6wlbb4 .pie-audio-container {grid-area:audio;margin:0;justify-self:end;}.layout-inline_sentence.has-inline-audio.svelte-6wlbb4 .template-line:where(.svelte-6wlbb4) {grid-area:template;margin:var(--mpb-inline-template-margin-top, 0.45rem) 0 0;text-align:center;justify-self:center;}.layout-inline_sentence.has-inline-audio.svelte-6wlbb4 fieldset:where(.svelte-6wlbb4) {grid-area:choices;margin-top:var(--mpb-inline-choices-margin-top, 0.25rem);}\n\n /* Match Learnosity responsive behavior for CQT audio-blank layouts:\n at smaller widths, audio control shifts left and answer tiles stack. */\n @media (max-width: 760px) {.layout-audio_blank_only.svelte-6wlbb4 .pie-audio-container,\n .layout-stimulus_image_blank.svelte-6wlbb4 .pie-audio-container,\n .layout-token_sequence.svelte-6wlbb4 .pie-audio-container,\n .layout-inline_sentence.has-inline-audio.svelte-6wlbb4 .pie-audio-container {width:100%;margin-left:0;margin-right:0;justify-content:flex-start;justify-self:start;}.layout-audio_blank_only.svelte-6wlbb4 .pie-choices:where(.svelte-6wlbb4),\n .layout-token_sequence.svelte-6wlbb4 .pie-choices:where(.svelte-6wlbb4),\n .layout-stimulus_image_blank.svelte-6wlbb4 .pie-choices:where(.svelte-6wlbb4),\n .layout-inline_sentence.has-inline-audio.svelte-6wlbb4 .pie-choices:where(.svelte-6wlbb4) {flex-direction:column;align-items:flex-start;justify-content:flex-start;}\n }"
8695
8690
  };
8696
8691
  function McPopulatedBlank($$anchor, $$props) {
8697
8692
  push($$props, true);