@eeacms/volto-eea-chatbot 4.1.4 → 4.1.5
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 +2 -0
- package/package.json +1 -1
- package/src/ChatBlock/components/HalloumiFeedback.jsx +6 -2
- package/src/ChatBlock/components/markdown/ClaimModal.jsx +10 -2
- package/src/ChatBlock/components/markdown/colors.js +26 -45
- package/src/ChatBlock/components/markdown/colors.less +27 -18
- package/src/ChatBlock/components/markdown/colors.test.js +71 -86
- package/src/ChatBlock/style.less +38 -12
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. Dates are d
|
|
|
4
4
|
|
|
5
5
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
6
6
|
|
|
7
|
+
### [4.1.5](https://github.com/eea/volto-eea-chatbot/compare/4.1.4...4.1.5) - 13 October 2026
|
|
8
|
+
|
|
7
9
|
### [4.1.4](https://github.com/eea/volto-eea-chatbot/compare/4.1.3...4.1.4) - 12 October 2026
|
|
8
10
|
|
|
9
11
|
#### :rocket: New Features
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ import { Button, Message, MessageContent } from 'semantic-ui-react';
|
|
|
4
4
|
import { serializeNodes } from '@plone/volto-slate/editor/render';
|
|
5
5
|
import Spinner from './Spinner';
|
|
6
6
|
import SVGIcon from './Icon';
|
|
7
|
-
import {
|
|
7
|
+
import { getVerdictBgColor } from './markdown/colors';
|
|
8
8
|
|
|
9
9
|
import GlassesIcon from '@eeacms/volto-eea-chatbot/icons/glasses.svg';
|
|
10
10
|
import RotateIcon from '@eeacms/volto-eea-chatbot/icons/rotate.svg';
|
|
@@ -114,9 +114,13 @@ const HalloumiFeedback = ({
|
|
|
114
114
|
color={scoreColor}
|
|
115
115
|
className={cx(
|
|
116
116
|
'claim-message',
|
|
117
|
+
// The summary chip wears the same three hues as the claims it
|
|
118
|
+
// summarises - the old eight-step red-to-green gradient implied a
|
|
119
|
+
// precision the score never had, skipped yellow entirely, and gave a
|
|
120
|
+
// colour-blind reader one ramp instead of three findings.
|
|
117
121
|
emptyClaims
|
|
118
122
|
? 'claim-empty claim-gray-500'
|
|
119
|
-
:
|
|
123
|
+
: getVerdictBgColor(score / 100),
|
|
120
124
|
)}
|
|
121
125
|
icon
|
|
122
126
|
>
|
|
@@ -38,9 +38,17 @@ export function ClaimModal({ claim, markers, text, citedSources }) {
|
|
|
38
38
|
|
|
39
39
|
return (
|
|
40
40
|
<Modal
|
|
41
|
-
className={cx(
|
|
41
|
+
className={cx(
|
|
42
|
+
'claim-modal',
|
|
43
|
+
getVerdictBgColor(claim.score, claim.context_limited),
|
|
44
|
+
)}
|
|
42
45
|
trigger={
|
|
43
|
-
<span
|
|
46
|
+
<span
|
|
47
|
+
className={cx(
|
|
48
|
+
'claim',
|
|
49
|
+
getVerdictBgColor(claim.score, claim.context_limited),
|
|
50
|
+
)}
|
|
51
|
+
>
|
|
44
52
|
{text}
|
|
45
53
|
</span>
|
|
46
54
|
}
|
|
@@ -1,51 +1,32 @@
|
|
|
1
|
-
export function getSupportedTextColor(score) {
|
|
2
|
-
if (0 <= score && score < 0.5) {
|
|
3
|
-
return 'text-red-500';
|
|
4
|
-
} else if (0.5 <= score && score <= 1) {
|
|
5
|
-
return 'text-green-500';
|
|
6
|
-
}
|
|
7
|
-
return 'text-gray-500';
|
|
8
|
-
}
|
|
9
|
-
|
|
10
1
|
/**
|
|
11
|
-
* Colour for a
|
|
2
|
+
* Colour for a verdict: one per claim (`ClaimModal`) and one for the
|
|
3
|
+
* answer-level summary (`HalloumiFeedback`).
|
|
12
4
|
*
|
|
13
5
|
* A per-claim score is a verdict (supported 1.0, not_enough_info 0.4,
|
|
14
|
-
* contradicted 0.0), not a measurement on a 0-1 scale, so
|
|
15
|
-
*
|
|
16
|
-
* and it put `not_enough_info` in the
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
6
|
+
* contradicted 0.0 - see `scoreToVerdict`), not a measurement on a 0-1 scale, so
|
|
7
|
+
* the eight-step red-to-green gradient that used to live in this file implied a
|
|
8
|
+
* confidence level that was never computed, and it put `not_enough_info` in the
|
|
9
|
+
* red family, which reads as "the sources contradict this" when they actually
|
|
10
|
+
* said nothing.
|
|
11
|
+
*
|
|
12
|
+
* Three verdicts, three hues: red for a conflict, yellow for silence, green for
|
|
13
|
+
* support. Hue alone is not enough - for red-green colour blindness the two ends
|
|
14
|
+
* of that ramp are the same colour - so each hue also gets its own underline
|
|
15
|
+
* style (see `.claim` in `ChatBlock/style.less`) and the modal names the verdict
|
|
16
|
+
* in words.
|
|
21
17
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
18
|
+
* A claim whose sources were only partly loaded (`contextLimited`) is yellow
|
|
19
|
+
* whatever score it got: the header already refuses to name a verdict for it
|
|
20
|
+
* ("Couldn't be checked"), so colouring it green or red would claim more than the
|
|
21
|
+
* check established.
|
|
22
|
+
*
|
|
23
|
+
* The class names must exist in `colors.less`: the `claim-*` utilities come from
|
|
24
|
+
* an explicit list of `.generate-colors()` calls, and an unlisted class renders
|
|
25
|
+
* unstyled, silently. `colors.test.js` asserts the two stay in step.
|
|
25
26
|
*/
|
|
26
|
-
export function getVerdictBgColor(score,
|
|
27
|
-
if (
|
|
28
|
-
if (score >= 0.
|
|
29
|
-
return
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
export function getSupportedBgColor(score, prefix = 'bg') {
|
|
33
|
-
if (0 <= score && score < 0.125) {
|
|
34
|
-
return `${prefix}-red-500`;
|
|
35
|
-
} else if (0.125 <= score && score < 0.25) {
|
|
36
|
-
return `${prefix}-red-400`;
|
|
37
|
-
} else if (0.25 <= score && score < 0.375) {
|
|
38
|
-
return `${prefix}-red-300`;
|
|
39
|
-
} else if (0.375 <= score && score < 0.5) {
|
|
40
|
-
return `${prefix}-red-200`;
|
|
41
|
-
} else if (0.5 <= score && score < 0.625) {
|
|
42
|
-
return `${prefix}-green-200`;
|
|
43
|
-
} else if (0.625 <= score && score < 0.75) {
|
|
44
|
-
return `${prefix}-green-300`;
|
|
45
|
-
} else if (0.75 <= score && score < 0.875) {
|
|
46
|
-
return `${prefix}-green-400`;
|
|
47
|
-
} else if (0.875 <= score && score <= 1) {
|
|
48
|
-
return `${prefix}-green-500`;
|
|
49
|
-
}
|
|
50
|
-
return `${prefix}-gray-500`;
|
|
27
|
+
export function getVerdictBgColor(score, contextLimited = false) {
|
|
28
|
+
if (contextLimited) return 'claim-yellow-500';
|
|
29
|
+
if (score >= 0.8) return 'claim-green-500';
|
|
30
|
+
if (score >= 0.1) return 'claim-yellow-500';
|
|
31
|
+
return 'claim-red-500';
|
|
51
32
|
}
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
@green-400: #4ade80;
|
|
17
17
|
@green-500: #22c55e;
|
|
18
18
|
@green-100: #dcfce7; //
|
|
19
|
-
@green-700: #166534; //
|
|
20
19
|
|
|
21
20
|
@yellow-500: #eab308; //
|
|
22
21
|
@yellow-400: #facc15; //
|
|
@@ -30,7 +29,7 @@
|
|
|
30
29
|
|
|
31
30
|
@lime-500: #84cc16; //
|
|
32
31
|
|
|
33
|
-
.generate-colors(@namespace, @name, @color, @lighter-value: 20, @darker-value: 20) {
|
|
32
|
+
.generate-colors(@namespace, @name, @color, @lighter-value: 20, @darker-value: 20, @stroke-value: 0) {
|
|
34
33
|
@var-name: ~'--@{namespace}-color';
|
|
35
34
|
|
|
36
35
|
.@{namespace}-@{name} {
|
|
@@ -38,24 +37,34 @@
|
|
|
38
37
|
--@{namespace}-color: @color;
|
|
39
38
|
--@{namespace}-lighter-color: lighten(@color, @lighter-value);
|
|
40
39
|
--@{namespace}-darker-color: darken(@color, @darker-value);
|
|
40
|
+
// The colour for thin strokes - the claim underline, the verdict scale. A hue
|
|
41
|
+
// that is a fine fill is too light as a 2px line on white: yellow-500 is
|
|
42
|
+
// 1.92:1 there and green-500 2.28:1. Each call below darkens its stroke just
|
|
43
|
+
// enough to clear 3:1 (WCAG 1.4.11, non-text contrast) without moving the
|
|
44
|
+
// hue: red-500 3.76:1 untouched, green-500 -8 -> 3.32:1, yellow-500 -12 ->
|
|
45
|
+
// 3.37:1.
|
|
46
|
+
--@{namespace}-stroke-color: darken(@color, @stroke-value);
|
|
41
47
|
}
|
|
42
48
|
}
|
|
43
49
|
|
|
50
|
+
// Only the classes `getVerdictBgColor` emits are generated - an unlisted class
|
|
51
|
+
// renders unstyled, silently, with no colour and no error. `colors.test.js`
|
|
52
|
+
// asserts the two lists stay in step.
|
|
53
|
+
//
|
|
54
|
+
// The three verdicts get three hues, not a ladder: red = contradicted, yellow =
|
|
55
|
+
// not_enough_info, green = supported. Two greens were tried and failed - they
|
|
56
|
+
// read as one colour, and for red-green colour blindness red and green read as
|
|
57
|
+
// one colour too, which is why the underline *style* also changes per verdict
|
|
58
|
+
// (see `.claim` in `../style.less`).
|
|
59
|
+
//
|
|
60
|
+
// lighter/darker are set so the chip's text (`--claim-darker-color`) on the
|
|
61
|
+
// chip's background (`--claim-lighter-color`) clears WCAG AA: 5.51:1 red,
|
|
62
|
+
// 4.81:1 yellow, 5.55:1 green.
|
|
44
63
|
.generate-colors(claim, gray-500, @gray-500, 45, 20);
|
|
45
64
|
.generate-colors(claim, red-500, @red-500, 35, 20);
|
|
46
|
-
.
|
|
47
|
-
|
|
48
|
-
.
|
|
49
|
-
|
|
50
|
-
.generate-colors(claim,
|
|
51
|
-
.generate-colors(claim, green-
|
|
52
|
-
.generate-colors(claim, green-200, @green-200, 10, 45);
|
|
53
|
-
|
|
54
|
-
// The three claim verdicts are a green ladder plus red: green-700 = supported, the
|
|
55
|
-
// darkest, green-300 = not confirmed by the sources, red-500 = contradicted. Kept
|
|
56
|
-
// deliberately far apart in lightness - two adjacent greens read as one colour and the
|
|
57
|
-
// distinction is lost. The darken value on green-300 is high (48) because it is the
|
|
58
|
-
// chip's text colour on a pale background: at 35 the pair was 2.7:1, unreadable.
|
|
59
|
-
// `getVerdictBgColor` emits these class names, and a test asserts every emitted class
|
|
60
|
-
// is generated here.
|
|
61
|
-
.generate-colors(claim, green-700, @green-700, 32, 8);
|
|
65
|
+
// not_enough_info: the sources said nothing. Not red - that means they said
|
|
66
|
+
// something else - and not gray, which is reserved for the no-verdict-at-all
|
|
67
|
+
// state. It sits alongside the yellow citation highlights, which are fills
|
|
68
|
+
// (@yellow-100/200) while a claim is a stroke, so the two do not read alike.
|
|
69
|
+
.generate-colors(claim, yellow-500, @yellow-500, 45, 20, 12);
|
|
70
|
+
.generate-colors(claim, green-500, @green-500, 45, 20, 8);
|
|
@@ -1,38 +1,68 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { scoreToVerdict } from '../../utils';
|
|
4
|
+
import { getVerdictBgColor } from './colors';
|
|
5
|
+
|
|
6
|
+
const VERDICT_SCORES = [
|
|
7
|
+
['supported', 1.0],
|
|
8
|
+
['not_enough_info', 0.4],
|
|
9
|
+
['contradicted', 0.0],
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const hueOf = (cls) => cls.match(/claim-([a-z]+)-/)[1];
|
|
6
13
|
|
|
7
14
|
describe('getVerdictBgColor', () => {
|
|
8
15
|
it('gives each verdict one colour band', () => {
|
|
9
|
-
expect(getVerdictBgColor(1.0)).toBe('claim-green-
|
|
10
|
-
expect(getVerdictBgColor(0.8)).toBe('claim-green-
|
|
11
|
-
expect(getVerdictBgColor(0.4)).toBe('claim-
|
|
12
|
-
expect(getVerdictBgColor(0.1)).toBe('claim-
|
|
16
|
+
expect(getVerdictBgColor(1.0)).toBe('claim-green-500');
|
|
17
|
+
expect(getVerdictBgColor(0.8)).toBe('claim-green-500');
|
|
18
|
+
expect(getVerdictBgColor(0.4)).toBe('claim-yellow-500');
|
|
19
|
+
expect(getVerdictBgColor(0.1)).toBe('claim-yellow-500');
|
|
13
20
|
expect(getVerdictBgColor(0.0)).toBe('claim-red-500');
|
|
14
21
|
});
|
|
15
22
|
|
|
23
|
+
it('uses three different hue families, one per verdict', () => {
|
|
24
|
+
// A reader who cannot separate red from green must still tell the verdicts
|
|
25
|
+
// apart, so no two of them may share a hue family. The green ladder this
|
|
26
|
+
// replaced (green-700 / green-300 / red-500) failed that: two of the three
|
|
27
|
+
// verdicts were "green".
|
|
28
|
+
const hues = VERDICT_SCORES.map(([, score]) =>
|
|
29
|
+
hueOf(getVerdictBgColor(score)),
|
|
30
|
+
);
|
|
31
|
+
expect(new Set(hues)).toEqual(new Set(['green', 'yellow', 'red']));
|
|
32
|
+
});
|
|
33
|
+
|
|
16
34
|
it('keeps not_enough_info out of the red family', () => {
|
|
17
35
|
// red reads as "the sources contradict this"; they said nothing
|
|
18
|
-
expect(getVerdictBgColor(0.4)).not.
|
|
36
|
+
expect(hueOf(getVerdictBgColor(0.4))).not.toBe('red');
|
|
19
37
|
});
|
|
20
38
|
|
|
21
|
-
it('
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
expect(
|
|
25
|
-
|
|
26
|
-
|
|
39
|
+
it('marks claims that could not be checked yellow, whatever the score', () => {
|
|
40
|
+
// a claim checked against only part of its sources gets no verdict in the
|
|
41
|
+
// header ("Couldn't be checked"), so the colour must not claim one either
|
|
42
|
+
expect(getVerdictBgColor(1.0, true)).toBe('claim-yellow-500');
|
|
43
|
+
expect(getVerdictBgColor(0.0, true)).toBe('claim-yellow-500');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('colours verdicts exactly where scoreToVerdict names them', () => {
|
|
47
|
+
// the colour bands and the wording in the modal are computed separately from
|
|
48
|
+
// the same thresholds; if they drift, the chip says one thing and the header
|
|
49
|
+
// another
|
|
50
|
+
const hueForVerdict = {
|
|
51
|
+
'claim-green-500': 'supported',
|
|
52
|
+
'claim-yellow-500': 'not_enough_info',
|
|
53
|
+
'claim-red-500': 'contradicted',
|
|
54
|
+
};
|
|
55
|
+
for (const score of [0, 0.05, 0.09, 0.1, 0.4, 0.79, 0.8, 1]) {
|
|
56
|
+
expect(hueForVerdict[getVerdictBgColor(score)]).toBe(
|
|
57
|
+
scoreToVerdict(score),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
27
60
|
});
|
|
28
61
|
|
|
29
62
|
it('only emits classes that colors.less actually generates', () => {
|
|
30
63
|
// an unlisted .generate-colors() call means the class renders unstyled, which
|
|
31
64
|
// fails silently - no colour, no error
|
|
32
|
-
const less =
|
|
33
|
-
require('path').join(__dirname, 'colors.less'),
|
|
34
|
-
'utf-8',
|
|
35
|
-
);
|
|
65
|
+
const less = fs.readFileSync(path.join(__dirname, 'colors.less'), 'utf-8');
|
|
36
66
|
for (const cls of [
|
|
37
67
|
getVerdictBgColor(1.0),
|
|
38
68
|
getVerdictBgColor(0.4),
|
|
@@ -43,72 +73,27 @@ describe('getVerdictBgColor', () => {
|
|
|
43
73
|
);
|
|
44
74
|
}
|
|
45
75
|
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
describe('getSupportedTextColor', () => {
|
|
49
|
-
it('should return "text-red-500" for scores between 0 and 0.5', () => {
|
|
50
|
-
expect(getSupportedTextColor(0)).toBe('text-red-500');
|
|
51
|
-
expect(getSupportedTextColor(0.4)).toBe('text-red-500');
|
|
52
|
-
expect(getSupportedTextColor(0.499)).toBe('text-red-500');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('should return "text-green-500" for scores between 0.5 and 1', () => {
|
|
56
|
-
expect(getSupportedTextColor(0.5)).toBe('text-green-500');
|
|
57
|
-
expect(getSupportedTextColor(0.75)).toBe('text-green-500');
|
|
58
|
-
expect(getSupportedTextColor(1)).toBe('text-green-500');
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('should return "text-gray-500" for invalid scores', () => {
|
|
62
|
-
expect(getSupportedTextColor(-1)).toBe('text-gray-500');
|
|
63
|
-
expect(getSupportedTextColor(1.5)).toBe('text-gray-500');
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
76
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
expect(
|
|
88
|
-
|
|
89
|
-
// Test for scores in the range [0.5, 0.625)
|
|
90
|
-
expect(getSupportedBgColor(0.5)).toBe('bg-green-200');
|
|
91
|
-
expect(getSupportedBgColor(0.55)).toBe('bg-green-200');
|
|
92
|
-
expect(getSupportedBgColor(0.624)).toBe('bg-green-200');
|
|
93
|
-
|
|
94
|
-
// Test for scores in the range [0.625, 0.75)
|
|
95
|
-
expect(getSupportedBgColor(0.625)).toBe('bg-green-300');
|
|
96
|
-
expect(getSupportedBgColor(0.7)).toBe('bg-green-300');
|
|
97
|
-
expect(getSupportedBgColor(0.749)).toBe('bg-green-300');
|
|
98
|
-
|
|
99
|
-
// Test for scores in the range [0.75, 0.875)
|
|
100
|
-
expect(getSupportedBgColor(0.75)).toBe('bg-green-400');
|
|
101
|
-
expect(getSupportedBgColor(0.8)).toBe('bg-green-400');
|
|
102
|
-
expect(getSupportedBgColor(0.874)).toBe('bg-green-400');
|
|
103
|
-
|
|
104
|
-
// Test for scores in the range [0.875, 1]
|
|
105
|
-
expect(getSupportedBgColor(0.875)).toBe('bg-green-500');
|
|
106
|
-
expect(getSupportedBgColor(0.9)).toBe('bg-green-500');
|
|
107
|
-
expect(getSupportedBgColor(1)).toBe('bg-green-500');
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('should return "bg-gray-500" for invalid scores', () => {
|
|
111
|
-
expect(getSupportedBgColor(-1)).toBe('bg-gray-500');
|
|
112
|
-
expect(getSupportedBgColor(1.1)).toBe('bg-gray-500');
|
|
77
|
+
it('gives each verdict class its own underline style in style.less', () => {
|
|
78
|
+
// colour is not the only signal: each hue also has a line style (wavy /
|
|
79
|
+
// dotted / dashed). Renaming a class here without renaming the LESS selector
|
|
80
|
+
// would drop the shape distinction silently, so assert both lists agree.
|
|
81
|
+
const less = fs.readFileSync(
|
|
82
|
+
path.join(__dirname, '..', '..', 'style.less'),
|
|
83
|
+
'utf-8',
|
|
84
|
+
);
|
|
85
|
+
const styles = {};
|
|
86
|
+
for (const cls of [
|
|
87
|
+
getVerdictBgColor(1.0),
|
|
88
|
+
getVerdictBgColor(0.4),
|
|
89
|
+
getVerdictBgColor(0.0),
|
|
90
|
+
]) {
|
|
91
|
+
const rule = less.match(
|
|
92
|
+
new RegExp(`&\\.${cls} \\{\\s*text-decoration-style: ([a-z]+);`),
|
|
93
|
+
);
|
|
94
|
+
expect(rule).not.toBeNull();
|
|
95
|
+
styles[cls] = rule[1];
|
|
96
|
+
}
|
|
97
|
+
expect(new Set(Object.values(styles)).size).toBe(3);
|
|
113
98
|
});
|
|
114
99
|
});
|
package/src/ChatBlock/style.less
CHANGED
|
@@ -693,13 +693,35 @@ mark {
|
|
|
693
693
|
font-size: 14px;
|
|
694
694
|
}
|
|
695
695
|
|
|
696
|
+
// The underline on a checked claim carries the verdict, and hue alone cannot do
|
|
697
|
+
// that job: for red-green colour blindness the good and the bad end of the ramp
|
|
698
|
+
// are the same colour. So hue and line style change together -
|
|
699
|
+
// contradicted ~~~~ wavy ~~~~ red
|
|
700
|
+
// not_enough_info ···· dotted ···· yellow
|
|
701
|
+
// supported ---- dashed ---- green
|
|
702
|
+
// The stroke uses `--claim-stroke-color` (see `components/markdown/colors.less`),
|
|
703
|
+
// a darkened variant of the hue: these hues are fine as large fills but a 2px
|
|
704
|
+
// line of yellow-500 is 1.92:1 against white and green-500 is 2.28:1.
|
|
696
705
|
.claim {
|
|
697
706
|
position: relative;
|
|
698
707
|
display: inline;
|
|
699
708
|
cursor: pointer;
|
|
700
|
-
text-decoration:
|
|
701
|
-
text-decoration-
|
|
702
|
-
text-decoration-
|
|
709
|
+
text-decoration-color: var(--claim-stroke-color);
|
|
710
|
+
text-decoration-line: underline;
|
|
711
|
+
text-decoration-thickness: 2px;
|
|
712
|
+
text-underline-offset: 0.2em;
|
|
713
|
+
|
|
714
|
+
&.claim-red-500 {
|
|
715
|
+
text-decoration-style: wavy;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
&.claim-yellow-500 {
|
|
719
|
+
text-decoration-style: dotted;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
&.claim-green-500 {
|
|
723
|
+
text-decoration-style: dashed;
|
|
724
|
+
}
|
|
703
725
|
|
|
704
726
|
&:hover {
|
|
705
727
|
background-color: var(--claim-lighter-color);
|
|
@@ -753,7 +775,7 @@ mark {
|
|
|
753
775
|
padding: 1em 1.25em;
|
|
754
776
|
border: none;
|
|
755
777
|
border-radius: 0 @borderRadius @borderRadius 0;
|
|
756
|
-
border-left: 4px solid var(--claim-color);
|
|
778
|
+
border-left: 4px solid var(--claim-stroke-color);
|
|
757
779
|
margin: 0;
|
|
758
780
|
background-color: @gray-50;
|
|
759
781
|
color: @grey;
|
|
@@ -827,25 +849,29 @@ mark {
|
|
|
827
849
|
flex: 1;
|
|
828
850
|
border-radius: 3px;
|
|
829
851
|
background-color: @gray-200;
|
|
830
|
-
// An inset ring rather than a border: the
|
|
831
|
-
//
|
|
832
|
-
//
|
|
852
|
+
// An inset ring rather than a border: the filled step is a thin bar of a hue
|
|
853
|
+
// that is under 3:1 against white (yellow-500 is 1.92:1), so without an
|
|
854
|
+
// outline the middle position - the one that needs pointing out most - reads
|
|
855
|
+
// as empty. No layout shift, unlike a border.
|
|
833
856
|
box-shadow: inset 0 0 0 1px @gray-300;
|
|
834
857
|
}
|
|
835
858
|
|
|
859
|
+
// The three hues match the claim underlines: red contradicted, yellow
|
|
860
|
+
// not_enough_info, green supported. Colour is decoration here - which step is
|
|
861
|
+
// filled is the finding, and it reads with no colour vision at all.
|
|
836
862
|
.verdict-scale[data-verdict='contradicted'] .verdict-step:nth-child(1) {
|
|
837
863
|
background-color: @red-500;
|
|
838
864
|
}
|
|
839
865
|
|
|
840
|
-
//
|
|
841
|
-
// something else.
|
|
842
|
-
//
|
|
866
|
+
// Yellow, not red: sources saying nothing is not the same finding as sources
|
|
867
|
+
// saying something else. Grey is reserved for the `unknown` state below, where
|
|
868
|
+
// there is no verdict at all.
|
|
843
869
|
.verdict-scale[data-verdict='not_enough_info'] .verdict-step:nth-child(2) {
|
|
844
|
-
background-color: @
|
|
870
|
+
background-color: @yellow-500;
|
|
845
871
|
}
|
|
846
872
|
|
|
847
873
|
.verdict-scale[data-verdict='supported'] .verdict-step:nth-child(3) {
|
|
848
|
-
background-color: @green-
|
|
874
|
+
background-color: @green-500;
|
|
849
875
|
}
|
|
850
876
|
|
|
851
877
|
// Checked over search snippets only: no position can be claimed.
|