@citolab/qti-components 8.0.0 → 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cdn/index.global.js +1 -1
- package/cdn/index.js +591 -217
- package/dist/base.d.ts +135 -9
- package/dist/{chunk-MJVPNN7W.js → chunk-3AJOQCDU.js} +2 -2
- package/dist/chunk-7HC5SMCU.js +102 -0
- package/dist/chunk-7HC5SMCU.js.map +1 -0
- package/dist/{chunk-MGF5F3S2.js → chunk-CAO4JRLY.js} +2 -2
- package/dist/{chunk-5SJS36BI.js → chunk-F4BNJWYN.js} +1 -1
- package/dist/{chunk-5SJS36BI.js.map → chunk-F4BNJWYN.js.map} +1 -1
- package/dist/{chunk-5QYII4OZ.js → chunk-LVBIQDPM.js} +2 -2
- package/dist/{chunk-W4QZPCNC.js → chunk-QECOPVPO.js} +2 -2
- package/dist/{chunk-LZJHHZQ2.js → chunk-RXGDQDOV.js} +177 -173
- package/dist/chunk-RXGDQDOV.js.map +1 -0
- package/dist/{chunk-KYEZRYO6.js → chunk-XU7HNT4F.js} +4 -4
- package/dist/computed-item.types.d-BKZdSLLJ.d.ts +19 -0
- package/dist/computed.types.d-rKLSPvVG.d.ts +42 -0
- package/dist/config.context.d-BvU9r-Mg.d.ts +91 -0
- package/dist/corrections.d.ts +432 -1
- package/dist/corrections.js +5 -5
- package/dist/elements.d.ts +393 -1
- package/dist/index.d.ts +58 -9
- package/dist/index.js +16 -8
- package/dist/interaction.d-D4MstYRz.d.ts +115 -0
- package/dist/interaction.interface.d-CvjHM2bi.d.ts +46 -0
- package/dist/interactions.d.ts +1569 -1
- package/dist/interactions.js +2 -2
- package/dist/item.css +588 -214
- package/dist/item.d.ts +92 -1
- package/dist/item.js +3 -3
- package/dist/item.types.d-QU7LljEK.d.ts +82 -0
- package/dist/loader.d.ts +22 -1
- package/dist/processing.d.ts +730 -1
- package/dist/qti-components-jsx.d.ts +221 -231
- package/dist/qti-components-jsx.js +1 -0
- package/dist/qti-condition-expression.d-aMvBvpc6.d.ts +8 -0
- package/dist/qti-expression.d-BietT_8b.d.ts +31 -0
- package/dist/qti-feedback.d-DZbad0RN.d.ts +21 -0
- package/dist/qti-rule-base.d-ajOnfGXY.d.ts +16 -0
- package/dist/qti-transform-test.d-BL7GGFJj.d.ts +70 -0
- package/dist/qti.context.d-CejJMlfZ.d.ts +50 -0
- package/dist/response.d-_8y4kjGP.d.ts +106 -0
- package/dist/test.d.ts +699 -1
- package/dist/test.js +11 -3
- package/dist/transformers.d.ts +18 -1
- package/package.json +11 -8
- package/dist/chunk-LZJHHZQ2.js.map +0 -1
- package/dist/chunk-WCQ26LV7.js +0 -102
- package/dist/chunk-WCQ26LV7.js.map +0 -1
- /package/dist/{chunk-MJVPNN7W.js.map → chunk-3AJOQCDU.js.map} +0 -0
- /package/dist/{chunk-MGF5F3S2.js.map → chunk-CAO4JRLY.js.map} +0 -0
- /package/dist/{chunk-5QYII4OZ.js.map → chunk-LVBIQDPM.js.map} +0 -0
- /package/dist/{chunk-W4QZPCNC.js.map → chunk-QECOPVPO.js.map} +0 -0
- /package/dist/{chunk-KYEZRYO6.js.map → chunk-XU7HNT4F.js.map} +0 -0
package/dist/chunk-WCQ26LV7.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
e,
|
|
3
|
-
f,
|
|
4
|
-
n
|
|
5
|
-
} from "./chunk-64XSIBGO.js";
|
|
6
|
-
import {
|
|
7
|
-
E
|
|
8
|
-
} from "./chunk-H3J7LFRO.js";
|
|
9
|
-
|
|
10
|
-
// ../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/node/directives/private-async-helpers.js
|
|
11
|
-
var s = class {
|
|
12
|
-
constructor(t) {
|
|
13
|
-
this.G = t;
|
|
14
|
-
}
|
|
15
|
-
disconnect() {
|
|
16
|
-
this.G = void 0;
|
|
17
|
-
}
|
|
18
|
-
reconnect(t) {
|
|
19
|
-
this.G = t;
|
|
20
|
-
}
|
|
21
|
-
deref() {
|
|
22
|
-
return this.G;
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
var i = class {
|
|
26
|
-
constructor() {
|
|
27
|
-
this.Y = void 0, this.Z = void 0;
|
|
28
|
-
}
|
|
29
|
-
get() {
|
|
30
|
-
return this.Y;
|
|
31
|
-
}
|
|
32
|
-
pause() {
|
|
33
|
-
this.Y ??= new Promise((t) => this.Z = t);
|
|
34
|
-
}
|
|
35
|
-
resume() {
|
|
36
|
-
this.Z?.(), this.Y = this.Z = void 0;
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
// ../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/node/directives/until.js
|
|
41
|
-
var n2 = (t) => !n(t) && "function" == typeof t.then;
|
|
42
|
-
var h = 1073741823;
|
|
43
|
-
var c = class extends f {
|
|
44
|
-
constructor() {
|
|
45
|
-
super(...arguments), this._$Cwt = h, this._$Cbt = [], this._$CK = new s(this), this._$CX = new i();
|
|
46
|
-
}
|
|
47
|
-
render(...s2) {
|
|
48
|
-
return s2.find((t) => !n2(t)) ?? E;
|
|
49
|
-
}
|
|
50
|
-
update(s2, i2) {
|
|
51
|
-
const e2 = this._$Cbt;
|
|
52
|
-
let r = e2.length;
|
|
53
|
-
this._$Cbt = i2;
|
|
54
|
-
const o = this._$CK, c2 = this._$CX;
|
|
55
|
-
this.isConnected || this.disconnected();
|
|
56
|
-
for (let t = 0; t < i2.length && !(t > this._$Cwt); t++) {
|
|
57
|
-
const s3 = i2[t];
|
|
58
|
-
if (!n2(s3)) return this._$Cwt = t, s3;
|
|
59
|
-
t < r && s3 === e2[t] || (this._$Cwt = h, r = 0, Promise.resolve(s3).then(async (t2) => {
|
|
60
|
-
for (; c2.get(); ) await c2.get();
|
|
61
|
-
const i3 = o.deref();
|
|
62
|
-
if (void 0 !== i3) {
|
|
63
|
-
const e3 = i3._$Cbt.indexOf(s3);
|
|
64
|
-
e3 > -1 && e3 < i3._$Cwt && (i3._$Cwt = e3, i3.setValue(t2));
|
|
65
|
-
}
|
|
66
|
-
}));
|
|
67
|
-
}
|
|
68
|
-
return E;
|
|
69
|
-
}
|
|
70
|
-
disconnected() {
|
|
71
|
-
this._$CK.disconnect(), this._$CX.pause();
|
|
72
|
-
}
|
|
73
|
-
reconnected() {
|
|
74
|
-
this._$CK.reconnect(this), this._$CX.resume();
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
var m = e(c);
|
|
78
|
-
|
|
79
|
-
// inline:../../../../qti-theme/src/item.css?inline
|
|
80
|
-
var item_default = "@layer qti-base, qti-components, qti-utilities, qti-variants, qti-extended;\n@layer qti-components.reset, qti-components.global, qti-components.interactions.base,\n qti-components.interactions.brand, qti-components.interactions.states,\n qti-components.interactions.corrections, qti-components.elements, qti-components.overrides;\n/* https://www.imsglobal.org/sites/default/files/spec/qti/v3/guide/img/qti3p0.css */\n/* ============================\n QTI 3 shared css\n 1. Display\n 2. Special Flex styles\n 3. Margin\n 4. Padding\n 5. Horizontal Alignment styles\n 6. Vertical Alignment styles\n 7. Height\n 8. Width\n 9. Text-Indent\n 10. List Style\n 11. Layout\n 12. Other QTI 3 presentation utilities\n ============================ */\n/* ==========\n Display css\n =========== */\n.qti-display-inline {\n display: inline;\n}\n.qti-display-inline-block {\n display: inline-block;\n}\n.qti-display-block {\n display: block;\n}\n.qti-display-flex {\n display: flex;\n}\n.qti-display-inline-flex {\n display: inline-flex;\n}\n.qti-display-grid {\n display: grid;\n}\n.qti-display-inline-grid {\n display: inline-grid;\n}\n.qti-display-table {\n display: table;\n}\n.qti-display-table-cell {\n display: table-cell;\n}\n.qti-display-table-row {\n display: table-row;\n}\n.qti-display-list-item {\n display: list-item;\n}\n.qti-display-inherit {\n display: inherit;\n}\n/* \n * hidden to screen readers and sighted\n */\n.qti-hidden {\n display: none;\n}\n/*\n * visible to screen readers, hidden to sighted\n */\n.qti-visually-hidden {\n position: fixed !important;\n overflow: hidden;\n clip: rect(1px 1px 1px 1px);\n height: 1px;\n width: 1px;\n border: 0;\n margin: -1px;\n}\n/* =============================\n Special flex styles\n ============================= */\n.qti-flex-direction-column {\n flex-direction: column;\n}\n.qti-flex-direction-row {\n flex-direction: row;\n}\n.qti-flex-grow-1 {\n flex-grow: 1;\n}\n.qti-flex-grow-0 {\n flex-grow: 0;\n}\n/* =========\n Margin css\n ========== */\n/**\n * For margin Top and Bottom and Left and Right\n */\n.qti-margin-0 {\n margin: 0 !important;\n}\n.qti-margin-1 {\n margin: 0.25rem !important;\n}\n.qti-margin-2 {\n margin: 0.5rem !important;\n}\n.qti-margin-3 {\n margin: 1rem !important;\n}\n.qti-margin-4 {\n margin: 1.5rem !important;\n}\n.qti-margin-5 {\n margin: 3rem !important;\n}\n.qti-margin-auto {\n margin: auto !important;\n}\n/*\n For margin Left and Right\n */\n.qti-margin-x-0 {\n margin-right: 0 !important;\n margin-left: 0 !important;\n}\n.qti-margin-x-1 {\n margin-right: 0.25rem !important;\n margin-left: 0.25rem !important;\n}\n.qti-margin-x-2 {\n margin-right: 0.5rem !important;\n margin-left: 0.5rem !important;\n}\n.qti-margin-x-3 {\n margin-right: 1rem !important;\n margin-left: 1rem !important;\n}\n.qti-margin-x-4 {\n margin-right: 1.5rem !important;\n margin-left: 1.5rem !important;\n}\n.qti-margin-x-5 {\n margin-right: 3rem !important;\n margin-left: 3rem !important;\n}\n.qti-margin-x-auto {\n margin-right: auto !important;\n margin-left: auto !important;\n}\n/*\n For margin Top and Bottom\n */\n.qti-margin-y-0 {\n margin-top: 0 !important;\n margin-bottom: 0 !important;\n}\n.qti-margin-y-1 {\n margin-top: 0.25rem !important;\n margin-bottom: 0.25rem !important;\n}\n.qti-margin-y-2 {\n margin-top: 0.5rem !important;\n margin-bottom: 0.5rem !important;\n}\n.qti-margin-y-3 {\n margin-top: 1rem !important;\n margin-bottom: 1rem !important;\n}\n.qti-margin-y-4 {\n margin-top: 1.5rem !important;\n margin-bottom: 1.5rem !important;\n}\n.qti-margin-y-5 {\n margin-top: 3rem !important;\n margin-bottom: 3rem !important;\n}\n.qti-margin-y-auto {\n margin-top: auto !important;\n margin-bottom: auto !important;\n}\n/*\n For margin Top\n */\n.qti-margin-t-0 {\n margin-top: 0 !important;\n}\n.qti-margin-t-1 {\n margin-top: 0.25rem !important;\n}\n.qti-margin-t-2 {\n margin-top: 0.5rem !important;\n}\n.qti-margin-t-3 {\n margin-top: 1rem !important;\n}\n.qti-margin-t-4 {\n margin-top: 1.5rem !important;\n}\n.qti-margin-t-5 {\n margin-top: 3rem !important;\n}\n.qti-margin-t-auto {\n margin-top: auto !important;\n}\n/* \n For margin Bottom\n */\n.qti-margin-b-0 {\n margin-bottom: 0 !important;\n}\n.qti-margin-b-1 {\n margin-bottom: 0.25rem !important;\n}\n.qti-margin-b-2 {\n margin-bottom: 0.5rem !important;\n}\n.qti-margin-b-3 {\n margin-bottom: 1rem !important;\n}\n.qti-margin-b-4 {\n margin-bottom: 1.5rem !important;\n}\n.qti-margin-b-5 {\n margin-bottom: 3rem !important;\n}\n.qti-margin-b-auto {\n margin-bottom: auto !important;\n}\n/*\n For margin Start LTR\n */\n.qti-margin-s-0 {\n margin-left: 0 !important;\n}\n.qti-margin-s-1 {\n margin-left: 0.25rem !important;\n}\n.qti-margin-s-2 {\n margin-left: 0.5rem !important;\n}\n.qti-margin-s-3 {\n margin-left: 1rem !important;\n}\n.qti-margin-s-4 {\n margin-left: 1.5rem !important;\n}\n.qti-margin-s-5 {\n margin-left: 3rem !important;\n}\n.qti-margin-s-auto {\n margin-left: auto !important;\n}\n/* \n For margin End LTR\n */\n.qti-margin-e-0 {\n margin-right: 0 !important;\n}\n.qti-margin-e-1 {\n margin-right: 0.25rem !important;\n}\n.qti-margin-e-2 {\n margin-right: 0.5rem !important;\n}\n.qti-margin-e-3 {\n margin-right: 1rem !important;\n}\n.qti-margin-e-4 {\n margin-right: 1.5rem !important;\n}\n.qti-margin-e-5 {\n margin-right: 3rem !important;\n}\n.qti-margin-e-auto {\n margin-right: auto !important;\n}\n/* =========\n Padding css\n ========== */\n/*\n For padding Top and Bottom and Left and Right\n */\n.qti-padding-0 {\n padding: 0 !important;\n}\n.qti-padding-1 {\n padding: 0.25rem !important;\n}\n.qti-padding-2 {\n padding: 0.5rem !important;\n}\n.qti-padding-3 {\n padding: 1rem !important;\n}\n.qti-padding-4 {\n padding: 1.5rem !important;\n}\n.qti-padding-5 {\n padding: 3rem !important;\n}\n/*\n For padding Left and Right\n */\n.qti-padding-x-0 {\n padding-right: 0 !important;\n padding-left: 0 !important;\n}\n.qti-padding-x-1 {\n padding-right: 0.25rem !important;\n padding-left: 0.25rem !important;\n}\n.qti-padding-x-2 {\n padding-right: 0.5rem !important;\n padding-left: 0.5rem !important;\n}\n.qti-padding-x-3 {\n padding-right: 1rem !important;\n padding-left: 1rem !important;\n}\n.qti-padding-x-4 {\n padding-right: 1.5rem !important;\n padding-left: 1.5rem !important;\n}\n.qti-padding-x-5 {\n padding-right: 3rem !important;\n padding-left: 3rem !important;\n}\n/*\n For padding Top and Bottom\n */\n.qti-padding-y-0 {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n}\n.qti-padding-y-1 {\n padding-top: 0.25rem !important;\n padding-bottom: 0.25rem !important;\n}\n.qti-padding-y-2 {\n padding-top: 0.5rem !important;\n padding-bottom: 0.5rem !important;\n}\n.qti-padding-y-3 {\n padding-top: 1rem !important;\n padding-bottom: 1rem !important;\n}\n.qti-padding-y-4 {\n padding-top: 1.5rem !important;\n padding-bottom: 1.5rem !important;\n}\n.qti-padding-y-5 {\n padding-top: 3rem !important;\n padding-bottom: 3rem !important;\n}\n/*\n For padding Top\n */\n.qti-padding-t-0 {\n padding-top: 0 !important;\n}\n.qti-padding-t-1 {\n padding-top: 0.25rem !important;\n}\n.qti-padding-t-2 {\n padding-top: 0.5rem !important;\n}\n.qti-padding-t-3 {\n padding-top: 1rem !important;\n}\n.qti-padding-t-4 {\n padding-top: 1.5rem !important;\n}\n.qti-padding-t-5 {\n padding-top: 3rem !important;\n}\n/*\n For padding Bottom\n */\n.qti-padding-b-0 {\n padding-bottom: 0 !important;\n}\n.qti-padding-b-1 {\n padding-bottom: 0.25rem !important;\n}\n.qti-padding-b-2 {\n padding-bottom: 0.5rem !important;\n}\n.qti-padding-b-3 {\n padding-bottom: 1rem !important;\n}\n.qti-padding-b-4 {\n padding-bottom: 1.5rem !important;\n}\n.qti-padding-b-5 {\n padding-bottom: 3rem !important;\n}\n/*\n For padding Start LTR\n */\n.qti-padding-s-0 {\n padding-left: 0 !important;\n}\n.qti-padding-s-1 {\n padding-left: 0.25rem !important;\n}\n.qti-padding-s-2 {\n padding-left: 0.5rem !important;\n}\n.qti-padding-s-3 {\n padding-left: 1rem !important;\n}\n.qti-padding-s-4 {\n padding-left: 1.5rem !important;\n}\n.qti-padding-s-5 {\n padding-left: 3rem !important;\n}\n/*\n For padding End LTR\n */\n.qti-padding-e-0 {\n padding-right: 0 !important;\n}\n.qti-padding-e-1 {\n padding-right: 0.25rem !important;\n}\n.qti-padding-e-2 {\n padding-right: 0.5rem !important;\n}\n.qti-padding-e-3 {\n padding-right: 1rem !important;\n}\n.qti-padding-e-4 {\n padding-right: 1.5rem !important;\n}\n.qti-padding-e-5 {\n padding-right: 3rem !important;\n}\n/* ====================\n Horizontal alignment\n ==================== */\n.qti-align-left {\n text-align: left;\n}\n.qti-align-center {\n text-align: center;\n}\n.qti-align-right {\n text-align: right;\n}\n/* ==================\n Vertical alignment\n ================== */\n.qti-valign-top {\n vertical-align: top;\n}\n.qti-valign-middle {\n vertical-align: middle;\n}\n.qti-valign-baseline {\n vertical-align: baseline;\n}\n.qti-valign-bottom {\n vertical-align: bottom;\n}\n/* =============\n Height styles\n ============= */\n.qti-height-0 {\n height: 0;\n}\n.qti-height-px {\n height: 1px;\n}\n.qti-height-0p5 {\n height: 0.125rem;\n}\n.qti-height-1 {\n height: 0.25rem;\n}\n.qti-height-1p5 {\n height: 0.375rem;\n}\n.qti-height-2 {\n height: 0.5rem;\n}\n.qti-height-2p5 {\n height: 0.625rem;\n}\n.qti-height-3 {\n height: 0.75rem;\n}\n.qti-height-3p5 {\n height: 0.875rem;\n}\n.qti-height-4 {\n height: 1rem;\n}\n.qti-height-5 {\n height: 1.25rem;\n}\n.qti-height-6 {\n height: 1.5rem;\n}\n.qti-height-7 {\n height: 1.75rem;\n}\n.qti-height-8 {\n height: 2rem;\n}\n.qti-height-9 {\n height: 2.25rem;\n}\n.qti-height-10 {\n height: 2.5rem;\n}\n.qti-height-11 {\n height: 2.75rem;\n}\n.qti-height-12 {\n height: 3rem;\n}\n.qti-height-14 {\n height: 3.5rem;\n}\n.qti-height-16 {\n height: 4rem;\n}\n.qti-height-20 {\n height: 5rem;\n}\n.qti-height-24 {\n height: 6rem;\n}\n.qti-height-28 {\n height: 7rem;\n}\n.qti-height-32 {\n height: 8rem;\n}\n.qti-height-36 {\n height: 9rem;\n}\n.qti-height-40 {\n height: 10rem;\n}\n.qti-height-44 {\n height: 11rem;\n}\n.qti-height-48 {\n height: 12rem;\n}\n.qti-height-52 {\n height: 13rem;\n}\n.qti-height-56 {\n height: 14rem;\n}\n.qti-height-60 {\n height: 15rem;\n}\n.qti-height-64 {\n height: 16rem;\n}\n.qti-height-72 {\n height: 18rem;\n}\n.qti-height-80 {\n height: 20rem;\n}\n.qti-height-96 {\n height: 24rem;\n}\n.qti-height-1-2 {\n height: 50%;\n}\n.qti-height-1-3 {\n height: 33.3333%;\n}\n.qti-height-2-3 {\n height: 66.6667%;\n}\n.qti-height-1-4 {\n height: 25%;\n}\n.qti-height-2-4 {\n height: 50%;\n}\n.qti-height-3-4 {\n height: 75%;\n}\n.qti-height-1-5 {\n height: 20%;\n}\n.qti-height-2-5 {\n height: 40%;\n}\n.qti-height-3-5 {\n height: 60%;\n}\n.qti-height-4-5 {\n height: 80%;\n}\n.qti-height-1-6 {\n height: 16.6667%;\n}\n.qti-height-2-6 {\n height: 33.3333%;\n}\n.qti-height-3-6 {\n height: 50%;\n}\n.qti-height-4-6 {\n height: 66.6667%;\n}\n.qti-height-5-6 {\n height: 83.3333%;\n}\n.qti-height-auto {\n height: auto;\n}\n.qti-height-full {\n height: 100%;\n}\n/* ============\n Width styles\n ============ */\n.qti-width-0 {\n width: 0;\n}\n.qti-width-px {\n width: 1px;\n}\n.qti-width-0p5 {\n width: 0.125rem;\n}\n.qti-width-1 {\n width: 0.25rem;\n}\n.qti-width-1p5 {\n width: 0.375rem;\n}\n.qti-width-2 {\n width: 0.5rem;\n}\n.qti-width-2p5 {\n width: 0.625rem;\n}\n.qti-width-3 {\n width: 0.75rem;\n}\n.qti-width-3p5 {\n width: 0.875rem;\n}\n.qti-width-4 {\n width: 1rem;\n}\n.qti-width-5 {\n width: 1.25rem;\n}\n.qti-width-6 {\n width: 1.5rem;\n}\n.qti-width-7 {\n width: 1.75rem;\n}\n.qti-width-8 {\n width: 2rem;\n}\n.qti-width-9 {\n width: 2.25rem;\n}\n.qti-width-10 {\n width: 2.5rem;\n}\n.qti-width-11 {\n width: 2.75rem;\n}\n.qti-width-12 {\n width: 3rem;\n}\n.qti-width-14 {\n width: 3.5rem;\n}\n.qti-width-16 {\n width: 4rem;\n}\n.qti-width-20 {\n width: 5rem;\n}\n.qti-width-24 {\n width: 6rem;\n}\n.qti-width-28 {\n width: 7rem;\n}\n.qti-width-32 {\n width: 8rem;\n}\n.qti-width-36 {\n width: 9rem;\n}\n.qti-width-40 {\n width: 10rem;\n}\n.qti-width-44 {\n width: 11rem;\n}\n.qti-width-48 {\n width: 12rem;\n}\n.qti-width-52 {\n width: 13rem;\n}\n.qti-width-56 {\n width: 14rem;\n}\n.qti-width-60 {\n width: 15rem;\n}\n.qti-width-64 {\n width: 16rem;\n}\n.qti-width-72 {\n width: 18rem;\n}\n.qti-width-80 {\n width: 20rem;\n}\n.qti-width-96 {\n width: 24rem;\n}\n.qti-width-auto {\n width: auto;\n}\n.qti-width-1-2 {\n width: 50%;\n}\n.qti-width-1-3 {\n width: 33.3333%;\n}\n.qti-width-2-3 {\n width: 66.6667%;\n}\n.qti-width-1-4 {\n width: 25%;\n}\n.qti-width-2-4 {\n width: 50%;\n}\n.qti-width-3-4 {\n width: 75%;\n}\n.qti-width-1-5 {\n width: 20%;\n}\n.qti-width-2-5 {\n width: 40%;\n}\n.qti-width-3-5 {\n width: 60%;\n}\n.qti-width-4-5 {\n width: 80%;\n}\n.qti-width-1-6 {\n width: 16.6667%;\n}\n.qti-width-2-6 {\n width: 33.3333%;\n}\n.qti-width-3-6 {\n width: 50%;\n}\n.qti-width-4-6 {\n width: 66.6667%;\n}\n.qti-width-5-6 {\n width: 83.3333%;\n}\n.qti-width-1-12 {\n width: 8.3333%;\n}\n.qti-width-2-12 {\n width: 16.6667%;\n}\n.qti-width-3-12 {\n width: 25%;\n}\n.qti-width-4-12 {\n width: 33.3333%;\n}\n.qti-width-5-12 {\n width: 41.6667%;\n}\n.qti-width-6-12 {\n width: 50%;\n}\n.qti-width-7-12 {\n width: 58.3333%;\n}\n.qti-width-8-12 {\n width: 66.6667%;\n}\n.qti-width-9-12 {\n width: 75%;\n}\n.qti-width-10-12 {\n width: 83.3333%;\n}\n.qti-width-11-12 {\n width: 91.6667%;\n}\n.qti-width-full,\n.qti-fullwidth {\n width: 100%;\n}\n/* ==================\n Text Indent styles\n ================== */\n.qti-text-indent-0 {\n text-indent: 0;\n}\n.qti-text-indent-px {\n text-indent: 1px;\n}\n.qti-text-indent-0p5 {\n text-indent: 0.125rem;\n}\n.qti-text-indent-1 {\n text-indent: 0.25rem;\n}\n.qti-text-indent-1p5 {\n text-indent: 0.375rem;\n}\n.qti-text-indent-2 {\n text-indent: 0.5rem;\n}\n.qti-text-indent-2p5 {\n text-indent: 0.625rem;\n}\n.qti-text-indent-3 {\n text-indent: 0.75rem;\n}\n.qti-text-indent-3p5 {\n text-indent: 0.875rem;\n}\n.qti-text-indent-4 {\n text-indent: 1rem;\n}\n.qti-text-indent-5 {\n text-indent: 1.25rem;\n}\n.qti-text-indent-6 {\n text-indent: 1.5rem;\n}\n.qti-text-indent-7 {\n text-indent: 1.75rem;\n}\n.qti-text-indent-8 {\n text-indent: 2rem;\n}\n.qti-text-indent-12 {\n text-indent: 3rem;\n}\n.qti-text-indent-16 {\n text-indent: 4rem;\n}\n.qti-text-indent-20 {\n text-indent: 5rem;\n}\n.qti-text-indent-24 {\n text-indent: 6rem;\n}\n.qti-text-indent-28 {\n text-indent: 7rem;\n}\n.qti-text-indent-32 {\n text-indent: 8rem;\n}\n/* =================\n List Style styles\n ================= */\n.qti-list-style-type-none {\n list-style-type: none;\n}\n.qti-list-style-type-disc {\n list-style-type: disc;\n}\n.qti-list-style-type-circle {\n list-style-type: circle;\n}\n.qti-list-style-type-square {\n list-style-type: square;\n}\n.qti-list-style-type-decimal {\n list-style-type: decimal;\n}\n.qti-list-style-type-decimal-leading-zero {\n list-style-type: decimal-leading-zero;\n}\n.qti-list-style-type-lower-alpha {\n list-style-type: lower-alpha;\n}\n.qti-list-style-type-upper-alpha {\n list-style-type: upper-alpha;\n}\n.qti-list-style-type-lower-roman {\n list-style-type: lower-roman;\n}\n.qti-list-style-type-upper-roman {\n list-style-type: upper-roman;\n}\n.qti-list-style-type-lower-latin {\n list-style-type: lower-latin;\n}\n.qti-list-style-type-upper-latin {\n list-style-type: upper-latin;\n}\n.qti-list-style-type-lower-greek {\n list-style-type: lower-greek;\n}\n.qti-list-style-type-arabic-indic {\n list-style-type: arabic-indic;\n}\n.qti-list-style-type-armenian {\n list-style-type: armenian;\n}\n.qti-list-style-type-lower-armenian {\n list-style-type: lower-armenian;\n}\n.qti-list-style-type-upper-armenian {\n list-style-type: upper-armenian;\n}\n.qti-list-style-type-bengali {\n list-style-type: bengali;\n}\n.qti-list-style-type-cambodian {\n list-style-type: cambodian;\n}\n.qti-list-style-type-simp-chinese-formal {\n list-style-type: simp-chinese-formal;\n}\n.qti-list-style-type-simp-chinese-informal {\n list-style-type: simp-chinese-informal;\n}\n.qti-list-style-type-trad-chinese-formal {\n list-style-type: trad-chinese-formal;\n}\n.qti-list-style-type-trad-chinese-informal {\n list-style-type: trad-chinese-informal;\n}\n.qti-list-style-type-cjk-ideographic {\n list-style-type: cjk-ideographic;\n}\n.qti-list-style-type-cjk-heavenly-stem {\n list-style-type: cjk-heavenly-stem;\n}\n.qti-list-style-type-cjk-earthly-branch {\n list-style-type: cjk-earthly-branch;\n}\n.qti-list-style-type-devanagari {\n list-style-type: devanagari;\n}\n.qti-list-style-type-ethiopic-halehame-ti-er {\n list-style-type: ethiopic-halehame-ti-er;\n}\n.qti-list-style-type-ethiopic-halehame-ti-et {\n list-style-type: ethiopic-halehame-ti-et;\n}\n.qti-list-style-type-ethiopic-halehame-am {\n list-style-type: ethiopic-halehame-am;\n}\n.qti-list-style-type-ethiopic-halehame {\n list-style-type: ethiopic-halehame;\n}\n.qti-list-style-type-georgian {\n list-style-type: georgian;\n}\n.qti-list-style-type-gujarati {\n list-style-type: gujarati;\n}\n.qti-list-style-type-gurmukhi {\n list-style-type: gurmukhi;\n}\n.qti-list-style-type-hangul {\n list-style-type: hangul;\n}\n.qti-list-style-type-hangul-consonant {\n list-style-type: hangul-consonant;\n}\n.qti-list-style-type-hebrew {\n list-style-type: hebrew;\n}\n.qti-list-style-type-hiragana {\n list-style-type: hiragana;\n}\n.qti-list-style-type-hiragana-iroha {\n list-style-type: hiragana-iroha;\n}\n.qti-list-style-type-khmer {\n list-style-type: khmer;\n}\n.qti-list-style-type-korean-hangul-formal {\n list-style-type: korean-hangul-formal;\n}\n.qti-list-style-type-korean-hanja-formal {\n list-style-type: korean-hanja-formal;\n}\n.qti-list-style-type-korean-hanja-informal {\n list-style-type: korean-hanja-informal;\n}\n.qti-list-style-type-lao {\n list-style-type: lao;\n}\n.qti-list-style-type-malayalam {\n list-style-type: malayalam;\n}\n.qti-list-style-type-mongolian {\n list-style-type: mongolian;\n}\n.qti-list-style-type-myanmar {\n list-style-type: myanmar;\n}\n.qti-list-style-type-oriya {\n list-style-type: oriya;\n}\n.qti-list-style-type-persian {\n list-style-type: persian;\n}\n.qti-list-style-type-thai {\n list-style-type: thai;\n}\n.qti-list-style-type-tibetan {\n list-style-type: tibetan;\n}\n.qti-list-style-type-telugu {\n list-style-type: telugu;\n}\n.qti-list-style-type-urdu {\n list-style-type: urdu;\n}\n/* =========================\n Other QTI 3 Presentation Utilities\n ========================= */\n.qti-bordered {\n border: 1px solid var(--table-border-color);\n}\n.qti-underline {\n text-decoration: underline;\n text-decoration-color: var(--foreground);\n}\n.qti-italic {\n font-style: italic;\n}\n.qti-well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: var(--well-bg);\n border: var(--well-border);\n border-radius: 4px;\n box-shadow: var(--well-box-shadow);\n}\n/* Set writing-mode to vertical-rl \n Typical for CJK vertical text */\n.qti-writing-mode-vertical-rl {\n writing-mode: vertical-rl;\n}\n/* Set writing-mode to vertical-lr\n Typical for Mongolian vertical text */\n.qti-writing-mode-vertical-lr {\n writing-mode: vertical-lr;\n}\n/* Set writing-mode to horizontal-tb \n Browser default */\n.qti-writing-mode-horizontal-tb {\n writing-mode: horizontal-tb;\n}\n/* Float an element left */\n.qti-float-left {\n float: left;\n}\n/* Float an element right */\n.qti-float-right {\n float: right;\n}\n/* Remove a float */\n.qti-float-none {\n float: none;\n}\n/* Clearfix Hack to apply to a container of \n floated content that overflows the container. */\n.qti-float-clearfix::after {\n content: '';\n clear: both;\n display: table;\n}\n.qti-float-clear-left\n .qti-float-clear-right\n .qti-float-clear-both\n \n /* Set text-orientation to upright */\n .qti-text-orientation-upright {\n text-orientation: upright;\n}\n@layer qti-base {\n .qti-layout-row {\n display: flex;\n flex-wrap: wrap;\n width: 100%;\n gap: 2.1276595745%;\n }\n\n .qti-layout-row [class*='qti-layout-col']:not(:empty) {\n box-sizing: border-box;\n }\n\n .qti-layout-row [class*='qti-layout-col']:empty {\n width: 0;\n overflow: hidden; /* to fully collapse if there\u2019s padding or borders */\n }\n\n .qti-layout-col1 {\n width: 6.3829787234%;\n }\n\n .qti-layout-col2 {\n width: 14.8936170213%;\n }\n\n .qti-layout-col3 {\n width: 23.4042553191%;\n }\n\n .qti-layout-col4 {\n width: 31.914893617%;\n }\n\n .qti-layout-col5 {\n width: 40.4255319149%;\n }\n\n .qti-layout-col6 {\n width: 48.9361702128%;\n }\n\n .qti-layout-col7 {\n width: 57.4468085106%;\n }\n\n .qti-layout-col8 {\n width: 65.9574468085%;\n }\n\n .qti-layout-col9 {\n width: 74.4680851064%;\n }\n\n .qti-layout-col10 {\n width: 82.9787234043%;\n }\n\n .qti-layout-col11 {\n width: 91.4893617021%;\n }\n\n .qti-layout-col12 {\n width: 100%;\n }\n\n .qti-layout-offset1 {\n margin-left: 8.5106382979%;\n }\n\n .qti-layout-offset2 {\n margin-left: 17.0212765957%;\n }\n\n .qti-layout-offset3 {\n margin-left: 25.5319148936%;\n }\n\n .qti-layout-offset4 {\n margin-left: 34.0425531915%;\n }\n\n .qti-layout-offset5 {\n margin-left: 42.5531914894%;\n }\n\n .qti-layout-offset6 {\n margin-left: 51.0638297872%;\n }\n\n .qti-layout-offset7 {\n margin-left: 59.5744680851%;\n }\n\n .qti-layout-offset8 {\n margin-left: 68.085106383%;\n }\n\n .qti-layout-offset9 {\n margin-left: 76.5957446809%;\n }\n\n .qti-layout-offset10 {\n margin-left: 85.1063829787%;\n }\n\n .qti-layout-offset11 {\n margin-left: 93.6170212766%;\n }\n\n @media (width <= 767px) {\n [class*='qti-layout-col'] {\n width: 100%;\n }\n }\n}\n@layer qti-components.reset{\n@scope (qti-item-body, .ProseMirror) {\n :scope,\n :scope * {\n box-sizing: border-box;\n }\n\n :scope *::before,\n :scope *::after {\n box-sizing: border-box;\n }\n\n button,\n input,\n textarea,\n select {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n color: inherit;\n font: inherit;\n }\n\n img {\n max-width: 100%;\n height: auto;\n }\n\n [response-identifier] img {\n max-width: none;\n }\n\n /* Normalize author markup paragraphs only in text-bearing QTI elements:\n - If a paragraph is the only child, remove all margins.\n - Keep this explicit allowlist to avoid resetting arbitrary wrapper markup. */\n :scope\n :is(\n qti-prompt,\n qti-simple-choice,\n qti-associable-choice,\n qti-simple-associable-choice,\n qti-gap,\n qti-gap-text,\n qti-inline-choice,\n qti-hottext\n )\n > p:only-of-type {\n margin: 0;\n }\n}\n}\n/* Plain HTML author markup (tables, lists, rules). Sibling to reset.css, same @scope. */\n/**\n * Prose \u2014 plain HTML author markup: tables, lists, headings, rules.\n *\n * Sibling to reset.css and scoped the same way, to (qti-item-body, .ProseMirror), so one definition\n * covers a rendered item body and an editor document. reset.css normalises author markup; this file\n * is where it is given a look.\n *\n * \u2500\u2500 Why the two scopes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Rendered QTI deliberately has almost no prose look. A table in an item body is author content, and\n * the QTI presentation vocabulary makes its appearance an author decision \u2014 `.qti-bordered` exists\n * precisely so borders are opt-in. Styling tables by default here would take that decision away and\n * silently restyle every delivered item.\n *\n * Authoring is the opposite: you cannot edit a grid you cannot see. So the visible grid is scoped to\n * `.ProseMirror` alone, and the shared scope stays reserved for rules that are genuinely wanted in\n * both places. Keeping the split explicit is the point \u2014 a rule moved from the second scope to the\n * first is a change to delivered items, and should be made on purpose (and re-baselined: the\n * Kennisnet VRT set covers this markup).\n *\n * Values route through the theme tokens with a literal fallback, so prose tracks a brand for free.\n * The --qti-prose-* names are the override surface.\n */\n@layer qti-components.elements {\n /*\n * Shared \u2014 rendered item bodies AND editor documents.\n *\n * Intentionally empty. Every candidate rule so far (border-collapse, cell padding) moves rendered\n * items, which is a product decision rather than a styling gap. Add here only with that in mind.\n */\n\n /*\n * Authoring only \u2014 the editor's document.\n */\n @scope (.ProseMirror) {\n :scope table {\n border-collapse: collapse;\n margin: var(--qti-prose-table-margin, 0.5rem 0);\n }\n\n :scope th,\n :scope td {\n border: var(--qti-prose-table-border-width, var(--qti-border-thickness, 1px))\n var(--qti-prose-table-border-style, var(--qti-border-style, solid))\n var(--qti-prose-table-border-color, var(--qti-border-color, #c6cad0));\n padding: var(--qti-prose-table-cell-padding, 0.25rem 0.5rem);\n text-align: left;\n /* No vertical-align here: that is layout, and the theme does not do layout (CONTRACT.md \xA77,\n enforced by qti/no-layout-in-theme). Cells keep the UA default. */\n }\n\n :scope th {\n background: var(--qti-prose-table-header-bg, var(--qti-bg-active, #f6f8fa));\n font-weight: 600;\n }\n }\n}\n/* QTI base \u2014 design variables. The theme's tokens; a substrate overrides their VALUES to\n * express its look (value first). Split from qti-base.css. See also qti-mixins.css.\n *\n * Grouped into the same cascade layers the rest of the theme uses (see index.css and\n * CONTRACT.md \xA72), so the declaring layer documents which kind of rule OWNS a token.\n *\n * The layer is documentation, not scope: custom properties are inherited computed values, so a\n * token declared in `global` is readable from a rule in any layer, and `var()` substitution across\n * these blocks (--qti-correct reads --qti-success) is layer-independent. Nothing here renders\n * differently for being grouped this way.\n */\n/* ============================================================================\n * global \u2014 the foundation every layer reads.\n * ========================================================================== */\n@layer qti-components.global {\n :root,\n :host {\n /* \u2500\u2500 Base palette \u2014 six concrete brand colours. A theme replaces just these. \u2500\u2500 */\n --qti-primary: #0175aa; /* purple \u2014 chips, selection, active border */\n --qti-success: #2b830e; /* green */\n --qti-warning: #ffa700; /* amber */\n --qti-error: #df0000; /* red */\n --qti-info: #007ac3; /* blue \u2014 the answer key */\n\n /* Foreground on a primary fill. */\n --qti-primary-fg: #fff;\n\n /* \u2500\u2500 Rhythm \u2500\u2500\n *\n * --qti-spacing is THE unit, and the only knob here a substrate normally touches. Everything\n * else in this group is a multiple of it, so retuning the whole theme's rhythm is one value.\n *\n * It replaces --qti-padding-vertical (0.5rem) and --qti-padding-horizontal (1rem). Those were\n * always the unit and twice the unit, and every derived value in the theme was a half, whole\n * or double of one of them, so the pair carried no information the single token doesn't.\n *\n * This is the SPACING axis \u2014 distance between and around things. The SIZING axis is separate\n * and deliberately not derived from it: --qti-control-size (radio/checkbox), --qti-correction-size,\n * --qti-graphic-order-pin-size, --qti-select-point-marker-size. A control's size tracks its\n * content and its hit target, not the page's rhythm, so the two scales move independently.\n *\n * THE CONTRACT: an interaction reads exactly one of the three composites below, whole. Never\n * --qti-spacing itself, and never a calc() over one of them \u2014 a call site that needs \"half of\n * a gap\" is describing a fourth value that does not exist in the vocabulary, and the answer is\n * to pick the nearest of the three, not to do arithmetic:\n *\n * --qti-padding-box the block box (choice, rubric, modal, field, listbox row, cell)\n * --qti-padding-inline the inline box (hottext, inline-choice trigger, gap)\n * --qti-gap every scalar (flex/grid gap, margin, offset, inset)\n *\n * --qti-spacing is the unit those are cut from, not a value to paint with. It is the only knob\n * a substrate normally sets; the three move with it.\n *\n * --qti-gap doubling as \"every scalar\" is the load-bearing compromise. Two of the three tokens\n * are shorthands, so nothing else in the vocabulary can express a bare length \u2014 which is why\n * the theme's old 0.25rem and 0.5rem one-off offsets are now all 1rem. That was chosen over\n * adding scalar steps: one obvious gap everywhere beats six near-identical spacings nobody can\n * tell apart. If a specific spot genuinely needs to be tighter, give it a NAMED component token\n * (like --qti-drag-padding below) rather than reaching back for --qti-spacing.\n *\n * --qti-padding-inline was the literal `2px 4px`, which is exactly a quarter and a half of the\n * unit at a 16px root. Deriving it means it now scales with the rhythm rather than staying\n * pinned to device pixels \u2014 the same reason nothing else here is a literal.\n *\n * Widening boxes without touching the vertical rhythm means re-declaring --qti-padding-box,\n * which is deliberate: that is a different look, not a different unit. */\n --qti-spacing: 0.5rem;\n --qti-padding-box: var(--qti-spacing) calc(var(--qti-spacing) * 2);\n --qti-padding-inline: calc(var(--qti-spacing) / 4) calc(var(--qti-spacing) / 2);\n --qti-gap: calc(var(--qti-spacing) * 2);\n\n --qti-popover-z-index: 1000;\n --qti-overlay-z-index: 1;\n /* Motion master switch \u2014 a unitless multiplier every animation scales by. 1 = normal, 0 = no\n motion. CSS transitions read it through the calc below; the drag-drop FLIP engine reads\n --qti-motion directly (see flip.utils.ts resolveMotionScale), so this one token silences both\n CSS motion and JS FLIP. A theme or PNP profile can set 0.5 to slow motion or 0 to stop it. */\n --qti-motion: 1;\n --qti-motion-duration-fast: calc(150ms * var(--qti-motion));\n\n /* \u2500\u2500 The component surface \u2500\u2500\n The theme's base surface and border. Read by the paint contract below, and \u2014 separately \u2014 by\n ~30 rules that draw a box WITHOUT going through it (tables, the modal, hotspot rings, the\n slider track). Those two readerships are why this group and the --qti-component-* group below\n are not one group: this is \"the theme's surface colour\", that is \"what a component paints when\n it says nothing\", and only the second is the paint contract's business. */\n --qti-bg: white;\n\n /*\n * THE body text colour \u2014 the foreground half of the --qti-bg / --qti-fg pair.\n *\n * `inherit`, deliberately: the base theme does not impose a text colour. What a candidate reads\n * is the host page's, and a brand that wants its own sets this token (kennisnet.css declares\n * #3e144e). A base theme that cannot pick the colour also cannot pick a failing one.\n *\n * This is a FIX that changes nothing, which is the point. It read `var(#aaaaaa)` \u2014 a value\n * wrapped in var() by mistake in 925bdf14. var() takes a custom-property name, so `#aaaaaa` made\n * the whole thing invalid and --qti-fg computed to the guaranteed-invalid value; every\n * `color: var(--qti-fg)` was then invalid at computed-value time and, `color` being inherited,\n * fell back to the inherited colour. Measured, and `inherit` is precisely that behaviour said out\n * loud.\n *\n * Not restored to a literal `#aaaaaa`: at 2.3:1 on white that fails WCAG AA for body text, and\n * the readers here are the candidate's own typed answer (@mixin field-typography paints the\n * text-entry input and the extended-text textarea), the inline-choice value, the validation\n * message and modal feedback.\n *\n * Every reader is a colour context, which is what makes the keyword safe: it reaches `color`\n * either directly or through a role slot (--field-color, --drag-color, @mixin candidate).\n */\n --qti-fg: inherit;\n --qti-border-thickness: 1px; /* some places need the width alone (invariance, calcs) */\n --qti-border-style: solid;\n --qti-border-color: var(--qti-primary); /* THE border colour \u2014 the border half of the --qti-bg / --qti-fg pair */\n --qti-border-radius: 0.2rem;\n --qti-border: var(--qti-border-thickness) var(--qti-border-style) var(--qti-border-color);\n\n /* \u2500\u2500 The paint contract's resting look \u2500\u2500\n *\n * One token per PAINT property, and the whole of what a role paints when it sets nothing. These\n * used to be written inline in the mixin, as the second half of `var(--component-x, var(--qti-y))`\n * \u2014 the fallbacks were the resting look, but you had to read the mixin to find out what it was.\n * They are tokens now, so the resting box reads in one place and a substrate can retune it here\n * (value first) without touching a mixin.\n *\n * `@mixin paint $role` in qti-mixins.css binds each property to exactly one of these, behind a\n * per-role override SLOT: `var(--drag-background-color, var(--qti-component-background-color))`.\n * Two tiers, and they are different kinds of name on purpose \u2014\n * --qti-component-* a TOKEN. Declared here, has a value, applies to every role at once.\n * --drag-* / --drop-* a SLOT. Declared nowhere; only ever read with this fallback. Setting one\n * repaints that role alone, so a drop can no longer leak its fill into a\n * chip standing in it (custom properties inherit \u2014 the old single\n * --component-* namespace meant every nested component shared one value).\n *\n * `color: inherit` is the resting rule for text, not a colour: a component's text is whatever the\n * text around it is. It reaches `color` through the same two-tier chain as everything else, and\n * lands on the inherited colour either way \u2014 the keyword substitutes, and if a browser rejects a\n * CSS-wide keyword out of var() it is invalid at computed-value time, which for an inherited\n * property is also the inherited value. */\n --qti-component-background-color: var(--qti-bg);\n --qti-component-background-image: none;\n --qti-component-border-width: var(--qti-border-thickness);\n --qti-component-border-style: var(--qti-border-style);\n --qti-component-border-color: var(--qti-border-color);\n --qti-component-border-radius: var(--qti-border-radius);\n --qti-component-box-shadow: none;\n --qti-component-color: inherit;\n --qti-component-opacity: 1;\n --qti-component-filter: none;\n\n /* Interaction icon masks. Kept in the QTI namespace so the theme has no FontAwesome runtime\n dependency. One vocabulary, kept together even though --qti-times-mask is read only from\n the corrections layer. */\n --qti-check-mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z'/%3E%3C/svg%3E\");\n --qti-times-mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512'%3E%3Cpath d='M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z'/%3E%3C/svg%3E\");\n /* fa-minus. The third verdict glyph, beside the tick and the cross: neither right nor wrong.\n A SHAPE rather than a shade, deliberately \u2014 colour alone would leave partial credit\n indistinguishable from correct for a colour-blind candidate, or in greyscale print. */\n --qti-partial-mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M432 256c0 17.7-14.3 32-32 32L48 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l352 0c17.7 0 32 14.3 32 32z'/%3E%3C/svg%3E\");\n --qti-grip-mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath d='M40 352l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40zm192 0l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40zM40 320c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0zM232 192l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40zM40 160c-22.1 0-40-17.9-40-40L0 72C0 49.9 17.9 32 40 32l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0zM232 32l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40z'/%3E%3C/svg%3E\");\n --qti-chevron-mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z'/%3E%3C/svg%3E\");\n\n /*\n * Accent-tinted \"active / selected surface\" \u2014 the canonical pair, and one of the global knobs a\n * theme flips. Read by `@mixin act` and ~27 call sites across drag-drop, inline-choice,\n * match-tabular, slider and modal-feedback: the paint for \"this box is active / open / a drop\n * target.\" Derived from --qti-primary over --qti-bg so it tracks the accent colour (and, later, a\n * light/dark surface). The --qti-drop-highlight-* vocabulary in interactions.states is the\n * drop-specific SUPERSET that derives from THESE and adds the shadow + `enabled` variants.\n *\n * Deliberately in `global`: they are read from both interactions.base and interactions.states,\n * so no single interaction layer could own them.\n */\n --qti-bg-active: color-mix(in srgb, var(--qti-primary) 8%, var(--qti-bg));\n --qti-border-active: var(--qti-primary);\n\n /* \u2500\u2500 Slated for removal \u2014 still referenced across mixins/interactions \u2500\u2500\n *\n * focus \u2192 a single :focus-visible outline (no vars)\n * Two treatments already read these two tokens: `@mixin foc` draws a box-shadow, while\n * hotspot and graphic-order draw an outline. The box-shadow half is the problem \u2014 four\n * rules contest that one property on a drag (the resting lift, the in-flight elevation,\n * the placeholder's inset, the focus ring) and the last one declared wins, so focusing a\n * drag mid-interaction drops either its ring or its elevation. An outline joins no such\n * fight and costs no layout. The theme also still says `:focus` 24 times against\n * `:focus-visible` 10, so most rings show on mouse click too. Once it is an outline on\n * :focus-visible, a width and a colour are not worth tokenising.\n * The four --qti-light-* and --qti-dark-* tokens that used to sit here are now\n * --qti-selections-*, in the interactions.states block below. They are not a page-level light\n * and dark theme, which is what the old names suggested and why they were mistaken for dead.\n */\n --qti-focus-border-width: 2px;\n --qti-focus-color: var(--qti-info);\n }\n\n /* Honour the OS \"reduce motion\" request through the same knob, so CSS transitions and the JS FLIP\n engine switch off together. A theme can still force motion back on by re-declaring --qti-motion\n with higher specificity. */\n @media (prefers-reduced-motion: reduce) {\n :root,\n :host {\n --qti-motion: 0;\n }\n }\n}\n/* ============================================================================\n * interactions.base \u2014 interaction-local geometry. Neutral, no judgement colour.\n * ========================================================================== */\n@layer qti-components.interactions.base {\n :root,\n :host {\n /*\n * The radio/checkbox box. Named for what it is \u2014 every other name around it already says\n * \"control\": part=\"control\", part=\"control-mark\", the control-selected mixin. \"form\" was the odd\n * one out and needed a comment to say which form thing it meant.\n *\n * `em`, so a control is sized by the text it belongs to. A radio labels its choice; when that\n * choice's text is set larger or smaller the control should follow, which `rem` cannot do \u2014 it\n * is pinned to the root regardless of the type around it. Same reasoning, and the same value, as\n * --qti-correction-size above: \"em keeps a badge sized by the text it annotates\". It is also\n * what the SIZING note at the top of this file already claims for these tokens \u2014 that a\n * control's size tracks its content rather than the page's rhythm.\n *\n * Costs nothing to adopt: every control host in the interactions renders at the root size today\n * (measured across choice, tabular match and hottext), so 1em and 1rem compute identically and\n * no baseline moves. It simply starts behaving when someone scales type.\n *\n * The one thing to watch is the hit target in deliberately small text, where the control now\n * shrinks with it. Nothing in the theme sets a smaller size yet, so a floor would be a magic\n * number guarding a case that does not exist; add `max()` here if a real one turns up.\n */\n --qti-control-size: 1em;\n\n /* The correction badge's box. Named in two comments above and read by correction.styles.ts, but\n never actually declared until now \u2014 every badge has been running on that file's `1em`\n fallback. Declared at the same value, so nothing moves; it is a real token now rather than a\n promise, and `em` keeps a badge sized by the text it annotates (same reasoning as\n --qti-glyph-gap below). */\n --qti-correction-size: 1em;\n\n /*\n * How far a correction badge sits from the trailing edge of the box it annotates.\n *\n * Only two interactions need it. Everywhere else the badge is a child of the element that carries\n * the padding, so it is inset by that padding for free \u2014 12.8px on a chip, 16px on a choice, 4px\n * on a hottext, each matching how far that container insets its own text. text-entry and\n * inline-choice are the exception: their field box is the HOST, but the padding lives on an inner\n * ::part(input) / ::part(trigger), so their badge falls outside it and lands on the border. This\n * gives it back the same inset the text has.\n *\n * A NAMED token rather than a calc over --qti-padding-box, which the rhythm contract above\n * forbids; --qti-drag-padding is the precedent. It is --qti-gap because that IS the horizontal\n * half of --qti-padding-box, which is the padding those two inner elements use.\n */\n --qti-correction-inset: var(--qti-gap);\n\n /* The line-height @mixin field-typography gives a field. A token because the correction badge\n has to be exactly one field-line tall to centre on the text, and `1lh` cannot be used for it:\n that unit resolves against the badge's own inherited line-height, not the field's. */\n --qti-field-line-height: 1.5;\n\n /*\n * The left gutter a multi-line field reserves for its correction badge.\n *\n * DERIVED, so it cannot drift from the thing it makes room for: the badge's own inset, plus the\n * badge, plus the gap that separates it from the first character. It used to be a literal\n * `2.4rem` in the theme that had to be kept in agreement, by hand and across two packages, with a\n * `0.75rem` left offset in the correction component.\n *\n * Reserved unconditionally, graded or not \u2014 padding is layout, and switching it on a verdict\n * would re-wrap the candidate's answer at the moment they are marked.\n */\n --qti-correction-gutter: calc(var(--qti-correction-inset) + var(--qti-correction-size) + var(--qti-glyph-gap));\n\n /* The gap between a masked glyph and the text it sits beside \u2014 the drag-handle grip before a\n label, the correction badge after one. `em`, because a glyph is sized off the text it\n annotates (see --qti-correction-size, --qti-grip-mask), so its gap has to scale with the same\n text rather than with the root.\n Deliberately NOT --qti-gap: that token is the page's rhythm at 1rem, an order of magnitude too\n wide for a glyph sitting inside a line. This is the \"named component token\" escape hatch the\n rhythm block above sanctions, and it is ONE token rather than two because the grip's 0.5em and\n the badge's 0.4em were the same measurement written twice.\n 0.5em is the grip's old value, so the grip is untouched and the badge moved out by 0.1em. That\n is not cosmetic in the VRT corpus: the badge is inline, so a tenth of an em reflows the line it\n sits on, and 10 of 17 kennisnet snapshots shifted (up to 2.9% of pixels). Bisected the other\n way, 0.4em holds the badge still and moves 5 drag/order snapshots instead \u2014 there is no value\n that is neutral for both, which is exactly why the duplication was worth collapsing. */\n --qti-glyph-gap: 0.5em;\n /*\n * \u2500\u2500 Drop sizing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Four names, one job each, and \u2014 the load-bearing part \u2014 TWO OF THEM ARE DELIBERATELY NOT\n * DECLARED HERE:\n *\n * --qti-dropzone-min-height / -min-width MEASURED. The box a drop reserves for one chip.\n * --qti-drag-min-width MEASURED. A chip's own floor \u2014 the mixin writes it the same value\n * as the drop's, which is what keeps a chip and its slot the same\n * size without a rule relating the two. Unmeasured it is 0, from\n * the fallback in `@mixin drag`, so a chip keeps its natural width.\n * --qti-drop-card-min-height a category card: match's targets, which collect several answers.\n * --qti-drop-track-min-width the floor of a drops-grid COLUMN. Not a drop's own width.\n *\n * Only the last two are declared below. The measured three are written at runtime and never\n * declared anywhere \u2014 see the paragraph under this list, and no-declared-measured-token.mjs.\n *\n * WHY THE MEASURED PAIR MUST STAY UNDECLARED. Every consumer reads them as\n * `var(--qti-dropzone-min-*, N)`, where N is what that drop is worth when nothing measured it.\n * Declaring them here makes every one of those fallbacks unreachable \u2014 the inherited value\n * always substitutes \u2014 so the whole \"unmeasured\" branch silently disappears, and code that\n * DECLINES to publish a measurement (see `autoSizeDropzoneWidth`, which is how an authored\n * `data-choices-container-width` takes the width axis) no longer means \"no floor\", it means\n * \"whatever is declared here\". That is not hypothetical: declaring them at 3rem/8rem gave a\n * graphic-gap-match hotspot a 128px min-width that beat its authored 100px `width`, and shrank\n * match's cards from 4rem to 3rem without any rule mentioning cards. Both were one commit.\n *\n * If you want to size drops by hand with no mixin involved, set the two names on the\n * interaction \u2014 closer than :root, which is where an owner of an axis is supposed to speak.\n * `tools/stylelint/no-declared-measured-token.js` enforces the rule so this cannot come back.\n *\n * WHY A CHIP AND A DROP HAVE SEPARATE WIDTHS, when the measurement gives them the same value:\n * their unmeasured defaults differ. A chip with no measurement keeps its natural width, so 0.\n * A drop with no measurement is at its consumer's fallback. `@mixin drag` reads\n * --qti-drag-min-width; everything else reads --qti-dropzone-min-width.\n *\n * WHY THE CARD IS SEPARATE: match publishes no measurement at all, so a card sized off\n * --qti-dropzone-min-height depends entirely on a fallback literal \u2014 fragile, as above. Whether\n * a drop is a slot or a card is a property of the INTERACTION, not of a drop's own `match-max`:\n * a match-max=\"1\" target is still a category that happens to take one answer, and it sits in a\n * grid beside targets that take several.\n *\n * WHY THE TRACK IS SEPARATE: a column floor and a drop's own min-width are unrelated jobs that\n * happened to want the same number in order. --qti-drop-min-width already died of exactly that\n * conflation (it meant the track floor in order and a drop's min-width in the match target),\n * and folding the track back into --qti-dropzone-min-width reintroduced it. A track that must\n * also fit a measured chip asks for both by hand:\n * minmax(max(var(--qti-drop-track-min-width), var(--qti-dropzone-min-width, 0px)), 1fr)\n *\n * Everything else that used to be a token here is a plain declaration on the one rule that wants\n * it, because `::part(drop)` already reaches every drop from the document and a custom property\n * in between adds a name without adding reach:\n *\n * --qti-drop-min-height, --qti-drop-min-width, --qti-match-target-min-width collapsed into\n * the names above.\n * --qti-dropzone-padding, --qti-drop-gap deleted outright. A slot needs no inset at all (its\n * reservation is already the chip's border-box, so nothing is squeezed\n * and nothing shows), and the one drop that wants room \u2014 match's card \u2014\n * declares padding and gap on its own rule.\n *\n * See DROP-SIZING.md.\n */\n --qti-drop-card-min-height: 4rem;\n --qti-drop-track-min-width: 7.5rem;\n\n /* Hotspot selection ring (qti-hotspot-interaction.css). Resting stroke width \u2014 :state(checked)\n and the correction states double it. The halo is the near-white outline either side of the\n ring that keeps it legible over any background image (vocab \xA71.2.5.1 \"suitable contrast\").\n --qti-hotspot-ring-color is intentionally NOT defined here: it is neutral by default and set\n contextually by the qti-selections-* classes and the correction states. */\n --qti-hotspot-ring-width: 2px;\n --qti-hotspot-halo: rgb(255 255 255 / 85%);\n\n /*\n * A drag's own padding, deliberately not --qti-padding-box.\n *\n * `em`, so a drag scales with the text it wraps rather than with the root \u2014 a drag is a token\n * around a word, and a word that grows should take its box with it.\n *\n * It is a token because the two mixins that used to draw a drag disagreed here: `drag` paid the\n * horizontal padding (then --qti-padding-horizontal, now `calc(var(--qti-spacing) * 2)`) and\n * the old `chip` paid `0.8em`, and which one you got depended on the layer the call site\n * happened to sit in. That is the associate bug recorded in drag-drop.invariance.spec.ts \u2014 one\n * drag, two box models, depending on where it was standing.\n */\n --qti-drag-padding: var(--qti-spacing) 0.8em;\n }\n}\n/* ============================================================================\n * interactions.states \u2014 state paint. Selection, drag, and drop-target highlight.\n * ========================================================================== */\n@layer qti-components.interactions.states {\n :root,\n :host {\n /* Selection is a semantic alias of the primary action colour. */\n --qti-selected-bg: var(--qti-primary);\n --qti-selected-color: var(--qti-primary-fg);\n\n /* The hole a lifted / placed chip leaves \u2014 derived from primary. */\n --qti-placeholder-bg: color-mix(in srgb, var(--qti-primary) 20%, white);\n --qti-placeholder-shadow: 0 4px color-mix(in srgb, var(--qti-primary) 30%, white);\n\n /*\n * Selections theming, for the two interactions Appendix B.1 of the QTI 3 implementation guide\n * defines it on: qti-hotspot-interaction and qti-graphic-gap-match-interaction.\n *\n * `light` and `dark` name the colour the *selection* is drawn in, not a page theme \u2014 light for\n * a selection sitting over a dark image, dark for one over a light image. The point is that a\n * selection stays visible whatever the picture underneath it looks like, which is why the item\n * author chooses, not the substrate.\n *\n * These were --qti-selections-light-bg and friends, in `global`. Both halves of that were wrong:\n * the names read as a light/dark page theme, which is how they came to be mistaken for a dead\n * feature, and `global` claimed an ownership they never had \u2014 nothing outside these two\n * interactions' state rules reads them.\n */\n --qti-selections-light-bg: #f0f0f0;\n --qti-selections-light-border: #d0d0d0;\n --qti-selections-dark-bg: #1f2937;\n --qti-selections-dark-border: #64748b;\n\n /* A drop-zone highlight is a background FILL only \u2014 no border, no ring \u2014 which keeps it\n layout-invariant for free (a fill never reflows; the old inset ring existed only to avoid the\n 2px shift a real border would cause). One authored knob:\n --qti-drop-highlight-bg the fill on the hovered / active target (follows --qti-bg-active)\n --qti-drop-highlight-enabled-bg every droppable zone: a fainter wash, derived from it\n --qti-drop-highlight-border-color / -border-width stay ONLY for the chip-in-flight outline\n (the dragging / return-anchor states), which marks the dragged element, not the zone. */\n --qti-drop-highlight-bg: var(--qti-bg-active);\n --qti-drop-highlight-enabled-bg: color-mix(in srgb, var(--qti-drop-highlight-bg) 50%, transparent);\n --qti-drop-highlight-border-color: var(--qti-border-active);\n --qti-drop-highlight-border-width: 2px;\n }\n}\n/* ============================================================================\n * interactions.corrections \u2014 answer-key and judgement paint.\n * ========================================================================== */\n@layer qti-components.interactions.corrections {\n :root,\n :host {\n /* \u2500\u2500 Correction \u2014 derived from the semantic colours, never set directly. \u2500\u2500 */\n --qti-correct: var(--qti-success);\n --qti-incorrect: var(--qti-error);\n --qti-partially-correct: var(--qti-warning);\n --qti-correct-light: color-mix(in srgb, var(--qti-correct) 5%, white);\n --qti-incorrect-light: color-mix(in srgb, var(--qti-incorrect) 5%, white);\n --qti-partially-correct-light: color-mix(in srgb, var(--qti-partially-correct) 5%, white);\n\n /* The answer key \u2014 the info blue, deliberately NOT the correct green. */\n --qti-answer-fg: var(--qti-info);\n --qti-answer-bg: color-mix(in srgb, var(--qti-info) 5%, white);\n --qti-answer-border: var(--qti-info);\n }\n}\n/* QTI base \u2014 shared theme utilities and semantic mixins. Only styles >=2 interactions share.\n * They read the variables in qti-variables.css; a substrate re-colours by overriding those, not\n * by editing these. Split from qti-base.css. */\n/* ============================================================================\n * Shared semantic mixins. Only styles \u22652 interactions share belong here.\n * A substrate expresses its look by overriding the --qti-* variables above\n * (value first), not by editing these.\n * ========================================================================== */\n/*\n * Semantic PostCSS mixins. Interaction files wire these to their own parts and states; the mixins\n * own the shared visual vocabulary.\n */\n/*\n * A candidate correction, painted through the corrected element's own role.\n *\n * $role is whatever that element already is \u2014 `drag`, `button`, `text`, `field` \u2014 so this is the same\n * shape as `@mixin paint $role`, and obeys the same rule: a state repaints by SWITCHING a variable,\n * never by re-declaring the property. A brand's `--drag-*` and a correction's `--drag-*` are then the\n * same kind of declaration, resolved by the cascade rather than by one of them being a direct\n * property that the other can never outrank.\n *\n * This replaces the `candidate-chip` / `candidate-tint` pair. That split existed for one reason,\n * stated in candidate-chip's own comment: only chips had a paint binding, so everything else had to\n * correct by writing the four properties directly. Choices are `button`, text fields are `text` and\n * inline-choice is `field` now, so the reason is gone and one parameterized mixin covers all of them.\n *\n * `candidate-tint` below survives for the one corrected element with no role: qti-hottext.\n */\n/*\n * THE paint contract. Every visual component is, at rest, a bordered box on the theme's surface.\n * This mixin binds each PAINT property to a variable exactly once, so a state \u2014 or a theme \u2014\n * repaints by SWITCHING a variable, never by re-declaring the property. Selectors choose state;\n * variables describe appearance; this block renders it.\n *\n * $role is what the thing IS \u2014 `drag`, `drop`, `button`, `text`, `field`, `menu`, `message`. Nothing\n * calls this mixin directly: each role below calls it once with its own name, and that is the only\n * place a role name is written. Every property then reads a two-tier chain:\n *\n * var(--$(role)-background-color, var(--qti-component-background-color))\n * ^ per-role SLOT, declared nowhere ^ TOKEN, declared in qti-variables.css\n *\n * The role in the slot name is the point. It replaces a single shared --component-* namespace, and\n * custom properties INHERIT: under one namespace a drop that set --component-background-color\n * repainted every chip standing inside it, and --component-box-shadow set on a drop mid-drag leaked\n * its elevation onto nested chips (the bug the `no @mixin dragging on the drop` note in\n * qti-match-interaction.css records). A chip reads --drag-*, its drop reads --drop-*, so the values\n * still inherit but no longer collide, and `border-style` no longer has to be set as a direct\n * property to stay off the chips (see `drop`).\n *\n * Only paint lives here. box-sizing / padding / margin are STRUCTURAL and stay on the role (see\n * `drag`) \u2014 a state must never switch them or the layout-invariance contract breaks. For the same\n * reason border-WIDTH and -radius are bound but must be treated as constant: a state may switch\n * --drag-border-color, never --drag-border-width.\n *\n * Deliberately NOT bound yet (they need their own VRT-checked step): `outline` (touches focus\n * rings) and a MULTI-SLOT box-shadow (so an element's resting lift, in-flight elevation and focus\n * ring compose instead of the last one winning \u2014 the documented four-rule box-shadow contention).\n */\n/*\n * A drag: the movable token a candidate drags into a gap, a drop target or an order slot.\n *\n * This used to be two mixins. `drag` drew the box \u2014 box-sizing, colours, a full `border`\n * shorthand, padding, `cursor: grab` \u2014 and a second one, `chip`, drew the skin over it: the same\n * colours again, the same radius, a lift, and `border-color` alone. Because that second mixin only\n * ever recoloured a border it could not create, the two had to be applied in that order, from two\n * different cascade layers, to every drag. Nine call sites did; a few did not, and those are where\n * the box models diverged \u2014 see the `associate` note in drag-drop.invariance.spec.ts.\n *\n * They are one mixin now, and it is the whole drag. Nothing has to run before it.\n *\n * `box-sizing` is deliberate and load-bearing: the floating clone is appended to document.body,\n * outside reset.css's `@scope (qti-item-body)`, so it inherits no border-box from anywhere else.\n * Under content-box the clone's padding would be added to the width the drag code measured off the\n * bank drag, and it would jump the instant it was picked up.\n */\n/*\n * The hole a drag leaves behind: lifted mid-drag, or spent because its copy is already placed.\n *\n * A modifier, not a look of its own. It repaints a drag and must be applied *with* `drag`, never\n * instead of it \u2014 `border-color: transparent` needs a border to make transparent, and the padding\n * holding the hole open is the drag's own. That is why `:state(drag)` survives `:state(placeholder)`:\n * an element that stopped being a drag when it was placed would take its footprint with it, and the\n * bank would reflow around a hole 20px narrower than the thing that left it. Measured in\n * drag-drop.invariance.spec.ts, \"a spent drag keeps its footprint\".\n */\n/*\n * The drag handle glyph.\n *\n * `display` and `vertical-align` are layout, and stay here under the pseudo-element exception in\n * CONTRACT.md \xA77: the theme creates this ::before, so no component can lay it out. One of its three\n * call sites is `[data-drag-clone]::before`, on a div the drag-drop JS builds at pointer-down \u2014\n * there is no shadow root to delegate to at all.\n *\n * The exception is narrow on purpose: a theme-authored pseudo may place its own box and nothing\n * else. This one is in flow and takes real width, which is why it reserves an explicit width/height\n * rather than leaning on the glyph's intrinsic size.\n */\n/*\n * The radio and the checkbox: the other two things this theme draws as boxes, and so the other two\n * paint roles. A control is a bordered box on the theme's surface exactly like a chip is, and now it\n * says so \u2014 a third party restyles a checkbox through --checkbox-background-color the same way it\n * restyles a chip through --drag-background-color, with no specificity contest and nothing to\n * redeclare.\n *\n * They replace `form`, and the single-use `bordered` it wrapped, which hand-wrote this same paint\n * outside the contract: a `border` shorthand, then `border-width: thin` over the top of it, then a\n * hardcoded `background-color: white`. All three land on identical computed values through the\n * contract \u2014 `thin` computes to 1px, which is what --qti-component-border-width is, and\n * --qti-component-background-color resolves through --qti-bg to white. Measured, not assumed. The\n * one behavioural difference is the improvement: the fill now tracks the theme's surface instead of\n * being pinned to the literal `white`.\n *\n * Size stays a DIRECT property, deliberately. --qti-control-size is the control's box, and the\n * layout-invariance contract says a state may repaint a control but must never resize it, so this is\n * not a paint slot. Centring the mark is the component's job \u2014 qti-simple-choice.styles.ts already\n * flex-centres [part='control'].\n */\n/*\n * The mark inside a control at rest \u2014 the box the tick or dot is drawn into. Structural.\n *\n * A FRACTION of the control, not a px subtraction. `calc(size - 6px)` only holds its proportion at\n * one size: override --qti-control-size to 12px and the mark is 6px (50%) instead of 10px (62.5%),\n * and at 6px it disappears. The ratio is what the design is; the subtraction was that ratio frozen\n * at 16px. 0.625 reproduces today's 10px exactly, so nothing moves.\n *\n * A percentage would resolve against the control's CONTENT box, which is the border-box minus its\n * border \u2014 so it would silently shrink by the border width and change with it. Multiplying the token\n * keeps the mark proportional to the control's outer size, which is the size anyone reasons about.\n */\n/* A choice with its native control shown (radio/checkbox visible) is the button component with its\n border made TRANSPARENT \u2014 no visible box, but the 1px edge is still there, so it is the same box\n as the `.qti-input-control-hidden` choice (which keeps the border visible because the whole choice\n is the control). Only the border COLOUR differs between the two, never the geometry. */\n/*\n * The mark a checked control shows: a dot for a radio, a tick for a checkbox.\n *\n * A glyph, not a box \u2014 the same kind of thing as `grip` and `answer-check`, so it gets a colour slot\n * rather than a whole paint role. It is drawn ON the checked control's accent fill (see\n * `control-selected`), so the two belong together: override --checkbox-background-color and you will\n * usually want --checkbox-mark-color with it. `white` is the resting value, not a fixed one.\n */\n/*\n * `act`, `readonly` and `dis` used to live here. They are written out at their call sites now, for\n * two different reasons.\n *\n * `readonly` and `dis` were a single `cursor` declaration each \u2014 `pointer` and `not-allowed`. A mixin\n * that expands to one property is a name you have to look up to read the rule, and thirteen call\n * sites had to. THE POLICY THEY CARRIED IS STILL THE POLICY, and is worth stating once here rather\n * than losing with them: **the theme's disabled and readonly treatments are cursor ONLY.** No dimming,\n * no desaturation, no border change. A disabled control keeps its box and its colour so the page does\n * not reflow or change meaning when an item locks; the pointer is the whole affordance. If that ever\n * stops being true it becomes a state that PAINTS, and it belongs in the contract below, not in a\n * one-line mixin.\n *\n * `act` set `border-color` + `background-color` directly, which is what the paint contract exists to\n * stop: a state repaints by SWITCHING a variable, never by re-declaring the property. Its three call\n * sites split two ways \u2014 the selected choice now switches --button-* (see qti-choice-interaction.css,\n * where half of `act` turned out to be dead code), while the two `::part(drags)` containers keep\n * direct properties because a bare slot container has no paint role to switch.\n *\n * `act-bor` was deleted outright: zero call sites anywhere in the repo.\n */\n/* `foc` stays a mixin, deliberately, and is the one state still painting a property directly.\n * Twenty call sites read it, and it draws a `box-shadow` \u2014 the property the contract has NOT bound\n * (see `paint` above). Routing it through --<role>-box-shadow would not fix anything: focus would\n * then fight the resting lift and the in-flight elevation over one slot, last-declared winning,\n * which is the four-rule contention already documented in qti-variables.css. The intended fix\n * recorded there is a single `outline` on `:focus-visible`, and that is its own VRT-checked step. */\n/* A choice \u2014 the clickable box a candidate picks. Nothing but the resting paint box; its padding is\n the interaction's (qti-choice-interaction.css), because a choice's box differs per interaction and\n a chip's does not. */\n/* A text field: the paint box, plus the cursor that says you can type in it. */\n/*\n * A drop target: the paint box with a DASHED edge, the one thing that distinguishes it from every\n * other role.\n *\n * The re-declaration is deliberate. `@mixin paint drop` has just bound `border-style` to\n * `var(--drop-border-style, var(--qti-component-border-style))` \u2014 solid, like everything else \u2014 and\n * this overrides it with the same slot in front, so `dashed` is the drop's DEFAULT rather than a\n * value welded shut: a brand still switches the edge with --drop-border-style.\n *\n * It could not be expressed as a variable at all under the old shared namespace. `border-style` was\n * a direct property precisely because setting --component-border-style would have inherited the\n * dashed edge onto any chip standing in the drop (it did \u2014 a chip in a drop went dashed). Now that\n * the chip reads --drag-border-style, the drop's value inherits past it harmlessly.\n */\n/* The initial `display: none` moved to `validationMessage` in qti-base \u2014 the message is a shadow\n node, and `Interaction.reportValidity` already owns showing and hiding it with an inline style.\n What is left here is paint. */\n/*\n * The three roles qti-inline-choice-interaction is built from. They are separate roles rather than\n * one, because they nest: the menu and the message live in the field's shadow root, so under a\n * shared namespace the field's own paint inherited into both and a brand could not restyle the\n * field without restyling its popover.\n *\n * `field` the closed combobox \u2014 the box a candidate reads the chosen option in.\n * `menu` the popover the options are listed in.\n * `message` the validation box. Its warning colouring is the interaction's, not the role's.\n */\n/*\n * A drag in flight. The other `drag` modifier \u2014 applied with `drag`, never instead of it.\n *\n * `rotate` is paint: a transform never reflows, which is what lets a drag tilt under the cursor\n * without the bank shifting behind it.\n */\n/*\n * A drag darkens a little under the pointer. The one hover affordance in the theme.\n *\n * Guarded by `@media (hover: hover)` because touch does not lack hover \u2014 it emulates it, and the\n * emulated state sticks after a tap. Without the guard a tapped drag stays darkened until the\n * candidate taps somewhere else, which is worse than no hover at all.\n *\n * So this may only ever decorate. Everything it says is also said by `:focus-visible` for a keyboard\n * and by the resting state for a finger; nothing is communicated by hover alone.\n *\n * A background darken, not a lift: it costs no layout (paint only, never reflows the bank), and it\n * reads the chip's own `--drag-background-color`, so it tracks whatever the brand painted the\n * chip \u2014 primary under Kennisnet, white otherwise. `background-color` is a DIRECT property (not\n * `--drag-background-color`): it overrides the `@mixin paint drag` binding on `:hover`, does not\n * self-reference the var it reads, and \u2014 being a real property \u2014 never inherits into children.\n *\n * It replaces `hov`, an empty mixin invoked 21 times, applied to drags, drop targets, textareas,\n * hotspots and choices alike where no single rule suited all of them. This one is called from drags\n * only.\n */\n@layer qti-components.interactions.base {\n qti-choice-interaction {\n /* A choice hugs its text here, unlike order-interaction, which uses the same element as a chip.\n Handed to qti-simple-choice.styles.ts, which owns the label's box; this was an\n `::part(label) { display: inline !important }`. */\n --qti-choice-label-display: inline;\n\n & qti-simple-choice {\n padding: var(--qti-padding-box);\n width: -moz-fit-content;\n width: fit-content;\n cursor: pointer;\n }\n\n &.qti-input-control-hidden {\n & qti-simple-choice {\n background-color: var(--button-background-color, var(--qti-component-background-color));\n background-image: var(--button-background-image, var(--qti-component-background-image));\n border-width: var(--button-border-width, var(--qti-component-border-width));\n border-style: var(--button-border-style, var(--qti-component-border-style));\n border-color: var(--button-border-color, var(--qti-component-border-color));\n border-radius: var(--button-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--button-box-shadow, var(--qti-component-box-shadow));\n color: var(--button-color, var(--qti-component-color));\n opacity: var(--button-opacity, var(--qti-component-opacity));\n filter: var(--button-filter, var(--qti-component-filter));\n --qti-control-display: none;\n &:focus {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n }\n }\n }\n\n &:not(.qti-input-control-hidden) {\n & qti-simple-choice {\n background-color: var(--button-background-color, var(--qti-component-background-color));\n background-image: var(--button-background-image, var(--qti-component-background-image));\n border-width: var(--button-border-width, var(--qti-component-border-width));\n border-style: var(--button-border-style, var(--qti-component-border-style));\n border-color: var(--button-border-color, var(--qti-component-border-color));\n border-radius: var(--button-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--button-box-shadow, var(--qti-component-box-shadow));\n color: var(--button-color, var(--qti-component-color));\n opacity: var(--button-opacity, var(--qti-component-opacity));\n filter: var(--button-filter, var(--qti-component-filter));\n --button-border-color: transparent;\n gap: 0.5rem;\n outline: none;\n cursor: pointer;\n\n &:focus {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n }\n\n &::part(control-mark) {\n width: calc(var(--qti-control-size) * 0.625);\n height: calc(var(--qti-control-size) * 0.625);\n }\n }\n }\n }\n}\n@layer qti-components.interactions.states {\n qti-choice-interaction {\n &.qti-input-control-hidden qti-simple-choice {\n /* A selected choice, repainted through the button role's slots rather than by re-declaring the\n properties \u2014 the choice is `@mixin button`, so switching a variable is all a state has to do.\n This used to be `@mixin act` followed by two direct declarations, and half of `act` was dead\n on arrival: it set `background-color: var(--qti-bg-active)` and the very next line overwrote\n it with the selected fill. Only its border-colour ever survived, which is written out here. */\n &:state(checked) {\n --button-border-color: var(--qti-border-active);\n --button-background-color: var(--qti-selected-bg);\n --button-color: var(--qti-selected-color);\n }\n\n &:state(readonly) {\n cursor: pointer;\n }\n\n &:state(disabled) {\n cursor: not-allowed;\n }\n }\n\n &:not(.qti-input-control-hidden) qti-simple-choice {\n &:state(readonly) {\n cursor: pointer;\n }\n\n &:state(disabled) {\n cursor: not-allowed;\n }\n\n &:state(radio)::part(control) {\n background-color: var(--radio-background-color, var(--qti-component-background-color));\n background-image: var(--radio-background-image, var(--qti-component-background-image));\n border-width: var(--radio-border-width, var(--qti-component-border-width));\n border-style: var(--radio-border-style, var(--qti-component-border-style));\n border-color: var(--radio-border-color, var(--qti-component-border-color));\n border-radius: var(--radio-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--radio-box-shadow, var(--qti-component-box-shadow));\n color: var(--radio-color, var(--qti-component-color));\n opacity: var(--radio-opacity, var(--qti-component-opacity));\n filter: var(--radio-filter, var(--qti-component-filter));\n width: var(--qti-control-size);\n height: var(--qti-control-size);\n /* Round. Re-declared with the slot in FRONT of the default, the same shape as the drop's dashed\n edge, so a brand can square a radio off without having to out-specify this rule. */\n border-radius: var(--radio-border-radius, 100%);\n }\n\n &:state(radio):state(checked)::part(control),\n &:state(checkbox):state(checked)::part(control) {\n border-color: var(--qti-primary);\n background-color: var(--qti-primary);\n }\n\n &:state(radio):state(checked)::part(control-mark) {\n /* 0.5 reproduces today's 8px at the default 1rem \u2014 see mark-size for why this is a fraction. */\n width: calc(var(--qti-control-size) * 0.5);\n height: calc(var(--qti-control-size) * 0.5);\n background-color: var(--radio-mark-color, white);\n border-radius: 100%;\n }\n\n &:state(checkbox)::part(control) {\n background-color: var(--checkbox-background-color, var(--qti-component-background-color));\n background-image: var(--checkbox-background-image, var(--qti-component-background-image));\n border-width: var(--checkbox-border-width, var(--qti-component-border-width));\n border-style: var(--checkbox-border-style, var(--qti-component-border-style));\n border-color: var(--checkbox-border-color, var(--qti-component-border-color));\n border-radius: var(--checkbox-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--checkbox-box-shadow, var(--qti-component-box-shadow));\n color: var(--checkbox-color, var(--qti-component-color));\n opacity: var(--checkbox-opacity, var(--qti-component-opacity));\n filter: var(--checkbox-filter, var(--qti-component-filter));\n width: var(--qti-control-size);\n height: var(--qti-control-size);\n }\n\n &:state(checkbox):state(checked)::part(control-mark) {\n background-color: var(--checkbox-mark-color, white);\n -webkit-mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' width='100%' height='100%' viewBox='0 0 24 24'%3E%3Cpath d='M20.285 2l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285z'/%3E%3C/svg%3E\");\n mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' width='100%' height='100%' viewBox='0 0 24 24'%3E%3Cpath d='M20.285 2l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285z'/%3E%3C/svg%3E\");\n }\n }\n }\n}\n@layer qti-components.interactions.corrections {\n qti-choice-interaction {\n & qti-simple-choice {\n &:state(correct-response),\n &[data-correct-response='true'] {\n &::after {\n content: '\\02714';\n color: var(--qti-correct);\n }\n }\n }\n\n &.qti-input-control-hidden qti-simple-choice {\n /*\n * Paint only. This used to set `border: 3px` over a 1px base and add `padding-right: 30px`,\n * so revealing the answer key grew every correct choice and reflowed the list around it.\n *\n * The thicker edge is now an inset shadow, which paints inside the existing border box and\n * costs no layout. The 30px that kept the label clear of the tick is reserved in the base\n * rule for this variant, whether or not a tick ever appears.\n */\n &:state(correct-response),\n &[data-correct-response='true'] {\n border-color: var(--qti-correct) !important;\n box-shadow: inset 0 0 0 2px var(--qti-correct);\n\n &::after {\n content: '\\02714';\n color: var(--qti-correct);\n position: absolute;\n top: 5px;\n right: 5px;\n font-size: 1.2em;\n font-weight: bold;\n }\n }\n\n /* Candidate judgement follows checked/correct-response paint so it can\n recolour the same stable box without changing its geometry. */\n &:state(candidate-correct) {\n --button-background-color: var(--qti-correct-light);\n --button-color: var(--qti-correct);\n --button-border-color: var(--qti-correct);\n --button-box-shadow: none;\n }\n\n &:state(candidate-incorrect) {\n --button-background-color: var(--qti-incorrect-light);\n --button-color: var(--qti-incorrect);\n --button-border-color: var(--qti-incorrect);\n --button-box-shadow: none;\n }\n }\n\n &:not(.qti-input-control-hidden) qti-simple-choice {\n &:state(candidate-correct) {\n background-color: transparent;\n\n &:state(radio):state(checked)::part(control),\n &:state(checkbox):state(checked)::part(control) {\n border-color: var(--qti-correct);\n background-color: var(--qti-correct);\n }\n }\n\n &:state(candidate-incorrect) {\n background-color: transparent;\n\n &:state(radio):state(checked)::part(control),\n &:state(checkbox):state(checked)::part(control) {\n border-color: var(--qti-incorrect);\n background-color: var(--qti-incorrect);\n }\n }\n }\n }\n\n .full-correct-response qti-choice-interaction {\n qti-simple-choice {\n &:state(radio):state(checked)::part(control),\n &:state(checkbox):state(checked)::part(control) {\n background-color: var(--qti-answer-bg);\n color: var(--qti-answer-fg);\n border-color: var(--qti-answer-border);\n }\n\n &:state(radio):state(checked)::part(control-mark),\n &:state(checkbox):state(checked)::part(control-mark) {\n background-color: var(--qti-answer-fg);\n }\n }\n\n &.qti-input-control-hidden qti-simple-choice:state(checked) {\n background-color: var(--qti-answer-bg);\n color: var(--qti-answer-fg);\n border-color: var(--qti-answer-border);\n }\n }\n}\n@layer qti-components.interactions.base {\n qti-graphic-gap-match-interaction {\n &.qti-selections-light {\n &:state(dragzone-active)::part(drags) {\n background-color: var(--qti-selections-light-bg);\n border-color: var(--qti-selections-light-border);\n }\n &:state(dragzone-enabled)::part(drags) {\n background-color: var(--qti-selections-light-bg);\n }\n }\n &.qti-selections-dark {\n &:state(dragzone-active)::part(drags) {\n background-color: var(--qti-selections-dark-bg);\n border-color: var(--qti-selections-dark-border);\n }\n &:state(dragzone-enabled)::part(drags) {\n background-color: var(--qti-selections-dark-bg);\n }\n }\n &:state(dragzone-active)::part(drags) {\n background-color: var(--qti-bg-active);\n }\n &:state(dragzone-enabled)::part(drags) {\n background-color: var(--qti-bg-active);\n }\n /*\n * A graphic-gap-match chip is an image, not a card. No drag-card styling \u2014 no border, no padding,\n * no background \u2014 the tile is its picture and nothing else.\n *\n * The bank chip and the placed chip must still be the same size (drag-drop.invariance.spec.ts),\n * and they are: NEITHER gets chip styling. The earlier 18px height jump came from the bank being\n * plain while the placed chip picked up drag-card styling through `::part(drag)` \u2014 fixed then by\n * giving both the card, fixed better now by giving neither.\n */\n /* A chip here is a tile filling its hotspot, so it centres its content \u2014 see\n qti-gap-img.styles.ts and qti-gap-text.styles.ts. gap-match's chips hug their text instead,\n which is why the justification is a variable rather than the element's default. */\n & qti-gap-img,\n & qti-gap-text {\n --qti-drag-justify-content: center;\n cursor: grab;\n }\n /* The placed chip, in the hotspot's shadow. Plain too, to match the bank. Focus ring only. */\n & qti-associable-hotspot::part(drag) {\n &:focus {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n }\n }\n /*\n * The hotspot centres its chip in qti-associable-hotspot.styles.ts.\n *\n * There is no `.qti-unselected-hidden` rule for this interaction, and none is needed. Appendix\n * B.1 lists the class here, and \xA73.2.6's requirement is to hide hotspots in their unselected and\n * unfocused state \u2014 which this transparent border already does, for every item, whether or not\n * the author sets the class. An empty hotspot shows nothing; `:state(filled)` paints the\n * selection, and `:state(enabled)` / `:state(hover)` / `:state(active)` bring the drop targets\n * up for the duration of a drag so the candidate can still aim.\n *\n * Adding a rule would be a redundant restatement of this line. Left as a note rather than code\n * because the absence is the kind of thing that reads as an oversight.\n */\n & qti-associable-hotspot {\n border: 2px solid transparent;\n /* No .qti-selections-* branches in here. They used to sit inside both of these blocks as\n *descendant* selectors \u2014 and the class is on the interaction, an ancestor of this hotspot,\n so they never matched anything. The pair that works is scoped from the interaction down;\n see the :state(filled) rules below.\n A drop target lighting up mid-drag is not a \"selection\" anyway \u2014 Appendix B calls this\n Selections theming, and the selection is the chip once it has landed. */\n &[enabled] {\n background-color: var(--qti-bg-active);\n }\n &[active] {\n background-color: var(--qti-bg-active);\n }\n &[disabled] {\n cursor: not-allowed;\n &:not(:empty) {\n cursor: default !important;\n }\n }\n &:not(:empty) {\n border-color: var(--qti-border) !important;\n }\n /*\n * Two rules were deleted here, both unreachable, both about padding:\n *\n * &:empty::after { padding: var(--qti-padding-md) var(--qti-padding-lg); content: '\\0000a0' }\n * &:not(:empty) { padding: 0; width: auto }\n *\n * `--qti-padding-md` / `--qti-padding-lg` are defined nowhere in the repo, so the first was\n * invalid at computed-value time and paid no padding. It could not have rendered regardless:\n * a hotspot renders its chips into `<div part=\"drop\">` in its own shadow root and has no\n * `<slot>`, so the host is always `:empty` to a selector AND its `::after` \u2014 light-tree\n * content \u2014 is never assigned anywhere. The nbsp was not holding an empty hotspot open;\n * --qti-dropzone-min-* on the host is. By the same reasoning `:not(:empty)` never matches.\n *\n * They are named here rather than silently dropped because they read like the hotspot's\n * padding story, and the next person to give drops an inset should know there isn't one.\n */\n\n /* `flex: 1` left with the rest of the drop's layout; dropRegion in qti-base gives a hosted\n chip `flex: 0 0 auto`, which is the invariance rule this was quietly fighting. */\n &:not(:empty) > * {\n transform: rotate(0);\n box-shadow: 0 0 0 1px #e5e7eb;\n }\n }\n /* `:not(:empty)` tested light-DOM children. The hotspot renders its chips into its own shadow\n root now, so it is always \"empty\" to a selector \u2014 `:state(filled)` is the occupancy hook. */\n &.qti-selections-light qti-associable-hotspot:state(filled) {\n background-color: var(--qti-selections-light-bg) !important;\n border-color: var(--qti-selections-light-border) !important;\n }\n &.qti-selections-dark qti-associable-hotspot:state(filled) {\n background-color: var(--qti-selections-dark-bg) !important;\n border-color: var(--qti-selections-dark-border) !important;\n }\n & img {\n margin: 0;\n padding: 0;\n }\n }\n}\n@layer qti-components.interactions.states {\n qti-graphic-gap-match-interaction qti-gap-img:state(placeholder),\n qti-graphic-gap-match-interaction qti-gap-text:state(placeholder) {\n /* The hole a spent/lifted chip leaves. Like the chip (see `drag`), it sets NO fill / border /\n shadow of its own \u2014 those inherit from the resting chip (neutral by default, the brand's fill\n when an overlay is on), so a brand can restyle the hole via a `:state(placeholder)` overlay\n with no specificity contest. The one thing a placeholder always does, in every brand, is hide\n its text; the default hole is then just the resting chip (white + border) with its text gone. */\n --drag-color: transparent;\n --drag-opacity: 1;\n pointer-events: none;\n }\n\n qti-graphic-gap-match-interaction qti-gap-img:state(dragging),\n qti-graphic-gap-match-interaction qti-gap-text:state(dragging) {\n /* The hole a spent/lifted chip leaves. Like the chip (see `drag`), it sets NO fill / border /\n shadow of its own \u2014 those inherit from the resting chip (neutral by default, the brand's fill\n when an overlay is on), so a brand can restyle the hole via a `:state(placeholder)` overlay\n with no specificity contest. The one thing a placeholder always does, in every brand, is hide\n its text; the default hole is then just the resting chip (white + border) with its text gone. */\n --drag-color: transparent;\n --drag-opacity: 1;\n border-color: var(--qti-drop-highlight-border-color);\n box-shadow: inset 0 0 0 var(--qti-drop-highlight-border-width, 2px)\n color-mix(in srgb, var(--qti-drop-highlight-border-color) 55%, transparent);\n pointer-events: none;\n }\n\n qti-graphic-gap-match-interaction qti-gap-img:state(return-anchor),\n qti-graphic-gap-match-interaction qti-gap-text:state(return-anchor) {\n border-color: var(--qti-drop-highlight-border-color);\n box-shadow: inset 0 0 0 var(--qti-drop-highlight-border-width, 2px)\n color-mix(in srgb, var(--qti-drop-highlight-border-color) 55%, transparent);\n }\n\n qti-graphic-gap-match-interaction qti-gap-img:state(placeholder) > *,\n qti-graphic-gap-match-interaction qti-gap-text:state(placeholder) > *,\n qti-graphic-gap-match-interaction qti-gap-img:state(dragging) > *,\n qti-graphic-gap-match-interaction qti-gap-text:state(dragging) > * {\n visibility: hidden;\n }\n\n qti-graphic-gap-match-interaction qti-associable-hotspot:state(hover),\n qti-graphic-gap-match-interaction qti-associable-hotspot:state(active) {\n background-color: var(--qti-drop-highlight-bg);\n }\n\n qti-graphic-gap-match-interaction qti-associable-hotspot:state(enabled) {\n background-color: var(--qti-drop-highlight-enabled-bg);\n }\n}\n@layer qti-components.interactions.corrections {\n /*\n * The one place this interaction joins the shared chip vocabulary: its chips take no card, but they\n * do take a correction.\n *\n * And it is the one that must stay DIRECT. Everywhere else a correction switches --<role>-* slots\n * (`@mixin candidate drag`), because the chip is painted by `@mixin paint drag` and reads them.\n * A ggm chip is a photograph on a picture with no paint role at all \u2014 nothing reads --drag-*, so\n * routing the correction through a slot would simply erase it. Measured: a corrected ggm chip is\n * pale-green filled with a green tint on its text and no border at all.\n *\n * Moved here from qti-corrections.css, whose unscoped `:state(drag)` rule covered these chips\n * along with everyone else's.\n */\n qti-graphic-gap-match-interaction :state(drag):state(candidate-correct),\n qti-graphic-gap-match-interaction qti-associable-hotspot::part(drag):state(candidate-correct) {\n background-color: var(--qti-correct-light);\n color: var(--qti-correct);\n border-color: var(--qti-correct);\n box-shadow: none;\n }\n\n qti-graphic-gap-match-interaction :state(drag):state(candidate-incorrect),\n qti-graphic-gap-match-interaction qti-associable-hotspot::part(drag):state(candidate-incorrect) {\n background-color: var(--qti-incorrect-light);\n color: var(--qti-incorrect);\n border-color: var(--qti-incorrect);\n box-shadow: none;\n }\n}\n@layer qti-components.interactions.base {\n qti-text-entry-interaction {\n background-color: var(--text-background-color, var(--qti-component-background-color));\n background-image: var(--text-background-image, var(--qti-component-background-image));\n border-width: var(--text-border-width, var(--qti-component-border-width));\n border-style: var(--text-border-style, var(--qti-component-border-style));\n border-color: var(--text-border-color, var(--qti-component-border-color));\n border-radius: var(--text-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--text-box-shadow, var(--qti-component-box-shadow));\n color: var(--text-color, var(--qti-component-color));\n opacity: var(--text-opacity, var(--qti-component-opacity));\n filter: var(--text-filter, var(--qti-component-filter));\n cursor: text;\n\n &::part(input) {\n color: var(--qti-fg);\n font-family: inherit;\n font-size: 1rem;\n line-height: 1.5;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n border: none;\n background-color: transparent;\n }\n }\n}\n@layer qti-components.interactions.corrections {\n qti-text-entry-interaction {\n &:state(candidate-partially-correct) {\n --text-background-color: var(--qti-partially-correct-light);\n --text-color: var(--qti-partially-correct);\n --text-border-color: var(--qti-partially-correct);\n --text-box-shadow: none;\n }\n\n &:state(candidate-correct) {\n --text-background-color: var(--qti-correct-light);\n --text-color: var(--qti-correct);\n --text-border-color: var(--qti-correct);\n --text-box-shadow: none;\n }\n\n &:state(candidate-incorrect) {\n --text-background-color: var(--qti-incorrect-light);\n --text-color: var(--qti-incorrect);\n --text-border-color: var(--qti-incorrect);\n --text-box-shadow: none;\n }\n }\n\n /* The answer key, moved here from the shared qti-corrections.css: it names this interaction, so\n this file owns it. */\n .full-correct-response qti-text-entry-interaction {\n background-color: var(--qti-answer-bg);\n color: var(--qti-answer-fg);\n border-color: var(--qti-answer-border);\n\n &::part(input) {\n color: var(--qti-answer-fg);\n }\n\n &::part(answer) {\n content: '';\n background-color: var(--qti-answer-fg);\n mask: var(--qti-check-mask) no-repeat center / contain;\n -webkit-mask: var(--qti-check-mask) no-repeat center / contain;\n }\n }\n}\n@layer qti-components.interactions.base {\n qti-extended-text-interaction {\n /*\n * One block, and the ORDER inside it matters.\n *\n * These were two separate `::part(textarea)` rules, `@mixin text` first and\n * `@mixin field-typography` second. Both declare `color`, so the later one won and the textarea's\n * text was pinned to `--qti-fg` \u2014 which meant the paint contract's `color` binding was dead, and\n * a correction switching --text-color could not recolour a single character. Measured: a\n * corrected extended-text stayed black.\n *\n * `@mixin text` goes last so the binding is the winning declaration. Nothing moves at rest: the\n * slot is unset, so it falls through to --qti-component-color, which is `inherit` \u2014 the same\n * colour the textarea already had.\n */\n &::part(textarea) {\n color: var(--qti-fg);\n font-family: inherit;\n font-size: 1rem;\n line-height: 1.5;\n /*\n * The left gutter is reserved ALWAYS, graded or not.\n *\n * It holds the correction badge, and it is stated here rather than in the corrections layer\n * because padding is layout: switching it on `:state(candidate-*)` would re-wrap every line of\n * the candidate's answer at the moment the verdict appears. That is the layout-invariance\n * contract, and the lint rule enforces it \u2014 the old class-based rules broke it and were never\n * live enough for anyone to notice.\n *\n * qti-choice-interaction reserves its answer-key tick the same way, for the same reason: \"the\n * 30px that kept the label clear of the tick is reserved in the base rule, whether or not a\n * tick ever appears.\"\n *\n * --qti-correction-gutter is derived from the badge's inset, size and glyph gap, so this\n * padding and the badge's own offsets cannot drift apart. It was a literal 2.4rem that had to\n * be kept in agreement by hand with numbers in a different package.\n */\n padding: var(--qti-spacing) var(--qti-correction-inset) var(--qti-spacing)\n var(--qti-correction-gutter);\n\n background-color: var(--text-background-color, var(--qti-component-background-color));\n\n background-image: var(--text-background-image, var(--qti-component-background-image));\n\n border-width: var(--text-border-width, var(--qti-component-border-width));\n\n border-style: var(--text-border-style, var(--qti-component-border-style));\n\n border-color: var(--text-border-color, var(--qti-component-border-color));\n\n border-radius: var(--text-border-radius, var(--qti-component-border-radius));\n\n box-shadow: var(--text-box-shadow, var(--qti-component-box-shadow));\n\n color: var(--text-color, var(--qti-component-color));\n\n opacity: var(--text-opacity, var(--qti-component-opacity));\n\n filter: var(--text-filter, var(--qti-component-filter));\n\n cursor: text;\n }\n\n /*\n * format=\"preformatted\". Painted here rather than in the component's shadow root, because\n * `field-typography` above sets `font-family: inherit` through `::part()` \u2014 a document rule,\n * which beats any shadow-root rule no matter its specificity. A `:host([format=...])` rule\n * inside the component would simply never win once this theme is loaded.\n *\n * A textarea already preserves whitespace, so what preformatted adds is the monospace grid\n * that makes the preserved indentation legible. Typography only \u2014 the box does not move, so\n * the layout-invariance contract holds.\n */\n &[format='preformatted']::part(textarea) {\n font-family: monospace;\n -moz-tab-size: 2;\n -o-tab-size: 2;\n tab-size: 2;\n }\n }\n}\n/*\n * Corrections for the one interaction that cannot judge itself.\n *\n * An extended-text response is free prose, so there is no correct answer to compare against and the\n * component computes no verdict \u2014 it extends `Interaction` directly, exactly as\n * correct-response.mixin.ts prescribes for extended-text, upload and media. The verdict arrives from\n * OUTSIDE instead (a teacher, a marking service), through qti-extended-text-interaction-correction.ts,\n * which publishes it as the same `:state(candidate-*)` custom states every other interaction uses.\n * From here the difference is invisible: these rules are the same shape as text-entry's.\n *\n * They were keyed on `.correct`, `.partiallyCorrect` and `.incorrect` until now \u2014 plain light-DOM\n * classes that nothing in the repo ever set, with the camelCase among kebab-case states marking them\n * as an older API. They never rendered once. The `padding-left` is the part of them that was right\n * all along: a gutter reserved for a glyph that until now was never drawn.\n *\n * The gutter is spacing, not paint, so it stays a direct declaration. The badge that sits in it is\n * positioned by the component and painted by ::part(correction-*) in qti-corrections.css.\n */\n@layer qti-components.interactions.corrections {\n qti-extended-text-interaction {\n &:state(candidate-correct)::part(textarea) {\n --text-background-color: var(--qti-correct-light);\n --text-color: var(--qti-correct);\n --text-border-color: var(--qti-correct);\n --text-box-shadow: none;\n }\n\n /* Amber throughout \u2014 text, edge and wash \u2014 exactly like text-entry's partial rule, which is the\n reference for a field taking a verdict. It read `var(--qti-fg)` for the text until now, so the\n words stayed the body colour while everything around them went amber; that was carried over\n verbatim from the dead class-based rules, where it had never rendered and so was never seen to\n be wrong. With --qti-fg now correctly resolving to `inherit`, it would have been more visibly\n wrong still. */\n &:state(candidate-partially-correct)::part(textarea) {\n --text-background-color: var(--qti-partially-correct-light);\n --text-color: var(--qti-partially-correct);\n --text-border-color: var(--qti-partially-correct);\n --text-box-shadow: none;\n }\n\n &:state(candidate-incorrect)::part(textarea) {\n --text-background-color: var(--qti-incorrect-light);\n --text-color: var(--qti-incorrect);\n --text-border-color: var(--qti-incorrect);\n --text-box-shadow: none;\n }\n }\n}\n/* This interaction's chip vocabulary \u2014 the bank chip, the placed chip, and the floating clone. One\n such block per drag-drop interaction, opt-in; graphic-gap-match deliberately declares none. See\n qti-states.css. Kept in the `global` layer, where these rules used to live, so cascade position is\n unchanged.\n\n The clone is matched by [data-drag-interaction] rather than by ancestry because it is a SIBLING of\n the interaction, not a descendant: createDragClone appends it to the interaction's ROOT (the\n item's shadow root), which is the same tree \u2014 so the theme can reach it \u2014 but not the same\n subtree. The attribute is also what tells one interaction's chip in flight from another's, which\n is how graphic-gap-match stays out without anyone writing an exclusion.\n\n Its grip hangs off ::part(control)::before, the same place the resting chip's does, so a chip does\n not move its handle when you pick it up. That the part is reachable at all is new: while the clone\n sat on document.body it was in a different tree from the adopted stylesheet, and nothing \u2014 no\n part, no attribute \u2014 could style it. */\n@layer qti-components.global {\n qti-gap-match-interaction :state(drag),\n [data-drag-clone][data-drag-interaction='qti-gap-match-interaction'] {\n /* Structural \u2014 the load-bearing box, never state-switched (layout-invariance contract).\n box-sizing is deliberate: the clone is appended to document.body outside reset.css's\n @scope, so it inherits no border-box from anywhere else and this sets it. */\n box-sizing: border-box;\n padding: var(--qti-drag-padding);\n cursor: grab;\n /*\n * A chip is as wide as the drop it belongs in \u2014 the SAME token the drop reserves with.\n *\n * That one shared name is what makes a drag and its drop the same size without any arithmetic\n * relating them: the interaction measures the widest chip, publishes it once, and both the chip\n * and the slot read it. See DROP-SIZING.md.\n *\n * It applies in the bank as well as in the drop, which is the point: the chip already has the\n * width before it is dropped, so nothing changes size at the moment of landing. That keeps \"a chip\n * is the same chip wherever it lives\" true (drag-drop.invariance.spec.ts) and keeps the return\n * animation from ending in a snap. A `width: 100%` on the placed chip gives the same still image\n * and breaks both \u2014 it is what order used to do, and why order was the one entry in\n * CHIP_BOX_KNOWN_BAD.\n *\n * min-width, not width, so a chip whose own text is wider than the widest measured chip still\n * fits. Defaults to 0 where nothing is measured (match, whose targets are cards, not slots), so\n * those chips keep their natural width with no rule needed to say so.\n *\n * --qti-drag-min-width, not --qti-dropzone-min-width: the mixin writes the same measured value to\n * both, so a chip and its slot still match, but the two need OPPOSITE defaults when nothing is\n * measured \u2014 a chip must stay at its natural width, while a drops-grid track must not collapse.\n * See the note on both names in qti-variables.css.\n */\n min-width: var(--qti-drag-min-width, 0);\n /* Paint via the contract, nothing more \u2014 the --qti-component-* tokens already make a neutral\n bordered box (white surface, theme border, no shadow). The chip sets NO --drag-* of its own,\n and that is load-bearing: if it did (e.g. `--drag-background-color: white`), that declaration\n would ride the interaction's HIGH-specificity chip selector\n (`qti-gap-match-interaction qti-gap::part(drag)`) and out-specify a brand overlay's bare\n `qti-gap::part(drag)`, so the brand could never win. With nothing set here, the overlay's\n --drag-* is the only declaration and wins with no specificity/layer contest. */\n background-color: var(--drag-background-color, var(--qti-component-background-color));\n background-image: var(--drag-background-image, var(--qti-component-background-image));\n border-width: var(--drag-border-width, var(--qti-component-border-width));\n border-style: var(--drag-border-style, var(--qti-component-border-style));\n border-color: var(--drag-border-color, var(--qti-component-border-color));\n border-radius: var(--drag-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drag-box-shadow, var(--qti-component-box-shadow));\n color: var(--drag-color, var(--qti-component-color));\n opacity: var(--drag-opacity, var(--qti-component-opacity));\n filter: var(--drag-filter, var(--qti-component-filter));\n }\n\n qti-gap-match-interaction :state(drag)::part(control)::before,\n qti-gap-match-interaction::part(drag-control)::before,\n qti-gap-match-interaction ::part(drag-control)::before,\n [data-drag-clone][data-drag-interaction='qti-gap-match-interaction']::part(control)::before {\n content: '';\n display: inline-block;\n vertical-align: middle;\n width: 0.75em;\n height: 1em;\n margin-right: var(--qti-glyph-gap);\n background-color: currentColor;\n mask: var(--qti-grip-mask) no-repeat center / contain;\n -webkit-mask: var(--qti-grip-mask) no-repeat center / contain;\n }\n}\n@layer qti-components.interactions.base {\n qti-gap-match-interaction {\n /* No qti-selections-light / -dark here. Appendix B.1 of the QTI 3 implementation guide defines\n that class pair for qti-hotspot-interaction and qti-graphic-gap-match-interaction only, and no\n item in this repo ever set it on a gap-match interaction. What was here overrode the neutral\n drag-bank highlight below with --qti-light-* and --qti-dark-* colours that nothing could reach. */\n &:state(dragzone-active)::part(drags) {\n background-color: var(--qti-bg-active);\n }\n &:state(dragzone-enabled)::part(drags) {\n background-color: var(--qti-bg-active);\n }\n /* Editor fake-drag projected into a filled <qti-gap>'s shadow. Same `drag`\n styling so the editor preview matches runtime when a gap is filled. */\n & qti-gap::part(drag) {\n /* Structural \u2014 the load-bearing box, never state-switched (layout-invariance contract).\n box-sizing is deliberate: the clone is appended to document.body outside reset.css's\n @scope, so it inherits no border-box from anywhere else and this sets it. */\n box-sizing: border-box;\n padding: var(--qti-drag-padding);\n cursor: grab;\n /*\n * A chip is as wide as the drop it belongs in \u2014 the SAME token the drop reserves with.\n *\n * That one shared name is what makes a drag and its drop the same size without any arithmetic\n * relating them: the interaction measures the widest chip, publishes it once, and both the chip\n * and the slot read it. See DROP-SIZING.md.\n *\n * It applies in the bank as well as in the drop, which is the point: the chip already has the\n * width before it is dropped, so nothing changes size at the moment of landing. That keeps \"a chip\n * is the same chip wherever it lives\" true (drag-drop.invariance.spec.ts) and keeps the return\n * animation from ending in a snap. A `width: 100%` on the placed chip gives the same still image\n * and breaks both \u2014 it is what order used to do, and why order was the one entry in\n * CHIP_BOX_KNOWN_BAD.\n *\n * min-width, not width, so a chip whose own text is wider than the widest measured chip still\n * fits. Defaults to 0 where nothing is measured (match, whose targets are cards, not slots), so\n * those chips keep their natural width with no rule needed to say so.\n *\n * --qti-drag-min-width, not --qti-dropzone-min-width: the mixin writes the same measured value to\n * both, so a chip and its slot still match, but the two need OPPOSITE defaults when nothing is\n * measured \u2014 a chip must stay at its natural width, while a drops-grid track must not collapse.\n * See the note on both names in qti-variables.css.\n */\n min-width: var(--qti-drag-min-width, 0);\n /* Paint via the contract, nothing more \u2014 the --qti-component-* tokens already make a neutral\n bordered box (white surface, theme border, no shadow). The chip sets NO --drag-* of its own,\n and that is load-bearing: if it did (e.g. `--drag-background-color: white`), that declaration\n would ride the interaction's HIGH-specificity chip selector\n (`qti-gap-match-interaction qti-gap::part(drag)`) and out-specify a brand overlay's bare\n `qti-gap::part(drag)`, so the brand could never win. With nothing set here, the overlay's\n --drag-* is the only declaration and wins with no specificity/layer contest. */\n background-color: var(--drag-background-color, var(--qti-component-background-color));\n background-image: var(--drag-background-image, var(--qti-component-background-image));\n border-width: var(--drag-border-width, var(--qti-component-border-width));\n border-style: var(--drag-border-style, var(--qti-component-border-style));\n border-color: var(--drag-border-color, var(--qti-component-border-color));\n border-radius: var(--drag-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drag-box-shadow, var(--qti-component-box-shadow));\n color: var(--drag-color, var(--qti-component-color));\n opacity: var(--drag-opacity, var(--qti-component-opacity));\n filter: var(--drag-filter, var(--qti-component-filter));\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(\n in srgb,\n var(--drag-background-color, var(--qti-component-background-color)) 92%,\n black\n );\n }\n }\n &:focus {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n }\n }\n & qti-gap-text {\n /* Structural \u2014 the load-bearing box, never state-switched (layout-invariance contract).\n box-sizing is deliberate: the clone is appended to document.body outside reset.css's\n @scope, so it inherits no border-box from anywhere else and this sets it. */\n box-sizing: border-box;\n padding: var(--qti-drag-padding);\n cursor: grab;\n /*\n * A chip is as wide as the drop it belongs in \u2014 the SAME token the drop reserves with.\n *\n * That one shared name is what makes a drag and its drop the same size without any arithmetic\n * relating them: the interaction measures the widest chip, publishes it once, and both the chip\n * and the slot read it. See DROP-SIZING.md.\n *\n * It applies in the bank as well as in the drop, which is the point: the chip already has the\n * width before it is dropped, so nothing changes size at the moment of landing. That keeps \"a chip\n * is the same chip wherever it lives\" true (drag-drop.invariance.spec.ts) and keeps the return\n * animation from ending in a snap. A `width: 100%` on the placed chip gives the same still image\n * and breaks both \u2014 it is what order used to do, and why order was the one entry in\n * CHIP_BOX_KNOWN_BAD.\n *\n * min-width, not width, so a chip whose own text is wider than the widest measured chip still\n * fits. Defaults to 0 where nothing is measured (match, whose targets are cards, not slots), so\n * those chips keep their natural width with no rule needed to say so.\n *\n * --qti-drag-min-width, not --qti-dropzone-min-width: the mixin writes the same measured value to\n * both, so a chip and its slot still match, but the two need OPPOSITE defaults when nothing is\n * measured \u2014 a chip must stay at its natural width, while a drops-grid track must not collapse.\n * See the note on both names in qti-variables.css.\n */\n min-width: var(--qti-drag-min-width, 0);\n /* Paint via the contract, nothing more \u2014 the --qti-component-* tokens already make a neutral\n bordered box (white surface, theme border, no shadow). The chip sets NO --drag-* of its own,\n and that is load-bearing: if it did (e.g. `--drag-background-color: white`), that declaration\n would ride the interaction's HIGH-specificity chip selector\n (`qti-gap-match-interaction qti-gap::part(drag)`) and out-specify a brand overlay's bare\n `qti-gap::part(drag)`, so the brand could never win. With nothing set here, the overlay's\n --drag-* is the only declaration and wins with no specificity/layer contest. */\n background-color: var(--drag-background-color, var(--qti-component-background-color));\n background-image: var(--drag-background-image, var(--qti-component-background-image));\n border-width: var(--drag-border-width, var(--qti-component-border-width));\n border-style: var(--drag-border-style, var(--qti-component-border-style));\n border-color: var(--drag-border-color, var(--qti-component-border-color));\n border-radius: var(--drag-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drag-box-shadow, var(--qti-component-box-shadow));\n color: var(--drag-color, var(--qti-component-color));\n opacity: var(--drag-opacity, var(--qti-component-opacity));\n filter: var(--drag-filter, var(--qti-component-filter));\n &[dragging] {\n pointer-events: none;\n rotate: -2deg;\n --drag-box-shadow: 0 8px 12px rgb(0 0 0 / 20%), 0 4px 8px rgb(0 0 0 / 10%);\n }\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(\n in srgb,\n var(--drag-background-color, var(--qti-component-background-color)) 92%,\n black\n );\n }\n }\n &:focus {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n }\n }\n & qti-gap {\n background-color: var(--drop-background-color, var(--qti-component-background-color));\n background-image: var(--drop-background-image, var(--qti-component-background-image));\n border-width: var(--drop-border-width, var(--qti-component-border-width));\n border-style: var(--drop-border-style, var(--qti-component-border-style));\n border-color: var(--drop-border-color, var(--qti-component-border-color));\n border-radius: var(--drop-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drop-box-shadow, var(--qti-component-box-shadow));\n color: var(--drop-color, var(--qti-component-color));\n opacity: var(--drop-opacity, var(--qti-component-opacity));\n filter: var(--drop-filter, var(--qti-component-filter));\n border-style: var(--drop-border-style, dashed);\n\n /*\n * A filled gap hides its dashed edge, exactly as a filled order slot does\n * (qti-order-interaction.css, `::part(filled)`).\n *\n * The dashed edge says \"something goes here\". Once something does, it is only a second frame\n * drawn a pixel outside the chip's own border \u2014 two nested outlines around one word, which is\n * what the drop inset made visible: before it, the two borders sat on top of each other and\n * the duplication did not show.\n *\n * Transparent rather than `none`, and the width is untouched: `@mixin paint drop` binds\n * border-width and border-color to separate slots, so switching only the colour keeps the\n * gap's box identical empty or filled. `border-style: none` would collapse the width and move\n * every word on the line the instant a chip landed \u2014 the exact reflow qti-gap.styles.ts and\n * drag-drop.invariance.spec.ts exist to prevent.\n */\n &:state(filled):is(:not([match-max]), [match-max='1']) {\n --drop-border-color: transparent;\n }\n\n &[disabled] {\n cursor: not-allowed;\n /* `:not(:empty)` tested light-DOM children; the gap renders its chips into its own shadow\n root now, so it is always \"empty\" to a selector. `:state(filled)` is the occupancy hook. */\n &:state(filled) {\n cursor: default !important;\n }\n }\n &[enabled] {\n background-color: var(--qti-bg-active);\n }\n &[active] {\n background-color: var(--qti-bg-active);\n }\n /* The gap's own box \u2014 inline-flex, centred, baseline-aligned \u2014 is in qti-gap.styles.ts. */\n\n /*\n * A gap is the same size empty or filled.\n *\n * It used to be sized two different ways: empty, by a padded non-breaking space; filled,\n * by `padding: 0` and whatever the chip happened to be. So a gap resized the moment a chip\n * landed in it, and \u2014 worse \u2014 every *other* gap on the line reflowed with it. Now the\n * interaction publishes the widest and tallest chip as --qti-dropzone-min-*, and the gap\n * reserves exactly that on its slot (see qti-gap.styles.ts).\n *\n * The `&:empty::after { content: '\\0000a0' }` that used to hold an empty gap open is gone\n * with it: it sat *beside* the slot, so an empty gap was one non-breaking space wider than\n * the same gap once filled.\n */\n\n /*\n * No `flex: 1`. A chip is the same size wherever it lives \u2014 in the bank, mid-drag as a\n * clone, or dropped. Stretching it to its container's width made the drop the one place\n * where that stopped being true.\n *\n * The chip is in the gap's shadow root, so `> *` cannot select it and `::part(drag)` is the\n * only way in.\n */\n /* `flex: 0 0 auto` is dropRegion's, in qti-base \u2014 a drop hosts a chip, it never sizes one.\n The rotate reset and the outline are paint. */\n &::part(drag) {\n transform: rotate(0);\n /* Through the drag slot so a correction (which sets --drag-box-shadow) composes over it\n instead of this leaking onto a corrected chip. */\n --drag-box-shadow: 0 0 0 1px #e5e7eb;\n }\n }\n }\n\n /* The source and placed chip share one visual component. */\n qti-gap-match-interaction qti-gap-text:state(drag),\n qti-gap-match-interaction qti-gap::part(drag) {\n /* Structural \u2014 the load-bearing box, never state-switched (layout-invariance contract).\n box-sizing is deliberate: the clone is appended to document.body outside reset.css's\n @scope, so it inherits no border-box from anywhere else and this sets it. */\n box-sizing: border-box;\n padding: var(--qti-drag-padding);\n cursor: grab;\n /*\n * A chip is as wide as the drop it belongs in \u2014 the SAME token the drop reserves with.\n *\n * That one shared name is what makes a drag and its drop the same size without any arithmetic\n * relating them: the interaction measures the widest chip, publishes it once, and both the chip\n * and the slot read it. See DROP-SIZING.md.\n *\n * It applies in the bank as well as in the drop, which is the point: the chip already has the\n * width before it is dropped, so nothing changes size at the moment of landing. That keeps \"a chip\n * is the same chip wherever it lives\" true (drag-drop.invariance.spec.ts) and keeps the return\n * animation from ending in a snap. A `width: 100%` on the placed chip gives the same still image\n * and breaks both \u2014 it is what order used to do, and why order was the one entry in\n * CHIP_BOX_KNOWN_BAD.\n *\n * min-width, not width, so a chip whose own text is wider than the widest measured chip still\n * fits. Defaults to 0 where nothing is measured (match, whose targets are cards, not slots), so\n * those chips keep their natural width with no rule needed to say so.\n *\n * --qti-drag-min-width, not --qti-dropzone-min-width: the mixin writes the same measured value to\n * both, so a chip and its slot still match, but the two need OPPOSITE defaults when nothing is\n * measured \u2014 a chip must stay at its natural width, while a drops-grid track must not collapse.\n * See the note on both names in qti-variables.css.\n */\n min-width: var(--qti-drag-min-width, 0);\n /* Paint via the contract, nothing more \u2014 the --qti-component-* tokens already make a neutral\n bordered box (white surface, theme border, no shadow). The chip sets NO --drag-* of its own,\n and that is load-bearing: if it did (e.g. `--drag-background-color: white`), that declaration\n would ride the interaction's HIGH-specificity chip selector\n (`qti-gap-match-interaction qti-gap::part(drag)`) and out-specify a brand overlay's bare\n `qti-gap::part(drag)`, so the brand could never win. With nothing set here, the overlay's\n --drag-* is the only declaration and wins with no specificity/layer contest. */\n background-color: var(--drag-background-color, var(--qti-component-background-color));\n background-image: var(--drag-background-image, var(--qti-component-background-image));\n border-width: var(--drag-border-width, var(--qti-component-border-width));\n border-style: var(--drag-border-style, var(--qti-component-border-style));\n border-color: var(--drag-border-color, var(--qti-component-border-color));\n border-radius: var(--drag-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drag-box-shadow, var(--qti-component-box-shadow));\n color: var(--drag-color, var(--qti-component-color));\n opacity: var(--drag-opacity, var(--qti-component-opacity));\n filter: var(--drag-filter, var(--qti-component-filter));\n }\n}\n@layer qti-components.interactions.states {\n /* One chip vocabulary across the bank and placed drags for gap-match only. */\n qti-gap::part(drag) {\n /* Structural \u2014 the load-bearing box, never state-switched (layout-invariance contract).\n box-sizing is deliberate: the clone is appended to document.body outside reset.css's\n @scope, so it inherits no border-box from anywhere else and this sets it. */\n box-sizing: border-box;\n padding: var(--qti-drag-padding);\n cursor: grab;\n /*\n * A chip is as wide as the drop it belongs in \u2014 the SAME token the drop reserves with.\n *\n * That one shared name is what makes a drag and its drop the same size without any arithmetic\n * relating them: the interaction measures the widest chip, publishes it once, and both the chip\n * and the slot read it. See DROP-SIZING.md.\n *\n * It applies in the bank as well as in the drop, which is the point: the chip already has the\n * width before it is dropped, so nothing changes size at the moment of landing. That keeps \"a chip\n * is the same chip wherever it lives\" true (drag-drop.invariance.spec.ts) and keeps the return\n * animation from ending in a snap. A `width: 100%` on the placed chip gives the same still image\n * and breaks both \u2014 it is what order used to do, and why order was the one entry in\n * CHIP_BOX_KNOWN_BAD.\n *\n * min-width, not width, so a chip whose own text is wider than the widest measured chip still\n * fits. Defaults to 0 where nothing is measured (match, whose targets are cards, not slots), so\n * those chips keep their natural width with no rule needed to say so.\n *\n * --qti-drag-min-width, not --qti-dropzone-min-width: the mixin writes the same measured value to\n * both, so a chip and its slot still match, but the two need OPPOSITE defaults when nothing is\n * measured \u2014 a chip must stay at its natural width, while a drops-grid track must not collapse.\n * See the note on both names in qti-variables.css.\n */\n min-width: var(--qti-drag-min-width, 0);\n /* Paint via the contract, nothing more \u2014 the --qti-component-* tokens already make a neutral\n bordered box (white surface, theme border, no shadow). The chip sets NO --drag-* of its own,\n and that is load-bearing: if it did (e.g. `--drag-background-color: white`), that declaration\n would ride the interaction's HIGH-specificity chip selector\n (`qti-gap-match-interaction qti-gap::part(drag)`) and out-specify a brand overlay's bare\n `qti-gap::part(drag)`, so the brand could never win. With nothing set here, the overlay's\n --drag-* is the only declaration and wins with no specificity/layer contest. */\n background-color: var(--drag-background-color, var(--qti-component-background-color));\n background-image: var(--drag-background-image, var(--qti-component-background-image));\n border-width: var(--drag-border-width, var(--qti-component-border-width));\n border-style: var(--drag-border-style, var(--qti-component-border-style));\n border-color: var(--drag-border-color, var(--qti-component-border-color));\n border-radius: var(--drag-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drag-box-shadow, var(--qti-component-box-shadow));\n color: var(--drag-color, var(--qti-component-color));\n opacity: var(--drag-opacity, var(--qti-component-opacity));\n filter: var(--drag-filter, var(--qti-component-filter));\n }\n\n qti-gap-match-interaction qti-gap-text:state(placeholder) {\n /* The hole a spent/lifted chip leaves. Like the chip (see `drag`), it sets NO fill / border /\n shadow of its own \u2014 those inherit from the resting chip (neutral by default, the brand's fill\n when an overlay is on), so a brand can restyle the hole via a `:state(placeholder)` overlay\n with no specificity contest. The one thing a placeholder always does, in every brand, is hide\n its text; the default hole is then just the resting chip (white + border) with its text gone. */\n --drag-color: transparent;\n --drag-opacity: 1;\n pointer-events: none;\n }\n\n qti-gap-match-interaction qti-gap-text:state(dragging) {\n /* The hole a spent/lifted chip leaves. Like the chip (see `drag`), it sets NO fill / border /\n shadow of its own \u2014 those inherit from the resting chip (neutral by default, the brand's fill\n when an overlay is on), so a brand can restyle the hole via a `:state(placeholder)` overlay\n with no specificity contest. The one thing a placeholder always does, in every brand, is hide\n its text; the default hole is then just the resting chip (white + border) with its text gone. */\n --drag-color: transparent;\n --drag-opacity: 1;\n border-color: var(--qti-drop-highlight-border-color);\n box-shadow: inset 0 0 0 var(--qti-drop-highlight-border-width, 2px)\n color-mix(in srgb, var(--qti-drop-highlight-border-color) 55%, transparent);\n pointer-events: none;\n }\n\n qti-gap-match-interaction qti-gap-text:state(return-anchor) {\n border-color: var(--qti-drop-highlight-border-color);\n box-shadow: inset 0 0 0 var(--qti-drop-highlight-border-width, 2px)\n color-mix(in srgb, var(--qti-drop-highlight-border-color) 55%, transparent);\n }\n\n qti-gap-match-interaction qti-gap:state(hover),\n qti-gap-match-interaction qti-gap:state(active) {\n background-color: var(--qti-drop-highlight-bg);\n }\n\n qti-gap-match-interaction qti-gap:state(enabled) {\n background-color: var(--qti-drop-highlight-enabled-bg);\n }\n}\n@layer qti-components.interactions.corrections {\n /* A corrected chip \u2014 in the bank, and where it landed. Moved here from the shared\n qti-corrections.css, which claimed `:state(drag)` for EVERY interaction in one unscoped rule:\n the same shape the chip vocabulary had before it was sprayed, and moved for the same reason.\n `[data-drag-clone]:state(candidate-*)` went with it and is deliberately NOT reproduced \u2014 a clone\n carries no custom states at all, because cloneNode does not copy ElementInternals (measured), so\n that selector never matched anything. */\n /* The two `qti-gap-text:state(candidate-*) { --drag-background-color: var(--qti-correct) }` rules\n that used to sit here are gone, and were already dead: the shared rule declared `background-color`\n DIRECTLY, which outranks the paint binding that reads --drag-background-color, so the solid fill\n they asked for never rendered \u2014 a corrected gap-text has always been the pale tint. Left in place\n they would have come alive the moment the shared rule left, and turned solid. */\n qti-gap-match-interaction :state(drag):state(candidate-correct),\n qti-gap::part(drag):state(candidate-correct) {\n --drag-background-color: var(--qti-correct-light);\n --drag-color: var(--qti-correct);\n --drag-border-color: var(--qti-correct);\n --drag-box-shadow: none;\n }\n\n qti-gap-match-interaction :state(drag):state(candidate-incorrect),\n qti-gap::part(drag):state(candidate-incorrect) {\n --drag-background-color: var(--qti-incorrect-light);\n --drag-color: var(--qti-incorrect);\n --drag-border-color: var(--qti-incorrect);\n --drag-box-shadow: none;\n }\n\n /* The bank is hidden by the interaction itself, on :host([answer-key]) \u2014 see\n qti-gap-match-interaction.styles.ts. */\n}\n/*\n * A hotspot is indicated by a RING, not a fill \u2014 matching QTI 3's own figures (Impl-Guide Fig\n * 54/55) and its shared-vocab wording (\"selection lines and fills\", the line being the ring).\n *\n * The ring is drawn on the INSIDE of the shape, two ways, because the shapes are two kinds of box:\n *\n * circle / ellipse / rect an inset `box-shadow` \u2014 it follows `border-radius`, sits inside the\n * element edge, and never changes the footprint or the hit area.\n *\n * poly a `clip-path` box has no border-box edge for a shadow or border to\n * paint on, so the outline comes from an SVG stroke used as a `mask`,\n * published per-hotspot by positionShapes (hotspot.ts) as\n * --qti-shape-outline. clip-path stays ON (it is the click hit-area) and\n * the mask layers over it, so the stroke's outer half is clipped and a\n * clean inner-edge ring remains \u2014 the same inner edge the box shapes get.\n *\n * Colour and weight are one vocabulary across both:\n * --qti-hotspot-ring-color the ring colour. Unset by default (neutral); set by the selections\n * classes and the correction states below.\n * --qti-hotspot-ring-width resting stroke width; :state(checked) doubles it.\n * --qti-hotspot-halo a near-white outline either side of the ring, so it stays legible on\n * any photograph \u2014 the spec's \"suitable contrast against a background\".\n *\n * This replaces the previous fill treatment and the `@mixin spot` era entirely.\n */\n@layer qti-components.interactions.base {\n qti-hotspot-interaction qti-hotspot-choice {\n /* Resting: a faint neutral ring, so the regions are discoverable before any interaction. */\n --hs-ring: color-mix(in srgb, var(--qti-hotspot-ring-color, var(--qti-border-color)) 60%, transparent);\n --hs-width: var(--qti-hotspot-ring-width, 2px);\n --hs-halo: var(--qti-hotspot-halo, rgb(255 255 255 / 85%));\n\n background-color: transparent;\n cursor: pointer;\n transition:\n background-color 0.15s ease,\n box-shadow 0.15s ease;\n }\n\n /* Box shapes: the ring is an inset shadow, with a halo just inside it and 1px just outside. */\n qti-hotspot-interaction qti-hotspot-choice:not([shape='poly']) {\n box-shadow:\n inset 0 0 0 var(--hs-width) var(--hs-ring),\n inset 0 0 0 calc(var(--hs-width) + 1px) var(--hs-halo),\n 0 0 0 1px var(--hs-halo);\n }\n\n /*\n * Poly: the ring is the published outline, coloured by `background-color` and clipped to the\n * inside by the clip-path positionShapes leaves in place. The masked stroke has an alpha edge, so\n * its halo comes from `drop-shadow` tracing that edge \u2014 no second mask or pseudo-element, and it\n * follows the ring on both sides. (Box shapes can't do this: their ring is a box-shadow, so a\n * drop-shadow would trace the whole box, not the ring \u2014 hence the inset-shadow halo above.)\n */\n qti-hotspot-interaction qti-hotspot-choice[shape='poly'] {\n background-color: var(--hs-ring);\n mask: var(--qti-shape-outline) center / 100% 100% no-repeat;\n -webkit-mask: var(--qti-shape-outline) center / 100% 100% no-repeat;\n filter: drop-shadow(0 0 1px var(--hs-halo)) drop-shadow(0 0 1px var(--hs-halo));\n }\n\n /* Hover strengthens the resting ring rather than filling the shape. */\n qti-hotspot-interaction qti-hotspot-choice:hover {\n --hs-ring: color-mix(in srgb, var(--qti-hotspot-ring-color, var(--qti-border-active)) 90%, transparent);\n }\n\n qti-hotspot-interaction qti-hotspot-choice:focus-visible {\n outline: var(--qti-focus-border-width) solid var(--qti-focus-color);\n outline-offset: 2px;\n }\n}\n@layer qti-components.interactions.states {\n /*\n * Selected: a bold, saturated ring \u2014 the Fig-54 weight jump. Same var, so poly and box shapes\n * move together; poly additionally swaps to the bold outline mask.\n */\n qti-hotspot-interaction qti-hotspot-choice:state(checked) {\n --hs-ring: var(--qti-hotspot-ring-color, var(--qti-selected-bg));\n --hs-width: calc(var(--qti-hotspot-ring-width, 2px) * 2);\n }\n\n qti-hotspot-interaction qti-hotspot-choice[shape='poly']:state(checked) {\n mask-image: var(--qti-shape-outline-bold);\n -webkit-mask-image: var(--qti-shape-outline-bold);\n }\n\n /*\n * Selections theming \u2014 QTI 3 shared interaction vocabulary, Appendix B.1 / vocab \xA71.2.5.1.\n *\n * The image under a hotspot is one the theme has never seen, so the item author picks the\n * contrast. Per the spec, the class names describe the SELECTION colour:\n * qti-selections-light \u2192 a light selection, for a dark background image\n * qti-selections-dark \u2192 a dark selection, for a light background image\n * Both just set the one ring-colour var; the -border token is the line colour, matching how\n * graphic-gap-match already spends these tokens.\n */\n qti-hotspot-interaction.qti-selections-light qti-hotspot-choice {\n --qti-hotspot-ring-color: var(--qti-selections-light-border);\n }\n\n qti-hotspot-interaction.qti-selections-dark qti-hotspot-choice {\n --qti-hotspot-ring-color: var(--qti-selections-dark-border);\n }\n\n /*\n * qti-unselected-hidden \u2014 vocab \xA71.2.5.1: selections \"visually hidden until focused or selected\".\n *\n * Blank the ring and its halo while a hotspot is neither selected nor keyboard-focused. Both\n * halves matter: the picture reads cleanly, and a *focused* hotspot returns, which is the only\n * thing that keeps the interaction operable from a keyboard \u2014 drop `:not(:focus-visible)` and\n * tabbing through an item shows nothing.\n *\n * In `states` so it outranks the base ring and the `:hover` strengthen: hover must NOT reveal, or\n * sweeping the pointer hands back the answer regions one at a time.\n */\n qti-hotspot-interaction.qti-unselected-hidden qti-hotspot-choice:not(:state(checked)):not(:focus-visible) {\n --hs-ring: transparent;\n --hs-halo: transparent;\n }\n\n qti-hotspot-interaction qti-hotspot-choice:state(disabled),\n qti-hotspot-interaction qti-hotspot-choice:state(readonly) {\n cursor: not-allowed;\n pointer-events: none;\n }\n}\n@layer qti-components.interactions.corrections {\n /*\n * Correction and answer-key views recolour the same ring \u2014 one var, so circle and poly stay in\n * step. Answer-key first, so a candidate's own judgement wins when both are present.\n */\n qti-hotspot-interaction qti-hotspot-choice:state(correct-response) {\n --qti-hotspot-ring-color: var(--qti-answer-border);\n }\n\n qti-hotspot-interaction qti-hotspot-choice:state(candidate-correct) {\n --qti-hotspot-ring-color: var(--qti-correct);\n }\n\n qti-hotspot-interaction qti-hotspot-choice:state(candidate-incorrect) {\n --qti-hotspot-ring-color: var(--qti-incorrect);\n }\n\n .full-correct-response qti-hotspot-interaction qti-hotspot-choice:state(checked) {\n --qti-hotspot-ring-color: var(--qti-answer-border);\n }\n\n /*\n * A correction or answer-key ring reads at full strength and bold weight, whether or not the\n * candidate selected that hotspot \u2014 an unselected correct answer still needs to be seen. (A\n * candidate-* hotspot is usually also :state(checked) and would be bold anyway; correct-response\n * on an unselected region is the case this covers.)\n */\n qti-hotspot-interaction qti-hotspot-choice:state(correct-response),\n qti-hotspot-interaction qti-hotspot-choice:state(candidate-correct),\n qti-hotspot-interaction qti-hotspot-choice:state(candidate-incorrect),\n .full-correct-response qti-hotspot-interaction qti-hotspot-choice:state(checked) {\n --hs-ring: var(--qti-hotspot-ring-color);\n --hs-width: calc(var(--qti-hotspot-ring-width, 2px) * 2);\n }\n\n qti-hotspot-interaction qti-hotspot-choice[shape='poly']:state(correct-response),\n qti-hotspot-interaction qti-hotspot-choice[shape='poly']:state(candidate-correct),\n qti-hotspot-interaction qti-hotspot-choice[shape='poly']:state(candidate-incorrect),\n .full-correct-response qti-hotspot-interaction qti-hotspot-choice[shape='poly']:state(checked) {\n mask-image: var(--qti-shape-outline-bold);\n -webkit-mask-image: var(--qti-shape-outline-bold);\n }\n}\n@layer qti-components.interactions.base {\n qti-hottext-interaction {\n --qti-hottext-selected-padding: var(--qti-padding-inline);\n\n /* The native radio/checkbox control is never rendered for a hottext \u2014 every variant uses custom\n styling. That is unconditional, so it lives in qti-hottext.styles.ts, which hides [part='control']\n outright. It used to be repeated here three times, once per variant, under an unconditional rule\n that had already hidden it. */\n /*\n * The selected padding, reserved unselected.\n *\n * :state(checked) used to swap 'var(--qti-border-thickness) 0' for the padding above, so\n * clicking a word grew it and reflowed the sentence around it \u2014 every following word moved, and\n * on a wrapping line so did the paragraph. That is the 'checked' entry the .stylelintrc.mjs\n * comment describes.\n *\n * Reserving it here is the same move as the 'border: 2px solid transparent' that holds a drop\n * target's active border: pay the space once, up front, so the state has only to repaint. Every\n * hottext is now the size a selected one used to be, which is also a larger target to click.\n */\n qti-hottext {\n padding: var(--qti-hottext-selected-padding);\n }\n\n &.qti-input-control-hidden {\n qti-hottext {\n border: var(--qti-border);\n border-radius: var(--qti-border-radius);\n\n background: var(--qti-bg);\n\n\n &:focus {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n }\n }\n }\n\n &.qti-unselected-hidden {\n qti-hottext {\n border: 0;\n border-radius: var(--qti-border-radius);\n background-color: transparent;\n outline: none;\n cursor: pointer;\n\n\n &:focus {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n }\n }\n }\n\n &:not(.qti-input-control-hidden, .qti-unselected-hidden) {\n qti-hottext {\n border: var(--qti-border);\n border-radius: var(--qti-border-radius);\n\n\n &:focus {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n }\n }\n }\n }\n}\n@layer qti-components.interactions.states {\n qti-hottext-interaction {\n &.qti-input-control-hidden qti-hottext {\n &:state(checked) {\n border-color: var(--qti-border-active);\n background-color: var(--qti-selected-bg);\n color: var(--qti-selected-color);\n }\n\n &:state(readonly) {\n cursor: pointer;\n }\n\n &:state(disabled) {\n cursor: not-allowed;\n }\n }\n\n &.qti-unselected-hidden qti-hottext,\n &:not(.qti-input-control-hidden, .qti-unselected-hidden) qti-hottext {\n &:state(checked) {\n background-color: var(--qti-selected-bg);\n color: var(--qti-selected-color);\n }\n\n &:state(readonly) {\n cursor: pointer;\n }\n\n &:state(disabled) {\n cursor: not-allowed;\n }\n }\n }\n}\n@layer qti-components.interactions.corrections {\n qti-hottext-interaction {\n qti-hottext {\n &:state(correct-response),\n &[data-correct-response='true'] {\n &::after {\n content: '\\02714';\n color: var(--qti-correct);\n }\n }\n }\n\n &.qti-input-control-hidden qti-hottext {\n &:state(candidate-correct) {\n background-color: var(--qti-correct-light);\n color: var(--qti-correct);\n border-color: var(--qti-correct);\n box-shadow: none;\n }\n\n &:state(candidate-incorrect) {\n background-color: var(--qti-incorrect-light);\n color: var(--qti-incorrect);\n border-color: var(--qti-incorrect);\n box-shadow: none;\n }\n }\n\n &.qti-unselected-hidden qti-hottext,\n &:not(.qti-input-control-hidden, .qti-unselected-hidden) qti-hottext {\n &:state(candidate-correct) {\n background-color: var(--qti-correct-light);\n color: var(--qti-correct);\n border-color: var(--qti-correct);\n box-shadow: none;\n box-shadow: inset 0 0 0 var(--qti-border-thickness) var(--qti-correct);\n }\n\n &:state(candidate-incorrect) {\n background-color: var(--qti-incorrect-light);\n color: var(--qti-incorrect);\n border-color: var(--qti-incorrect);\n box-shadow: none;\n box-shadow: inset 0 0 0 var(--qti-border-thickness) var(--qti-incorrect);\n }\n }\n }\n\n .full-correct-response qti-hottext-interaction.qti-input-control-hidden qti-hottext:state(checked),\n .full-correct-response qti-hottext-interaction.qti-unselected-hidden qti-hottext:state(checked),\n .full-correct-response\n qti-hottext-interaction:not(.qti-input-control-hidden, .qti-unselected-hidden)\n qti-hottext:state(checked) {\n background-color: var(--qti-answer-bg);\n color: var(--qti-answer-fg);\n border-color: var(--qti-answer-border);\n\n border-width: var(--qti-border-thickness);\n border-style: var(--qti-border-style);\n padding-inline-start: calc(1em + var(--qti-gap));\n padding-inline-end: var(--qti-gap);\n\n /* The icon must not become the inline-flex container's first baseline item. */\n &::before {\n content: '';\n background-color: var(--qti-answer-fg);\n mask: var(--qti-check-mask) no-repeat center / contain;\n -webkit-mask: var(--qti-check-mask) no-repeat center / contain;\n\n position: absolute;\n inset-inline-start: var(--qti-gap);\n top: 50%;\n translate: 0 -50%;\n display: block;\n width: 1em;\n height: 1em;\n }\n }\n}\n@layer qti-components.interactions.base {\n qti-inline-choice-interaction {\n /* \u2500\u2500 Host: the closed combobox, painted through the paint contract so a brand can restyle the\n field the same way it restyles chips/buttons/text-fields. Its one field-specific default (fg\n text, rather than the contract's `inherit`) is set as --field-* so it stays overridable \u2014 and\n being the field's own slot, it no longer inherits into the menu and message below. Those two\n still land on the same colour: they inherit the field's computed `color`. */\n background-color: var(--field-background-color, var(--qti-component-background-color));\n background-image: var(--field-background-image, var(--qti-component-background-image));\n border-width: var(--field-border-width, var(--qti-component-border-width));\n border-style: var(--field-border-style, var(--qti-component-border-style));\n border-color: var(--field-border-color, var(--qti-component-border-color));\n border-radius: var(--field-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--field-box-shadow, var(--qti-component-box-shadow));\n color: var(--field-color, var(--qti-component-color));\n opacity: var(--field-opacity, var(--qti-component-opacity));\n filter: var(--field-filter, var(--qti-component-filter));\n --field-color: var(--qti-fg);\n\n /* \u2500\u2500 Trigger: transparent inside the field border \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n &::part(trigger) {\n padding: var(--qti-padding-box);\n background: transparent;\n border: none;\n border-radius: inherit;\n color: inherit;\n cursor: pointer;\n font-size: inherit;\n }\n\n &::part(trigger):focus-visible {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n outline: none;\n }\n\n &:state(disabled)::part(trigger),\n &[disabled]::part(trigger) {\n cursor: not-allowed;\n }\n\n &:state(readonly)::part(trigger),\n &[readonly]::part(trigger) {\n cursor: pointer;\n }\n\n /* \u2500\u2500 Chevron icon: color only; transform/mask/size live in shadow DOM \u2500\u2500\u2500\u2500\u2500\u2500 */\n &::part(dropdown-icon) {\n color: inherit;\n }\n\n &::part(dropdown-icon-open) {\n color: inherit;\n }\n\n /* \u2500\u2500 Popover menu: the neutral paint box (fill + border + radius all default). \u2500\u2500 */\n /*\n * No padding of its own \u2014 the rows go edge to edge, and each row pays --qti-padding-box, the\n * SAME token the trigger pays.\n *\n * That is what makes the closed and the open control read as one thing: the selected value in\n * the trigger and the option rows beneath it now start on exactly the same vertical line\n * (measured: both 17px from the visible edge; before, the option text sat at 21px because the\n * menu added --qti-padding-inline on top of the row's own --qti-padding-box).\n *\n * It also fixes the disproportion: the highlighted row used to float with 5px beside it and 3px\n * above it. Two spacing tokens for one box is the whole bug \u2014 the vocabulary says pick one and\n * spend it whole (see the CONTRACT note on --qti-padding-box / -inline / --qti-gap).\n */\n &::part(menu) {\n background-color: var(--menu-background-color, var(--qti-component-background-color));\n background-image: var(--menu-background-image, var(--qti-component-background-image));\n border-width: var(--menu-border-width, var(--qti-component-border-width));\n border-style: var(--menu-border-style, var(--qti-component-border-style));\n border-color: var(--menu-border-color, var(--qti-component-border-color));\n border-radius: var(--menu-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--menu-box-shadow, var(--qti-component-box-shadow));\n color: var(--menu-color, var(--qti-component-color));\n opacity: var(--menu-opacity, var(--qti-component-opacity));\n filter: var(--menu-filter, var(--qti-component-filter));\n padding: 0;\n }\n\n /*\n * Open, the trigger and the menu are one shape, so the edges that meet lose their rounding.\n *\n * Both boxes are rounded on all four corners at rest, and the sides already line up to the pixel\n * (measured: trigger and menu both 97.38 \u2192 464.80, seam at 0.00). What read as \"not quite\n * meeting\" was purely the two curves pinching inward at the join, with the two borders stacked\n * between them.\n *\n * Paint only \u2014 border-radius changes nothing about where anything sits, so this is safe in a\n * state (CONTRACT.md \xA72: a state must not change layout metrics).\n *\n * The host is the box that carries the field's border; the trigger inside it is transparent with\n * `border-radius: inherit`, so squaring the host is what squares the visible bottom edge.\n *\n * Known limit: the menu may flip above the trigger when there is no room below\n * (`position-try-fallbacks: flip-block` in the shadow styles). These two rules assume it sits\n * below, so a flipped menu keeps its rounding at the join. There is no selector for \"which\n * position-try fallback applied\", so fixing that needs a signal from script, and it is not worth\n * one for the rarer case.\n */\n &:state(open) {\n border-end-start-radius: 0;\n border-end-end-radius: 0;\n }\n\n &::part(menu):popover-open {\n border-start-start-radius: 0;\n border-start-end-radius: 0;\n }\n\n /* \u2500\u2500 Option rows \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n &::part(option) {\n padding: var(--qti-padding-box);\n border: 0;\n /* Square, because a row now spans the full width of the menu. The menu's own radius still\n rounds the first and last row, since it clips them (overflow: auto). A radius here would\n only show as a notch inside a straight edge. */\n border-radius: 0;\n background: transparent;\n cursor: pointer;\n }\n\n &::part(option):focus-visible {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n outline: none;\n }\n\n &::part(option-selected) {\n background-color: var(--qti-bg-active);\n }\n\n /* \u2500\u2500 Slotted qti-inline-choice elements (light DOM) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* The option row's own box \u2014 flex, centred, single-line, clipped \u2014 is in\n qti-inline-choice.styles.ts. */\n & qti-inline-choice {\n width: 100%;\n box-sizing: border-box;\n padding: var(--qti-padding-box);\n border: 0;\n /* Square, for the same reason as ::part(option) above \u2014 these are the other half of the same\n list (the prompt row is a part, the authored choices are slotted), and they have to agree. */\n border-radius: 0;\n background: transparent;\n text-align: left;\n font: inherit;\n color: inherit;\n cursor: pointer;\n text-overflow: ellipsis;\n }\n\n & qti-inline-choice:focus-visible {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n outline: none;\n }\n\n & qti-inline-choice:state(checked) {\n background-color: var(--qti-bg-active);\n }\n\n /* \u2500\u2500 Validation message: a warning box, painted through the contract. Its non-neutral bits\n (fg text, amber-tinted fill, amber border) are --message-* overrides; width/style/radius\n come from the defaults. \u2500\u2500 */\n &::part(message) {\n background-color: var(--message-background-color, var(--qti-component-background-color));\n background-image: var(--message-background-image, var(--qti-component-background-image));\n border-width: var(--message-border-width, var(--qti-component-border-width));\n border-style: var(--message-border-style, var(--qti-component-border-style));\n border-color: var(--message-border-color, var(--qti-component-border-color));\n border-radius: var(--message-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--message-box-shadow, var(--qti-component-box-shadow));\n color: var(--message-color, var(--qti-component-color));\n opacity: var(--message-opacity, var(--qti-component-opacity));\n filter: var(--message-filter, var(--qti-component-filter));\n --message-color: var(--qti-fg);\n --message-background-color: color-mix(in srgb, var(--qti-warning) 5%, white);\n --message-border-color: var(--qti-warning);\n padding: var(--qti-padding-box);\n }\n }\n}\n@layer qti-components.interactions.corrections {\n /* The answer key, moved here from the shared qti-corrections.css: it names this interaction, so\n this file owns it. */\n .full-correct-response qti-inline-choice-interaction {\n background-color: var(--qti-answer-bg);\n color: var(--qti-answer-fg);\n border-color: var(--qti-answer-border);\n\n &::part(value) {\n color: var(--qti-answer-fg);\n }\n }\n\n qti-inline-choice-interaction {\n &:state(candidate-correct) {\n --field-background-color: var(--qti-correct-light);\n --field-color: var(--qti-correct);\n --field-border-color: var(--qti-correct);\n --field-box-shadow: none;\n\n &::part(trigger) {\n background: transparent;\n }\n }\n\n &:state(candidate-incorrect) {\n --field-background-color: var(--qti-incorrect-light);\n --field-color: var(--qti-incorrect);\n --field-border-color: var(--qti-incorrect);\n --field-box-shadow: none;\n\n &::part(trigger) {\n background: transparent;\n }\n }\n }\n}\n/* This interaction's chip vocabulary \u2014 the bank chip, the placed chip, and the floating clone. One\n such block per drag-drop interaction, opt-in; graphic-gap-match deliberately declares none. See\n qti-states.css. Kept in the `global` layer, where these rules used to live, so cascade position is\n unchanged.\n\n The clone is matched by [data-drag-interaction] rather than by ancestry because it is a SIBLING of\n the interaction, not a descendant: createDragClone appends it to the interaction's ROOT (the\n item's shadow root), which is the same tree \u2014 so the theme can reach it \u2014 but not the same\n subtree. The attribute is also what tells one interaction's chip in flight from another's, which\n is how graphic-gap-match stays out without anyone writing an exclusion.\n\n Its grip hangs off ::part(control)::before, the same place the resting chip's does, so a chip does\n not move its handle when you pick it up. That the part is reachable at all is new: while the clone\n sat on document.body it was in a different tree from the adopted stylesheet, and nothing \u2014 no\n part, no attribute \u2014 could style it. */\n@layer qti-components.global {\n qti-match-interaction :state(drag),\n [data-drag-clone][data-drag-interaction='qti-match-interaction'] {\n /* Structural \u2014 the load-bearing box, never state-switched (layout-invariance contract).\n box-sizing is deliberate: the clone is appended to document.body outside reset.css's\n @scope, so it inherits no border-box from anywhere else and this sets it. */\n box-sizing: border-box;\n padding: var(--qti-drag-padding);\n cursor: grab;\n /*\n * A chip is as wide as the drop it belongs in \u2014 the SAME token the drop reserves with.\n *\n * That one shared name is what makes a drag and its drop the same size without any arithmetic\n * relating them: the interaction measures the widest chip, publishes it once, and both the chip\n * and the slot read it. See DROP-SIZING.md.\n *\n * It applies in the bank as well as in the drop, which is the point: the chip already has the\n * width before it is dropped, so nothing changes size at the moment of landing. That keeps \"a chip\n * is the same chip wherever it lives\" true (drag-drop.invariance.spec.ts) and keeps the return\n * animation from ending in a snap. A `width: 100%` on the placed chip gives the same still image\n * and breaks both \u2014 it is what order used to do, and why order was the one entry in\n * CHIP_BOX_KNOWN_BAD.\n *\n * min-width, not width, so a chip whose own text is wider than the widest measured chip still\n * fits. Defaults to 0 where nothing is measured (match, whose targets are cards, not slots), so\n * those chips keep their natural width with no rule needed to say so.\n *\n * --qti-drag-min-width, not --qti-dropzone-min-width: the mixin writes the same measured value to\n * both, so a chip and its slot still match, but the two need OPPOSITE defaults when nothing is\n * measured \u2014 a chip must stay at its natural width, while a drops-grid track must not collapse.\n * See the note on both names in qti-variables.css.\n */\n min-width: var(--qti-drag-min-width, 0);\n /* Paint via the contract, nothing more \u2014 the --qti-component-* tokens already make a neutral\n bordered box (white surface, theme border, no shadow). The chip sets NO --drag-* of its own,\n and that is load-bearing: if it did (e.g. `--drag-background-color: white`), that declaration\n would ride the interaction's HIGH-specificity chip selector\n (`qti-gap-match-interaction qti-gap::part(drag)`) and out-specify a brand overlay's bare\n `qti-gap::part(drag)`, so the brand could never win. With nothing set here, the overlay's\n --drag-* is the only declaration and wins with no specificity/layer contest. */\n background-color: var(--drag-background-color, var(--qti-component-background-color));\n background-image: var(--drag-background-image, var(--qti-component-background-image));\n border-width: var(--drag-border-width, var(--qti-component-border-width));\n border-style: var(--drag-border-style, var(--qti-component-border-style));\n border-color: var(--drag-border-color, var(--qti-component-border-color));\n border-radius: var(--drag-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drag-box-shadow, var(--qti-component-box-shadow));\n color: var(--drag-color, var(--qti-component-color));\n opacity: var(--drag-opacity, var(--qti-component-opacity));\n filter: var(--drag-filter, var(--qti-component-filter));\n }\n\n qti-match-interaction :state(drag)::part(control)::before,\n qti-match-interaction::part(drag-control)::before,\n qti-match-interaction ::part(drag-control)::before,\n [data-drag-clone][data-drag-interaction='qti-match-interaction']::part(control)::before {\n content: '';\n display: inline-block;\n vertical-align: middle;\n width: 0.75em;\n height: 1em;\n margin-right: var(--qti-glyph-gap);\n background-color: currentColor;\n mask: var(--qti-grip-mask) no-repeat center / contain;\n -webkit-mask: var(--qti-grip-mask) no-repeat center / contain;\n }\n}\n@layer qti-components.interactions.base {\n /* Editor fake-drag projected into the target choice's `drop` shadow.\n Reuses the same `drag` styling so the editor preview matches what students\n see when an answer is dropped onto a target at runtime. */\n qti-simple-associable-choice::part(drag) {\n /* Structural \u2014 the load-bearing box, never state-switched (layout-invariance contract).\n box-sizing is deliberate: the clone is appended to document.body outside reset.css's\n @scope, so it inherits no border-box from anywhere else and this sets it. */\n box-sizing: border-box;\n padding: var(--qti-drag-padding);\n cursor: grab;\n /*\n * A chip is as wide as the drop it belongs in \u2014 the SAME token the drop reserves with.\n *\n * That one shared name is what makes a drag and its drop the same size without any arithmetic\n * relating them: the interaction measures the widest chip, publishes it once, and both the chip\n * and the slot read it. See DROP-SIZING.md.\n *\n * It applies in the bank as well as in the drop, which is the point: the chip already has the\n * width before it is dropped, so nothing changes size at the moment of landing. That keeps \"a chip\n * is the same chip wherever it lives\" true (drag-drop.invariance.spec.ts) and keeps the return\n * animation from ending in a snap. A `width: 100%` on the placed chip gives the same still image\n * and breaks both \u2014 it is what order used to do, and why order was the one entry in\n * CHIP_BOX_KNOWN_BAD.\n *\n * min-width, not width, so a chip whose own text is wider than the widest measured chip still\n * fits. Defaults to 0 where nothing is measured (match, whose targets are cards, not slots), so\n * those chips keep their natural width with no rule needed to say so.\n *\n * --qti-drag-min-width, not --qti-dropzone-min-width: the mixin writes the same measured value to\n * both, so a chip and its slot still match, but the two need OPPOSITE defaults when nothing is\n * measured \u2014 a chip must stay at its natural width, while a drops-grid track must not collapse.\n * See the note on both names in qti-variables.css.\n */\n min-width: var(--qti-drag-min-width, 0);\n /* Paint via the contract, nothing more \u2014 the --qti-component-* tokens already make a neutral\n bordered box (white surface, theme border, no shadow). The chip sets NO --drag-* of its own,\n and that is load-bearing: if it did (e.g. `--drag-background-color: white`), that declaration\n would ride the interaction's HIGH-specificity chip selector\n (`qti-gap-match-interaction qti-gap::part(drag)`) and out-specify a brand overlay's bare\n `qti-gap::part(drag)`, so the brand could never win. With nothing set here, the overlay's\n --drag-* is the only declaration and wins with no specificity/layer contest. */\n background-color: var(--drag-background-color, var(--qti-component-background-color));\n background-image: var(--drag-background-image, var(--qti-component-background-image));\n border-width: var(--drag-border-width, var(--qti-component-border-width));\n border-style: var(--drag-border-style, var(--qti-component-border-style));\n border-color: var(--drag-border-color, var(--qti-component-border-color));\n border-radius: var(--drag-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drag-box-shadow, var(--qti-component-box-shadow));\n color: var(--drag-color, var(--qti-component-color));\n opacity: var(--drag-opacity, var(--qti-component-opacity));\n filter: var(--drag-filter, var(--qti-component-filter));\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(\n in srgb,\n var(--drag-background-color, var(--qti-component-background-color)) 92%,\n black\n );\n }\n }\n &:focus {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n }\n }\n\n qti-match-interaction:not(.qti-match-tabular) {\n /*\n * The drag bank does not light up while you drag.\n *\n * `:state(dragzone-enabled)` filled it, `:state(dragzone-active)` drew a solid\n * `--qti-border-active` box around it, and both fired the instant a chip was picked up \u2014 so\n * lifting a chip framed the container the chip had just left. The one thing worth looking at is\n * the hole, and the chip paints that itself via `:state(dragging)` / `:state(placeholder)`.\n *\n * `border: 2px solid transparent` went with them. It was reserving space for the active border,\n * so that a transient state could repaint without resizing. With nothing left to repaint, it is\n * 4px of nothing.\n */\n /* Non-tabular match wants an inline label, so a chip hugs its text. Handed to\n qti-simple-associable-choice.styles.ts, which owns its own box; this used to be an\n `::part(label) { display: inline !important }` further down the file. */\n --qti-choice-label-display: inline;\n\n /* The bank and the target grid are laid out in qti-match-interaction.styles.ts. Only the gaps\n are still set here \u2014 gap is spacing. */\n & qti-simple-match-set:first-of-type {\n gap: var(--qti-gap);\n & qti-simple-associable-choice {\n /* Structural \u2014 the load-bearing box, never state-switched (layout-invariance contract).\n box-sizing is deliberate: the clone is appended to document.body outside reset.css's\n @scope, so it inherits no border-box from anywhere else and this sets it. */\n box-sizing: border-box;\n padding: var(--qti-drag-padding);\n cursor: grab;\n /*\n * A chip is as wide as the drop it belongs in \u2014 the SAME token the drop reserves with.\n *\n * That one shared name is what makes a drag and its drop the same size without any arithmetic\n * relating them: the interaction measures the widest chip, publishes it once, and both the chip\n * and the slot read it. See DROP-SIZING.md.\n *\n * It applies in the bank as well as in the drop, which is the point: the chip already has the\n * width before it is dropped, so nothing changes size at the moment of landing. That keeps \"a chip\n * is the same chip wherever it lives\" true (drag-drop.invariance.spec.ts) and keeps the return\n * animation from ending in a snap. A `width: 100%` on the placed chip gives the same still image\n * and breaks both \u2014 it is what order used to do, and why order was the one entry in\n * CHIP_BOX_KNOWN_BAD.\n *\n * min-width, not width, so a chip whose own text is wider than the widest measured chip still\n * fits. Defaults to 0 where nothing is measured (match, whose targets are cards, not slots), so\n * those chips keep their natural width with no rule needed to say so.\n *\n * --qti-drag-min-width, not --qti-dropzone-min-width: the mixin writes the same measured value to\n * both, so a chip and its slot still match, but the two need OPPOSITE defaults when nothing is\n * measured \u2014 a chip must stay at its natural width, while a drops-grid track must not collapse.\n * See the note on both names in qti-variables.css.\n */\n min-width: var(--qti-drag-min-width, 0);\n /* Paint via the contract, nothing more \u2014 the --qti-component-* tokens already make a neutral\n bordered box (white surface, theme border, no shadow). The chip sets NO --drag-* of its own,\n and that is load-bearing: if it did (e.g. `--drag-background-color: white`), that declaration\n would ride the interaction's HIGH-specificity chip selector\n (`qti-gap-match-interaction qti-gap::part(drag)`) and out-specify a brand overlay's bare\n `qti-gap::part(drag)`, so the brand could never win. With nothing set here, the overlay's\n --drag-* is the only declaration and wins with no specificity/layer contest. */\n background-color: var(--drag-background-color, var(--qti-component-background-color));\n background-image: var(--drag-background-image, var(--qti-component-background-image));\n border-width: var(--drag-border-width, var(--qti-component-border-width));\n border-style: var(--drag-border-style, var(--qti-component-border-style));\n border-color: var(--drag-border-color, var(--qti-component-border-color));\n border-radius: var(--drag-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drag-box-shadow, var(--qti-component-box-shadow));\n color: var(--drag-color, var(--qti-component-color));\n opacity: var(--drag-opacity, var(--qti-component-opacity));\n filter: var(--drag-filter, var(--qti-component-filter));\n &[dragging] {\n pointer-events: none;\n rotate: -2deg;\n --drag-box-shadow: 0 8px 12px rgb(0 0 0 / 20%), 0 4px 8px rgb(0 0 0 / 10%);\n }\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(\n in srgb,\n var(--drag-background-color, var(--qti-component-background-color)) 92%,\n black\n );\n }\n }\n &:focus {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n }\n }\n }\n & qti-simple-match-set:last-of-type {\n gap: var(--qti-gap);\n & > qti-simple-associable-choice {\n & img {\n max-width: 100%;\n height: auto;\n }\n &[disabled] {\n &::part(drop) {\n cursor: not-allowed;\n }\n }\n /*\n * No `[active]` highlight. Lifting a chip used to draw a solid `--qti-border-active` border\n * around every drop container at once, which says \"any of these\" \u2014 the least useful thing to\n * say. What the candidate wants to see is the hole the chip left, and that is painted on the\n * chip's own `:state(dragging)` / `:state(placeholder)`.\n */\n &::part(drop) {\n background-color: var(--drop-background-color, var(--qti-component-background-color));\n background-image: var(--drop-background-image, var(--qti-component-background-image));\n border-width: var(--drop-border-width, var(--qti-component-border-width));\n border-style: var(--drop-border-style, var(--qti-component-border-style));\n border-color: var(--drop-border-color, var(--qti-component-border-color));\n border-radius: var(--drop-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drop-box-shadow, var(--qti-component-box-shadow));\n color: var(--drop-color, var(--qti-component-color));\n opacity: var(--drop-opacity, var(--qti-component-opacity));\n filter: var(--drop-filter, var(--qti-component-filter));\n border-style: var(--drop-border-style, dashed);\n /* No `@mixin dragging` on the drop. It is a chip-in-flight look (rotate, pointer-events:\n none, elevation) that never belonged on a drop. Per the comment above, a drop shows\n nothing while a drag is in flight; the hole is painted on the chip. (Its elevation also\n used to LEAK: `dragging` sets --component-box-shadow, one shared namespace, so it\n inherited onto every chip nested in the drop. That half is now structural \u2014 `dragging`\n sets --drag-box-shadow, which a drop does not read.) */\n &:focus {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n }\n margin-top: var(--qti-gap);\n gap: var(--qti-gap);\n }\n & > *:not(qti-simple-associable-choice) {\n pointer-events: none;\n }\n /* A chip nested in a drop target renders no drop region of its own. That is the component's\n own rule \u2014 qti-simple-associable-choice.styles.ts hides [part~='drop'] for both\n :host(:state(drag)) and :host([part~='drag']), which covers the placed clone this\n selector could not reach. */\n & > qti-simple-associable-choice {\n /* Structural \u2014 the load-bearing box, never state-switched (layout-invariance contract).\n box-sizing is deliberate: the clone is appended to document.body outside reset.css's\n @scope, so it inherits no border-box from anywhere else and this sets it. */\n box-sizing: border-box;\n padding: var(--qti-drag-padding);\n cursor: grab;\n /*\n * A chip is as wide as the drop it belongs in \u2014 the SAME token the drop reserves with.\n *\n * That one shared name is what makes a drag and its drop the same size without any arithmetic\n * relating them: the interaction measures the widest chip, publishes it once, and both the chip\n * and the slot read it. See DROP-SIZING.md.\n *\n * It applies in the bank as well as in the drop, which is the point: the chip already has the\n * width before it is dropped, so nothing changes size at the moment of landing. That keeps \"a chip\n * is the same chip wherever it lives\" true (drag-drop.invariance.spec.ts) and keeps the return\n * animation from ending in a snap. A `width: 100%` on the placed chip gives the same still image\n * and breaks both \u2014 it is what order used to do, and why order was the one entry in\n * CHIP_BOX_KNOWN_BAD.\n *\n * min-width, not width, so a chip whose own text is wider than the widest measured chip still\n * fits. Defaults to 0 where nothing is measured (match, whose targets are cards, not slots), so\n * those chips keep their natural width with no rule needed to say so.\n *\n * --qti-drag-min-width, not --qti-dropzone-min-width: the mixin writes the same measured value to\n * both, so a chip and its slot still match, but the two need OPPOSITE defaults when nothing is\n * measured \u2014 a chip must stay at its natural width, while a drops-grid track must not collapse.\n * See the note on both names in qti-variables.css.\n */\n min-width: var(--qti-drag-min-width, 0);\n /* Paint via the contract, nothing more \u2014 the --qti-component-* tokens already make a neutral\n bordered box (white surface, theme border, no shadow). The chip sets NO --drag-* of its own,\n and that is load-bearing: if it did (e.g. `--drag-background-color: white`), that declaration\n would ride the interaction's HIGH-specificity chip selector\n (`qti-gap-match-interaction qti-gap::part(drag)`) and out-specify a brand overlay's bare\n `qti-gap::part(drag)`, so the brand could never win. With nothing set here, the overlay's\n --drag-* is the only declaration and wins with no specificity/layer contest. */\n background-color: var(--drag-background-color, var(--qti-component-background-color));\n background-image: var(--drag-background-image, var(--qti-component-background-image));\n border-width: var(--drag-border-width, var(--qti-component-border-width));\n border-style: var(--drag-border-style, var(--qti-component-border-style));\n border-color: var(--drag-border-color, var(--qti-component-border-color));\n border-radius: var(--drag-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drag-box-shadow, var(--qti-component-box-shadow));\n color: var(--drag-color, var(--qti-component-color));\n opacity: var(--drag-opacity, var(--qti-component-opacity));\n filter: var(--drag-filter, var(--qti-component-filter));\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(\n in srgb,\n var(--drag-background-color, var(--qti-component-background-color)) 92%,\n black\n );\n }\n }\n &:focus {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n }\n }\n }\n }\n }\n\n qti-match-interaction:not(.qti-match-tabular)\n qti-simple-match-set:first-of-type\n qti-simple-associable-choice:state(drag),\n qti-match-interaction:not(.qti-match-tabular) qti-simple-associable-choice::part(drag) {\n /* Structural \u2014 the load-bearing box, never state-switched (layout-invariance contract).\n box-sizing is deliberate: the clone is appended to document.body outside reset.css's\n @scope, so it inherits no border-box from anywhere else and this sets it. */\n box-sizing: border-box;\n padding: var(--qti-drag-padding);\n cursor: grab;\n /*\n * A chip is as wide as the drop it belongs in \u2014 the SAME token the drop reserves with.\n *\n * That one shared name is what makes a drag and its drop the same size without any arithmetic\n * relating them: the interaction measures the widest chip, publishes it once, and both the chip\n * and the slot read it. See DROP-SIZING.md.\n *\n * It applies in the bank as well as in the drop, which is the point: the chip already has the\n * width before it is dropped, so nothing changes size at the moment of landing. That keeps \"a chip\n * is the same chip wherever it lives\" true (drag-drop.invariance.spec.ts) and keeps the return\n * animation from ending in a snap. A `width: 100%` on the placed chip gives the same still image\n * and breaks both \u2014 it is what order used to do, and why order was the one entry in\n * CHIP_BOX_KNOWN_BAD.\n *\n * min-width, not width, so a chip whose own text is wider than the widest measured chip still\n * fits. Defaults to 0 where nothing is measured (match, whose targets are cards, not slots), so\n * those chips keep their natural width with no rule needed to say so.\n *\n * --qti-drag-min-width, not --qti-dropzone-min-width: the mixin writes the same measured value to\n * both, so a chip and its slot still match, but the two need OPPOSITE defaults when nothing is\n * measured \u2014 a chip must stay at its natural width, while a drops-grid track must not collapse.\n * See the note on both names in qti-variables.css.\n */\n min-width: var(--qti-drag-min-width, 0);\n /* Paint via the contract, nothing more \u2014 the --qti-component-* tokens already make a neutral\n bordered box (white surface, theme border, no shadow). The chip sets NO --drag-* of its own,\n and that is load-bearing: if it did (e.g. `--drag-background-color: white`), that declaration\n would ride the interaction's HIGH-specificity chip selector\n (`qti-gap-match-interaction qti-gap::part(drag)`) and out-specify a brand overlay's bare\n `qti-gap::part(drag)`, so the brand could never win. With nothing set here, the overlay's\n --drag-* is the only declaration and wins with no specificity/layer contest. */\n background-color: var(--drag-background-color, var(--qti-component-background-color));\n background-image: var(--drag-background-image, var(--qti-component-background-image));\n border-width: var(--drag-border-width, var(--qti-component-border-width));\n border-style: var(--drag-border-style, var(--qti-component-border-style));\n border-color: var(--drag-border-color, var(--qti-component-border-color));\n border-radius: var(--drag-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drag-box-shadow, var(--qti-component-box-shadow));\n color: var(--drag-color, var(--qti-component-color));\n opacity: var(--drag-opacity, var(--qti-component-opacity));\n filter: var(--drag-filter, var(--qti-component-filter));\n }\n\n qti-match-interaction:not(.qti-match-tabular) qti-simple-match-set:last-of-type > qti-simple-associable-choice {\n /* The label's `align-content` and the drop's `flex-grow` are the target card's own box, and live\n in qti-simple-associable-choice.styles.ts under :host(:state(droppable)). `box-sizing` went with\n them: border-box is already the reset, and content-box is what dropRegion sets. */\n &::part(label) {\n border: var(--qti-border);\n border-top-left-radius: var(--qti-border-radius);\n border-top-right-radius: var(--qti-border-radius);\n padding: 0.5rem;\n }\n\n &::part(drop) {\n margin-top: 0;\n border-top: none;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n background: none;\n }\n\n }\n}\n@layer qti-components.interactions.states {\n qti-match-interaction:not(.qti-match-tabular)\n qti-simple-match-set:first-of-type\n qti-simple-associable-choice:state(dragging),\n qti-match-interaction:not(.qti-match-tabular)\n qti-simple-match-set:first-of-type\n qti-simple-associable-choice:state(placeholder) {\n /* The hole a spent/lifted chip leaves. Like the chip (see `drag`), it sets NO fill / border /\n shadow of its own \u2014 those inherit from the resting chip (neutral by default, the brand's fill\n when an overlay is on), so a brand can restyle the hole via a `:state(placeholder)` overlay\n with no specificity contest. The one thing a placeholder always does, in every brand, is hide\n its text; the default hole is then just the resting chip (white + border) with its text gone. */\n --drag-color: transparent;\n --drag-opacity: 1;\n pointer-events: none;\n }\n\n qti-match-interaction:not(.qti-match-tabular)\n qti-simple-match-set:first-of-type\n qti-simple-associable-choice:state(dragging)\n > *,\n qti-match-interaction:not(.qti-match-tabular)\n qti-simple-match-set:first-of-type\n qti-simple-associable-choice:state(placeholder)\n > * {\n visibility: hidden;\n }\n\n qti-match-interaction:not(.qti-match-tabular)\n qti-simple-match-set:first-of-type\n qti-simple-associable-choice:state(dragging)::part(label),\n qti-match-interaction:not(.qti-match-tabular)\n qti-simple-match-set:first-of-type\n qti-simple-associable-choice:state(placeholder)::part(label) {\n visibility: hidden;\n }\n\n qti-match-interaction:not(.qti-match-tabular)\n qti-simple-match-set:last-of-type\n > qti-simple-associable-choice:state(hover)::part(drop),\n qti-match-interaction:not(.qti-match-tabular)\n qti-simple-match-set:last-of-type\n > qti-simple-associable-choice:state(active)::part(drop) {\n background-color: var(--qti-drop-highlight-bg);\n }\n\n qti-match-interaction:not(.qti-match-tabular)\n qti-simple-match-set:last-of-type\n > qti-simple-associable-choice:state(enabled)::part(drop) {\n background-color: var(--qti-drop-highlight-enabled-bg);\n }\n\n qti-match-interaction:not(.qti-match-tabular)\n qti-simple-match-set:first-of-type\n qti-simple-associable-choice:state(dragging) {\n border-color: var(--qti-drop-highlight-border-color);\n box-shadow: inset 0 0 0 var(--qti-drop-highlight-border-width, 2px)\n color-mix(in srgb, var(--qti-drop-highlight-border-color) 55%, transparent);\n }\n\n qti-match-interaction:not(.qti-match-tabular)\n qti-simple-match-set:first-of-type\n qti-simple-associable-choice:state(return-anchor) {\n border-color: var(--qti-drop-highlight-border-color);\n box-shadow: inset 0 0 0 var(--qti-drop-highlight-border-width, 2px)\n color-mix(in srgb, var(--qti-drop-highlight-border-color) 55%, transparent);\n }\n}\n@layer qti-components.interactions.corrections {\n /* A corrected chip \u2014 in the bank, and where it landed. Moved here from the shared\n qti-corrections.css, which claimed `:state(drag)` for EVERY interaction in one unscoped rule:\n the same shape the chip vocabulary had before it was sprayed, and moved for the same reason.\n `[data-drag-clone]:state(candidate-*)` went with it and is deliberately NOT reproduced \u2014 a clone\n carries no custom states at all, because cloneNode does not copy ElementInternals (measured), so\n that selector never matched anything. */\n /* Two rules setting `--drag-background-color` on the placed chip are gone from here, dead for the\n same reason as gap-match's: the shared rule's DIRECT `background-color` outranked the binding\n that reads them, so their solid fill never rendered. They out-specify this block, so leaving them\n would have flipped a corrected placed chip from the pale tint to solid. */\n qti-match-interaction:not(.qti-match-tabular) :state(drag):state(candidate-correct),\n qti-match-interaction:not(.qti-match-tabular) qti-simple-associable-choice::part(drag):state(candidate-correct) {\n --drag-background-color: var(--qti-correct-light);\n --drag-color: var(--qti-correct);\n --drag-border-color: var(--qti-correct);\n --drag-box-shadow: none;\n }\n\n qti-match-interaction:not(.qti-match-tabular) :state(drag):state(candidate-incorrect),\n qti-match-interaction:not(.qti-match-tabular) qti-simple-associable-choice::part(drag):state(candidate-incorrect) {\n --drag-background-color: var(--qti-incorrect-light);\n --drag-color: var(--qti-incorrect);\n --drag-border-color: var(--qti-incorrect);\n --drag-box-shadow: none;\n }\n\n /* The bank is hidden by the interaction itself, on :host([answer-key]) \u2014 see\n qti-match-interaction.styles.ts. */\n}\n@layer qti-components.interactions.base {\n qti-match-interaction.qti-match-tabular {\n &::part(corner) {\n border: var(--qti-border-thickness) var(--qti-border-style) var(--qti-border-color);\n background-color: var(--qti-bg-active);\n }\n\n &::part(input-cell) {\n border: var(--qti-border-thickness) var(--qti-border-style) var(--qti-border-color);\n padding: var(--qti-padding-box);\n transition: background-color 0.1s ease;\n }\n &::part(input-cell):hover {\n background-color: var(--qti-bg-active);\n }\n\n /* Indicator parts mirror qti-choice-interaction / qti-hottext-interaction: `control` is the\n outer box, `control-mark` the inner indicator.\n\n The cells are plain spans in this interaction's shadow root, so they have no\n ElementInternals and cannot carry `:state()` \u2014 and `::part(x):state(y)` needs the state on\n the part's own element. They therefore carry variant *part tokens*, which deliberately\n reuse the state vocabulary: radio | checkbox, checked, correct | incorrect. */\n &::part(control radio) {\n background-color: var(--radio-background-color, var(--qti-component-background-color));\n background-image: var(--radio-background-image, var(--qti-component-background-image));\n border-width: var(--radio-border-width, var(--qti-component-border-width));\n border-style: var(--radio-border-style, var(--qti-component-border-style));\n border-color: var(--radio-border-color, var(--qti-component-border-color));\n border-radius: var(--radio-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--radio-box-shadow, var(--qti-component-box-shadow));\n color: var(--radio-color, var(--qti-component-color));\n opacity: var(--radio-opacity, var(--qti-component-opacity));\n filter: var(--radio-filter, var(--qti-component-filter));\n width: var(--qti-control-size);\n height: var(--qti-control-size);\n /* Round. Re-declared with the slot in FRONT of the default, the same shape as the drop's dashed\n edge, so a brand can square a radio off without having to out-specify this rule. */\n border-radius: var(--radio-border-radius, 100%);\n }\n &::part(control checkbox) {\n background-color: var(--checkbox-background-color, var(--qti-component-background-color));\n background-image: var(--checkbox-background-image, var(--qti-component-background-image));\n border-width: var(--checkbox-border-width, var(--qti-component-border-width));\n border-style: var(--checkbox-border-style, var(--qti-component-border-style));\n border-color: var(--checkbox-border-color, var(--qti-component-border-color));\n border-radius: var(--checkbox-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--checkbox-box-shadow, var(--qti-component-box-shadow));\n color: var(--checkbox-color, var(--qti-component-color));\n opacity: var(--checkbox-opacity, var(--qti-component-opacity));\n filter: var(--checkbox-filter, var(--qti-component-filter));\n width: var(--qti-control-size);\n height: var(--qti-control-size);\n }\n &::part(control-mark) {\n width: calc(var(--qti-control-size) * 0.625);\n height: calc(var(--qti-control-size) * 0.625);\n }\n /* Both `control` and `control-mark` carry the variant tokens, so themes can repaint the\n outer box and the inner indicator independently. Scope the base inner-fill rules to\n `control-mark` so the classic dot-in-ring look stays intact; themes wanting the inverted\n look (outer filled, e.g. Kennisnet) add their own `::part(control checked)` rules. */\n &::part(control-mark radio checked) {\n /* 0.5 reproduces today's 8px at the default 1rem \u2014 see mark-size for why this is a fraction. */\n width: calc(var(--qti-control-size) * 0.5);\n height: calc(var(--qti-control-size) * 0.5);\n background-color: var(--radio-mark-color, white);\n border-radius: 100%;\n }\n &::part(control-mark checkbox checked) {\n background-color: var(--checkbox-mark-color, white);\n -webkit-mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' width='100%' height='100%' viewBox='0 0 24 24'%3E%3Cpath d='M20.285 2l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285z'/%3E%3C/svg%3E\");\n mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' width='100%' height='100%' viewBox='0 0 24 24'%3E%3Cpath d='M20.285 2l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285z'/%3E%3C/svg%3E\");\n }\n }\n\n /* Header cells \u2014 the two slotted <qti-simple-match-set>s have display:contents\n so their child choices act as row/column header cells. Written as a fully-\n qualified descendant selector (not nested under qti-match-tabular's `&`)\n because native CSS nesting parses `& <ident>` as compound, not descendant,\n and the rule wouldn't match. :first-of-type / :last-of-type drive the\n row vs col differentiation in both runtime (slot attribute set\n declaratively) and the editor (manual slot assignment, no slot attribute).\n Drag is disabled in tabular mode; override the mixin's inline cursor:grab. */\n qti-match-interaction.qti-match-tabular qti-simple-match-set qti-simple-associable-choice {\n background-color: var(--qti-bg-active);\n font-weight: 500;\n padding: var(--qti-padding-box);\n border: var(--qti-border-thickness) var(--qti-border-style) var(--qti-border-color);\n cursor: default !important;\n }\n\n /* A header cell centres its label. The choice is a light-DOM grandchild of the slotted match-set,\n so nothing in the interaction's shadow root can select it; these two variables are the hand-off,\n and qti-simple-associable-choice.styles.ts spends them on its own :host box. */\n qti-match-interaction.qti-match-tabular qti-simple-match-set {\n --qti-choice-align-items: center;\n }\n qti-match-interaction.qti-match-tabular qti-simple-match-set:last-of-type {\n --qti-choice-justify-content: center;\n }\n qti-match-interaction.qti-match-tabular qti-simple-match-set:last-of-type qti-simple-associable-choice {\n text-align: center;\n }\n\n qti-match-interaction.qti-match-tabular {\n /* The 1px gap is the grid's own background showing through, which is what draws the cell rules.\n Clipping it to the rounded corners is layout and lives in qti-match-interaction-tabular.styles.ts. */\n &::part(grid) {\n gap: 1px;\n background-color: var(--qti-border-color);\n border: var(--qti-border-thickness) solid var(--qti-border-color);\n border-radius: var(--qti-border-radius);\n }\n\n &::part(corner),\n &::part(input-cell) {\n border: none;\n background-color: white;\n }\n\n &::part(control checked) {\n border-color: var(--qti-primary);\n background-color: var(--qti-primary);\n }\n\n &::part(control-mark radio checked) {\n background-color: white;\n width: calc(var(--qti-control-size) * 0.5);\n height: calc(var(--qti-control-size) * 0.5);\n }\n\n &::part(control-mark checkbox checked) {\n background-color: white;\n }\n }\n\n qti-match-interaction.qti-match-tabular qti-simple-match-set qti-simple-associable-choice {\n border: none;\n background-color: white;\n }\n}\n@layer qti-components.interactions.corrections {\n qti-match-interaction.qti-match-tabular {\n /* Correct / incorrect overlays \u2014 border on the outer box, and a matching fill on the inner\n indicator when checked. */\n &::part(control correct) {\n border-color: var(--qti-correct);\n }\n &::part(control incorrect) {\n border-color: var(--qti-incorrect);\n }\n\n &::part(control checked correct) {\n border-color: var(--qti-correct);\n background-color: var(--qti-correct);\n }\n\n &::part(control checked incorrect) {\n border-color: var(--qti-incorrect);\n background-color: var(--qti-incorrect);\n }\n }\n\n .full-correct-response qti-match-interaction.qti-match-tabular {\n &::part(control checked) {\n background-color: var(--qti-answer-bg);\n color: var(--qti-answer-fg);\n border-color: var(--qti-answer-border);\n }\n\n &::part(control-mark radio checked),\n &::part(control-mark checkbox checked) {\n background-color: var(--qti-answer-fg);\n }\n }\n}\n/* This interaction's chip vocabulary \u2014 the bank chip, the placed chip, and the floating clone. One\n such block per drag-drop interaction, opt-in; graphic-gap-match deliberately declares none. See\n qti-states.css. Kept in the `global` layer, where these rules used to live, so cascade position is\n unchanged.\n\n The clone is matched by [data-drag-interaction] rather than by ancestry because it is a SIBLING of\n the interaction, not a descendant: createDragClone appends it to the interaction's ROOT (the\n item's shadow root), which is the same tree \u2014 so the theme can reach it \u2014 but not the same\n subtree. The attribute is also what tells one interaction's chip in flight from another's, which\n is how graphic-gap-match stays out without anyone writing an exclusion.\n\n Its grip hangs off ::part(control)::before, the same place the resting chip's does, so a chip does\n not move its handle when you pick it up. That the part is reachable at all is new: while the clone\n sat on document.body it was in a different tree from the adopted stylesheet, and nothing \u2014 no\n part, no attribute \u2014 could style it. */\n@layer qti-components.global {\n qti-order-interaction :state(drag),\n [data-drag-clone][data-drag-interaction='qti-order-interaction'] {\n /* Structural \u2014 the load-bearing box, never state-switched (layout-invariance contract).\n box-sizing is deliberate: the clone is appended to document.body outside reset.css's\n @scope, so it inherits no border-box from anywhere else and this sets it. */\n box-sizing: border-box;\n padding: var(--qti-drag-padding);\n cursor: grab;\n /*\n * A chip is as wide as the drop it belongs in \u2014 the SAME token the drop reserves with.\n *\n * That one shared name is what makes a drag and its drop the same size without any arithmetic\n * relating them: the interaction measures the widest chip, publishes it once, and both the chip\n * and the slot read it. See DROP-SIZING.md.\n *\n * It applies in the bank as well as in the drop, which is the point: the chip already has the\n * width before it is dropped, so nothing changes size at the moment of landing. That keeps \"a chip\n * is the same chip wherever it lives\" true (drag-drop.invariance.spec.ts) and keeps the return\n * animation from ending in a snap. A `width: 100%` on the placed chip gives the same still image\n * and breaks both \u2014 it is what order used to do, and why order was the one entry in\n * CHIP_BOX_KNOWN_BAD.\n *\n * min-width, not width, so a chip whose own text is wider than the widest measured chip still\n * fits. Defaults to 0 where nothing is measured (match, whose targets are cards, not slots), so\n * those chips keep their natural width with no rule needed to say so.\n *\n * --qti-drag-min-width, not --qti-dropzone-min-width: the mixin writes the same measured value to\n * both, so a chip and its slot still match, but the two need OPPOSITE defaults when nothing is\n * measured \u2014 a chip must stay at its natural width, while a drops-grid track must not collapse.\n * See the note on both names in qti-variables.css.\n */\n min-width: var(--qti-drag-min-width, 0);\n /* Paint via the contract, nothing more \u2014 the --qti-component-* tokens already make a neutral\n bordered box (white surface, theme border, no shadow). The chip sets NO --drag-* of its own,\n and that is load-bearing: if it did (e.g. `--drag-background-color: white`), that declaration\n would ride the interaction's HIGH-specificity chip selector\n (`qti-gap-match-interaction qti-gap::part(drag)`) and out-specify a brand overlay's bare\n `qti-gap::part(drag)`, so the brand could never win. With nothing set here, the overlay's\n --drag-* is the only declaration and wins with no specificity/layer contest. */\n background-color: var(--drag-background-color, var(--qti-component-background-color));\n background-image: var(--drag-background-image, var(--qti-component-background-image));\n border-width: var(--drag-border-width, var(--qti-component-border-width));\n border-style: var(--drag-border-style, var(--qti-component-border-style));\n border-color: var(--drag-border-color, var(--qti-component-border-color));\n border-radius: var(--drag-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drag-box-shadow, var(--qti-component-box-shadow));\n color: var(--drag-color, var(--qti-component-color));\n opacity: var(--drag-opacity, var(--qti-component-opacity));\n filter: var(--drag-filter, var(--qti-component-filter));\n }\n\n qti-order-interaction :state(drag)::part(control)::before,\n qti-order-interaction::part(drag-control)::before,\n qti-order-interaction ::part(drag-control)::before,\n [data-drag-clone][data-drag-interaction='qti-order-interaction']::part(control)::before {\n content: '';\n display: inline-block;\n vertical-align: middle;\n width: 0.75em;\n height: 1em;\n margin-right: var(--qti-glyph-gap);\n background-color: currentColor;\n mask: var(--qti-grip-mask) no-repeat center / contain;\n -webkit-mask: var(--qti-grip-mask) no-repeat center / contain;\n }\n}\n@layer qti-components.interactions.base {\n qti-order-interaction {\n &:state(dragzone-active)::part(drags) {\n background-color: var(--qti-bg-active);\n }\n &:state(dragzone-enabled)::part(drags) {\n background-color: var(--qti-bg-active);\n }\n &::part(drag),\n & qti-simple-choice,\n /* Editor fake-drag projected into the interaction's drop-target shadow.\n Same `drag` styling so editor previews match runtime visuals. */\n &::part(drag) {\n /* Structural \u2014 the load-bearing box, never state-switched (layout-invariance contract).\n box-sizing is deliberate: the clone is appended to document.body outside reset.css's\n @scope, so it inherits no border-box from anywhere else and this sets it. */\n box-sizing: border-box;\n padding: var(--qti-drag-padding);\n cursor: grab;\n /*\n * A chip is as wide as the drop it belongs in \u2014 the SAME token the drop reserves with.\n *\n * That one shared name is what makes a drag and its drop the same size without any arithmetic\n * relating them: the interaction measures the widest chip, publishes it once, and both the chip\n * and the slot read it. See DROP-SIZING.md.\n *\n * It applies in the bank as well as in the drop, which is the point: the chip already has the\n * width before it is dropped, so nothing changes size at the moment of landing. That keeps \"a chip\n * is the same chip wherever it lives\" true (drag-drop.invariance.spec.ts) and keeps the return\n * animation from ending in a snap. A `width: 100%` on the placed chip gives the same still image\n * and breaks both \u2014 it is what order used to do, and why order was the one entry in\n * CHIP_BOX_KNOWN_BAD.\n *\n * min-width, not width, so a chip whose own text is wider than the widest measured chip still\n * fits. Defaults to 0 where nothing is measured (match, whose targets are cards, not slots), so\n * those chips keep their natural width with no rule needed to say so.\n *\n * --qti-drag-min-width, not --qti-dropzone-min-width: the mixin writes the same measured value to\n * both, so a chip and its slot still match, but the two need OPPOSITE defaults when nothing is\n * measured \u2014 a chip must stay at its natural width, while a drops-grid track must not collapse.\n * See the note on both names in qti-variables.css.\n */\n min-width: var(--qti-drag-min-width, 0);\n /* Paint via the contract, nothing more \u2014 the --qti-component-* tokens already make a neutral\n bordered box (white surface, theme border, no shadow). The chip sets NO --drag-* of its own,\n and that is load-bearing: if it did (e.g. `--drag-background-color: white`), that declaration\n would ride the interaction's HIGH-specificity chip selector\n (`qti-gap-match-interaction qti-gap::part(drag)`) and out-specify a brand overlay's bare\n `qti-gap::part(drag)`, so the brand could never win. With nothing set here, the overlay's\n --drag-* is the only declaration and wins with no specificity/layer contest. */\n background-color: var(--drag-background-color, var(--qti-component-background-color));\n background-image: var(--drag-background-image, var(--qti-component-background-image));\n border-width: var(--drag-border-width, var(--qti-component-border-width));\n border-style: var(--drag-border-style, var(--qti-component-border-style));\n border-color: var(--drag-border-color, var(--qti-component-border-color));\n border-radius: var(--drag-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drag-box-shadow, var(--qti-component-box-shadow));\n color: var(--drag-color, var(--qti-component-color));\n opacity: var(--drag-opacity, var(--qti-component-opacity));\n filter: var(--drag-filter, var(--qti-component-filter));\n &[dragging] {\n pointer-events: none;\n rotate: -2deg;\n --drag-box-shadow: 0 8px 12px rgb(0 0 0 / 20%), 0 4px 8px rgb(0 0 0 / 10%);\n }\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(\n in srgb,\n var(--drag-background-color, var(--qti-component-background-color)) 92%,\n black\n );\n }\n }\n &:focus {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n }\n }\n /* Editor fake-drag projected into the interaction's drop-target shadow.\n Same `drag` styling so editor previews match runtime visuals.\n Kept as its own block (separate from the runtime-drag selector list\n above) because a CSS comment between commas inside a nested selector\n list is silently dropped by Chrome's nesting parser. */\n &::part(drag) {\n /* Structural \u2014 the load-bearing box, never state-switched (layout-invariance contract).\n box-sizing is deliberate: the clone is appended to document.body outside reset.css's\n @scope, so it inherits no border-box from anywhere else and this sets it. */\n box-sizing: border-box;\n padding: var(--qti-drag-padding);\n cursor: grab;\n /*\n * A chip is as wide as the drop it belongs in \u2014 the SAME token the drop reserves with.\n *\n * That one shared name is what makes a drag and its drop the same size without any arithmetic\n * relating them: the interaction measures the widest chip, publishes it once, and both the chip\n * and the slot read it. See DROP-SIZING.md.\n *\n * It applies in the bank as well as in the drop, which is the point: the chip already has the\n * width before it is dropped, so nothing changes size at the moment of landing. That keeps \"a chip\n * is the same chip wherever it lives\" true (drag-drop.invariance.spec.ts) and keeps the return\n * animation from ending in a snap. A `width: 100%` on the placed chip gives the same still image\n * and breaks both \u2014 it is what order used to do, and why order was the one entry in\n * CHIP_BOX_KNOWN_BAD.\n *\n * min-width, not width, so a chip whose own text is wider than the widest measured chip still\n * fits. Defaults to 0 where nothing is measured (match, whose targets are cards, not slots), so\n * those chips keep their natural width with no rule needed to say so.\n *\n * --qti-drag-min-width, not --qti-dropzone-min-width: the mixin writes the same measured value to\n * both, so a chip and its slot still match, but the two need OPPOSITE defaults when nothing is\n * measured \u2014 a chip must stay at its natural width, while a drops-grid track must not collapse.\n * See the note on both names in qti-variables.css.\n */\n min-width: var(--qti-drag-min-width, 0);\n /* Paint via the contract, nothing more \u2014 the --qti-component-* tokens already make a neutral\n bordered box (white surface, theme border, no shadow). The chip sets NO --drag-* of its own,\n and that is load-bearing: if it did (e.g. `--drag-background-color: white`), that declaration\n would ride the interaction's HIGH-specificity chip selector\n (`qti-gap-match-interaction qti-gap::part(drag)`) and out-specify a brand overlay's bare\n `qti-gap::part(drag)`, so the brand could never win. With nothing set here, the overlay's\n --drag-* is the only declaration and wins with no specificity/layer contest. */\n background-color: var(--drag-background-color, var(--qti-component-background-color));\n background-image: var(--drag-background-image, var(--qti-component-background-image));\n border-width: var(--drag-border-width, var(--qti-component-border-width));\n border-style: var(--drag-border-style, var(--qti-component-border-style));\n border-color: var(--drag-border-color, var(--qti-component-border-color));\n border-radius: var(--drag-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drag-box-shadow, var(--qti-component-box-shadow));\n color: var(--drag-color, var(--qti-component-color));\n opacity: var(--drag-opacity, var(--qti-component-opacity));\n filter: var(--drag-filter, var(--qti-component-filter));\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(\n in srgb,\n var(--drag-background-color, var(--qti-component-background-color)) 92%,\n black\n );\n }\n }\n &:focus {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n }\n }\n /* The drag's box \u2014 flex, centred, clipped \u2014 is in qti-order-interaction.styles.ts. */\n &::part(drag) {\n /*\n * No `width: 100%`. It stretched a placed chip to its slot, which is why order was the one\n * entry in CHIP_BOX_KNOWN_BAD (\"the placed chip stretches to fill its drop slot\") \u2014 a 55px\n * chip in the bank became 666px once dropped. The slot is now sized from the chip instead of\n * the other way round (qti-order-interaction.styles.ts), so there is nothing left to fill.\n *\n * Every chip now takes --qti-dropzone-min-width instead (see `@mixin drag`), the same name the\n * slot reserves with \u2014 so chip and slot are the same size by construction, in the bank as well\n * as in the drop, and nothing changes size at the moment of landing.\n */\n text-overflow: ellipsis;\n }\n /*\n * Moved out of qti-order-interaction.styles.ts, which is layout-only now.\n *\n * The drop's flags are part tokens rather than attributes: an attribute selector cannot follow\n * `::part()`, and a <div> cannot carry a custom state (attachInternals throws). See setDropFlag.\n */\n &::part(active) {\n background-color: var(--qti-bg-active);\n }\n\n &::part(enabled) {\n background-color: var(--qti-bg-active);\n }\n\n /* A filled drop hides its dashed outline and drop-here glyph. Hides \u2014 the border keeps its\n width, or the column shifts 2px the instant a chip lands. */\n &::part(filled) {\n --qti-drop-border: var(--qti-border-thickness, 1px) solid transparent;\n --qti-drop-bg-img: none;\n }\n\n &::part(drops) {\n gap: var(--qti-gap);\n }\n &::part(drags) {\n gap: var(--qti-gap);\n }\n &::part(drop) {\n background-color: var(--drop-background-color, var(--qti-component-background-color));\n background-image: var(--drop-background-image, var(--qti-component-background-image));\n border-width: var(--drop-border-width, var(--qti-component-border-width));\n border-style: var(--drop-border-style, var(--qti-component-border-style));\n border-color: var(--drop-border-color, var(--qti-component-border-color));\n border-radius: var(--drop-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drop-box-shadow, var(--qti-component-box-shadow));\n color: var(--drop-color, var(--qti-component-color));\n opacity: var(--drop-opacity, var(--qti-component-opacity));\n filter: var(--drop-filter, var(--qti-component-filter));\n border-style: var(--drop-border-style, dashed);\n &[enabled] {\n background-color: var(--qti-bg-active);\n }\n &:focus {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n }\n }\n &::part(active) {\n background-color: var(--qti-bg-active);\n }\n }\n\n qti-order-interaction {\n &::part(drops) {\n margin: 0;\n border-radius: var(--qti-border-radius);\n }\n\n /* No `height: 100%` here. The drop is a grid item, so `align-self: stretch` already fills the\n row \u2014 and because dropRegion makes it content-box, a percentage height made its border-box\n overhang the row by the border (42px row, 44px drop), which the drop's padding then doubled.\n It also defeated `:has(qti-simple-choice) [part~='drop'] { align-self: flex-start }` in\n qti-order-interaction.styles.ts. */\n &::part(drop) {\n border: var(--qti-drop-border, var(--qti-border-thickness) dashed var(--qti-border-color));\n border-radius: var(--qti-border-radius);\n background-color: transparent;\n background-image: var(--qti-drop-bg-img, revert-layer);\n }\n\n &::part(active) {\n background-color: var(--qti-bg-active);\n }\n\n &::part(enabled) {\n background-color: var(--qti-bg-active);\n }\n\n /* `align-self` on a populated drop, and the drops' and drag's `overflow: visible` that let a\n chip's shadow escape its column, are laid out in qti-order-interaction.styles.ts. */\n /* The placed order chip's INVERTED look (white fill, dark text, neutral border) is\n Kennisnet-specific \u2014 a light chip against the primary bank \u2014 so it lives in the Kennisnet brand\n overlay (apps/e2e/src/stories/kennisnet/kennisnet.css), not here. In the base theme a placed\n chip is just the neutral chip (@mixin drag), and its corrections route through --drag-*\n like every other chip. */\n & qti-simple-choice {\n /* Structural \u2014 the load-bearing box, never state-switched (layout-invariance contract).\n box-sizing is deliberate: the clone is appended to document.body outside reset.css's\n @scope, so it inherits no border-box from anywhere else and this sets it. */\n box-sizing: border-box;\n padding: var(--qti-drag-padding);\n cursor: grab;\n /*\n * A chip is as wide as the drop it belongs in \u2014 the SAME token the drop reserves with.\n *\n * That one shared name is what makes a drag and its drop the same size without any arithmetic\n * relating them: the interaction measures the widest chip, publishes it once, and both the chip\n * and the slot read it. See DROP-SIZING.md.\n *\n * It applies in the bank as well as in the drop, which is the point: the chip already has the\n * width before it is dropped, so nothing changes size at the moment of landing. That keeps \"a chip\n * is the same chip wherever it lives\" true (drag-drop.invariance.spec.ts) and keeps the return\n * animation from ending in a snap. A `width: 100%` on the placed chip gives the same still image\n * and breaks both \u2014 it is what order used to do, and why order was the one entry in\n * CHIP_BOX_KNOWN_BAD.\n *\n * min-width, not width, so a chip whose own text is wider than the widest measured chip still\n * fits. Defaults to 0 where nothing is measured (match, whose targets are cards, not slots), so\n * those chips keep their natural width with no rule needed to say so.\n *\n * --qti-drag-min-width, not --qti-dropzone-min-width: the mixin writes the same measured value to\n * both, so a chip and its slot still match, but the two need OPPOSITE defaults when nothing is\n * measured \u2014 a chip must stay at its natural width, while a drops-grid track must not collapse.\n * See the note on both names in qti-variables.css.\n */\n min-width: var(--qti-drag-min-width, 0);\n /* Paint via the contract, nothing more \u2014 the --qti-component-* tokens already make a neutral\n bordered box (white surface, theme border, no shadow). The chip sets NO --drag-* of its own,\n and that is load-bearing: if it did (e.g. `--drag-background-color: white`), that declaration\n would ride the interaction's HIGH-specificity chip selector\n (`qti-gap-match-interaction qti-gap::part(drag)`) and out-specify a brand overlay's bare\n `qti-gap::part(drag)`, so the brand could never win. With nothing set here, the overlay's\n --drag-* is the only declaration and wins with no specificity/layer contest. */\n background-color: var(--drag-background-color, var(--qti-component-background-color));\n background-image: var(--drag-background-image, var(--qti-component-background-image));\n border-width: var(--drag-border-width, var(--qti-component-border-width));\n border-style: var(--drag-border-style, var(--qti-component-border-style));\n border-color: var(--drag-border-color, var(--qti-component-border-color));\n border-radius: var(--drag-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drag-box-shadow, var(--qti-component-box-shadow));\n color: var(--drag-color, var(--qti-component-color));\n opacity: var(--drag-opacity, var(--qti-component-opacity));\n filter: var(--drag-filter, var(--qti-component-filter));\n }\n\n &::part(container) {\n gap: 2rem;\n }\n\n /*\n * Orientation is laid out by qti-order-interaction.styles.ts. What used to be here:\n *\n * [orientation='horizontal']::part(drops) restated the styles.ts base rule verbatim, which\n * cancelled that file's `grid-auto-flow: column`. It only matched the attribute, so\n * `orientation=\"horizontal\"` wrapped while `class=\"qti-orientation-horizontal\"` did not \u2014\n * two spellings of one QTI concept rendering differently. Both now wrap; see styles.ts.\n *\n * ::part(container) { flex-direction } inert. The container is `display: grid` with\n * grid-template-areas, so flex-direction never applied. It was reaching for \"horizontal puts\n * the bank above the drops\" \u2014 that is what the qti-choices-top/bottom/left/right classes\n * already do, and wiring orientation to them is a behaviour change, not a move.\n */\n &[orientation='vertical'] {\n & qti-simple-choice,\n &::part(drag) {\n width: auto;\n }\n }\n }\n}\n@layer qti-components.interactions.states {\n qti-order-interaction qti-simple-choice:state(placeholder) {\n /* The hole a spent/lifted chip leaves. Like the chip (see `drag`), it sets NO fill / border /\n shadow of its own \u2014 those inherit from the resting chip (neutral by default, the brand's fill\n when an overlay is on), so a brand can restyle the hole via a `:state(placeholder)` overlay\n with no specificity contest. The one thing a placeholder always does, in every brand, is hide\n its text; the default hole is then just the resting chip (white + border) with its text gone. */\n --drag-color: transparent;\n --drag-opacity: 1;\n pointer-events: none;\n }\n\n qti-order-interaction qti-simple-choice:state(dragging) {\n /* The hole a spent/lifted chip leaves. Like the chip (see `drag`), it sets NO fill / border /\n shadow of its own \u2014 those inherit from the resting chip (neutral by default, the brand's fill\n when an overlay is on), so a brand can restyle the hole via a `:state(placeholder)` overlay\n with no specificity contest. The one thing a placeholder always does, in every brand, is hide\n its text; the default hole is then just the resting chip (white + border) with its text gone. */\n --drag-color: transparent;\n --drag-opacity: 1;\n border-color: var(--qti-drop-highlight-border-color);\n box-shadow: inset 0 0 0 var(--qti-drop-highlight-border-width, 2px)\n color-mix(in srgb, var(--qti-drop-highlight-border-color) 55%, transparent);\n pointer-events: none;\n }\n\n qti-order-interaction qti-simple-choice:state(return-anchor) {\n border-color: var(--qti-drop-highlight-border-color);\n box-shadow: inset 0 0 0 var(--qti-drop-highlight-border-width, 2px)\n color-mix(in srgb, var(--qti-drop-highlight-border-color) 55%, transparent);\n }\n\n qti-order-interaction::part(hover),\n qti-order-interaction::part(active) {\n background-color: var(--qti-drop-highlight-bg);\n }\n\n qti-order-interaction::part(enabled) {\n background-color: var(--qti-drop-highlight-enabled-bg);\n }\n}\n@layer qti-components.interactions.corrections {\n /*\n * Judgement paint, via the shared mixin \u2014 every other drag-drop interaction already uses it.\n *\n * Order hand-rolled this, and set `background-color` and `border-color` but never `color`. The\n * drag handle is `background-color: currentColor` behind a mask (see `@mixin grip`), so it can\n * only be as green as the element's `color` \u2014 and `color` stayed at the `--qti-fg` the\n * base layer gives a drag, which is #3e144e and reads as black. Every other interaction's grip\n * turned green or red because `candidate-tint` sets all three.\n *\n * Two rules went with it, both dead: a `background-color: var(--qti-correct-response, \u2026)` pair\n * that the blocks below overrode at equal specificity, naming a `--qti-correct-response` token\n * that is not defined anywhere in the repo.\n *\n * Keep :state() in the same selector as ::part(). Native nesting after a pseudo-element is not\n * parsed consistently by the browser toolchain.\n */\n /* A corrected chip \u2014 in the bank, and where it landed. Moved here from the shared\n qti-corrections.css, which claimed `:state(drag)` for EVERY interaction in one unscoped rule:\n the same shape the chip vocabulary had before it was sprayed, and moved for the same reason.\n `[data-drag-clone]:state(candidate-*)` went with it and is deliberately NOT reproduced \u2014 a clone\n carries no custom states at all, because cloneNode does not copy ElementInternals (measured), so\n that selector never matched anything. */\n qti-order-interaction :state(drag):state(candidate-correct),\n qti-order-interaction::part(drag):state(candidate-correct) {\n --drag-background-color: var(--qti-correct-light);\n --drag-color: var(--qti-correct);\n --drag-border-color: var(--qti-correct);\n --drag-box-shadow: none;\n }\n\n qti-order-interaction :state(drag):state(candidate-incorrect),\n qti-order-interaction::part(drag):state(candidate-incorrect) {\n --drag-background-color: var(--qti-incorrect-light);\n --drag-color: var(--qti-incorrect);\n --drag-border-color: var(--qti-incorrect);\n --drag-box-shadow: none;\n }\n\n /* The bank is hidden by the interaction itself, on :host([answer-key]) \u2014 see\n qti-order-interaction.styles.ts. */\n}\n/* This interaction's chip vocabulary \u2014 the bank chip, the placed chip, and the floating clone. One\n such block per drag-drop interaction, opt-in; graphic-gap-match deliberately declares none. See\n qti-states.css. Kept in the `global` layer, where these rules used to live, so cascade position is\n unchanged.\n\n The clone is matched by [data-drag-interaction] rather than by ancestry because it is a SIBLING of\n the interaction, not a descendant: createDragClone appends it to the interaction's ROOT (the\n item's shadow root), which is the same tree \u2014 so the theme can reach it \u2014 but not the same\n subtree. The attribute is also what tells one interaction's chip in flight from another's, which\n is how graphic-gap-match stays out without anyone writing an exclusion.\n\n Its grip hangs off ::part(control)::before, the same place the resting chip's does, so a chip does\n not move its handle when you pick it up. That the part is reachable at all is new: while the clone\n sat on document.body it was in a different tree from the adopted stylesheet, and nothing \u2014 no\n part, no attribute \u2014 could style it.\n\n This interaction is the one that needed the global rule most: every `@mixin drag` further down\n targets `::part(drag)`, the PLACED chip, so the light-DOM bank chip drew its fill, radius, cursor\n and text colour from the removed rule and nothing else. Deleting that rule without this block\n turned the associate bank chips transparent, square and un-grabbable \u2014 measured, not guessed. */\n@layer qti-components.global {\n qti-associate-interaction :state(drag),\n [data-drag-clone][data-drag-interaction='qti-associate-interaction'] {\n /* Structural \u2014 the load-bearing box, never state-switched (layout-invariance contract).\n box-sizing is deliberate: the clone is appended to document.body outside reset.css's\n @scope, so it inherits no border-box from anywhere else and this sets it. */\n box-sizing: border-box;\n padding: var(--qti-drag-padding);\n cursor: grab;\n /*\n * A chip is as wide as the drop it belongs in \u2014 the SAME token the drop reserves with.\n *\n * That one shared name is what makes a drag and its drop the same size without any arithmetic\n * relating them: the interaction measures the widest chip, publishes it once, and both the chip\n * and the slot read it. See DROP-SIZING.md.\n *\n * It applies in the bank as well as in the drop, which is the point: the chip already has the\n * width before it is dropped, so nothing changes size at the moment of landing. That keeps \"a chip\n * is the same chip wherever it lives\" true (drag-drop.invariance.spec.ts) and keeps the return\n * animation from ending in a snap. A `width: 100%` on the placed chip gives the same still image\n * and breaks both \u2014 it is what order used to do, and why order was the one entry in\n * CHIP_BOX_KNOWN_BAD.\n *\n * min-width, not width, so a chip whose own text is wider than the widest measured chip still\n * fits. Defaults to 0 where nothing is measured (match, whose targets are cards, not slots), so\n * those chips keep their natural width with no rule needed to say so.\n *\n * --qti-drag-min-width, not --qti-dropzone-min-width: the mixin writes the same measured value to\n * both, so a chip and its slot still match, but the two need OPPOSITE defaults when nothing is\n * measured \u2014 a chip must stay at its natural width, while a drops-grid track must not collapse.\n * See the note on both names in qti-variables.css.\n */\n min-width: var(--qti-drag-min-width, 0);\n /* Paint via the contract, nothing more \u2014 the --qti-component-* tokens already make a neutral\n bordered box (white surface, theme border, no shadow). The chip sets NO --drag-* of its own,\n and that is load-bearing: if it did (e.g. `--drag-background-color: white`), that declaration\n would ride the interaction's HIGH-specificity chip selector\n (`qti-gap-match-interaction qti-gap::part(drag)`) and out-specify a brand overlay's bare\n `qti-gap::part(drag)`, so the brand could never win. With nothing set here, the overlay's\n --drag-* is the only declaration and wins with no specificity/layer contest. */\n background-color: var(--drag-background-color, var(--qti-component-background-color));\n background-image: var(--drag-background-image, var(--qti-component-background-image));\n border-width: var(--drag-border-width, var(--qti-component-border-width));\n border-style: var(--drag-border-style, var(--qti-component-border-style));\n border-color: var(--drag-border-color, var(--qti-component-border-color));\n border-radius: var(--drag-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drag-box-shadow, var(--qti-component-box-shadow));\n color: var(--drag-color, var(--qti-component-color));\n opacity: var(--drag-opacity, var(--qti-component-opacity));\n filter: var(--drag-filter, var(--qti-component-filter));\n }\n\n qti-associate-interaction :state(drag)::part(control)::before,\n qti-associate-interaction::part(drag-control)::before,\n qti-associate-interaction ::part(drag-control)::before,\n [data-drag-clone][data-drag-interaction='qti-associate-interaction']::part(control)::before {\n content: '';\n display: inline-block;\n vertical-align: middle;\n width: 0.75em;\n height: 1em;\n margin-right: var(--qti-glyph-gap);\n background-color: currentColor;\n mask: var(--qti-grip-mask) no-repeat center / contain;\n -webkit-mask: var(--qti-grip-mask) no-repeat center / contain;\n }\n}\n@layer qti-components.interactions.base {\n qti-associate-interaction {\n /*\n * NO `--drag-border-color: transparent` here, and that absence is the rule.\n *\n * It used to say transparent, on the reasoning that the bank chip's border should be INVISIBLE\n * rather than absent \u2014 same box as a placed chip, only the colour differs, so a chip does not\n * resize when it lands. The box argument is sound and unaffected either way: `@mixin drag` sets\n * border-WIDTH, so the 1px is reserved whatever the colour is.\n *\n * What it missed is that the neutral theme has no fill to fall back on. A bank chip is\n * --qti-component-background-color (white) on a white page, so zeroing the only edge it had left\n * bare text with a grip \u2014 no chip at all. It read as correct only under a brand, where the fill\n * draws the shape and the border is genuinely redundant (kennisnet sets its own transparent).\n * Associate was the only interaction doing this; match, order and gap-match all let the bank\n * chip take --qti-component-border-color, and now so does this one.\n */\n & qti-simple-associable-choice {\n padding: var(--qti-drag-padding);\n }\n\n &:state(dragzone-active) slot[name='qti-simple-associable-choice'] {\n background-color: var(--qti-bg-active);\n }\n &:state(dragzone-enabled) slot[name='qti-simple-associable-choice'] {\n background-color: var(--qti-bg-active);\n }\n & qti-simple-associable-choice::part(drag),\n /* A placed chip lives in a shadow root; `::part(drag)` is the only way in. This used to be\n `::part(qti-simple-associable-choice)`, a part named after the tag. */\n &::part(drag) {\n /* Structural \u2014 the load-bearing box, never state-switched (layout-invariance contract).\n box-sizing is deliberate: the clone is appended to document.body outside reset.css's\n @scope, so it inherits no border-box from anywhere else and this sets it. */\n box-sizing: border-box;\n padding: var(--qti-drag-padding);\n cursor: grab;\n /*\n * A chip is as wide as the drop it belongs in \u2014 the SAME token the drop reserves with.\n *\n * That one shared name is what makes a drag and its drop the same size without any arithmetic\n * relating them: the interaction measures the widest chip, publishes it once, and both the chip\n * and the slot read it. See DROP-SIZING.md.\n *\n * It applies in the bank as well as in the drop, which is the point: the chip already has the\n * width before it is dropped, so nothing changes size at the moment of landing. That keeps \"a chip\n * is the same chip wherever it lives\" true (drag-drop.invariance.spec.ts) and keeps the return\n * animation from ending in a snap. A `width: 100%` on the placed chip gives the same still image\n * and breaks both \u2014 it is what order used to do, and why order was the one entry in\n * CHIP_BOX_KNOWN_BAD.\n *\n * min-width, not width, so a chip whose own text is wider than the widest measured chip still\n * fits. Defaults to 0 where nothing is measured (match, whose targets are cards, not slots), so\n * those chips keep their natural width with no rule needed to say so.\n *\n * --qti-drag-min-width, not --qti-dropzone-min-width: the mixin writes the same measured value to\n * both, so a chip and its slot still match, but the two need OPPOSITE defaults when nothing is\n * measured \u2014 a chip must stay at its natural width, while a drops-grid track must not collapse.\n * See the note on both names in qti-variables.css.\n */\n min-width: var(--qti-drag-min-width, 0);\n /* Paint via the contract, nothing more \u2014 the --qti-component-* tokens already make a neutral\n bordered box (white surface, theme border, no shadow). The chip sets NO --drag-* of its own,\n and that is load-bearing: if it did (e.g. `--drag-background-color: white`), that declaration\n would ride the interaction's HIGH-specificity chip selector\n (`qti-gap-match-interaction qti-gap::part(drag)`) and out-specify a brand overlay's bare\n `qti-gap::part(drag)`, so the brand could never win. With nothing set here, the overlay's\n --drag-* is the only declaration and wins with no specificity/layer contest. */\n background-color: var(--drag-background-color, var(--qti-component-background-color));\n background-image: var(--drag-background-image, var(--qti-component-background-image));\n border-width: var(--drag-border-width, var(--qti-component-border-width));\n border-style: var(--drag-border-style, var(--qti-component-border-style));\n border-color: var(--drag-border-color, var(--qti-component-border-color));\n border-radius: var(--drag-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drag-box-shadow, var(--qti-component-box-shadow));\n color: var(--drag-color, var(--qti-component-color));\n opacity: var(--drag-opacity, var(--qti-component-opacity));\n filter: var(--drag-filter, var(--qti-component-filter));\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(\n in srgb,\n var(--drag-background-color, var(--qti-component-background-color)) 92%,\n black\n );\n }\n }\n &:focus {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n }\n &[dragging] {\n pointer-events: none;\n rotate: -2deg;\n --drag-box-shadow: 0 8px 12px rgb(0 0 0 / 20%), 0 4px 8px rgb(0 0 0 / 10%);\n }\n }\n /* Editor fake-drag projected into the interaction's drop-target shadow.\n Kept as a separate block (not merged into the comma list above)\n because a CSS comment between commas in a nested selector list is\n silently dropped by Chrome's nesting parser. */\n &::part(drag) {\n /* Structural \u2014 the load-bearing box, never state-switched (layout-invariance contract).\n box-sizing is deliberate: the clone is appended to document.body outside reset.css's\n @scope, so it inherits no border-box from anywhere else and this sets it. */\n box-sizing: border-box;\n padding: var(--qti-drag-padding);\n cursor: grab;\n /*\n * A chip is as wide as the drop it belongs in \u2014 the SAME token the drop reserves with.\n *\n * That one shared name is what makes a drag and its drop the same size without any arithmetic\n * relating them: the interaction measures the widest chip, publishes it once, and both the chip\n * and the slot read it. See DROP-SIZING.md.\n *\n * It applies in the bank as well as in the drop, which is the point: the chip already has the\n * width before it is dropped, so nothing changes size at the moment of landing. That keeps \"a chip\n * is the same chip wherever it lives\" true (drag-drop.invariance.spec.ts) and keeps the return\n * animation from ending in a snap. A `width: 100%` on the placed chip gives the same still image\n * and breaks both \u2014 it is what order used to do, and why order was the one entry in\n * CHIP_BOX_KNOWN_BAD.\n *\n * min-width, not width, so a chip whose own text is wider than the widest measured chip still\n * fits. Defaults to 0 where nothing is measured (match, whose targets are cards, not slots), so\n * those chips keep their natural width with no rule needed to say so.\n *\n * --qti-drag-min-width, not --qti-dropzone-min-width: the mixin writes the same measured value to\n * both, so a chip and its slot still match, but the two need OPPOSITE defaults when nothing is\n * measured \u2014 a chip must stay at its natural width, while a drops-grid track must not collapse.\n * See the note on both names in qti-variables.css.\n */\n min-width: var(--qti-drag-min-width, 0);\n /* Paint via the contract, nothing more \u2014 the --qti-component-* tokens already make a neutral\n bordered box (white surface, theme border, no shadow). The chip sets NO --drag-* of its own,\n and that is load-bearing: if it did (e.g. `--drag-background-color: white`), that declaration\n would ride the interaction's HIGH-specificity chip selector\n (`qti-gap-match-interaction qti-gap::part(drag)`) and out-specify a brand overlay's bare\n `qti-gap::part(drag)`, so the brand could never win. With nothing set here, the overlay's\n --drag-* is the only declaration and wins with no specificity/layer contest. */\n background-color: var(--drag-background-color, var(--qti-component-background-color));\n background-image: var(--drag-background-image, var(--qti-component-background-image));\n border-width: var(--drag-border-width, var(--qti-component-border-width));\n border-style: var(--drag-border-style, var(--qti-component-border-style));\n border-color: var(--drag-border-color, var(--qti-component-border-color));\n border-radius: var(--drag-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drag-box-shadow, var(--qti-component-box-shadow));\n color: var(--drag-color, var(--qti-component-color));\n opacity: var(--drag-opacity, var(--qti-component-opacity));\n filter: var(--drag-filter, var(--qti-component-filter));\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(\n in srgb,\n var(--drag-background-color, var(--qti-component-background-color)) 92%,\n black\n );\n }\n }\n &:focus {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n }\n }\n /* `display: grid` lives in qti-associate-interaction.styles.ts, which already declares it on\n [part~='drop'] alongside the drop's min-height/min-width. */\n /* A filled drop hides its dashed edge \u2014 see the note in qti-gap-match-interaction.css. Every\n associate drop holds exactly one chip (the div never carries match-max), so no capacity guard\n is needed here. Colour only: the width stays, or the row shifts when a chip lands. */\n &::part(filled) {\n --drop-border-color: transparent;\n }\n\n &::part(drop) {\n background-color: var(--drop-background-color, var(--qti-component-background-color));\n background-image: var(--drop-background-image, var(--qti-component-background-image));\n border-width: var(--drop-border-width, var(--qti-component-border-width));\n border-style: var(--drop-border-style, var(--qti-component-border-style));\n border-color: var(--drop-border-color, var(--qti-component-border-color));\n border-radius: var(--drop-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drop-box-shadow, var(--qti-component-box-shadow));\n color: var(--drop-color, var(--qti-component-color));\n opacity: var(--drop-opacity, var(--qti-component-opacity));\n filter: var(--drop-filter, var(--qti-component-filter));\n border-style: var(--drop-border-style, dashed);\n }\n &::part(drop):focus {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n }\n\n /*\n * Moved out of qti-associate-interaction.styles.ts, which is layout-only now. The drop's flags\n * are part tokens, not attributes: an attribute selector cannot follow ::part(), and a <div>\n * cannot carry a custom state (attachInternals throws). See setDropFlag.\n *\n * These come *after* ::part(drop). They tie it on specificity, so source order decides \u2014 put\n * them first and the idle drop repaints over its own active highlight.\n *\n * Deleted here: `&::part(drop)[dragging] { @mixin act }`. An attribute selector after ::part()\n * never matches; that rule has never applied.\n */\n &::part(active) {\n background-color: var(--qti-bg-active);\n }\n\n &::part(enabled) {\n background-color: var(--qti-bg-active);\n }\n }\n}\n@layer qti-components.interactions.states {\n qti-simple-associable-choice::part(drag),\n qti-associate-interaction::part(drag) {\n /* Structural \u2014 the load-bearing box, never state-switched (layout-invariance contract).\n box-sizing is deliberate: the clone is appended to document.body outside reset.css's\n @scope, so it inherits no border-box from anywhere else and this sets it. */\n box-sizing: border-box;\n padding: var(--qti-drag-padding);\n cursor: grab;\n /*\n * A chip is as wide as the drop it belongs in \u2014 the SAME token the drop reserves with.\n *\n * That one shared name is what makes a drag and its drop the same size without any arithmetic\n * relating them: the interaction measures the widest chip, publishes it once, and both the chip\n * and the slot read it. See DROP-SIZING.md.\n *\n * It applies in the bank as well as in the drop, which is the point: the chip already has the\n * width before it is dropped, so nothing changes size at the moment of landing. That keeps \"a chip\n * is the same chip wherever it lives\" true (drag-drop.invariance.spec.ts) and keeps the return\n * animation from ending in a snap. A `width: 100%` on the placed chip gives the same still image\n * and breaks both \u2014 it is what order used to do, and why order was the one entry in\n * CHIP_BOX_KNOWN_BAD.\n *\n * min-width, not width, so a chip whose own text is wider than the widest measured chip still\n * fits. Defaults to 0 where nothing is measured (match, whose targets are cards, not slots), so\n * those chips keep their natural width with no rule needed to say so.\n *\n * --qti-drag-min-width, not --qti-dropzone-min-width: the mixin writes the same measured value to\n * both, so a chip and its slot still match, but the two need OPPOSITE defaults when nothing is\n * measured \u2014 a chip must stay at its natural width, while a drops-grid track must not collapse.\n * See the note on both names in qti-variables.css.\n */\n min-width: var(--qti-drag-min-width, 0);\n /* Paint via the contract, nothing more \u2014 the --qti-component-* tokens already make a neutral\n bordered box (white surface, theme border, no shadow). The chip sets NO --drag-* of its own,\n and that is load-bearing: if it did (e.g. `--drag-background-color: white`), that declaration\n would ride the interaction's HIGH-specificity chip selector\n (`qti-gap-match-interaction qti-gap::part(drag)`) and out-specify a brand overlay's bare\n `qti-gap::part(drag)`, so the brand could never win. With nothing set here, the overlay's\n --drag-* is the only declaration and wins with no specificity/layer contest. */\n background-color: var(--drag-background-color, var(--qti-component-background-color));\n background-image: var(--drag-background-image, var(--qti-component-background-image));\n border-width: var(--drag-border-width, var(--qti-component-border-width));\n border-style: var(--drag-border-style, var(--qti-component-border-style));\n border-color: var(--drag-border-color, var(--qti-component-border-color));\n border-radius: var(--drag-border-radius, var(--qti-component-border-radius));\n box-shadow: var(--drag-box-shadow, var(--qti-component-box-shadow));\n color: var(--drag-color, var(--qti-component-color));\n opacity: var(--drag-opacity, var(--qti-component-opacity));\n filter: var(--drag-filter, var(--qti-component-filter));\n }\n\n qti-associate-interaction::part(hover),\n qti-associate-interaction::part(active) {\n background-color: var(--qti-drop-highlight-bg);\n }\n\n qti-associate-interaction::part(enabled) {\n background-color: var(--qti-drop-highlight-enabled-bg);\n }\n\n qti-associate-interaction qti-simple-associable-choice:state(placeholder) {\n /* The hole a spent/lifted chip leaves. Like the chip (see `drag`), it sets NO fill / border /\n shadow of its own \u2014 those inherit from the resting chip (neutral by default, the brand's fill\n when an overlay is on), so a brand can restyle the hole via a `:state(placeholder)` overlay\n with no specificity contest. The one thing a placeholder always does, in every brand, is hide\n its text; the default hole is then just the resting chip (white + border) with its text gone. */\n --drag-color: transparent;\n --drag-opacity: 1;\n border-color: transparent;\n pointer-events: none;\n }\n\n qti-associate-interaction qti-simple-associable-choice:state(dragging) {\n /* The hole a spent/lifted chip leaves. Like the chip (see `drag`), it sets NO fill / border /\n shadow of its own \u2014 those inherit from the resting chip (neutral by default, the brand's fill\n when an overlay is on), so a brand can restyle the hole via a `:state(placeholder)` overlay\n with no specificity contest. The one thing a placeholder always does, in every brand, is hide\n its text; the default hole is then just the resting chip (white + border) with its text gone. */\n --drag-color: transparent;\n --drag-opacity: 1;\n border-color: var(--qti-drop-highlight-border-color);\n box-shadow: inset 0 0 0 var(--qti-drop-highlight-border-width, 2px)\n color-mix(in srgb, var(--qti-drop-highlight-border-color) 55%, transparent);\n pointer-events: none;\n }\n\n qti-associate-interaction qti-simple-associable-choice:state(placeholder) > *,\n qti-associate-interaction qti-simple-associable-choice:state(dragging) > * {\n visibility: hidden;\n }\n\n qti-associate-interaction qti-simple-associable-choice:state(placeholder)::part(label),\n qti-associate-interaction qti-simple-associable-choice:state(dragging)::part(label) {\n visibility: hidden;\n }\n\n qti-associate-interaction qti-simple-associable-choice:state(return-anchor) {\n border-color: var(--qti-drop-highlight-border-color);\n box-shadow: inset 0 0 0 var(--qti-drop-highlight-border-width, 2px)\n color-mix(in srgb, var(--qti-drop-highlight-border-color) 55%, transparent);\n }\n}\n@layer qti-components.interactions.corrections {\n /* A corrected chip \u2014 in the bank, and where it landed. Moved here from the shared\n qti-corrections.css, which claimed `:state(drag)` for EVERY interaction in one unscoped rule:\n the same shape the chip vocabulary had before it was sprayed, and moved for the same reason.\n `[data-drag-clone]:state(candidate-*)` went with it and is deliberately NOT reproduced \u2014 a clone\n carries no custom states at all, because cloneNode does not copy ElementInternals (measured), so\n that selector never matched anything. */\n qti-associate-interaction :state(drag):state(candidate-correct),\n qti-simple-associable-choice::part(drag):state(candidate-correct),\n qti-associate-interaction::part(drag):state(candidate-correct) {\n --drag-background-color: var(--qti-correct-light);\n --drag-color: var(--qti-correct);\n --drag-border-color: var(--qti-correct);\n --drag-box-shadow: none;\n }\n\n qti-associate-interaction :state(drag):state(candidate-incorrect),\n qti-simple-associable-choice::part(drag):state(candidate-incorrect),\n qti-associate-interaction::part(drag):state(candidate-incorrect) {\n --drag-background-color: var(--qti-incorrect-light);\n --drag-color: var(--qti-incorrect);\n --drag-border-color: var(--qti-incorrect);\n --drag-box-shadow: none;\n }\n\n /* The bank is hidden by the interaction itself, on :host([answer-key]) \u2014 see\n qti-associate-interaction.styles.ts. */\n}\n@layer qti-components.interactions.base {\n qti-graphic-order-interaction {\n --qti-graphic-order-pin-size: calc(var(--qti-select-point-marker-size, 1.5rem) * 1.2);\n --qti-graphic-order-pin-anchor: -100%;\n --qti-graphic-order-pin-fill: var(--qti-background, #fff);\n --qti-graphic-order-pin-number-size: calc(var(--qti-graphic-order-pin-size) * 0.5);\n --qti-graphic-order-pin-svg: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512'%3E%3Cpath d='M192 500C145 434 106 386 77 344C49 305 32 272 32 224C32 135.6 103.6 64 192 64s160 71.6 160 160c0 48-17 81-45 120c-29 42-68 90-115 156z' fill='white' stroke='currentColor' stroke-width='28' stroke-linejoin='round'/%3E%3C/svg%3E\");\n\n & qti-hotspot-choice {\n width: 100%;\n height: 100%;\n background-color: color-mix(in srgb, var(--qti-border-color) 18%, transparent);\n padding: 0;\n cursor: pointer;\n transition:\n background-color var(--qti-motion-duration-fast, 0.15s) ease,\n border-color var(--qti-motion-duration-fast, 0.15s) ease;\n\n &[shape='circle'] {\n min-width: calc(var(--qti-graphic-order-pin-size) * 0.6);\n min-height: calc(var(--qti-graphic-order-pin-size) * 0.6);\n }\n\n &[shape='poly'] {\n background-color: color-mix(in srgb, var(--qti-border-color) 18%, transparent);\n box-shadow: none;\n }\n\n &[shape='circle']:hover,\n &[shape='rect']:hover,\n &[shape='ellipse']:hover {\n background-color: color-mix(in srgb, var(--qti-border-active) 12%, transparent);\n }\n\n &[shape='poly']:hover {\n background-color: color-mix(in srgb, var(--qti-border-active) 20%, transparent);\n box-shadow: none;\n }\n\n &[shape='circle']:focus-visible,\n &[shape='rect']:focus-visible,\n &[shape='ellipse']:focus-visible {\n outline: var(--qti-focus-border-width) solid var(--qti-focus-color);\n outline-offset: 2px;\n }\n\n &[shape='poly']:focus-visible {\n outline: var(--qti-focus-border-width) solid var(--qti-focus-color);\n outline-offset: 2px;\n }\n\n &[aria-ordervalue] {\n border: 0 !important;\n box-shadow: none;\n background-color: color-mix(in srgb, var(--qti-selected-bg) 35%, transparent);\n }\n\n &[aria-ordervalue]:hover,\n &[aria-ordervalue]:focus-visible {\n border-color: transparent !important;\n }\n &[aria-ordercorrectvalue][aria-ordervalue]:not([shape='circle'])::after {\n content: 'C=' attr(aria-ordercorrectvalue) ' R=' attr(aria-ordervalue) !important;\n color: var(--qti-correct);\n }\n &[aria-ordercorrectvalue]:not([aria-ordervalue]):not([shape='circle'])::after {\n content: 'C=' attr(aria-ordercorrectvalue) !important;\n color: var(--qti-correct);\n }\n }\n\n /* The pin's face. Where it sits \u2014 absolute, anchored to its hotspot, above the image \u2014 is\n placement, and lives in qti-graphic-order-interaction.styles.ts as ::slotted(). */\n & .cito-graphic-order-marker {\n width: var(--qti-graphic-order-pin-size);\n height: var(--qti-graphic-order-pin-size);\n padding-top: calc(var(--qti-graphic-order-pin-size) * 0.2);\n font-weight: 700;\n font-size: var(--qti-graphic-order-pin-number-size);\n line-height: 1;\n color: var(--qti-graphic-order-pin-color, var(--qti-selected-bg));\n text-shadow: 0 0 1px var(--qti-graphic-order-pin-fill);\n background: var(--qti-graphic-order-pin-svg) no-repeat center / contain;\n pointer-events: none;\n }\n\n & .cito-graphic-order-marker--poly {\n width: calc(var(--qti-select-point-marker-size, 1.5rem) * 0.72);\n height: calc(var(--qti-select-point-marker-size, 1.5rem) * 0.72);\n font-size: calc(var(--qti-select-point-marker-size, 1.5rem) * 0.45);\n padding-top: calc(var(--qti-select-point-marker-size, 1.5rem) * 0.14);\n }\n }\n}\n@layer qti-components.interactions.states {\n qti-graphic-order-interaction {\n & qti-hotspot-choice {\n &:state(readonly) {\n cursor: pointer;\n }\n\n &:state(disabled) {\n cursor: not-allowed;\n }\n\n &[shape='circle']:state(checked),\n &[shape='rect']:state(checked),\n &[shape='ellipse']:state(checked) {\n background-color: color-mix(in srgb, var(--qti-selected-bg) 35%, transparent);\n }\n\n &[shape='poly']:state(checked) {\n background-color: color-mix(in srgb, var(--qti-selected-bg) 40%, transparent);\n box-shadow: none;\n }\n\n &[aria-ordervalue]:state(checked),\n &[aria-ordervalue] {\n border-color: transparent !important;\n }\n }\n\n /* No qti-selections-light / -dark here. Appendix B.1 of the QTI 3 implementation guide defines\n that class pair for qti-hotspot-interaction and qti-graphic-gap-match-interaction only, and no\n item in this repo ever set it on a graphic-order interaction. What was here overrode the neutral\n drag-bank highlight below with --qti-light-* and --qti-dark-* colours that nothing could reach. */\n &:state(dragzone-active)::part(drags) {\n /* Direct properties, not slots: ::part(drags) is a bare slot container with no paint role of\n its own (the other interactions paint it with `@mixin dropping`, also direct), so there is\n no --<role>-* to switch. Inlined from `@mixin act`. */\n border-color: var(--qti-border-active);\n background-color: var(--qti-bg-active);\n }\n\n &:state(dragzone-enabled)::part(drags) {\n background-color: var(--qti-bg-active);\n }\n }\n}\n@layer qti-components.interactions.corrections {\n qti-graphic-order-interaction {\n & qti-hotspot-choice {\n &[shape='circle']:state(correct-response),\n &[shape='rect']:state(correct-response),\n &[shape='ellipse']:state(correct-response) {\n background-color: color-mix(in srgb, var(--qti-answer-bg) 75%, transparent);\n }\n\n &[shape='poly']:state(correct-response) {\n background-color: color-mix(in srgb, var(--qti-answer-bg) 80%, transparent);\n box-shadow: none;\n }\n\n &[shape='circle']:state(candidate-correct),\n &[shape='rect']:state(candidate-correct),\n &[shape='ellipse']:state(candidate-correct) {\n background-color: color-mix(in srgb, var(--qti-correct) 35%, transparent);\n }\n\n &[shape='poly']:state(candidate-correct) {\n background-color: color-mix(in srgb, var(--qti-correct) 40%, transparent);\n box-shadow: none;\n }\n\n &[shape='circle']:state(candidate-incorrect),\n &[shape='rect']:state(candidate-incorrect),\n &[shape='ellipse']:state(candidate-incorrect) {\n background-color: color-mix(in srgb, var(--qti-incorrect) 35%, transparent);\n }\n\n &[shape='poly']:state(candidate-incorrect) {\n background-color: color-mix(in srgb, var(--qti-incorrect) 40%, transparent);\n box-shadow: none;\n }\n\n &[aria-ordervalue]:state(correct-response),\n &[aria-ordervalue]:state(candidate-correct),\n &[aria-ordervalue]:state(candidate-incorrect) {\n border-color: transparent !important;\n }\n }\n }\n}\n@layer qti-components.interactions.base {\n qti-graphic-associate-interaction {\n /*\n * qti-associable-hotspot does not use ActiveElementMixin, so it carries no\n * checked/disabled/readonly custom states. Selection and correction are drawn as\n * ::part(line) / ::part(point) instead. Give it a `part=\"drag\"` and the\n * ActiveElementMixin states before adding state-dependent rules here.\n */\n & qti-associable-hotspot {\n /* `[shape='square']` used to be in this selector. It is not a QTI shape \u2014 the spec has\n circle, default, ellipse, poly and rect \u2014 positionShapes has no branch for it, and no item\n in the repo sets it. */\n &[shape='circle'] {\n background-color: transparent;\n &:focus {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n }\n }\n }\n }\n}\n@layer qti-components.interactions.states {\n qti-graphic-associate-interaction {\n\n\n /* No qti-selections-light / -dark here. Appendix B.1 of the QTI 3 implementation guide defines\n that class pair for qti-hotspot-interaction and qti-graphic-gap-match-interaction only, and no\n item in this repo ever set it on a graphic-associate interaction. What was here overrode the neutral\n drag-bank highlight below with --qti-light-* and --qti-dark-* colours that nothing could reach. */\n &:state(dragzone-active)::part(drags) {\n /* Direct properties, not slots: ::part(drags) is a bare slot container with no paint role of\n its own (the other interactions paint it with `@mixin dropping`, also direct), so there is\n no --<role>-* to switch. Inlined from `@mixin act`. */\n border-color: var(--qti-border-active);\n background-color: var(--qti-bg-active);\n }\n\n &:state(dragzone-enabled)::part(drags) {\n background-color: var(--qti-bg-active);\n }\n }\n}\n@layer qti-components.interactions.base {\n qti-slider-interaction {\n --qti-tick-color: rgb(229 231 235 / 100%);\n --qti-tick-width: 1px;\n }\n}\n@layer qti-components.interactions.base {\n qti-select-point-interaction {\n /*\n * Paint the marker through its own variable, not through `color`.\n *\n * This used to be `color: var(--qti-primary)` on the host, because the marker's mask is filled\n * with `var(--qti-select-point-marker-color, currentColor)`. But `color` inherits, so it also\n * repainted everything else inside the interaction \u2014 most visibly the `qti-prompt`, which turned\n * the question text primary-coloured for no reason.\n *\n * `--qti-select-point-marker-color` is the seam the component already publishes, and the one the\n * corrections mixin writes to. Setting it here rather than `color` leaves the prompt inheriting\n * ordinary body text, and corrections still win: they set the same variable on the point element\n * itself (`[part~='correct']`), which is deeper than this host declaration.\n */\n --qti-select-point-marker-color: var(--qti-primary);\n\n &::part(point) {\n &:focus {\n box-shadow: 0 0 0 var(--qti-focus-border-width) var(--qti-focus-color);\n }\n }\n }\n}\n@layer qti-components.interactions.corrections {\n qti-select-point-interaction {\n /* Same knob as the resting colour above \u2014 these set `color` before, which stopped reaching the\n marker once it was no longer painted with `currentColor`. */\n &::part(correct) {\n --qti-select-point-marker-color: var(--qti-correct);\n }\n\n &::part(incorrect) {\n --qti-select-point-marker-color: var(--qti-incorrect);\n }\n }\n}\n@layer qti-components.interactions.base {\n qti-position-object-stage {\n & qti-position-object-interaction {\n /* no styles necessary, only layout styles, defined in the component */\n }\n }\n}\n@layer qti-components.interactions.base {\n /* `display: block` lives in qti-prompt.styles.ts, which already declares it on :host. */\n}\n/*\n * What is LEFT here is what the base class owns.\n *\n * The chip corrections that used to head this file are gone, into the five drag interactions. They\n * were one unscoped `:state(drag)` rule claiming every interaction at once \u2014 the same shape the chip\n * vocabulary had in qti-states.css, moved for the same reason: an interaction's rules belong in that\n * interaction's file, and a shared rule is only honest when no interaction can own it.\n *\n * The rules below pass that test. `part=\"correction\"` is stamped by the correction machinery on every\n * corrected element, whichever interaction it belongs to, so no single file could own the badge; the\n * same goes for the `.full-correct-response` answer-key presentation, which is a property of the\n * cloned item rather than of any interaction. Spraying those would be four rules copied N times with\n * nothing gained and one of them eventually missed.\n *\n * Two things died in the move, both measured rather than reasoned:\n * - `[data-drag-clone]:state(candidate-*)` matched nothing, ever. cloneNode does not copy\n * ElementInternals, so a floating clone carries no custom states at all.\n * - a pair of `--drag-background-color` rules in gap-match and match asked for a SOLID fill that\n * never rendered, because the shared rule declared `background-color` directly and outranked the\n * binding that reads them. Deleted with the rule that was hiding them.\n */\n@layer qti-components.interactions.corrections {\n /*\n * The candidate-correction badge, `part=\"correction\"` on every corrected element.\n *\n * A token list, so `::part(correction-correct)` reaches the badge in the drag bank, on the\n * floating clone, and inside a drop target's shadow root \u2014 a token survives `exportparts`,\n * where a chained `::part()` would not.\n */\n ::part(correction-correct) {\n background-color: var(--qti-correct);\n mask: var(--qti-check-mask) no-repeat center / contain;\n -webkit-mask: var(--qti-check-mask) no-repeat center / contain;\n }\n\n ::part(correction-incorrect) {\n background-color: var(--qti-incorrect);\n mask: var(--qti-times-mask) no-repeat center / contain;\n -webkit-mask: var(--qti-times-mask) no-repeat center / contain;\n }\n\n\n\n /*\n * The third verdict. correction.styles.ts has always SIZED `correction-partially-correct` beside\n * its two siblings, but no rule anywhere ever painted it \u2014 so a partially-correct badge has been\n * an invisible 1em gap in every interaction, not just extended-text. This is repo-wide, and the\n * reason it went unnoticed is that partial credit needs a mapping to arise at all.\n */\n ::part(correction-partially-correct) {\n background-color: var(--qti-partially-correct);\n mask: var(--qti-partial-mask) no-repeat center / contain;\n -webkit-mask: var(--qti-partial-mask) no-repeat center / contain;\n }\n\n\n /* A cloned interaction presents the answer key rather than a candidate judgement. What is left\n here is the part no interaction owns: any chip, anywhere. The text-entry and inline-choice\n halves moved into their own files \u2014 those name one interaction each, so they belong to it. */\n .full-correct-response :state(drag),\n .full-correct-response ::part(drag) {\n background-color: var(--qti-answer-bg);\n color: var(--qti-answer-fg);\n border-color: var(--qti-answer-border);\n }\n\n .full-correct-response :state(drag)::part(control)::before,\n .full-correct-response ::part(drag-control)::before {\n content: '';\n background-color: var(--qti-answer-fg);\n mask: var(--qti-check-mask) no-repeat center / contain;\n -webkit-mask: var(--qti-check-mask) no-repeat center / contain;\n }\n}\n@layer qti-components.elements{\n/* `display: block` is declared by the element itself, in qti-rubric-block.ts. */\nqti-rubric-block {\n border: var(--qti-border-thickness) solid var(--qti-info);\n background-color: color-mix(in srgb, var(--qti-info) 5%, white);\n border-radius: var(--qti-border-radius);\n padding: var(--qti-padding-box);\n margin-top: var(--qti-gap);\n}\n}\n@layer qti-components.elements{\nqti-feedback-inline::part(feedback) {\n}\n}\n@layer qti-components.elements{\nqti-feedback-block::part(feedback) {\n}\n}\n@layer qti-components.elements{\n/*\n * Neither selector below matches anything this repo renders \u2014 `qti-modal-feedback-custom` is not a\n * registered element and nothing sets `.custom-modal-feedback`. They are kept as a downstream\n * integration point rather than deleted from a published stylesheet.\n *\n * Their `display` declarations are gone: a host app that renders these owns their box. What is left\n * is paint and spacing.\n */\n.custom-modal-feedback {\n width: 100%;\n border: var(--qti-border-thickness) solid var(--qti-info);\n background-color: var(--qti-bg);\n border-radius: var(--qti-border-radius);\n padding: var(--qti-padding-box);\n margin-top: var(--qti-gap);\n}\n}\n/*\n * Cross-interaction custom states.\n *\n * These states are set by the base `Interaction` class rather than by any one interaction,\n * so they mean the same thing everywhere and are styled once here instead of being copied\n * into each interaction's stylesheet.\n *\n * Per-interaction states (`checked`, `radio`, `dragzone-active`, \u2026) stay in their own files.\n *\n * The chip vocabulary used to live here too \u2014 one unscoped rule giving `@mixin drag` and `@mixin\n * grip` to every `:state(drag)` element and every floating clone. It is now declared once per\n * drag-drop interaction, in that interaction's own file (gap-match, order, associate, match), still\n * in this `global` layer so the cascade is unchanged.\n *\n * The move is what removed the exclusions. A universal claim (\"every drag is a chip\") needed a\n * carve-out for graphic-gap-match, whose chips are photographs sitting on a picture and must take\n * no card styling at all \u2014 written twice here as\n * `:where(:not(qti-graphic-gap-match-interaction *, [data-drag-interaction='\u2026']))`, and mirrored a\n * third and fourth time in the Kennisnet overlay. Declared per interaction the rule is an opt-in:\n * graphic-gap-match simply never states one, and there is no exception to write down anywhere.\n */\n@layer qti-components.global {\n ::part(message) {\n color: var(--qti-fg);\n background-color: color-mix(in srgb, var(--qti-warning) 5%, white);\n padding: var(--qti-padding-box);\n border: var(--qti-border-thickness) solid var(--qti-warning);\n border-radius: var(--qti-border-radius);\n margin-top: var(--qti-gap);\n }\n\n /*\n * The interaction is presenting the answer key rather than the candidate's own response.\n * Set on the interaction host by `Interaction.toggleInternalCorrectResponse`.\n *\n * Reaches the host wherever that base implementation runs, which is more than it looks: besides\n * `qti-extended-text-interaction`, both `ChoiceCorrectionMixin` and most per-interaction overrides\n * call `super`, so choice, hottext, select-point, slider, graphic-order and the rest set the host\n * state AND mark their individual choices with `correct-response` / `incorrect-response`.\n *\n * Three do not, and so never paint this outline: the drag-drop mixin replaces the implementation\n * outright, and the two inline interactions \u2014 text-entry and inline-choice \u2014 hand internal mode to\n * the full correct response, which presents the key in a sibling wrapper rather than on the host.\n *\n * The outline went unseen on hottext until its host stopped being an inline box (see the note in\n * qti-match-interaction.styles.ts) \u2014 an inline box whose children are all block has no area to\n * paint. If the double indication ever reads as too much, the fix is here or in the mixin, not\n * a display value.\n */\n :state(show-correct-response) {\n outline: var(--qti-border-thickness) dashed var(--qti-correct);\n outline-offset: 0.25rem;\n }\n}\n[view],\nqti-outcome-declaration,\nqti-response-declaration {\n display: none;\n}\n[view].show {\n display: block;\n}\n:host {\n box-sizing: border-box;\n}\nqti-test-part:not(:has(qti-assessment-item)),\nqti-assessment-section:not(:has(qti-assessment-item)),\nqti-assessment-item-ref:not(:has(qti-assessment-item)) {\n display: none;\n}\ndiv.full-correct-response {\n}\ndiv.full-correct-response-block {\n display: block;\n}\ndiv.full-correct-response-inline {\n display: inline-block;\n}\n";
|
|
81
|
-
|
|
82
|
-
export {
|
|
83
|
-
m,
|
|
84
|
-
item_default
|
|
85
|
-
};
|
|
86
|
-
/*! Bundled license information:
|
|
87
|
-
|
|
88
|
-
lit-html/node/directives/private-async-helpers.js:
|
|
89
|
-
(**
|
|
90
|
-
* @license
|
|
91
|
-
* Copyright 2021 Google LLC
|
|
92
|
-
* SPDX-License-Identifier: BSD-3-Clause
|
|
93
|
-
*)
|
|
94
|
-
|
|
95
|
-
lit-html/node/directives/until.js:
|
|
96
|
-
(**
|
|
97
|
-
* @license
|
|
98
|
-
* Copyright 2017 Google LLC
|
|
99
|
-
* SPDX-License-Identifier: BSD-3-Clause
|
|
100
|
-
*)
|
|
101
|
-
*/
|
|
102
|
-
//# sourceMappingURL=chunk-WCQ26LV7.js.map
|