@docpensieve/components 0.1.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/LICENSE +21 -0
- package/README.md +34 -0
- package/package.json +52 -0
- package/src/card.js +94 -0
- package/src/classes.js +90 -0
- package/src/columns.js +124 -0
- package/src/index.js +23 -0
- package/src/logo-icon.js +125 -0
- package/src/registry.js +62 -0
- package/src/scroll-to-top.js +65 -0
- package/src/site.js +110 -0
- package/src/skill.js +200 -0
- package/src/styles.js +35 -0
- package/src/time-timer.js +251 -0
- package/src/tooltip.js +89 -0
- package/src/tree.js +88 -0
- package/styles/components.css +590 -0
- package/types/card.d.ts +72 -0
- package/types/classes.d.ts +54 -0
- package/types/columns.d.ts +56 -0
- package/types/index.d.ts +22 -0
- package/types/logo-icon.d.ts +39 -0
- package/types/registry.d.ts +29 -0
- package/types/scroll-to-top.d.ts +32 -0
- package/types/site.d.ts +68 -0
- package/types/skill.d.ts +46 -0
- package/types/styles.d.ts +16 -0
- package/types/time-timer.d.ts +61 -0
- package/types/tooltip.d.ts +34 -0
- package/types/tree.d.ts +44 -0
package/src/tree.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collapsible tree.
|
|
3
|
+
*
|
|
4
|
+
* No JavaScript: expanding is the native behaviour of `<details>`, which
|
|
5
|
+
* works from the keyboard and stays printable.
|
|
6
|
+
*
|
|
7
|
+
* The structure is a nested list, not a `role="tree"`. That role promises a
|
|
8
|
+
* screen reader arrow-key navigation that nothing here would implement:
|
|
9
|
+
* announcing it would lie about what the page can do.
|
|
10
|
+
*
|
|
11
|
+
* @module @docpensieve/components/tree
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createContext, createElement as h, useContext } from 'react';
|
|
15
|
+
|
|
16
|
+
import { DocPensieveError } from '@docpensieve/shared';
|
|
17
|
+
|
|
18
|
+
import { classNames, cls } from './classes.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Marks the inside of a tree.
|
|
22
|
+
*
|
|
23
|
+
* A lone entry would produce an `<li>` outside any list: invalid HTML that no
|
|
24
|
+
* browser reports and nobody notices.
|
|
25
|
+
*/
|
|
26
|
+
const InTree = createContext(false);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Root of a tree.
|
|
30
|
+
*
|
|
31
|
+
* @param {{ className?: string, style?: object, children?: any }} props
|
|
32
|
+
*/
|
|
33
|
+
export function Tree({ className, style, children }) {
|
|
34
|
+
return h(
|
|
35
|
+
InTree.Provider,
|
|
36
|
+
{ value: true },
|
|
37
|
+
h('ul', { className: classNames(cls('tree'), className), style }, children),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Entry of a tree.
|
|
43
|
+
*
|
|
44
|
+
* With children, it is a collapsible branch; without, a leaf. The difference
|
|
45
|
+
* is read from the writing, with no prop to set.
|
|
46
|
+
*
|
|
47
|
+
* @param {{
|
|
48
|
+
* className?: string, style?: object, children?: any,
|
|
49
|
+
* label?: any, open?: boolean,
|
|
50
|
+
* }} props `open` expands the branch as soon as the page opens.
|
|
51
|
+
* @throws {DocPensieveError} Outside a `Tree`, or without a label.
|
|
52
|
+
*/
|
|
53
|
+
export function TreeItem({ className, style, children, label, open = false }) {
|
|
54
|
+
const inTree = useContext(InTree);
|
|
55
|
+
|
|
56
|
+
if (!inTree) {
|
|
57
|
+
throw new DocPensieveError('A <TreeItem> was written outside a <Tree>.', {
|
|
58
|
+
hint: 'Wrap the entries: <Tree><TreeItem label="…" /></Tree>.',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (label === undefined || label === null || label === '') {
|
|
63
|
+
throw new DocPensieveError('A <TreeItem> without a label.', {
|
|
64
|
+
hint: 'Give it a label: <TreeItem label="src" />.',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const leaf = children === undefined || children === null || children === false;
|
|
69
|
+
|
|
70
|
+
if (leaf) {
|
|
71
|
+
return h(
|
|
72
|
+
'li',
|
|
73
|
+
{ className: classNames(cls('treeItem', 'leaf'), className), style },
|
|
74
|
+
h('span', { className: cls('treeLabel') }, label),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return h(
|
|
79
|
+
'li',
|
|
80
|
+
{ className: classNames(cls('treeItem', 'branch'), className), style },
|
|
81
|
+
h(
|
|
82
|
+
'details',
|
|
83
|
+
{ className: cls('treeDetails'), open },
|
|
84
|
+
h('summary', { className: cls('treeLabel') }, label),
|
|
85
|
+
h('ul', { className: cls('tree') }, children),
|
|
86
|
+
),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Default structure of the shipped components.
|
|
3
|
+
*
|
|
4
|
+
* Everything is in `@layer components`, below the utilities in layer order. A
|
|
5
|
+
* `className="border-0"` set at use therefore always wins, whatever the place
|
|
6
|
+
* of this stylesheet in the final file. Without this layer, these rules beat
|
|
7
|
+
* the utilities and the author's `className` was silently ignored.
|
|
8
|
+
*
|
|
9
|
+
* Only structure lives here — wrappers, separators, spacing. Typography and
|
|
10
|
+
* colours are set at use, through `className`.
|
|
11
|
+
*
|
|
12
|
+
* Values come from the `--dp-*` tokens: the components follow the active
|
|
13
|
+
* theme's palette without knowing anything about it.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
@layer components {
|
|
17
|
+
/* --- Card -------------------------------------------------------------- */
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
* No `overflow: hidden` here, though it was the shortest path for the image
|
|
21
|
+
* to follow the corners: it would clip **everything** that sticks out,
|
|
22
|
+
* starting with a tooltip opened from the card body. The image corners are
|
|
23
|
+
* therefore rounded one by one, below.
|
|
24
|
+
*/
|
|
25
|
+
.dp-card {
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
/*
|
|
29
|
+
* Two cards following each other in the flow of the text would touch. In
|
|
30
|
+
* a column, the grid gap already does that job: the margin is taken back
|
|
31
|
+
* below, otherwise both would add up.
|
|
32
|
+
*/
|
|
33
|
+
margin-block-end: 1.3rem;
|
|
34
|
+
background: var(--dp-bg);
|
|
35
|
+
border: 1px solid var(--dp-border);
|
|
36
|
+
border-radius: var(--dp-radius);
|
|
37
|
+
color: inherit;
|
|
38
|
+
text-decoration: none;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.dp-card--elevated {
|
|
42
|
+
box-shadow: 0 4px 14px var(--dp-shadow);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.dp-column > .dp-card,
|
|
46
|
+
.dp-card-body > .dp-card:last-child {
|
|
47
|
+
margin-block-end: 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* A clickable card must signal itself on hover. */
|
|
51
|
+
a.dp-card:hover {
|
|
52
|
+
border-color: var(--dp-accent);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.dp-card-header {
|
|
56
|
+
padding: 0.85rem 1.1rem;
|
|
57
|
+
border-bottom: 1px solid var(--dp-border);
|
|
58
|
+
font-weight: 600;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.dp-card-body {
|
|
62
|
+
flex: 1;
|
|
63
|
+
padding: 1.1rem;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.dp-card-footer {
|
|
67
|
+
padding: 0.85rem 1.1rem;
|
|
68
|
+
border-top: 1px solid var(--dp-border);
|
|
69
|
+
font-size: 0.9rem;
|
|
70
|
+
color: var(--dp-text-soft);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* The content of a card does not have to inherit the prose margins. */
|
|
74
|
+
.dp-card-header > *:first-child,
|
|
75
|
+
.dp-card-body > *:first-child,
|
|
76
|
+
.dp-card-footer > *:first-child {
|
|
77
|
+
margin-top: 0;
|
|
78
|
+
}
|
|
79
|
+
.dp-card-header > *:last-child,
|
|
80
|
+
.dp-card-body > *:last-child,
|
|
81
|
+
.dp-card-footer > *:last-child {
|
|
82
|
+
margin-bottom: 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.dp-card-image {
|
|
86
|
+
display: block;
|
|
87
|
+
width: 100%;
|
|
88
|
+
height: auto;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* The image only rounds the corners it actually occupies. */
|
|
92
|
+
.dp-card-image:first-child {
|
|
93
|
+
border-start-start-radius: var(--dp-radius);
|
|
94
|
+
border-start-end-radius: var(--dp-radius);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.dp-card-image:last-child {
|
|
98
|
+
border-end-start-radius: var(--dp-radius);
|
|
99
|
+
border-end-end-radius: var(--dp-radius);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* --- Columns ----------------------------------------------------------- */
|
|
103
|
+
|
|
104
|
+
/*
|
|
105
|
+
* Without a declared width: as many columns as children, all equal.
|
|
106
|
+
* `minmax(0, 1fr)` rather than `1fr` — otherwise unbreakable content, a
|
|
107
|
+
* code block for instance, widens its column and unbalances the row.
|
|
108
|
+
*/
|
|
109
|
+
.dp-columns {
|
|
110
|
+
display: grid;
|
|
111
|
+
grid-auto-flow: column;
|
|
112
|
+
grid-auto-columns: minmax(0, 1fr);
|
|
113
|
+
gap: 1rem;
|
|
114
|
+
align-items: stretch;
|
|
115
|
+
width: 100%;
|
|
116
|
+
margin: 0 0 1.3rem;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/*
|
|
120
|
+
* With declared widths: twelve tracks, and each column takes what it asks
|
|
121
|
+
* for. The grid subtracts the gaps by itself, so a width has no calculation
|
|
122
|
+
* to make and changing `gap` breaks nothing.
|
|
123
|
+
*/
|
|
124
|
+
.dp-columns--twelfths {
|
|
125
|
+
grid-auto-flow: row;
|
|
126
|
+
grid-template-columns: repeat(12, minmax(0, 1fr));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.dp-column {
|
|
130
|
+
min-width: 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.dp-column--span-1 {
|
|
134
|
+
grid-column: span 1;
|
|
135
|
+
}
|
|
136
|
+
.dp-column--span-2 {
|
|
137
|
+
grid-column: span 2;
|
|
138
|
+
}
|
|
139
|
+
.dp-column--span-3 {
|
|
140
|
+
grid-column: span 3;
|
|
141
|
+
}
|
|
142
|
+
.dp-column--span-4 {
|
|
143
|
+
grid-column: span 4;
|
|
144
|
+
}
|
|
145
|
+
.dp-column--span-5 {
|
|
146
|
+
grid-column: span 5;
|
|
147
|
+
}
|
|
148
|
+
.dp-column--span-6 {
|
|
149
|
+
grid-column: span 6;
|
|
150
|
+
}
|
|
151
|
+
.dp-column--span-7 {
|
|
152
|
+
grid-column: span 7;
|
|
153
|
+
}
|
|
154
|
+
.dp-column--span-8 {
|
|
155
|
+
grid-column: span 8;
|
|
156
|
+
}
|
|
157
|
+
.dp-column--span-9 {
|
|
158
|
+
grid-column: span 9;
|
|
159
|
+
}
|
|
160
|
+
.dp-column--span-10 {
|
|
161
|
+
grid-column: span 10;
|
|
162
|
+
}
|
|
163
|
+
.dp-column--span-11 {
|
|
164
|
+
grid-column: span 11;
|
|
165
|
+
}
|
|
166
|
+
.dp-column--span-12 {
|
|
167
|
+
grid-column: span 12;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* Below tablet width, a grid of columns becomes unreadable. */
|
|
171
|
+
@media (max-width: 48rem) {
|
|
172
|
+
.dp-columns,
|
|
173
|
+
.dp-columns--twelfths {
|
|
174
|
+
grid-auto-flow: row;
|
|
175
|
+
grid-template-columns: minmax(0, 1fr);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.dp-column,
|
|
179
|
+
.dp-column[class*='dp-column--span-'] {
|
|
180
|
+
grid-column: auto;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* --- Tooltip ----------------------------------------------------------- */
|
|
185
|
+
|
|
186
|
+
/*
|
|
187
|
+
* The bubble is positioned absolutely: inside a container that clips its
|
|
188
|
+
* overflow, it will be cut. That is the limit of a tooltip without
|
|
189
|
+
* JavaScript, and it is accepted.
|
|
190
|
+
*/
|
|
191
|
+
.dp-tooltip {
|
|
192
|
+
position: relative;
|
|
193
|
+
display: inline-block;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/*
|
|
197
|
+
* Underlined in the text colour. The border tint fell to 1.2:1 against the
|
|
198
|
+
* background: nobody saw there was something to hover.
|
|
199
|
+
*/
|
|
200
|
+
.dp-tooltip-trigger {
|
|
201
|
+
border-bottom: 1px dashed currentColor;
|
|
202
|
+
cursor: help;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/*
|
|
206
|
+
* An invisible margin around the bubble. Without it, the pointer left the
|
|
207
|
+
* term, crossed the gap and lost the hover before reaching the bubble, which
|
|
208
|
+
* closed under it. Hidden along with the bubble, it catches nothing when the
|
|
209
|
+
* bubble is closed.
|
|
210
|
+
*/
|
|
211
|
+
.dp-tooltip-bubble::after {
|
|
212
|
+
content: '';
|
|
213
|
+
position: absolute;
|
|
214
|
+
inset: -0.6rem;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.dp-tooltip-bubble {
|
|
218
|
+
position: absolute;
|
|
219
|
+
z-index: 20;
|
|
220
|
+
width: max-content;
|
|
221
|
+
max-width: 16rem;
|
|
222
|
+
padding: 0.4rem 0.6rem;
|
|
223
|
+
border-radius: var(--dp-radius);
|
|
224
|
+
background: var(--dp-text);
|
|
225
|
+
color: var(--dp-bg);
|
|
226
|
+
font-size: 0.85rem;
|
|
227
|
+
line-height: 1.35;
|
|
228
|
+
text-align: left;
|
|
229
|
+
/*
|
|
230
|
+
* `visibility` on top of the opacity: a merely transparent bubble would
|
|
231
|
+
* stay hoverable and intercept the pointer.
|
|
232
|
+
*/
|
|
233
|
+
visibility: hidden;
|
|
234
|
+
opacity: 0;
|
|
235
|
+
transition:
|
|
236
|
+
opacity 0.12s ease,
|
|
237
|
+
visibility 0.12s;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.dp-tooltip:hover .dp-tooltip-bubble,
|
|
241
|
+
.dp-tooltip:focus-within .dp-tooltip-bubble {
|
|
242
|
+
visibility: visible;
|
|
243
|
+
opacity: 1;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.dp-tooltip--top .dp-tooltip-bubble {
|
|
247
|
+
bottom: calc(100% + 0.45rem);
|
|
248
|
+
left: 50%;
|
|
249
|
+
translate: -50% 0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.dp-tooltip--bottom .dp-tooltip-bubble {
|
|
253
|
+
top: calc(100% + 0.45rem);
|
|
254
|
+
left: 50%;
|
|
255
|
+
translate: -50% 0;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.dp-tooltip--left .dp-tooltip-bubble {
|
|
259
|
+
top: 50%;
|
|
260
|
+
right: calc(100% + 0.45rem);
|
|
261
|
+
translate: 0 -50%;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.dp-tooltip--right .dp-tooltip-bubble {
|
|
265
|
+
top: 50%;
|
|
266
|
+
left: calc(100% + 0.45rem);
|
|
267
|
+
translate: 0 -50%;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* --- Tree -------------------------------------------------------------- */
|
|
271
|
+
|
|
272
|
+
.dp-tree {
|
|
273
|
+
margin: 0 0 1.3rem;
|
|
274
|
+
padding-left: 0;
|
|
275
|
+
font-family: var(--dp-font-mono);
|
|
276
|
+
font-size: 0.9rem;
|
|
277
|
+
list-style: none;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* Nested levels are indented and carry a reminder rule. */
|
|
281
|
+
.dp-tree .dp-tree {
|
|
282
|
+
margin: 0;
|
|
283
|
+
padding-left: 0.85rem;
|
|
284
|
+
border-left: 1px solid var(--dp-rule);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.dp-tree-item {
|
|
288
|
+
margin: 0.1rem 0;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.dp-tree-label {
|
|
292
|
+
display: inline-flex;
|
|
293
|
+
align-items: center;
|
|
294
|
+
gap: 0.4rem;
|
|
295
|
+
padding: 0.1rem 0.35rem;
|
|
296
|
+
border-radius: var(--dp-radius);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
summary.dp-tree-label {
|
|
300
|
+
cursor: pointer;
|
|
301
|
+
/* The native triangle changes shape from one browser to another. */
|
|
302
|
+
list-style: none;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
summary.dp-tree-label::-webkit-details-marker {
|
|
306
|
+
display: none;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
summary.dp-tree-label:hover {
|
|
310
|
+
background: var(--dp-bg-soft);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
summary.dp-tree-label::before {
|
|
314
|
+
content: '';
|
|
315
|
+
width: 0.38rem;
|
|
316
|
+
height: 0.38rem;
|
|
317
|
+
border-right: 1.5px solid currentColor;
|
|
318
|
+
border-bottom: 1.5px solid currentColor;
|
|
319
|
+
rotate: -45deg;
|
|
320
|
+
transition: rotate 0.15s ease;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.dp-tree-details[open] > summary.dp-tree-label::before {
|
|
324
|
+
rotate: 45deg;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/* A leaf has no chevron: its label lines up with the branches. */
|
|
328
|
+
.dp-tree-item--leaf > .dp-tree-label {
|
|
329
|
+
padding-left: 1.13rem;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/* --- Back to top ------------------------------------------------------- */
|
|
333
|
+
|
|
334
|
+
.dp-scroll-top {
|
|
335
|
+
position: fixed;
|
|
336
|
+
right: 1.25rem;
|
|
337
|
+
bottom: 1.25rem;
|
|
338
|
+
z-index: 30;
|
|
339
|
+
display: inline-flex;
|
|
340
|
+
align-items: center;
|
|
341
|
+
justify-content: center;
|
|
342
|
+
width: 2.5rem;
|
|
343
|
+
height: 2.5rem;
|
|
344
|
+
border: 1px solid var(--dp-border);
|
|
345
|
+
border-radius: 999px;
|
|
346
|
+
background: var(--dp-bg);
|
|
347
|
+
box-shadow: 0 4px 14px var(--dp-shadow);
|
|
348
|
+
color: var(--dp-text);
|
|
349
|
+
text-decoration: none;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.dp-scroll-top:hover {
|
|
353
|
+
border-color: var(--dp-accent);
|
|
354
|
+
color: var(--dp-accent);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.dp-scroll-top-icon {
|
|
358
|
+
width: 1.1rem;
|
|
359
|
+
height: 1.1rem;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
@keyframes dp-scroll-top-appear {
|
|
363
|
+
from {
|
|
364
|
+
visibility: hidden;
|
|
365
|
+
opacity: 0;
|
|
366
|
+
scale: 0.8;
|
|
367
|
+
}
|
|
368
|
+
to {
|
|
369
|
+
visibility: visible;
|
|
370
|
+
opacity: 1;
|
|
371
|
+
scale: 1;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/*
|
|
376
|
+
* The button only appears once the page has scrolled. Where the scroll
|
|
377
|
+
* timeline is missing, it simply stays visible: always there is better than
|
|
378
|
+
* never there.
|
|
379
|
+
*/
|
|
380
|
+
@supports (animation-timeline: scroll()) {
|
|
381
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
382
|
+
.dp-scroll-top {
|
|
383
|
+
animation: dp-scroll-top-appear linear both;
|
|
384
|
+
animation-timeline: scroll(root block);
|
|
385
|
+
animation-range: 0 6rem;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/*
|
|
391
|
+
* The component exists to scroll: smooth scrolling is therefore its
|
|
392
|
+
* business, but only on the pages that use it.
|
|
393
|
+
*/
|
|
394
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
395
|
+
html:has(.dp-scroll-top) {
|
|
396
|
+
scroll-behavior: smooth;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/* --- Level gauge ------------------------------------------------------- */
|
|
401
|
+
|
|
402
|
+
.dp-skill {
|
|
403
|
+
margin: 0 0 1rem;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.dp-skill-head {
|
|
407
|
+
display: flex;
|
|
408
|
+
gap: 0.5rem;
|
|
409
|
+
align-items: baseline;
|
|
410
|
+
justify-content: space-between;
|
|
411
|
+
margin-bottom: 0.3rem;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.dp-skill-name {
|
|
415
|
+
display: inline-flex;
|
|
416
|
+
align-items: center;
|
|
417
|
+
gap: 0.4rem;
|
|
418
|
+
font-weight: 600;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/*
|
|
422
|
+
* The icon takes the gauge's tint: both designate the same thing, and seeing
|
|
423
|
+
* them match helps read a column of gauges at a glance.
|
|
424
|
+
*/
|
|
425
|
+
.dp-skill-icon {
|
|
426
|
+
display: inline-flex;
|
|
427
|
+
color: var(--dp-skill-color, var(--dp-accent));
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.dp-skill-value {
|
|
431
|
+
color: var(--dp-text-soft);
|
|
432
|
+
font-size: 0.85rem;
|
|
433
|
+
/* Fixed-width digits: a column of values stays aligned. */
|
|
434
|
+
font-variant-numeric: tabular-nums;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.dp-skill-track {
|
|
438
|
+
overflow: hidden;
|
|
439
|
+
height: 0.5rem;
|
|
440
|
+
border-radius: 999px;
|
|
441
|
+
background: var(--dp-bg-soft);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.dp-skill-fill {
|
|
445
|
+
width: var(--dp-skill-level, 0%);
|
|
446
|
+
height: 100%;
|
|
447
|
+
border-radius: inherit;
|
|
448
|
+
background: var(--dp-skill-color, var(--dp-accent));
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.dp-skill-note {
|
|
452
|
+
margin-top: 0.35rem;
|
|
453
|
+
color: var(--dp-text-soft);
|
|
454
|
+
font-size: 0.85rem;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
@keyframes dp-skill-grow {
|
|
458
|
+
from {
|
|
459
|
+
width: 0;
|
|
460
|
+
}
|
|
461
|
+
to {
|
|
462
|
+
width: var(--dp-skill-level, 0%);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/* The fill plays as it enters the viewport, where that is possible.
|
|
467
|
+
Elsewhere the bar is full from the start: the value is still read. */
|
|
468
|
+
@supports (animation-timeline: view()) {
|
|
469
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
470
|
+
.dp-skill-fill {
|
|
471
|
+
animation: dp-skill-grow linear both;
|
|
472
|
+
animation-timeline: view();
|
|
473
|
+
animation-range: entry 10% cover 60%;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/* --- Icon -------------------------------------------------------------- */
|
|
479
|
+
|
|
480
|
+
.dp-logo-icon {
|
|
481
|
+
display: inline-flex;
|
|
482
|
+
align-items: center;
|
|
483
|
+
justify-content: center;
|
|
484
|
+
inline-size: var(--dp-logo-icon-size, 1.5rem);
|
|
485
|
+
block-size: var(--dp-logo-icon-size, 1.5rem);
|
|
486
|
+
color: inherit;
|
|
487
|
+
vertical-align: -0.15em;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/*
|
|
491
|
+
* The SVG fills its wrapper. Its colour stays the file's business: a `fill`
|
|
492
|
+
* declaration here would override the `fill="none"` of an outline icon —
|
|
493
|
+
* CSS always wins over a presentation attribute — and make it solid. A file
|
|
494
|
+
* that relies on `currentColor` already follows the text colour through the
|
|
495
|
+
* inheritance set on the wrapper.
|
|
496
|
+
*/
|
|
497
|
+
.dp-logo-icon > svg {
|
|
498
|
+
inline-size: 100%;
|
|
499
|
+
block-size: 100%;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/* --- Reduced motion ---------------------------------------------------- */
|
|
503
|
+
|
|
504
|
+
@media (prefers-reduced-motion: reduce) {
|
|
505
|
+
.dp-tooltip-bubble,
|
|
506
|
+
summary.dp-tree-label::before {
|
|
507
|
+
transition: none;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/* --- Circle gauge ------------------------------------------------------ */
|
|
512
|
+
|
|
513
|
+
.dp-skill--circle {
|
|
514
|
+
display: inline-flex;
|
|
515
|
+
flex-direction: column;
|
|
516
|
+
align-items: center;
|
|
517
|
+
gap: 0.4rem;
|
|
518
|
+
margin-inline-end: 1.25rem;
|
|
519
|
+
text-align: center;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.dp-skill-dial {
|
|
523
|
+
position: relative;
|
|
524
|
+
inline-size: var(--dp-skill-size, 6rem);
|
|
525
|
+
block-size: var(--dp-skill-size, 6rem);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.dp-skill-dial svg {
|
|
529
|
+
inline-size: 100%;
|
|
530
|
+
block-size: 100%;
|
|
531
|
+
/* The stroke starts at three o'clock: bring it back to the top. */
|
|
532
|
+
rotate: -90deg;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.dp-skill-dial-track,
|
|
536
|
+
.dp-skill-dial-fill {
|
|
537
|
+
fill: none;
|
|
538
|
+
stroke-width: 3.2;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.dp-skill-dial-track {
|
|
542
|
+
stroke: var(--dp-bg-soft);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/*
|
|
546
|
+
* The radius gives the circle a circumference of a hundred: the level is
|
|
547
|
+
* therefore directly the drawn share, with no multiplication or rounding.
|
|
548
|
+
*/
|
|
549
|
+
.dp-skill-dial-fill {
|
|
550
|
+
stroke: var(--dp-skill-color, var(--dp-accent));
|
|
551
|
+
stroke-dasharray: var(--dp-skill-level, 0) 100;
|
|
552
|
+
stroke-linecap: round;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
.dp-skill-dial-value {
|
|
556
|
+
position: absolute;
|
|
557
|
+
inset: 0;
|
|
558
|
+
display: flex;
|
|
559
|
+
align-items: center;
|
|
560
|
+
justify-content: center;
|
|
561
|
+
font-size: 1.05rem;
|
|
562
|
+
font-weight: 600;
|
|
563
|
+
/* Fixed-width digits: the value does not dance from one gauge to the next. */
|
|
564
|
+
font-variant-numeric: tabular-nums;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.dp-skill--circle .dp-skill-name {
|
|
568
|
+
justify-content: center;
|
|
569
|
+
font-size: 0.9rem;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
@keyframes dp-skill-draw {
|
|
573
|
+
from {
|
|
574
|
+
stroke-dasharray: 0 100;
|
|
575
|
+
}
|
|
576
|
+
to {
|
|
577
|
+
stroke-dasharray: var(--dp-skill-level, 0) 100;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
@supports (animation-timeline: view()) {
|
|
582
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
583
|
+
.dp-skill-dial-fill {
|
|
584
|
+
animation: dp-skill-draw linear both;
|
|
585
|
+
animation-timeline: view();
|
|
586
|
+
animation-range: entry 10% cover 60%;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|