@citolab/tspci 2.12.1 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/package.json +14 -4
- package/preset/base/AUTHORING.md +171 -0
- package/preset/base/global.d.ts +12 -2
- package/preset/base/package.json +10 -1
- package/preset/base/src/config.json +37 -5
- package/preset/base/src/styles.css +96 -3
- package/preset/javascript/src/index.js +116 -114
- package/preset/javascript/src/pie.js +46 -0
- package/preset/preact/src/index.tsx +117 -114
- package/preset/preact/src/interaction.tsx +47 -34
- package/preset/preact/src/pie.tsx +46 -0
- package/preset/preact/src/store.ts +12 -14
- package/preset/tailwind/src/styles.css +4 -27
- package/preset/typescript/src/index.ts +123 -127
- package/preset/typescript/src/pie.ts +51 -0
- package/public/index.html +196 -51
- package/readme.md +107 -17
- package/scripts/test-init-local.sh +28 -0
- package/src/authoring-frame.js +85 -0
- package/src/authoring.js +114 -0
- package/src/rollup-plugin-authoring.js +32 -0
- package/src/rollup-plugin-postcss-string.js +37 -1
- package/src/rollup.config.dev.js +18 -1
- package/src/rollup.config.js +2 -0
- package/tspci-development.png +0 -0
- package/types/index.d.ts +136 -0
- package/preset/base/src/assets/procenten.png +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,34 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [2.14.0](https://github.com/Citolab/tspci/compare/v2.13.0...v2.14.0) (2026-10-14)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **tspci:** the preact template uses typed store actions and the unsubscribe function ([5297db7](https://github.com/Citolab/tspci/commit/5297db72a3d2c62cdba58c1b2fe49bbbdee1e024))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# [2.13.0](/compare/v2.12.1...v2.13.0) (2026-10-14)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* **tspci:** give Tailwind 4 variables their defaults in the PCI's shadow root 3d95703
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Features
|
|
26
|
+
|
|
27
|
+
* **tspci:** authoring descriptor, typed settings and a dev page without CDNs 436ef53
|
|
28
|
+
* **tspci:** authoring mode for PCIs that fill settings themselves 5892fa7
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
6
34
|
## [2.12.1](/compare/v2.12.0...v2.12.1) (2026-08-19)
|
|
7
35
|
|
|
8
36
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@citolab/tspci",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.0",
|
|
4
4
|
"description": "an opiniated typescript ims-pci builder with tao integration",
|
|
5
5
|
"author": "patrick <patrick.deklein@cito.nl>",
|
|
6
|
-
"repository":
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/Citolab/tspci.git",
|
|
9
|
+
"directory": "lib/tspci"
|
|
10
|
+
},
|
|
7
11
|
"bin": {
|
|
8
12
|
"tspci": "src/cli.js"
|
|
9
13
|
},
|
|
@@ -12,11 +16,13 @@
|
|
|
12
16
|
"init": "tspci init --force --path=../../pcis",
|
|
13
17
|
"test:init:local": "bash ./scripts/test-init-local.sh",
|
|
14
18
|
"test:init:smoke": "bash ./scripts/test-init-local.sh",
|
|
15
|
-
"test:init:matrix": "bash ./scripts/test-init-matrix.sh"
|
|
19
|
+
"test:init:matrix": "bash ./scripts/test-init-matrix.sh",
|
|
20
|
+
"test": "node --test test/*.test.mjs"
|
|
16
21
|
},
|
|
17
22
|
"type": "module",
|
|
18
23
|
"types": "types/index.d.ts",
|
|
19
24
|
"dependencies": {
|
|
25
|
+
"@citolab/qti-components": "^9.5.0",
|
|
20
26
|
"@compodoc/live-server": "^1.2.3",
|
|
21
27
|
"@inquirer/prompts": "^8.2.0",
|
|
22
28
|
"@rollup/plugin-alias": "^6.0.0",
|
|
@@ -30,16 +36,20 @@
|
|
|
30
36
|
"@tailwindcss/nesting": "0.0.0-insiders.565cd3e",
|
|
31
37
|
"@tailwindcss/postcss": "^4.1.18",
|
|
32
38
|
"autoprefixer": "10.4.23",
|
|
39
|
+
"bootstrap": "5.1.3",
|
|
33
40
|
"chalk": "5.6.2",
|
|
34
41
|
"chokidar": "^5.0.0",
|
|
35
42
|
"clear": "0.1.0",
|
|
36
43
|
"deepmerge": "^4.3.1",
|
|
37
44
|
"fast-glob": "^3.3.3",
|
|
38
45
|
"figlet": "1.10.0",
|
|
46
|
+
"htm": "3.1.1",
|
|
39
47
|
"is-module": "^1.0.0",
|
|
40
48
|
"postcss": "^8.5.6",
|
|
41
49
|
"postcss-import": "^16.1.1",
|
|
42
50
|
"postcss-load-config": "^6.0.1",
|
|
51
|
+
"preact": "10.28.2",
|
|
52
|
+
"requirejs": "2.3.8",
|
|
43
53
|
"rollup": "^4.59.0",
|
|
44
54
|
"rollup-plugin-livereload": "^2.0.5",
|
|
45
55
|
"tailwindcss": "^4.1.18",
|
|
@@ -55,5 +65,5 @@
|
|
|
55
65
|
"access": "public",
|
|
56
66
|
"registry": "https://registry.npmjs.org/"
|
|
57
67
|
},
|
|
58
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "d72febca7cea937f77be4cef910a7f2d72fd524f"
|
|
59
69
|
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Authoring: how an authoring system configures and scores this PCI
|
|
2
|
+
|
|
3
|
+
A PCI does not only run in a test player. An item author places it in an item in an authoring system (such as
|
|
4
|
+
qti-construction or TAO), sets it up, and decides what counts as a correct answer. This file explains what the
|
|
5
|
+
authoring system learns from your project and what your PCI has to do for it. tspci does the packaging; you only
|
|
6
|
+
fill in two files.
|
|
7
|
+
|
|
8
|
+
## 1. Settings: `src/config.json`
|
|
9
|
+
|
|
10
|
+
Every key is a setting of your PCI. In an item it becomes a `data-` attribute (`width` → `data-width`), and your PCI
|
|
11
|
+
receives it in `config.properties` (always as a string, as attributes are).
|
|
12
|
+
|
|
13
|
+
A value can be a plain default, as in every tspci project:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{ "title": "Percentages", "width": "200" }
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
or an object with a `default` plus hints for the author's form:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"width": { "default": "200", "editor": "integer", "min": 50, "label": { "en": "Width (px)", "nl": "Breedte (px)" } },
|
|
24
|
+
"mode": {
|
|
25
|
+
"default": "mixed",
|
|
26
|
+
"editor": "enum",
|
|
27
|
+
"options": [
|
|
28
|
+
{ "value": "mixed", "label": { "en": "Multiple colours" } },
|
|
29
|
+
{ "value": "single", "label": { "en": "One colour" } }
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
"colors": { "default": "#c91a09, #0055bf", "editor": "colorList" }
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| editor | the author gets | value |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `text` | a text field | any text |
|
|
39
|
+
| `textarea` | a larger text field | any text |
|
|
40
|
+
| `integer`, `number` | a number field (`min`, `max`) | `"200"` |
|
|
41
|
+
| `boolean` | a switch | `"true"` / `"false"` |
|
|
42
|
+
| `enum` | a dropdown of `options` | one of the option values |
|
|
43
|
+
| `color` | a colour picker | `"#4b9f4a"` |
|
|
44
|
+
| `colorList` | a list of colour pickers | `"#c91a09, #0055bf"` |
|
|
45
|
+
| `hidden` | nothing (set by the system, not the author) | |
|
|
46
|
+
| `pci` | a "Configure…" button that opens your PCI's own editor (section 5) | whatever your editor writes, as a string |
|
|
47
|
+
|
|
48
|
+
A plain value gets an editor from its value: `"true"`/`"false"` a switch, a whole number a number field, the rest a
|
|
49
|
+
text field. `label` is one string or one per language. An authoring system that does not know an editor shows a
|
|
50
|
+
text field, so a hint never makes a setting unusable.
|
|
51
|
+
|
|
52
|
+
The build passes **only the defaults** to your PCI, so `import configProps from "./config.json"` keeps working. Type
|
|
53
|
+
it with `PciProperties`:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { PciProperties } from "@citolab/tspci";
|
|
57
|
+
import configProps from "./config.json";
|
|
58
|
+
type PropTypes = PciProperties<typeof configProps>; // { width: string; mode: string; ... }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 2. The response: `package.json` → `config.tspci`
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
"config": {
|
|
65
|
+
"tspci": {
|
|
66
|
+
"typeIdentifier": "myPci",
|
|
67
|
+
"label": "My PCI",
|
|
68
|
+
"response": { "cardinality": "single", "baseType": "integer" },
|
|
69
|
+
"score": ["MATCH_CORRECT", "NONE"]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- **`response`** is the response variable your PCI fills with `getResponse()`. It must be what your code returns:
|
|
75
|
+
`{ base: { integer: 30 } }` is `single` / `integer`. The package's item declares it, and an authoring system uses it
|
|
76
|
+
for every new item with your PCI.
|
|
77
|
+
- **`score`** lists the scoring your PCI is made for: `MATCH_CORRECT` (the response equals the correct one) and/or
|
|
78
|
+
`NONE` (not scored, or scored by a person). TAO offers exactly these.
|
|
79
|
+
|
|
80
|
+
A PCI that reports several values (a moment and a position, say) uses one variable with **record** cardinality:
|
|
81
|
+
QTI binds a PCI to exactly one response variable, and a record has named fields of their own type.
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
"response": {
|
|
85
|
+
"cardinality": "record",
|
|
86
|
+
"fields": {
|
|
87
|
+
"time": { "baseType": "float", "tolerance": "range", "label": { "en": "Moment" } },
|
|
88
|
+
"point": { "baseType": "point", "tolerance": "area", "label": { "en": "Position" } }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`getResponse()` then returns `{ record: [{ name: "time", base: { float: 4.1 } }, { name: "point", base: { point: [455, 250] } }] }`.
|
|
94
|
+
`tolerance` tells the authoring system what kind of margin a score rule may give a field: `range` (between two
|
|
95
|
+
numbers) or `area` (a point inside a shape). Elements the author puts in your `qti-interaction-markup`, such as a
|
|
96
|
+
video, go in `markup`: `"markup": { "video": { "editor": "media", "accept": ["video/*"], "required": true } }`.
|
|
97
|
+
|
|
98
|
+
## 3. What the build makes of it
|
|
99
|
+
|
|
100
|
+
`tspci` (and `tspci --dev`) writes `dist/authoring.json`, generated from both files, and warns about what an
|
|
101
|
+
authoring system could not use (an unknown editor, an enum default that is not one of its options, a record field
|
|
102
|
+
without a base type). `tspci --target qti3` puts it in the package next to the bundle, as a file of the PCI resource
|
|
103
|
+
(`resources/pci/authoring.json`), like the SBOM; `--no-authoring` leaves it out. `tspci --target tao` builds TAO's
|
|
104
|
+
settings form from the same `config.json`.
|
|
105
|
+
|
|
106
|
+
## 4. Correct answers: what your PCI must do
|
|
107
|
+
|
|
108
|
+
Authors set the correct answer **by answering your PCI** in a preview ("answer in preview"): whatever `getResponse()`
|
|
109
|
+
reports becomes the correct response, or one of several accepted answers. Scoring then compares responses, so:
|
|
110
|
+
|
|
111
|
+
- **One answer, one response.** Report the meaning, not the route: two ways to build the same thing must give the
|
|
112
|
+
same response (sort lists, round numbers, leave out ids, order and timestamps). A response that differs in a
|
|
113
|
+
detail the author did not mean scores 0.
|
|
114
|
+
- **Report the type you declared.** A number as `integer`/`float`, not a JSON string with quotes in it.
|
|
115
|
+
- **Nothing answered, nothing reported.** Return `undefined` until the candidate did something.
|
|
116
|
+
- **Show a given answer.** `setResponse()` and `boundTo` must restore a response, and with `status: "solution"` your
|
|
117
|
+
PCI receives `responseDeclaration.correctResponse` and should show it (`showCorrectResponse()`); that is how an
|
|
118
|
+
author checks the answer and how a candidate sees it in review.
|
|
119
|
+
- **Margins live in the item, not in the PCI.** A time window or an area is response processing
|
|
120
|
+
(`qti-field-value`, `qti-gte`/`qti-lte`, `qti-inside`) that the authoring system writes from the author's rules.
|
|
121
|
+
|
|
122
|
+
## 5. Authoring mode: settings your PCI edits itself
|
|
123
|
+
|
|
124
|
+
Some settings cannot be a text field or a number: a shape, a set of areas, a path. Give such a setting
|
|
125
|
+
`"editor": "pci"` and let your PCI edit it. Your PCI then has a second mode, for the item author:
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
"partition": { "default": "", "editor": "pci", "label": { "en": "Shape to colour" } }
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
In `package.json`, `config.tspci.authoring` can set the button text and the size you would like
|
|
132
|
+
(`{ "label": { "en": "Draw the shape…" }, "width": 960, "height": 760 }`). The descriptor then has
|
|
133
|
+
`"authoring": { "properties": ["partition"], ... }`.
|
|
134
|
+
|
|
135
|
+
Next to `getInstance`, the object you register gets a method that an authoring system calls instead. A player never
|
|
136
|
+
calls it:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
getAuthoringInstance = (dom: HTMLElement, config: PciAuthoringConfiguration<PropTypes>): PciAuthoringInstance => {
|
|
140
|
+
// render your editor into dom, starting from config.properties; call config.onchange?.() after each change
|
|
141
|
+
const instance = {
|
|
142
|
+
getConfig: () => ({ partition: JSON.stringify(shape) }), // only the "pci" properties, as strings
|
|
143
|
+
validate: () => (shape.parts < 2 ? ["Draw at least one line"] : []), // optional: what keeps Done from finishing
|
|
144
|
+
destroy: () => { /* unmount */ },
|
|
145
|
+
};
|
|
146
|
+
config.onready?.(instance);
|
|
147
|
+
return instance;
|
|
148
|
+
};
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The authoring system runs this in a sandboxed iframe (TAO calls it directly in its creator page) and owns the
|
|
152
|
+
Done and Cancel buttons. Done writes what
|
|
153
|
+
`getConfig()` returns as `data-` attributes, and delivery reads them like any other setting. Two rules:
|
|
154
|
+
|
|
155
|
+
- **Delivery must not need authoring mode.** Without the attribute, or with one it cannot read, your PCI uses its
|
|
156
|
+
default. Other players and authoring systems never call `getAuthoringInstance`, and an authoring system that does
|
|
157
|
+
not know `pci` shows the setting as a text field.
|
|
158
|
+
- **No messages, no globals.** Your PCI never calls `postMessage`; the authoring system's own page in the iframe does
|
|
159
|
+
that.
|
|
160
|
+
|
|
161
|
+
`tspci --dev` shows the button in the config panel: it runs your editor in place of the preview, and Done updates
|
|
162
|
+
the property. See `docs/authoring-mode.md` in the tspci repository for the protocol and the reasons behind it.
|
|
163
|
+
|
|
164
|
+
## Checklist
|
|
165
|
+
|
|
166
|
+
- [ ] every setting in `src/config.json` has a `default`, an `editor` and a `label`
|
|
167
|
+
- [ ] `config.tspci.response` matches what `getResponse()` returns
|
|
168
|
+
- [ ] `getResponse()` gives the same response for the same answer, and `undefined` without one
|
|
169
|
+
- [ ] `setResponse()` / `boundTo` and `status: "solution"` show a given response
|
|
170
|
+
- [ ] `npm run prod` writes `dist/authoring.json` without warnings
|
|
171
|
+
- [ ] with an authoring mode: delivery works without the `pci` properties (it uses the defaults)
|
package/preset/base/global.d.ts
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
|
+
// Imports the build bundles into the PCI (see the readme): CSS as a string, images as a data URL.
|
|
1
2
|
declare module "*.css" {
|
|
2
3
|
const content: string;
|
|
3
4
|
export = content;
|
|
4
5
|
}
|
|
5
|
-
|
|
6
6
|
declare module "*.png" {
|
|
7
7
|
const content: string;
|
|
8
8
|
export = content;
|
|
9
9
|
}
|
|
10
|
+
declare module "*.jpg" {
|
|
11
|
+
const content: string;
|
|
12
|
+
export = content;
|
|
13
|
+
}
|
|
14
|
+
declare module "*.svg" {
|
|
15
|
+
const content: string;
|
|
16
|
+
export = content;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Provided by the delivery engine at runtime.
|
|
10
20
|
declare module "qtiCustomInteractionContext" {
|
|
11
21
|
const register: {register: (PortableInteraction) => void};
|
|
12
22
|
export = register;
|
|
13
|
-
}
|
|
23
|
+
}
|
package/preset/base/package.json
CHANGED
|
@@ -17,7 +17,16 @@
|
|
|
17
17
|
},
|
|
18
18
|
"config": {
|
|
19
19
|
"tspci": {
|
|
20
|
-
"typeIdentifier": "myPci"
|
|
20
|
+
"typeIdentifier": "myPci",
|
|
21
|
+
"label": "My PCI",
|
|
22
|
+
"response": {
|
|
23
|
+
"cardinality": "single",
|
|
24
|
+
"baseType": "integer"
|
|
25
|
+
},
|
|
26
|
+
"score": [
|
|
27
|
+
"MATCH_CORRECT",
|
|
28
|
+
"NONE"
|
|
29
|
+
]
|
|
21
30
|
}
|
|
22
31
|
},
|
|
23
32
|
"source": "src/index.ts"
|
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
{
|
|
2
|
-
"title"
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
2
|
+
"title": {
|
|
3
|
+
"default": "Percentages",
|
|
4
|
+
"editor": "text",
|
|
5
|
+
"label": { "en": "Title", "nl": "Titel" }
|
|
6
|
+
},
|
|
7
|
+
"question": {
|
|
8
|
+
"default": "What percentage of the circle is coloured?",
|
|
9
|
+
"editor": "textarea",
|
|
10
|
+
"label": { "en": "Question", "nl": "Vraag" }
|
|
11
|
+
},
|
|
12
|
+
"segments": {
|
|
13
|
+
"default": "10",
|
|
14
|
+
"editor": "integer",
|
|
15
|
+
"min": 2,
|
|
16
|
+
"max": 24,
|
|
17
|
+
"label": { "en": "Number of slices", "nl": "Aantal stukken" }
|
|
18
|
+
},
|
|
19
|
+
"colored": {
|
|
20
|
+
"default": "3",
|
|
21
|
+
"editor": "integer",
|
|
22
|
+
"min": 0,
|
|
23
|
+
"max": 24,
|
|
24
|
+
"label": { "en": "Coloured slices", "nl": "Gekleurde stukken" }
|
|
25
|
+
},
|
|
26
|
+
"color": {
|
|
27
|
+
"default": "#3b82f6",
|
|
28
|
+
"editor": "color",
|
|
29
|
+
"label": { "en": "Colour", "nl": "Kleur" }
|
|
30
|
+
},
|
|
31
|
+
"size": {
|
|
32
|
+
"default": "200",
|
|
33
|
+
"editor": "integer",
|
|
34
|
+
"min": 100,
|
|
35
|
+
"max": 400,
|
|
36
|
+
"label": { "en": "Size of the circle (px)", "nl": "Grootte van de cirkel (px)" }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -1,3 +1,96 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/* Rendered inside the PCI's shadow root: these rules do not leak into the item, and the item's CSS does not leak in. */
|
|
2
|
+
|
|
3
|
+
:host {
|
|
4
|
+
display: block;
|
|
5
|
+
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
6
|
+
color: #1f2933;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.pci {
|
|
10
|
+
box-sizing: border-box;
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
align-items: center;
|
|
14
|
+
gap: 16px;
|
|
15
|
+
width: fit-content;
|
|
16
|
+
max-width: 100%;
|
|
17
|
+
margin: 0 auto;
|
|
18
|
+
padding: 24px 28px;
|
|
19
|
+
border: 1px solid #e3e8ee;
|
|
20
|
+
border-radius: 16px;
|
|
21
|
+
background: linear-gradient(180deg, #ffffff 0%, #f7f9fb 100%);
|
|
22
|
+
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 8px 24px rgba(16, 24, 40, 0.06);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.title {
|
|
26
|
+
margin: 0;
|
|
27
|
+
font-size: 1.25rem;
|
|
28
|
+
font-weight: 650;
|
|
29
|
+
letter-spacing: -0.01em;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Scales down on a narrow screen; the viewBox keeps the drawing sharp. */
|
|
33
|
+
.pie {
|
|
34
|
+
max-width: 100%;
|
|
35
|
+
height: auto;
|
|
36
|
+
filter: drop-shadow(0 2px 3px rgba(16, 24, 40, 0.15));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.slice {
|
|
40
|
+
stroke: #ffffff;
|
|
41
|
+
stroke-width: 2;
|
|
42
|
+
}
|
|
43
|
+
/* Coloured slices carry a fill attribute (the author's colour); the others are grey. */
|
|
44
|
+
.slice:not([fill]) {
|
|
45
|
+
fill: #e5e9f0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.question {
|
|
49
|
+
max-width: 32ch;
|
|
50
|
+
text-align: center;
|
|
51
|
+
color: #52606d;
|
|
52
|
+
cursor: pointer;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.answer {
|
|
56
|
+
display: flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
gap: 6px;
|
|
59
|
+
font-size: 1.125rem;
|
|
60
|
+
font-weight: 600;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
input {
|
|
64
|
+
width: 4.5ch;
|
|
65
|
+
padding: 8px 10px;
|
|
66
|
+
font: inherit;
|
|
67
|
+
text-align: right;
|
|
68
|
+
color: inherit;
|
|
69
|
+
border: 1.5px solid #cbd2d9;
|
|
70
|
+
border-radius: 10px;
|
|
71
|
+
background: #ffffff;
|
|
72
|
+
transition: border-color 120ms, box-shadow 120ms;
|
|
73
|
+
-moz-appearance: textfield;
|
|
74
|
+
appearance: textfield;
|
|
75
|
+
}
|
|
76
|
+
/* A percentage is typed, not stepped: hide the spin buttons. */
|
|
77
|
+
input::-webkit-outer-spin-button,
|
|
78
|
+
input::-webkit-inner-spin-button {
|
|
79
|
+
-webkit-appearance: none;
|
|
80
|
+
margin: 0;
|
|
81
|
+
}
|
|
82
|
+
/* Keep a clearly visible focus ring for keyboard users. */
|
|
83
|
+
input:focus-visible {
|
|
84
|
+
outline: none;
|
|
85
|
+
border-color: #3b82f6;
|
|
86
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
|
|
87
|
+
}
|
|
88
|
+
input[aria-invalid="true"] {
|
|
89
|
+
border-color: #e11d48;
|
|
90
|
+
box-shadow: 0 0 0 3px rgba(225, 29, 72, 0.2);
|
|
91
|
+
}
|
|
92
|
+
input:disabled {
|
|
93
|
+
background: #f1f5f9;
|
|
94
|
+
color: #52606d;
|
|
95
|
+
cursor: not-allowed;
|
|
96
|
+
}
|