@birdapi/velinstyle 0.6.1 → 0.8.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/README.de.md +337 -316
- package/README.md +33 -12
- package/cli/blueprint.js +8 -0
- package/cli/blueprints/bottom-nav-mobile.html +17 -0
- package/cli/blueprints/cookie-consent.html +9 -0
- package/cli/blueprints/empty-state.html +5 -0
- package/cli/blueprints/filter-bar.html +15 -0
- package/cli/blueprints/notification-center.html +13 -0
- package/cli/blueprints/onboarding.html +23 -0
- package/cli/blueprints/pricing-table.html +20 -0
- package/cli/blueprints/settings-panel.html +20 -0
- package/cli/index.js +119 -3
- package/cli/layout-audit.js +325 -0
- package/cli/scaffold-recipes.json +70 -0
- package/cli/scaffold.js +155 -0
- package/cli/scanner.js +114 -0
- package/components/focus-manager.js +106 -80
- package/components/index.js +20 -1
- package/components/sanitize.js +29 -3
- package/components/shadow-a11y-styles.js +18 -0
- package/components/velin-accordion.js +112 -98
- package/components/velin-announcer.js +35 -0
- package/components/velin-bottom-nav.js +89 -0
- package/components/velin-carousel.js +40 -5
- package/components/velin-collapse.js +95 -65
- package/components/velin-combobox.js +149 -0
- package/components/velin-command.js +127 -0
- package/components/velin-counter.js +152 -0
- package/components/velin-drawer.js +6 -3
- package/components/velin-dropdown.js +33 -2
- package/components/velin-flip.js +220 -0
- package/components/velin-icon.js +43 -9
- package/components/velin-live-dot.js +85 -0
- package/components/velin-menubar.js +83 -0
- package/components/velin-modal.js +3 -1
- package/components/velin-popover.js +61 -21
- package/components/velin-rating.js +91 -0
- package/components/velin-reveal.js +80 -0
- package/components/velin-segmented-control.js +108 -0
- package/components/velin-sheet.js +107 -0
- package/components/velin-sparkline.js +207 -0
- package/components/velin-theme-toggle.js +277 -60
- package/components/velin-tooltip-wc.js +26 -2
- package/dist/velinstyle-components.iife.js +1849 -120
- package/dist/velinstyle-components.js +1871 -120
- package/dist/velinstyle-components.min.js +434 -91
- package/dist/velinstyle.css +640 -44
- package/dist/velinstyle.min.css +1 -1
- package/package.json +7 -3
- package/src/a11y/focus-not-obscured.css +21 -0
- package/src/a11y/forced-colors.css +86 -38
- package/src/a11y/high-contrast-aaa.css +37 -0
- package/src/a11y/preferences.css +106 -85
- package/src/a11y/security.css +119 -104
- package/src/a11y/target-size.css +32 -0
- package/src/base/reset.css +12 -1
- package/src/base/root.css +4 -4
- package/src/components/nav.css +152 -151
- package/src/tokens/motion.css +7 -0
- package/src/utilities/animation.css +97 -1
- package/src/utilities/chart-animation.css +101 -0
- package/src/utilities/filter-effects.css +103 -0
- package/src/utilities/safe-area.css +39 -0
- package/src/velinstyle.css +9 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@birdapi/velinstyle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"registry": "https://registry.npmjs.org"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"type": "git",
|
|
18
18
|
"url": "https://github.com/SkyliteDesign/velinstyle.git"
|
|
19
19
|
},
|
|
20
|
-
"homepage": "https://velinstyle
|
|
20
|
+
"homepage": "https://skylitedesign.github.io/velinstyle/",
|
|
21
21
|
"bugs": {
|
|
22
22
|
"url": "https://github.com/SkyliteDesign/velinstyle/issues"
|
|
23
23
|
},
|
|
@@ -41,7 +41,11 @@
|
|
|
41
41
|
"watch": "lightningcss --bundle src/velinstyle.css -o dist/velinstyle.css --watch",
|
|
42
42
|
"lint": "stylelint \"src/**/*.css\"",
|
|
43
43
|
"test": "vitest run",
|
|
44
|
-
"test:a11y": "node tests/a11y/run.js"
|
|
44
|
+
"test:a11y": "node tests/a11y/run.js",
|
|
45
|
+
"test:contrast": "node scripts/check-contrast.js",
|
|
46
|
+
"test:security": "vitest run tests/sanitize.test.js tests/cli-security-scanner.test.js",
|
|
47
|
+
"docs:prepare": "npm run build && node scripts/prepare-docs-pages.js",
|
|
48
|
+
"docs:sync-site": "npm run build && python ../velinstyle-site/tools/sync-0.7.0.py"
|
|
45
49
|
},
|
|
46
50
|
"keywords": [
|
|
47
51
|
"css",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* WCAG 2.4.11 Focus Not Obscured — scroll padding for fixed headers/nav */
|
|
2
|
+
@layer a11y {
|
|
3
|
+
:root {
|
|
4
|
+
--velin-nav-height: 4rem;
|
|
5
|
+
scroll-padding-block-start: var(--velin-nav-height);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.velin-scroll-pt-nav {
|
|
9
|
+
scroll-padding-block-start: var(--velin-nav-height);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.velin-scroll-pt-none {
|
|
13
|
+
scroll-padding-block-start: 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* Highlight skip target briefly when focused via skip link */
|
|
17
|
+
main:target,
|
|
18
|
+
[id="main"]:target {
|
|
19
|
+
scroll-margin-block-start: var(--velin-nav-height);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -1,38 +1,86 @@
|
|
|
1
|
-
@layer a11y {
|
|
2
|
-
@media (forced-colors: active) {
|
|
3
|
-
.velin-btn,
|
|
4
|
-
.velin-badge,
|
|
5
|
-
.velin-alert {
|
|
6
|
-
border: 1px solid ButtonText;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
.velin-btn--primary,
|
|
10
|
-
.velin-btn--secondary {
|
|
11
|
-
forced-color-adjust: none;
|
|
12
|
-
background-color: ButtonFace;
|
|
13
|
-
color: ButtonText;
|
|
14
|
-
border: 2px solid ButtonText;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.velin-btn--primary:hover,
|
|
18
|
-
.velin-btn--secondary:hover {
|
|
19
|
-
background-color: Highlight;
|
|
20
|
-
color: HighlightText;
|
|
21
|
-
border-color: Highlight;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.velin-input,
|
|
25
|
-
.velin-select,
|
|
26
|
-
.velin-textarea {
|
|
27
|
-
border: 1px solid ButtonText;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.velin-card {
|
|
31
|
-
border: 1px solid CanvasText;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.velin-nav__link[aria-current="page"] {
|
|
35
|
-
border-block-end: 2px solid Highlight;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
1
|
+
@layer a11y {
|
|
2
|
+
@media (forced-colors: active) {
|
|
3
|
+
.velin-btn,
|
|
4
|
+
.velin-badge,
|
|
5
|
+
.velin-alert {
|
|
6
|
+
border: 1px solid ButtonText;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.velin-btn--primary,
|
|
10
|
+
.velin-btn--secondary {
|
|
11
|
+
forced-color-adjust: none;
|
|
12
|
+
background-color: ButtonFace;
|
|
13
|
+
color: ButtonText;
|
|
14
|
+
border: 2px solid ButtonText;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.velin-btn--primary:hover,
|
|
18
|
+
.velin-btn--secondary:hover {
|
|
19
|
+
background-color: Highlight;
|
|
20
|
+
color: HighlightText;
|
|
21
|
+
border-color: Highlight;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.velin-input,
|
|
25
|
+
.velin-select,
|
|
26
|
+
.velin-textarea {
|
|
27
|
+
border: 1px solid ButtonText;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.velin-card {
|
|
31
|
+
border: 1px solid CanvasText;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.velin-nav__link[aria-current="page"] {
|
|
35
|
+
border-block-end: 2px solid Highlight;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.velin-switch__track {
|
|
39
|
+
border: 1px solid ButtonText;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.velin-switch__input:checked + .velin-switch__track {
|
|
43
|
+
background-color: Highlight;
|
|
44
|
+
border-color: Highlight;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.velin-pagination__link {
|
|
48
|
+
border: 1px solid ButtonText;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.velin-pagination__link--active,
|
|
52
|
+
.velin-pagination__link[aria-current="page"] {
|
|
53
|
+
background-color: Highlight;
|
|
54
|
+
color: HighlightText;
|
|
55
|
+
border-color: Highlight;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.velin-chip {
|
|
59
|
+
border: 1px solid ButtonText;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.velin-stepper .step__marker {
|
|
63
|
+
border: 2px solid ButtonText;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.velin-stepper .step--active .step__marker,
|
|
67
|
+
.velin-stepper .step[aria-current="step"] .step__marker {
|
|
68
|
+
background-color: Highlight;
|
|
69
|
+
color: HighlightText;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.velin-modal__backdrop,
|
|
73
|
+
.velin-drawer__backdrop {
|
|
74
|
+
background-color: Canvas;
|
|
75
|
+
opacity: 1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.velin-progress__bar {
|
|
79
|
+
border: 1px solid ButtonText;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.velin-progress__fill {
|
|
83
|
+
background-color: Highlight;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/* WCAG 1.4.6 Contrast (Enhanced) — optional AAA via data-velin-contrast="aaa" */
|
|
2
|
+
@layer a11y {
|
|
3
|
+
:root[data-velin-contrast="aaa"] {
|
|
4
|
+
--velin-color-text: oklch(8% 0.01 250);
|
|
5
|
+
--velin-color-text-muted: oklch(28% 0.01 250);
|
|
6
|
+
--velin-color-text-subtle: oklch(38% 0.01 250);
|
|
7
|
+
--velin-color-border: oklch(35% 0.01 250);
|
|
8
|
+
--velin-color-border-strong: oklch(20% 0.01 250);
|
|
9
|
+
--velin-color-primary: oklch(38% 0.18 250);
|
|
10
|
+
--velin-color-primary-hover: oklch(32% 0.18 250);
|
|
11
|
+
--velin-color-on-primary: oklch(100% 0 0);
|
|
12
|
+
--velin-color-focus: oklch(35% 0.22 250);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
:root[data-velin-contrast="aaa"][data-velin-theme="dark"],
|
|
16
|
+
:root[data-velin-contrast="aaa"]:not([data-velin-theme="light"]) {
|
|
17
|
+
@media (prefers-color-scheme: dark) {
|
|
18
|
+
--velin-color-text: oklch(98% 0.005 250);
|
|
19
|
+
--velin-color-text-muted: oklch(82% 0.005 250);
|
|
20
|
+
--velin-color-text-subtle: oklch(72% 0.005 250);
|
|
21
|
+
--velin-color-border: oklch(55% 0.01 250);
|
|
22
|
+
--velin-color-border-strong: oklch(70% 0.01 250);
|
|
23
|
+
--velin-color-primary: oklch(78% 0.14 250);
|
|
24
|
+
--velin-color-on-primary: oklch(8% 0.01 250);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
:root[data-velin-contrast="aaa"] .velin-btn {
|
|
29
|
+
border-width: 2px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
:root[data-velin-contrast="aaa"] .velin-input,
|
|
33
|
+
:root[data-velin-contrast="aaa"] .velin-select,
|
|
34
|
+
:root[data-velin-contrast="aaa"] .velin-textarea {
|
|
35
|
+
border-width: 2px;
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/a11y/preferences.css
CHANGED
|
@@ -1,85 +1,106 @@
|
|
|
1
|
-
@layer a11y {
|
|
2
|
-
/* Smart User-Preference Queries */
|
|
3
|
-
|
|
4
|
-
/* High Contrast -- thicker borders, stronger text */
|
|
5
|
-
@media (prefers-contrast: more) {
|
|
6
|
-
:root {
|
|
7
|
-
--velin-color-text: oklch(10% 0 0);
|
|
8
|
-
--velin-color-text-muted: oklch(30% 0 0);
|
|
9
|
-
--velin-color-border: oklch(30% 0 0);
|
|
10
|
-
--velin-color-border-strong: oklch(10% 0 0);
|
|
11
|
-
--velin-shadow-sm: none;
|
|
12
|
-
--velin-shadow-md: none;
|
|
13
|
-
--velin-shadow-lg: none;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
.velin-btn {
|
|
17
|
-
border-width: 2px;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
.velin-card {
|
|
21
|
-
border-width: 2px;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.velin-input,
|
|
25
|
-
.velin-select,
|
|
26
|
-
.velin-textarea {
|
|
27
|
-
border-width: 2px;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.velin-badge {
|
|
31
|
-
border: 1px solid currentColor;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.velin-divider {
|
|
35
|
-
border-width: 2px;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.velin-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
.velin-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
1
|
+
@layer a11y {
|
|
2
|
+
/* Smart User-Preference Queries */
|
|
3
|
+
|
|
4
|
+
/* High Contrast -- thicker borders, stronger text */
|
|
5
|
+
@media (prefers-contrast: more) {
|
|
6
|
+
:root {
|
|
7
|
+
--velin-color-text: oklch(10% 0 0);
|
|
8
|
+
--velin-color-text-muted: oklch(30% 0 0);
|
|
9
|
+
--velin-color-border: oklch(30% 0 0);
|
|
10
|
+
--velin-color-border-strong: oklch(10% 0 0);
|
|
11
|
+
--velin-shadow-sm: none;
|
|
12
|
+
--velin-shadow-md: none;
|
|
13
|
+
--velin-shadow-lg: none;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.velin-btn {
|
|
17
|
+
border-width: 2px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.velin-card {
|
|
21
|
+
border-width: 2px;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.velin-input,
|
|
25
|
+
.velin-select,
|
|
26
|
+
.velin-textarea {
|
|
27
|
+
border-width: 2px;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.velin-badge {
|
|
31
|
+
border: 1px solid currentColor;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.velin-divider {
|
|
35
|
+
border-width: 2px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.velin-nav__link {
|
|
39
|
+
font-weight: var(--velin-weight-semibold);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.velin-pagination__link {
|
|
43
|
+
border-width: 2px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.velin-table th,
|
|
47
|
+
.velin-table td {
|
|
48
|
+
border-width: 2px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.velin-stepper .step__connector {
|
|
52
|
+
block-size: 3px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.velin-stepper .step__label {
|
|
56
|
+
font-weight: var(--velin-weight-semibold);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Low Contrast -- softer, less visual density */
|
|
61
|
+
@media (prefers-contrast: less) {
|
|
62
|
+
:root {
|
|
63
|
+
--velin-color-border: oklch(85% 0.01 250);
|
|
64
|
+
--velin-color-border-strong: oklch(70% 0.01 250);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.velin-card {
|
|
68
|
+
border-color: transparent;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.velin-divider {
|
|
72
|
+
opacity: 0.5;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Reduced Transparency -- opaque backgrounds everywhere */
|
|
77
|
+
@media (prefers-reduced-transparency) {
|
|
78
|
+
:root {
|
|
79
|
+
--velin-color-overlay: oklch(15% 0 0);
|
|
80
|
+
--velin-color-surface-dim: oklch(95% 0.005 250);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.velin-card {
|
|
84
|
+
backdrop-filter: none;
|
|
85
|
+
background: var(--velin-color-surface-bright);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.velin-nav {
|
|
89
|
+
backdrop-filter: none;
|
|
90
|
+
background: var(--velin-color-surface-bright);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.velin-alert {
|
|
94
|
+
backdrop-filter: none;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.velin-chip {
|
|
98
|
+
backdrop-filter: none;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
[data-velin-theme="dark"] .velin-card,
|
|
102
|
+
[data-velin-theme="dark"] .velin-nav {
|
|
103
|
+
background: var(--velin-color-surface);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
package/src/a11y/security.css
CHANGED
|
@@ -1,104 +1,119 @@
|
|
|
1
|
-
/* ===================================================================
|
|
2
|
-
VelinStyle Security Utilities
|
|
3
|
-
CSS-level security helpers for content sandboxing and safe defaults.
|
|
4
|
-
=================================================================== */
|
|
5
|
-
|
|
6
|
-
@layer
|
|
7
|
-
|
|
8
|
-
/* ---- Clickjacking Protection ----
|
|
9
|
-
Wrap page content in .velin-secure-frame. If the page is loaded inside
|
|
10
|
-
an <iframe> without styles, the content stays hidden. Only when styles
|
|
11
|
-
load (same-origin) does the content appear. Complement with HTTP
|
|
12
|
-
X-Frame-Options / CSP frame-ancestors header for full protection. */
|
|
13
|
-
.velin-secure-frame {
|
|
14
|
-
display: none;
|
|
15
|
-
}
|
|
16
|
-
@media all {
|
|
17
|
-
.velin-secure-frame {
|
|
18
|
-
display: block;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/* ---- Safe External Links ----
|
|
23
|
-
Force rel="noopener noreferrer" behavior. The CSS-only version
|
|
24
|
-
provides a visual indicator. Developers should still add
|
|
25
|
-
rel="noopener noreferrer" in HTML; this is a safety net. */
|
|
26
|
-
a[target="_blank"]:not([rel~="noopener"]) {
|
|
27
|
-
&::after {
|
|
28
|
-
content: " ↗";
|
|
29
|
-
font-size: 0.75em;
|
|
30
|
-
vertical-align: super;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/* ---- User-Generated Content Container ----
|
|
35
|
-
Sandboxes untrusted content by resetting inherited styles,
|
|
36
|
-
enabling CSS containment, and hiding dangerous elements. */
|
|
37
|
-
.velin-user-content {
|
|
38
|
-
all: initial;
|
|
39
|
-
display: block;
|
|
40
|
-
font-family: inherit;
|
|
41
|
-
font-size: inherit;
|
|
42
|
-
line-height: inherit;
|
|
43
|
-
color: inherit;
|
|
44
|
-
contain: layout style paint;
|
|
45
|
-
overflow: hidden;
|
|
46
|
-
word-break: break-word;
|
|
47
|
-
overflow-wrap: break-word;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
.velin-user-content img,
|
|
51
|
-
.velin-user-content video {
|
|
52
|
-
max-width: 100%;
|
|
53
|
-
height: auto;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
.velin-user-content a {
|
|
57
|
-
color: var(--velin-color-primary, #2563eb);
|
|
58
|
-
text-decoration: underline;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/* Hide potentially dangerous embedded elements */
|
|
62
|
-
.velin-user-content script,
|
|
63
|
-
.velin-user-content iframe,
|
|
64
|
-
.velin-user-content object,
|
|
65
|
-
.velin-user-content embed,
|
|
66
|
-
.velin-user-content applet,
|
|
67
|
-
.velin-user-content form[action],
|
|
68
|
-
.velin-user-content [onload],
|
|
69
|
-
.velin-user-content [onerror],
|
|
70
|
-
.velin-user-content [onclick] {
|
|
71
|
-
display: none !important;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/* ---- Data Attribute Leakage Prevention ----
|
|
75
|
-
Prevent CSS exfiltration attacks via attribute selectors.
|
|
76
|
-
The content property is locked to normal on sensitive fields. */
|
|
77
|
-
input[type="password"]
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
input[type="
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
1
|
+
/* ===================================================================
|
|
2
|
+
VelinStyle Security Utilities
|
|
3
|
+
CSS-level security helpers for content sandboxing and safe defaults.
|
|
4
|
+
=================================================================== */
|
|
5
|
+
|
|
6
|
+
@layer security {
|
|
7
|
+
|
|
8
|
+
/* ---- Clickjacking Protection ----
|
|
9
|
+
Wrap page content in .velin-secure-frame. If the page is loaded inside
|
|
10
|
+
an <iframe> without styles, the content stays hidden. Only when styles
|
|
11
|
+
load (same-origin) does the content appear. Complement with HTTP
|
|
12
|
+
X-Frame-Options / CSP frame-ancestors header for full protection. */
|
|
13
|
+
.velin-secure-frame {
|
|
14
|
+
display: none;
|
|
15
|
+
}
|
|
16
|
+
@media all {
|
|
17
|
+
.velin-secure-frame {
|
|
18
|
+
display: block;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* ---- Safe External Links ----
|
|
23
|
+
Force rel="noopener noreferrer" behavior. The CSS-only version
|
|
24
|
+
provides a visual indicator. Developers should still add
|
|
25
|
+
rel="noopener noreferrer" in HTML; this is a safety net. */
|
|
26
|
+
a[target="_blank"]:not([rel~="noopener"]) {
|
|
27
|
+
&::after {
|
|
28
|
+
content: " ↗";
|
|
29
|
+
font-size: 0.75em;
|
|
30
|
+
vertical-align: super;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* ---- User-Generated Content Container ----
|
|
35
|
+
Sandboxes untrusted content by resetting inherited styles,
|
|
36
|
+
enabling CSS containment, and hiding dangerous elements. */
|
|
37
|
+
.velin-user-content {
|
|
38
|
+
all: initial;
|
|
39
|
+
display: block;
|
|
40
|
+
font-family: inherit;
|
|
41
|
+
font-size: inherit;
|
|
42
|
+
line-height: inherit;
|
|
43
|
+
color: inherit;
|
|
44
|
+
contain: layout style paint;
|
|
45
|
+
overflow: hidden;
|
|
46
|
+
word-break: break-word;
|
|
47
|
+
overflow-wrap: break-word;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.velin-user-content img,
|
|
51
|
+
.velin-user-content video {
|
|
52
|
+
max-width: 100%;
|
|
53
|
+
height: auto;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.velin-user-content a {
|
|
57
|
+
color: var(--velin-color-primary, #2563eb);
|
|
58
|
+
text-decoration: underline;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Hide potentially dangerous embedded elements */
|
|
62
|
+
.velin-user-content script,
|
|
63
|
+
.velin-user-content iframe,
|
|
64
|
+
.velin-user-content object,
|
|
65
|
+
.velin-user-content embed,
|
|
66
|
+
.velin-user-content applet,
|
|
67
|
+
.velin-user-content form[action],
|
|
68
|
+
.velin-user-content [onload],
|
|
69
|
+
.velin-user-content [onerror],
|
|
70
|
+
.velin-user-content [onclick] {
|
|
71
|
+
display: none !important;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* ---- Data Attribute Leakage Prevention ----
|
|
75
|
+
Prevent CSS exfiltration attacks via attribute selectors.
|
|
76
|
+
The content property is locked to normal on sensitive fields. */
|
|
77
|
+
input[type="password"] {
|
|
78
|
+
-webkit-text-security: disc;
|
|
79
|
+
}
|
|
80
|
+
input[type="password"]::after,
|
|
81
|
+
input[type="password"]::before {
|
|
82
|
+
content: normal !important;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* ---- Prevent Paste-jacking ----
|
|
86
|
+
Ensure user can always paste into inputs */
|
|
87
|
+
input:not([data-velin-no-paste]),
|
|
88
|
+
textarea:not([data-velin-no-paste]) {
|
|
89
|
+
-webkit-user-modify: read-write;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* ---- Disable autofill background color leaking ----
|
|
93
|
+
Prevents browsers from applying unwanted background colors
|
|
94
|
+
on autofilled inputs that could leak info via CSS. */
|
|
95
|
+
input:-webkit-autofill {
|
|
96
|
+
-webkit-box-shadow: 0 0 0 1000px var(--velin-color-surface-bright, #fff) inset;
|
|
97
|
+
-webkit-text-fill-color: var(--velin-color-text, #1a1a2e);
|
|
98
|
+
transition: background-color 600000s 0s, color 600000s 0s;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.velin-isolate {
|
|
102
|
+
isolation: isolate;
|
|
103
|
+
contain: layout style paint;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.velin-sensitive {
|
|
107
|
+
user-select: none;
|
|
108
|
+
-webkit-user-select: none;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.velin-overlay-lock {
|
|
112
|
+
overscroll-behavior: contain;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.velin-user-content img[src^="data:"] {
|
|
116
|
+
max-inline-size: 100%;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* WCAG 2.5.8 Target Size (Minimum) — 24×24 minimum; 44×44 for primary controls */
|
|
2
|
+
@layer a11y {
|
|
3
|
+
.velin-target-min {
|
|
4
|
+
min-block-size: 1.5rem; /* 24px */
|
|
5
|
+
min-inline-size: 1.5rem;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.velin-target-touch {
|
|
9
|
+
min-block-size: 2.75rem; /* 44px */
|
|
10
|
+
min-inline-size: 2.75rem;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.velin-alert__close,
|
|
14
|
+
.velin-toast-close {
|
|
15
|
+
min-block-size: 2.75rem;
|
|
16
|
+
min-inline-size: 2.75rem;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.velin-btn--sm {
|
|
20
|
+
min-block-size: 2.75rem;
|
|
21
|
+
min-inline-size: 2.75rem;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.velin-pagination__ellipsis {
|
|
25
|
+
min-inline-size: 2.75rem;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.velin-pagination--sm .velin-pagination__link {
|
|
29
|
+
min-block-size: 2.75rem;
|
|
30
|
+
min-inline-size: 2.75rem;
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/base/reset.css
CHANGED
|
@@ -152,7 +152,18 @@
|
|
|
152
152
|
velin-scroll-top:not(:defined),
|
|
153
153
|
velin-scrollspy:not(:defined),
|
|
154
154
|
velin-theme-toggle:not(:defined),
|
|
155
|
-
velin-icon:not(:defined)
|
|
155
|
+
velin-icon:not(:defined),
|
|
156
|
+
velin-combobox:not(:defined),
|
|
157
|
+
velin-bottom-nav:not(:defined),
|
|
158
|
+
velin-sheet:not(:defined),
|
|
159
|
+
velin-segmented-control:not(:defined),
|
|
160
|
+
velin-rating:not(:defined),
|
|
161
|
+
velin-menubar:not(:defined),
|
|
162
|
+
velin-command:not(:defined),
|
|
163
|
+
velin-announcer:not(:defined),
|
|
164
|
+
velin-sparkline:not(:defined),
|
|
165
|
+
velin-counter:not(:defined),
|
|
166
|
+
velin-live-dot:not(:defined) {
|
|
156
167
|
opacity: 0;
|
|
157
168
|
visibility: hidden;
|
|
158
169
|
}
|